From 1ea5694eb4daebf62d3a905fe24325f1956ab4d2 Mon Sep 17 00:00:00 2001 From: 18408672952 <2518665972@qq.com> Date: Fri, 27 Aug 2021 21:30:23 +0800 Subject: [PATCH 1/2] add third Signed-off-by: 18408672952 <2518665972@qq.com> --- BUILD.gn | 541 ++ CONTRIBUTING.md | 372 + COPYING | 26 + DCO-1.1.txt | 38 + OAT.xml | 72 + README.OpenSource | 11 + README.md | 386 +- clients/calibrator.c | 311 + clients/clickdot.c | 345 + clients/cliptest.c | 637 ++ clients/confine.c | 512 ++ clients/content_protection.c | 384 + clients/desktop-shell.c | 1559 ++++ clients/dnd.c | 867 ++ clients/editor.c | 1681 ++++ clients/eventdemo.c | 541 ++ clients/flower.c | 206 + clients/fullscreen.c | 581 ++ clients/gears.c | 503 ++ clients/image.c | 437 + clients/ivi-shell-user-interface.c | 1338 +++ clients/keyboard.c | 1041 +++ clients/meson.build | 373 + clients/multi-resource.c | 591 ++ clients/nested-client.c | 374 + clients/nested.c | 1137 +++ clients/presentation-shm.c | 956 ++ clients/resizor.c | 456 + clients/scaler.c | 326 + clients/screenshot.c | 329 + clients/simple-damage.c | 957 ++ clients/simple-dmabuf-egl.c | 1562 ++++ clients/simple-dmabuf-v4l.c | 1080 +++ clients/simple-egl.c | 896 ++ clients/simple-im.c | 526 ++ clients/simple-shm.c | 528 ++ clients/simple-touch.c | 360 + clients/smoke.c | 318 + clients/stacking.c | 309 + clients/subsurfaces.c | 811 ++ clients/terminal.c | 3185 +++++++ clients/touch-calibrator.c | 970 ++ clients/transformed.c | 303 + clients/weston-debug.c | 501 ++ clients/weston-info.c | 1890 ++++ clients/window.c | 6673 ++++++++++++++ clients/window.h | 744 ++ compositor/cms-colord.c | 588 ++ compositor/cms-helper.c | 136 + compositor/cms-helper.h | 75 + compositor/cms-static.c | 124 + compositor/executable.c | 34 + compositor/main.c | 3463 +++++++ compositor/meson.build | 187 + compositor/screen-share.c | 1184 +++ compositor/systemd-notify.c | 168 + compositor/testsuite-util.c | 62 + compositor/text-backend.c | 1099 +++ compositor/weston-screenshooter.c | 202 + compositor/weston.desktop | 5 + compositor/weston.h | 117 + compositor/xwayland.c | 212 + data/COPYING | 55 + data/background.png | Bin 0 -> 135501 bytes data/border.png | Bin 0 -> 1585 bytes data/fullscreen.png | Bin 0 -> 3152 bytes data/home.png | Bin 0 -> 4234 bytes data/icon_editor.png | Bin 0 -> 737 bytes data/icon_flower.png | Bin 0 -> 1160 bytes data/icon_ivi_clickdot.png | Bin 0 -> 30955 bytes data/icon_ivi_flower.png | Bin 0 -> 20214 bytes data/icon_ivi_simple-egl.png | Bin 0 -> 8688 bytes data/icon_ivi_simple-shm.png | Bin 0 -> 39650 bytes data/icon_ivi_smoke.png | Bin 0 -> 24569 bytes data/icon_terminal.png | Bin 0 -> 525 bytes data/icon_window.png | Bin 0 -> 86 bytes data/icons.svg | 1012 +++ data/meson.build | 29 + data/panel.png | Bin 0 -> 27761 bytes data/pattern.png | Bin 0 -> 825 bytes data/random.png | Bin 0 -> 4553 bytes data/sidebyside.png | Bin 0 -> 3630 bytes data/sign_close.png | Bin 0 -> 104 bytes data/sign_maximize.png | Bin 0 -> 85 bytes data/sign_minimize.png | Bin 0 -> 79 bytes data/terminal.png | Bin 0 -> 765 bytes data/tiling.png | Bin 0 -> 5148 bytes data/wayland.png | Bin 0 -> 4596 bytes data/wayland.svg | 101 + desktop-shell/exposay.c | 737 ++ desktop-shell/input-panel.c | 412 + desktop-shell/meson.build | 31 + desktop-shell/shell.c | 5253 +++++++++++ desktop-shell/shell.h | 263 + doc/doxygen/devtools.dox | 51 + doc/doxygen/tooldev.doxygen.in | 12 + doc/doxygen/tools.dox | 31 + doc/doxygen/tools.doxygen.in | 11 + doc/doxygen/tools_arch_new.gv | 85 + doc/doxygen/tools_arch_old.gv | 53 + doc/scripts/calibration-helper.bash | 66 + doc/scripts/gdb/flight_rec.py | 103 + doc/scripts/remoting-client-receive.bash | 38 + doc/sphinx/conf.py.in | 204 + doc/sphinx/doxygen.ini.in | 2480 +++++ doc/sphinx/index.rst | 34 + doc/sphinx/meson.build | 88 + doc/sphinx/run_doxygen_sphinx.sh.in | 2 + doc/sphinx/toc/kiosk-shell.rst | 17 + doc/sphinx/toc/libweston.rst | 46 + doc/sphinx/toc/libweston/compositor.rst | 14 + doc/sphinx/toc/libweston/head.rst | 44 + .../toc/libweston/images/create_output.msc | 31 + .../toc/libweston/images/create_output.png | Bin 0 -> 39923 bytes .../toc/libweston/images/destroy-output.msc | 29 + .../toc/libweston/images/destroy-output.png | Bin 0 -> 30249 bytes .../toc/libweston/images/head-destroyed.msc | 26 + .../toc/libweston/images/head-destroyed.png | Bin 0 -> 28457 bytes .../toc/libweston/images/initial-heads.msc | 35 + .../toc/libweston/images/initial-heads.png | Bin 0 -> 37770 bytes doc/sphinx/toc/libweston/images/meson.build | 13 + .../images/react-to-heads-changed.msc | 24 + .../images/react-to-heads-changed.png | Bin 0 -> 27241 bytes doc/sphinx/toc/libweston/log.rst | 215 + doc/sphinx/toc/libweston/meson.build | 14 + .../toc/libweston/output-management.rst | 104 + doc/sphinx/toc/libweston/output.rst | 30 + doc/sphinx/toc/meson.build | 13 + doc/sphinx/toc/test-suite-api.rst | 15 + doc/sphinx/toc/test-suite.rst | 269 + doc/wayland-screenshot.jpg | Bin 0 -> 143832 bytes fullscreen-shell/fullscreen-shell.c | 941 ++ fullscreen-shell/meson.build | 16 + include/config.h | 260 + include/libweston-desktop/libweston-desktop.h | 202 + include/libweston/backend-drm.h | 235 + include/libweston/backend-fbdev.h | 70 + include/libweston/backend-headless.h | 53 + include/libweston/backend-rdp.h | 75 + include/libweston/backend-wayland.h | 54 + include/libweston/backend-x11.h | 53 + include/libweston/config-parser.h | 130 + include/libweston/libweston.h | 2083 +++++ include/libweston/matrix.h | 85 + include/libweston/meson.build | 31 + include/libweston/plugin-registry.h | 55 + include/libweston/version.h.in | 50 + include/libweston/weston-log.h | 142 + include/libweston/windowed-output-api.h | 95 + include/libweston/xwayland-api.h | 176 + include/libweston/zalloc.h | 45 + include/meson.build | 6 + ivi-shell/README | 78 + ivi-shell/hmi-controller.c | 2006 +++++ ivi-shell/ivi-layout-export.h | 610 ++ ivi-shell/ivi-layout-private.h | 205 + ivi-shell/ivi-layout-shell.h | 67 + ivi-shell/ivi-layout-transition.c | 908 ++ ivi-shell/ivi-layout.c | 2127 +++++ ivi-shell/ivi-shell.c | 660 ++ ivi-shell/ivi-shell.h | 55 + ivi-shell/meson.build | 61 + ivi-shell/weston.ini.in | 98 + kiosk-shell/kiosk-shell-grab.c | 314 + kiosk-shell/kiosk-shell-grab.h | 43 + kiosk-shell/kiosk-shell.c | 1071 +++ kiosk-shell/kiosk-shell.h | 91 + kiosk-shell/meson.build | 29 + kiosk-shell/util.c | 168 + kiosk-shell/util.h | 48 + libweston-desktop/client.c | 212 + libweston-desktop/internal.h | 242 + libweston-desktop/libweston-desktop.c | 255 + libweston-desktop/meson.build | 36 + libweston-desktop/seat.c | 381 + libweston-desktop/surface.c | 830 ++ libweston-desktop/wl-shell.c | 497 + libweston-desktop/xdg-shell-v6.c | 1444 +++ libweston-desktop/xdg-shell.c | 1498 +++ libweston-desktop/xwayland.c | 425 + libweston/animation.c | 533 ++ libweston/backend-drm/auth/wayland_drm_auth.h | 76 + .../auth/wayland_drm_auth_server.c | 80 + .../auth/wayland_drm_auth_server.h | 47 + libweston/backend-drm/drm-gbm.c | 365 + libweston/backend-drm/drm-internal.h | 831 ++ libweston/backend-drm/drm-virtual.c | 362 + libweston/backend-drm/drm.c | 3086 +++++++ libweston/backend-drm/fb.c | 582 ++ libweston/backend-drm/kms.c | 1507 ++++ libweston/backend-drm/libbacklight.c | 318 + libweston/backend-drm/libbacklight.h | 79 + libweston/backend-drm/meson.build | 93 + libweston/backend-drm/modes.c | 812 ++ libweston/backend-drm/state-helpers.c | 473 + libweston/backend-drm/state-propose.c | 1149 +++ libweston/backend-drm/vaapi-recorder.c | 1161 +++ libweston/backend-drm/vaapi-recorder.h | 38 + libweston/backend-fbdev/fbdev.c | 999 ++ libweston/backend-fbdev/meson.build | 30 + libweston/backend-headless/headless.c | 521 ++ libweston/backend-headless/meson.build | 21 + libweston/backend-rdp/meson.build | 60 + libweston/backend-rdp/rdp.c | 1515 ++++ libweston/backend-wayland/meson.build | 44 + libweston/backend-wayland/wayland.c | 2915 ++++++ libweston/backend-x11/meson.build | 58 + libweston/backend-x11/x11.c | 1957 ++++ libweston/backend.h | 248 + libweston/bindings.c | 590 ++ libweston/clipboard.c | 308 + libweston/compositor.c | 8005 +++++++++++++++++ libweston/content-protection.c | 352 + libweston/data-device.c | 1370 +++ libweston/dbus.c | 408 + libweston/dbus.h | 110 + libweston/git-version.h | 1 + libweston/git-version.h.meson | 1 + libweston/input.c | 5063 +++++++++++ libweston/launcher-direct.c | 351 + libweston/launcher-impl.h | 49 + libweston/launcher-logind.c | 856 ++ libweston/launcher-util.c | 120 + libweston/launcher-util.h | 55 + libweston/launcher-weston-launch.c | 345 + libweston/libinput-device.c | 761 ++ libweston/libinput-device.h | 82 + libweston/libinput-seat.c | 490 + libweston/libinput-seat.h | 73 + libweston/libweston-internal.h | 334 + libweston/linux-dmabuf.c | 591 ++ libweston/linux-dmabuf.h | 103 + libweston/linux-explicit-synchronization.c | 287 + libweston/linux-explicit-synchronization.h | 39 + libweston/linux-sync-file-uapi.h | 30 + libweston/linux-sync-file.c | 83 + libweston/linux-sync-file.h | 38 + libweston/log.c | 157 + libweston/meson.build | 242 + libweston/noop-renderer.c | 123 + libweston/pixel-formats.c | 516 ++ libweston/pixel-formats.h | 262 + libweston/pixman-renderer-protected.h | 67 + libweston/pixman-renderer.c | 998 ++ libweston/pixman-renderer.h | 58 + libweston/plugin-registry.c | 156 + libweston/renderer-gl/egl-glue.c | 693 ++ libweston/renderer-gl/gl-renderer-internal.h | 154 + libweston/renderer-gl/gl-renderer.c | 3820 ++++++++ libweston/renderer-gl/gl-renderer.h | 228 + libweston/renderer-gl/meson.build | 39 + libweston/screenshooter.c | 494 + libweston/spring-tool.c | 79 + libweston/tde-render-part.c | 389 + libweston/tde-render-part.h | 49 + libweston/timeline.c | 461 + libweston/timeline.h | 103 + libweston/touch-calibration.c | 716 ++ libweston/version.h | 50 + libweston/vertex-clipping.c | 330 + libweston/vertex-clipping.h | 66 + libweston/weston-direct-display.c | 92 + libweston/weston-launch.c | 916 ++ libweston/weston-launch.h | 50 + libweston/weston-log-file.c | 98 + libweston/weston-log-flight-rec.c | 296 + libweston/weston-log-internal.h | 117 + libweston/weston-log-wayland.c | 290 + libweston/weston-log.c | 1011 +++ libweston/zoom.c | 184 + man/meson.build | 55 + man/weston-bindings.man | 136 + man/weston-debug.man | 51 + man/weston-drm.man | 240 + man/weston-rdp.man | 92 + man/weston.ini.man | 663 ++ man/weston.man | 377 + meson.build | 176 + meson_options.txt | 224 + notes.txt | 87 + pipewire/meson.build | 31 + pipewire/pipewire-plugin.c | 855 ++ pipewire/pipewire-plugin.h | 62 + protocol/drm-auth.xml | 34 + protocol/ivi-application.xml | 100 + protocol/ivi-hmi-controller.xml | 98 + protocol/meson.build | 77 + protocol/text-cursor-position.xml | 11 + protocol/weston-content-protection.xml | 251 + protocol/weston-debug.xml | 139 + protocol/weston-desktop-shell.xml | 135 + protocol/weston-direct-display.xml | 82 + protocol/weston-screenshooter.xml | 12 + protocol/weston-test.xml | 144 + protocol/weston-touch-calibration.xml | 342 + releasing.md | 72 + remoting/README | 28 + remoting/meson.build | 33 + remoting/remoting-plugin.c | 967 ++ remoting/remoting-plugin.h | 78 + shared/cairo-util.c | 635 ++ shared/cairo-util.h | 233 + shared/config-parser.c | 525 ++ shared/fd-util.h | 56 + shared/file-util.c | 146 + shared/file-util.h | 46 + shared/frame.c | 1021 +++ shared/helpers.h | 141 + shared/image-loader.c | 440 + shared/image-loader.h | 34 + shared/matrix.c | 276 + shared/meson.build | 84 + shared/option-parser.c | 204 + shared/os-compatibility.c | 407 + shared/os-compatibility.h | 74 + shared/platform.h | 167 + shared/string-helpers.h | 71 + shared/timespec-util.h | 259 + shared/weston-egl-ext.h | 221 + shared/xalloc.c | 55 + shared/xalloc.h | 51 + test-qc.c | 2 + tests/bad-buffer-test.c | 189 + tests/buffer-transforms-test.c | 137 + tests/config-parser-test.c | 632 ++ tests/devices-test.c | 356 + tests/drm-smoke-test.c | 72 + tests/event-test.c | 179 + tests/input-timestamps-helper.c | 177 + tests/input-timestamps-helper.h | 46 + tests/internal-screenshot-test.c | 178 + tests/internal-screenshot.ini | 3 + tests/ivi-layout-internal-test.c | 1022 +++ tests/ivi-layout-test-client.c | 509 ++ tests/ivi-layout-test-plugin.c | 1018 +++ tests/ivi-shell-app-test.c | 90 + tests/ivi-test.h | 42 + tests/keyboard-test.c | 169 + tests/linux-explicit-synchronization-test.c | 498 + tests/matrix-test.c | 424 + tests/meson.build | 346 + tests/output-transforms-test.c | 137 + tests/plugin-registry-test.c | 102 + tests/pointer-test.c | 467 + tests/presentation-test.c | 250 + tests/reference/basic-test-card.png | Bin 0 -> 8347 bytes .../reference/internal-screenshot-bad-00.png | Bin 0 -> 4204 bytes .../reference/internal-screenshot-good-00.png | Bin 0 -> 733 bytes .../output_1-180_buffer_1-NORMAL-00.png | Bin 0 -> 4642 bytes .../reference/output_1-180_buffer_2-90-00.png | Bin 0 -> 4869 bytes .../output_1-270_buffer_1-NORMAL-00.png | Bin 0 -> 4783 bytes .../reference/output_1-270_buffer_2-90-00.png | Bin 0 -> 4638 bytes .../output_1-90_buffer_1-NORMAL-00.png | Bin 0 -> 4880 bytes .../reference/output_1-90_buffer_2-90-00.png | Bin 0 -> 4655 bytes ...utput_1-FLIPPED_180_buffer_1-NORMAL-00.png | Bin 0 -> 4669 bytes .../output_1-FLIPPED_180_buffer_2-90-00.png | Bin 0 -> 4884 bytes ...utput_1-FLIPPED_270_buffer_1-NORMAL-00.png | Bin 0 -> 4789 bytes .../output_1-FLIPPED_270_buffer_2-90-00.png | Bin 0 -> 4570 bytes ...output_1-FLIPPED_90_buffer_1-NORMAL-00.png | Bin 0 -> 4914 bytes .../output_1-FLIPPED_90_buffer_2-90-00.png | Bin 0 -> 4667 bytes .../output_1-FLIPPED_buffer_1-NORMAL-00.png | Bin 0 -> 4570 bytes .../output_1-FLIPPED_buffer_2-90-00.png | Bin 0 -> 4892 bytes .../output_1-NORMAL_buffer_1-180-00.png | Bin 0 -> 4674 bytes .../output_1-NORMAL_buffer_1-270-00.png | Bin 0 -> 4877 bytes .../output_1-NORMAL_buffer_1-90-00.png | Bin 0 -> 4854 bytes .../output_1-NORMAL_buffer_1-FLIPPED-00.png | Bin 0 -> 4598 bytes ...utput_1-NORMAL_buffer_1-FLIPPED_180-00.png | Bin 0 -> 4667 bytes ...utput_1-NORMAL_buffer_1-FLIPPED_270-00.png | Bin 0 -> 4883 bytes ...output_1-NORMAL_buffer_1-FLIPPED_90-00.png | Bin 0 -> 4914 bytes .../output_1-NORMAL_buffer_1-NORMAL-00.png | Bin 0 -> 4655 bytes .../output_1-NORMAL_buffer_2-180-00.png | Bin 0 -> 4674 bytes .../output_1-NORMAL_buffer_2-90-00.png | Bin 0 -> 4854 bytes .../output_1-NORMAL_buffer_2-FLIPPED-00.png | Bin 0 -> 4598 bytes .../output_1-NORMAL_buffer_2-NORMAL-00.png | Bin 0 -> 4655 bytes ...output_1-NORMAL_buffer_3-FLIPPED_90-00.png | Bin 0 -> 4914 bytes .../output_1-NORMAL_buffer_3-NORMAL-00.png | Bin 0 -> 4655 bytes .../output_2-180_buffer_1-NORMAL-00.png | Bin 0 -> 14395 bytes .../reference/output_2-180_buffer_2-90-00.png | Bin 0 -> 5270 bytes .../reference/output_2-90_buffer_1-180-00.png | Bin 0 -> 14282 bytes .../reference/output_2-90_buffer_1-270-00.png | Bin 0 -> 14385 bytes .../reference/output_2-90_buffer_1-90-00.png | Bin 0 -> 14482 bytes .../output_2-90_buffer_1-FLIPPED-00.png | Bin 0 -> 14336 bytes .../output_2-90_buffer_1-FLIPPED_180-00.png | Bin 0 -> 14762 bytes .../output_2-90_buffer_1-FLIPPED_270-00.png | Bin 0 -> 14394 bytes .../output_2-90_buffer_1-FLIPPED_90-00.png | Bin 0 -> 14446 bytes .../output_2-90_buffer_1-NORMAL-00.png | Bin 0 -> 14832 bytes .../reference/output_2-90_buffer_2-180-00.png | Bin 0 -> 5217 bytes .../reference/output_2-90_buffer_2-90-00.png | Bin 0 -> 5179 bytes .../output_2-90_buffer_2-FLIPPED-00.png | Bin 0 -> 5258 bytes .../output_2-90_buffer_2-NORMAL-00.png | Bin 0 -> 5288 bytes .../output_2-90_buffer_3-FLIPPED_90-00.png | Bin 0 -> 5167 bytes .../output_2-90_buffer_3-NORMAL-00.png | Bin 0 -> 5288 bytes .../output_2-FLIPPED_buffer_1-NORMAL-00.png | Bin 0 -> 14407 bytes .../output_2-FLIPPED_buffer_2-90-00.png | Bin 0 -> 5360 bytes .../output_2-NORMAL_buffer_1-NORMAL-00.png | Bin 0 -> 14482 bytes .../output_2-NORMAL_buffer_2-90-00.png | Bin 0 -> 5313 bytes ...utput_3-FLIPPED_270_buffer_1-NORMAL-00.png | Bin 0 -> 12107 bytes .../output_3-FLIPPED_270_buffer_2-90-00.png | Bin 0 -> 12541 bytes .../output_3-NORMAL_buffer_1-NORMAL-00.png | Bin 0 -> 15856 bytes .../output_3-NORMAL_buffer_2-90-00.png | Bin 0 -> 11702 bytes tests/reference/subsurface_z_order-00.png | Bin 0 -> 122 bytes tests/reference/subsurface_z_order-01.png | Bin 0 -> 154 bytes tests/reference/subsurface_z_order-02.png | Bin 0 -> 166 bytes tests/reference/subsurface_z_order-03.png | Bin 0 -> 233 bytes tests/reference/subsurface_z_order-04.png | Bin 0 -> 237 bytes tests/reference/viewport_upscale_solid-00.png | Bin 0 -> 829 bytes tests/roles-test.c | 155 + tests/setbacklight.c | 191 + tests/string-test.c | 88 + tests/subsurface-shot-test.c | 200 + tests/subsurface-test.c | 768 ++ tests/surface-global-test.c | 91 + tests/surface-screenshot-test.c | 224 + tests/surface-test.c | 71 + tests/text-test.c | 234 + tests/timespec-test.c | 308 + tests/touch-test.c | 135 + tests/vertex-clip-test.c | 223 + tests/viewporter-shot-test.c | 89 + tests/viewporter-test.c | 519 ++ tests/weston-test-client-helper.c | 1899 ++++ tests/weston-test-client-helper.h | 287 + tests/weston-test-desktop-shell.c | 235 + tests/weston-test-fixture-compositor.c | 427 + tests/weston-test-fixture-compositor.h | 134 + tests/weston-test-runner.c | 620 ++ tests/weston-test-runner.h | 259 + tests/weston-test.c | 849 ++ tests/weston-testsuite-data.h | 88 + tests/xwayland-test.c | 119 + tools/zunitc/doc/zunitc.dox | 220 + tools/zunitc/inc/zunitc/zunitc.h | 743 ++ tools/zunitc/inc/zunitc/zunitc_impl.h | 105 + tools/zunitc/src/main.c | 58 + tools/zunitc/src/zuc_base_logger.c | 401 + tools/zunitc/src/zuc_base_logger.h | 38 + tools/zunitc/src/zuc_collector.c | 427 + tools/zunitc/src/zuc_collector.h | 58 + tools/zunitc/src/zuc_context.h | 58 + tools/zunitc/src/zuc_event.h | 86 + tools/zunitc/src/zuc_event_listener.h | 174 + tools/zunitc/src/zuc_junit_reporter.c | 479 + tools/zunitc/src/zuc_junit_reporter.h | 38 + tools/zunitc/src/zuc_types.h | 80 + tools/zunitc/src/zunitc_impl.c | 1578 ++++ tools/zunitc/test/fixtures_test.c | 106 + tools/zunitc/test/zunitc_test.c | 464 + wcap/README | 99 + wcap/main.c | 331 + wcap/meson.build | 21 + wcap/wcap-decode.c | 847 ++ wcap/wcap-decode.h | 68 + weston.ini | 16 + weston.ini.in | 87 + weston.rc | 15 + xwayland/dnd.c | 255 + xwayland/hash.c | 309 + xwayland/hash.h | 51 + xwayland/launcher.c | 409 + xwayland/meson.build | 44 + xwayland/selection.c | 762 ++ xwayland/window-manager.c | 2986 ++++++ xwayland/xwayland-internal-interface.h | 64 + xwayland/xwayland.h | 189 + 464 files changed, 169448 insertions(+), 39 deletions(-) create mode 100755 BUILD.gn create mode 100644 CONTRIBUTING.md create mode 100644 COPYING create mode 100644 DCO-1.1.txt create mode 100644 OAT.xml create mode 100644 README.OpenSource create mode 100644 clients/calibrator.c create mode 100644 clients/clickdot.c create mode 100644 clients/cliptest.c create mode 100644 clients/confine.c create mode 100644 clients/content_protection.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/image.c create mode 100644 clients/ivi-shell-user-interface.c create mode 100644 clients/keyboard.c create mode 100644 clients/meson.build create mode 100644 clients/multi-resource.c create mode 100644 clients/nested-client.c create mode 100644 clients/nested.c create mode 100644 clients/presentation-shm.c create mode 100644 clients/resizor.c create mode 100644 clients/scaler.c create mode 100644 clients/screenshot.c create mode 100644 clients/simple-damage.c create mode 100644 clients/simple-dmabuf-egl.c create mode 100644 clients/simple-dmabuf-v4l.c create mode 100644 clients/simple-egl.c create mode 100644 clients/simple-im.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/stacking.c create mode 100644 clients/subsurfaces.c create mode 100644 clients/terminal.c create mode 100644 clients/touch-calibrator.c create mode 100644 clients/transformed.c create mode 100644 clients/weston-debug.c create mode 100644 clients/weston-info.c create mode 100644 clients/window.c create mode 100644 clients/window.h create mode 100644 compositor/cms-colord.c create mode 100644 compositor/cms-helper.c create mode 100644 compositor/cms-helper.h create mode 100644 compositor/cms-static.c create mode 100644 compositor/executable.c create mode 100755 compositor/main.c create mode 100644 compositor/meson.build create mode 100644 compositor/screen-share.c create mode 100644 compositor/systemd-notify.c create mode 100644 compositor/testsuite-util.c create mode 100644 compositor/text-backend.c create mode 100755 compositor/weston-screenshooter.c create mode 100644 compositor/weston.desktop create mode 100644 compositor/weston.h create mode 100644 compositor/xwayland.c create mode 100644 data/COPYING create mode 100644 data/background.png create mode 100644 data/border.png create mode 100644 data/fullscreen.png create mode 100644 data/home.png create mode 100644 data/icon_editor.png create mode 100644 data/icon_flower.png create mode 100644 data/icon_ivi_clickdot.png create mode 100644 data/icon_ivi_flower.png create mode 100644 data/icon_ivi_simple-egl.png create mode 100644 data/icon_ivi_simple-shm.png create mode 100644 data/icon_ivi_smoke.png create mode 100644 data/icon_terminal.png create mode 100644 data/icon_window.png create mode 100644 data/icons.svg create mode 100644 data/meson.build create mode 100644 data/panel.png create mode 100644 data/pattern.png create mode 100644 data/random.png create mode 100644 data/sidebyside.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/tiling.png create mode 100644 data/wayland.png create mode 100644 data/wayland.svg create mode 100644 desktop-shell/exposay.c create mode 100644 desktop-shell/input-panel.c create mode 100644 desktop-shell/meson.build create mode 100755 desktop-shell/shell.c create mode 100644 desktop-shell/shell.h create mode 100644 doc/doxygen/devtools.dox create mode 100644 doc/doxygen/tooldev.doxygen.in create mode 100644 doc/doxygen/tools.dox create mode 100644 doc/doxygen/tools.doxygen.in create mode 100644 doc/doxygen/tools_arch_new.gv create mode 100644 doc/doxygen/tools_arch_old.gv create mode 100755 doc/scripts/calibration-helper.bash create mode 100755 doc/scripts/gdb/flight_rec.py create mode 100755 doc/scripts/remoting-client-receive.bash create mode 100644 doc/sphinx/conf.py.in create mode 100644 doc/sphinx/doxygen.ini.in create mode 100644 doc/sphinx/index.rst create mode 100644 doc/sphinx/meson.build create mode 100755 doc/sphinx/run_doxygen_sphinx.sh.in create mode 100644 doc/sphinx/toc/kiosk-shell.rst create mode 100644 doc/sphinx/toc/libweston.rst create mode 100644 doc/sphinx/toc/libweston/compositor.rst create mode 100644 doc/sphinx/toc/libweston/head.rst create mode 100644 doc/sphinx/toc/libweston/images/create_output.msc create mode 100644 doc/sphinx/toc/libweston/images/create_output.png create mode 100644 doc/sphinx/toc/libweston/images/destroy-output.msc create mode 100644 doc/sphinx/toc/libweston/images/destroy-output.png create mode 100644 doc/sphinx/toc/libweston/images/head-destroyed.msc create mode 100644 doc/sphinx/toc/libweston/images/head-destroyed.png create mode 100644 doc/sphinx/toc/libweston/images/initial-heads.msc create mode 100644 doc/sphinx/toc/libweston/images/initial-heads.png create mode 100644 doc/sphinx/toc/libweston/images/meson.build create mode 100644 doc/sphinx/toc/libweston/images/react-to-heads-changed.msc create mode 100644 doc/sphinx/toc/libweston/images/react-to-heads-changed.png create mode 100644 doc/sphinx/toc/libweston/log.rst create mode 100644 doc/sphinx/toc/libweston/meson.build create mode 100644 doc/sphinx/toc/libweston/output-management.rst create mode 100644 doc/sphinx/toc/libweston/output.rst create mode 100644 doc/sphinx/toc/meson.build create mode 100644 doc/sphinx/toc/test-suite-api.rst create mode 100644 doc/sphinx/toc/test-suite.rst create mode 100644 doc/wayland-screenshot.jpg create mode 100644 fullscreen-shell/fullscreen-shell.c create mode 100644 fullscreen-shell/meson.build create mode 100644 include/config.h create mode 100644 include/libweston-desktop/libweston-desktop.h create mode 100644 include/libweston/backend-drm.h create mode 100644 include/libweston/backend-fbdev.h create mode 100644 include/libweston/backend-headless.h create mode 100644 include/libweston/backend-rdp.h create mode 100644 include/libweston/backend-wayland.h create mode 100644 include/libweston/backend-x11.h create mode 100644 include/libweston/config-parser.h create mode 100755 include/libweston/libweston.h create mode 100644 include/libweston/matrix.h create mode 100644 include/libweston/meson.build create mode 100644 include/libweston/plugin-registry.h create mode 100644 include/libweston/version.h.in create mode 100755 include/libweston/weston-log.h create mode 100644 include/libweston/windowed-output-api.h create mode 100644 include/libweston/xwayland-api.h create mode 100644 include/libweston/zalloc.h create mode 100644 include/meson.build create mode 100644 ivi-shell/README create mode 100644 ivi-shell/hmi-controller.c create mode 100644 ivi-shell/ivi-layout-export.h create mode 100644 ivi-shell/ivi-layout-private.h create mode 100644 ivi-shell/ivi-layout-shell.h create mode 100644 ivi-shell/ivi-layout-transition.c create mode 100644 ivi-shell/ivi-layout.c create mode 100644 ivi-shell/ivi-shell.c create mode 100644 ivi-shell/ivi-shell.h create mode 100644 ivi-shell/meson.build create mode 100644 ivi-shell/weston.ini.in create mode 100644 kiosk-shell/kiosk-shell-grab.c create mode 100644 kiosk-shell/kiosk-shell-grab.h create mode 100644 kiosk-shell/kiosk-shell.c create mode 100644 kiosk-shell/kiosk-shell.h create mode 100644 kiosk-shell/meson.build create mode 100644 kiosk-shell/util.c create mode 100644 kiosk-shell/util.h create mode 100644 libweston-desktop/client.c create mode 100644 libweston-desktop/internal.h create mode 100755 libweston-desktop/libweston-desktop.c create mode 100644 libweston-desktop/meson.build create mode 100644 libweston-desktop/seat.c create mode 100644 libweston-desktop/surface.c create mode 100644 libweston-desktop/wl-shell.c create mode 100644 libweston-desktop/xdg-shell-v6.c create mode 100644 libweston-desktop/xdg-shell.c create mode 100644 libweston-desktop/xwayland.c create mode 100644 libweston/animation.c create mode 100755 libweston/backend-drm/auth/wayland_drm_auth.h create mode 100755 libweston/backend-drm/auth/wayland_drm_auth_server.c create mode 100755 libweston/backend-drm/auth/wayland_drm_auth_server.h create mode 100644 libweston/backend-drm/drm-gbm.c create mode 100755 libweston/backend-drm/drm-internal.h create mode 100644 libweston/backend-drm/drm-virtual.c create mode 100755 libweston/backend-drm/drm.c create mode 100644 libweston/backend-drm/fb.c create mode 100755 libweston/backend-drm/kms.c create mode 100644 libweston/backend-drm/libbacklight.c create mode 100644 libweston/backend-drm/libbacklight.h create mode 100644 libweston/backend-drm/meson.build create mode 100755 libweston/backend-drm/modes.c create mode 100644 libweston/backend-drm/state-helpers.c create mode 100644 libweston/backend-drm/state-propose.c create mode 100644 libweston/backend-drm/vaapi-recorder.c create mode 100644 libweston/backend-drm/vaapi-recorder.h create mode 100755 libweston/backend-fbdev/fbdev.c create mode 100644 libweston/backend-fbdev/meson.build create mode 100644 libweston/backend-headless/headless.c create mode 100644 libweston/backend-headless/meson.build create mode 100644 libweston/backend-rdp/meson.build create mode 100644 libweston/backend-rdp/rdp.c create mode 100644 libweston/backend-wayland/meson.build create mode 100644 libweston/backend-wayland/wayland.c create mode 100644 libweston/backend-x11/meson.build create mode 100644 libweston/backend-x11/x11.c create mode 100644 libweston/backend.h create mode 100755 libweston/bindings.c create mode 100644 libweston/clipboard.c create mode 100755 libweston/compositor.c create mode 100755 libweston/content-protection.c create mode 100644 libweston/data-device.c create mode 100644 libweston/dbus.c create mode 100644 libweston/dbus.h create mode 100644 libweston/git-version.h create mode 100644 libweston/git-version.h.meson create mode 100644 libweston/input.c create mode 100755 libweston/launcher-direct.c create mode 100644 libweston/launcher-impl.h create mode 100644 libweston/launcher-logind.c create mode 100644 libweston/launcher-util.c create mode 100644 libweston/launcher-util.h create mode 100644 libweston/launcher-weston-launch.c create mode 100644 libweston/libinput-device.c create mode 100644 libweston/libinput-device.h create mode 100644 libweston/libinput-seat.c create mode 100644 libweston/libinput-seat.h create mode 100755 libweston/libweston-internal.h create mode 100644 libweston/linux-dmabuf.c create mode 100644 libweston/linux-dmabuf.h create mode 100644 libweston/linux-explicit-synchronization.c create mode 100644 libweston/linux-explicit-synchronization.h create mode 100644 libweston/linux-sync-file-uapi.h create mode 100644 libweston/linux-sync-file.c create mode 100644 libweston/linux-sync-file.h create mode 100644 libweston/log.c create mode 100644 libweston/meson.build create mode 100644 libweston/noop-renderer.c create mode 100755 libweston/pixel-formats.c create mode 100644 libweston/pixel-formats.h create mode 100644 libweston/pixman-renderer-protected.h create mode 100755 libweston/pixman-renderer.c create mode 100644 libweston/pixman-renderer.h create mode 100644 libweston/plugin-registry.c create mode 100644 libweston/renderer-gl/egl-glue.c create mode 100644 libweston/renderer-gl/gl-renderer-internal.h create mode 100755 libweston/renderer-gl/gl-renderer.c create mode 100755 libweston/renderer-gl/gl-renderer.h create mode 100644 libweston/renderer-gl/meson.build create mode 100644 libweston/screenshooter.c create mode 100644 libweston/spring-tool.c create mode 100644 libweston/tde-render-part.c create mode 100644 libweston/tde-render-part.h create mode 100644 libweston/timeline.c create mode 100644 libweston/timeline.h create mode 100644 libweston/touch-calibration.c create mode 100644 libweston/version.h create mode 100644 libweston/vertex-clipping.c create mode 100644 libweston/vertex-clipping.h create mode 100644 libweston/weston-direct-display.c create mode 100644 libweston/weston-launch.c create mode 100644 libweston/weston-launch.h create mode 100644 libweston/weston-log-file.c create mode 100644 libweston/weston-log-flight-rec.c create mode 100644 libweston/weston-log-internal.h create mode 100644 libweston/weston-log-wayland.c create mode 100644 libweston/weston-log.c create mode 100644 libweston/zoom.c create mode 100644 man/meson.build create mode 100644 man/weston-bindings.man create mode 100644 man/weston-debug.man create mode 100644 man/weston-drm.man create mode 100644 man/weston-rdp.man create mode 100644 man/weston.ini.man create mode 100644 man/weston.man create mode 100644 meson.build create mode 100644 meson_options.txt create mode 100644 notes.txt create mode 100644 pipewire/meson.build create mode 100644 pipewire/pipewire-plugin.c create mode 100644 pipewire/pipewire-plugin.h create mode 100755 protocol/drm-auth.xml create mode 100644 protocol/ivi-application.xml create mode 100644 protocol/ivi-hmi-controller.xml create mode 100644 protocol/meson.build create mode 100644 protocol/text-cursor-position.xml create mode 100644 protocol/weston-content-protection.xml create mode 100644 protocol/weston-debug.xml create mode 100644 protocol/weston-desktop-shell.xml create mode 100644 protocol/weston-direct-display.xml create mode 100644 protocol/weston-screenshooter.xml create mode 100644 protocol/weston-test.xml create mode 100644 protocol/weston-touch-calibration.xml create mode 100644 releasing.md create mode 100644 remoting/README create mode 100644 remoting/meson.build create mode 100644 remoting/remoting-plugin.c create mode 100644 remoting/remoting-plugin.h 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/fd-util.h create mode 100644 shared/file-util.c create mode 100644 shared/file-util.h create mode 100644 shared/frame.c create mode 100644 shared/helpers.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/meson.build 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/platform.h create mode 100644 shared/string-helpers.h create mode 100644 shared/timespec-util.h create mode 100644 shared/weston-egl-ext.h create mode 100644 shared/xalloc.c create mode 100644 shared/xalloc.h create mode 100644 test-qc.c create mode 100644 tests/bad-buffer-test.c create mode 100644 tests/buffer-transforms-test.c create mode 100644 tests/config-parser-test.c create mode 100644 tests/devices-test.c create mode 100644 tests/drm-smoke-test.c create mode 100644 tests/event-test.c create mode 100644 tests/input-timestamps-helper.c create mode 100644 tests/input-timestamps-helper.h create mode 100644 tests/internal-screenshot-test.c create mode 100644 tests/internal-screenshot.ini create mode 100644 tests/ivi-layout-internal-test.c create mode 100644 tests/ivi-layout-test-client.c create mode 100644 tests/ivi-layout-test-plugin.c create mode 100644 tests/ivi-shell-app-test.c create mode 100644 tests/ivi-test.h create mode 100644 tests/keyboard-test.c create mode 100644 tests/linux-explicit-synchronization-test.c create mode 100644 tests/matrix-test.c create mode 100644 tests/meson.build create mode 100644 tests/output-transforms-test.c create mode 100644 tests/plugin-registry-test.c create mode 100644 tests/pointer-test.c create mode 100644 tests/presentation-test.c create mode 100644 tests/reference/basic-test-card.png create mode 100644 tests/reference/internal-screenshot-bad-00.png create mode 100644 tests/reference/internal-screenshot-good-00.png create mode 100644 tests/reference/output_1-180_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_1-180_buffer_2-90-00.png create mode 100644 tests/reference/output_1-270_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_1-270_buffer_2-90-00.png create mode 100644 tests/reference/output_1-90_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_1-90_buffer_2-90-00.png create mode 100644 tests/reference/output_1-FLIPPED_180_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_1-FLIPPED_180_buffer_2-90-00.png create mode 100644 tests/reference/output_1-FLIPPED_270_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_1-FLIPPED_270_buffer_2-90-00.png create mode 100644 tests/reference/output_1-FLIPPED_90_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_1-FLIPPED_90_buffer_2-90-00.png create mode 100644 tests/reference/output_1-FLIPPED_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_1-FLIPPED_buffer_2-90-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_1-180-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_1-270-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_1-90-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_1-FLIPPED-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_1-FLIPPED_180-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_1-FLIPPED_270-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_1-FLIPPED_90-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_2-180-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_2-90-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_2-FLIPPED-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_2-NORMAL-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_3-FLIPPED_90-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_3-NORMAL-00.png create mode 100644 tests/reference/output_2-180_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_2-180_buffer_2-90-00.png create mode 100644 tests/reference/output_2-90_buffer_1-180-00.png create mode 100644 tests/reference/output_2-90_buffer_1-270-00.png create mode 100644 tests/reference/output_2-90_buffer_1-90-00.png create mode 100644 tests/reference/output_2-90_buffer_1-FLIPPED-00.png create mode 100644 tests/reference/output_2-90_buffer_1-FLIPPED_180-00.png create mode 100644 tests/reference/output_2-90_buffer_1-FLIPPED_270-00.png create mode 100644 tests/reference/output_2-90_buffer_1-FLIPPED_90-00.png create mode 100644 tests/reference/output_2-90_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_2-90_buffer_2-180-00.png create mode 100644 tests/reference/output_2-90_buffer_2-90-00.png create mode 100644 tests/reference/output_2-90_buffer_2-FLIPPED-00.png create mode 100644 tests/reference/output_2-90_buffer_2-NORMAL-00.png create mode 100644 tests/reference/output_2-90_buffer_3-FLIPPED_90-00.png create mode 100644 tests/reference/output_2-90_buffer_3-NORMAL-00.png create mode 100644 tests/reference/output_2-FLIPPED_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_2-FLIPPED_buffer_2-90-00.png create mode 100644 tests/reference/output_2-NORMAL_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_2-NORMAL_buffer_2-90-00.png create mode 100644 tests/reference/output_3-FLIPPED_270_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_3-FLIPPED_270_buffer_2-90-00.png create mode 100644 tests/reference/output_3-NORMAL_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_3-NORMAL_buffer_2-90-00.png create mode 100644 tests/reference/subsurface_z_order-00.png create mode 100644 tests/reference/subsurface_z_order-01.png create mode 100644 tests/reference/subsurface_z_order-02.png create mode 100644 tests/reference/subsurface_z_order-03.png create mode 100644 tests/reference/subsurface_z_order-04.png create mode 100644 tests/reference/viewport_upscale_solid-00.png create mode 100644 tests/roles-test.c create mode 100644 tests/setbacklight.c create mode 100644 tests/string-test.c create mode 100644 tests/subsurface-shot-test.c create mode 100644 tests/subsurface-test.c create mode 100644 tests/surface-global-test.c create mode 100644 tests/surface-screenshot-test.c create mode 100644 tests/surface-test.c create mode 100644 tests/text-test.c create mode 100644 tests/timespec-test.c create mode 100644 tests/touch-test.c create mode 100644 tests/vertex-clip-test.c create mode 100644 tests/viewporter-shot-test.c create mode 100644 tests/viewporter-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-desktop-shell.c create mode 100644 tests/weston-test-fixture-compositor.c create mode 100644 tests/weston-test-fixture-compositor.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 100644 tests/weston-testsuite-data.h create mode 100644 tests/xwayland-test.c create mode 100644 tools/zunitc/doc/zunitc.dox create mode 100644 tools/zunitc/inc/zunitc/zunitc.h create mode 100644 tools/zunitc/inc/zunitc/zunitc_impl.h create mode 100644 tools/zunitc/src/main.c create mode 100644 tools/zunitc/src/zuc_base_logger.c create mode 100644 tools/zunitc/src/zuc_base_logger.h create mode 100644 tools/zunitc/src/zuc_collector.c create mode 100644 tools/zunitc/src/zuc_collector.h create mode 100644 tools/zunitc/src/zuc_context.h create mode 100644 tools/zunitc/src/zuc_event.h create mode 100644 tools/zunitc/src/zuc_event_listener.h create mode 100644 tools/zunitc/src/zuc_junit_reporter.c create mode 100644 tools/zunitc/src/zuc_junit_reporter.h create mode 100644 tools/zunitc/src/zuc_types.h create mode 100644 tools/zunitc/src/zunitc_impl.c create mode 100644 tools/zunitc/test/fixtures_test.c create mode 100644 tools/zunitc/test/zunitc_test.c create mode 100644 wcap/README create mode 100644 wcap/main.c create mode 100644 wcap/meson.build create mode 100644 wcap/wcap-decode.c create mode 100644 wcap/wcap-decode.h create mode 100644 weston.ini create mode 100644 weston.ini.in create mode 100644 weston.rc create mode 100644 xwayland/dnd.c create mode 100644 xwayland/hash.c create mode 100644 xwayland/hash.h create mode 100644 xwayland/launcher.c create mode 100644 xwayland/meson.build create mode 100644 xwayland/selection.c create mode 100644 xwayland/window-manager.c create mode 100644 xwayland/xwayland-internal-interface.h create mode 100644 xwayland/xwayland.h diff --git a/BUILD.gn b/BUILD.gn new file mode 100755 index 0000000..f2453a7 --- /dev/null +++ b/BUILD.gn @@ -0,0 +1,541 @@ +import("//build/ohos.gni") +import("//third_party/wayland_standard/wayland_protocol.gni") + + +this is test for ljqc 2021-07-21 + + +## Generate Wayland Protocols {{{ +wayland_protocol("text_cursor_position_protocol") { + sources = [ "protocol/text-cursor-position.xml" ] +} +wayland_protocol("weston_content_protection_protocol") { + sources = [ "protocol/weston-content-protection.xml" ] +} +wayland_protocol("weston_touch_calibration_protocol") { + sources = [ "protocol/weston-touch-calibration.xml" ] +} +wayland_protocol("weston_direct_display_protocol") { + sources = [ "protocol/weston-direct-display.xml" ] +} +wayland_protocol("weston_screenshooter_protocol") { + sources = [ "protocol/weston-screenshooter.xml" ] +} +wayland_protocol("ivi_application_protocol") { + sources = [ "protocol/ivi-application.xml" ] +} +wayland_protocol("drm_auth_protocol") { + sources = [ "protocol/drm-auth.xml" ] +} + +## Generate Wayland Protocols }}} + +## Build libshared.a {{{ +config("libshared_config") { + visibility = [ ":*" ] + + include_dirs = [ + "include", + "//third_party/wayland_standard/src", + "//third_party/wayland-ivi-extension_standard/include", + ] + + cflags = [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + ] +} + +config("libshared_public_config") { + include_dirs = [ "shared" ] + + cflags = [] +} + +ohos_static_library("libshared") { + sources = [ + "shared/config-parser.c", + "shared/file-util.c", + "shared/matrix.c", + "shared/option-parser.c", + "shared/os-compatibility.c", + ] + + configs = [ ":libshared_config" ] + + public_configs = [ ":libshared_public_config" ] + + deps = [] + + public_deps = [ "//third_party/wayland_standard:libwayland_server" ] +} + +## Build libshared.a }}} + +## Build libweston.so {{{ +config("libweston_config") { + visibility = [ ":*" ] + + include_dirs = [ + "//third_party/weston/include", + "libweston", + "//third_party/wayland_standard/src", + "//third_party/wayland-ivi-extension_standard/include", + "//third_party/weston", + ] + + cflags = [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wno-unused-function", + "-Wno-missing-field-initializers", + "-Wno-unused-variable", + "-Wno-implicit-function-declaration", + ] +} + +config("libweston_public_config") { + include_dirs = [ + "include", + "libweston/backend-drm", + "//third_party/bounds_checking_function/include", + ] + + cflags = [] +} + +ohos_shared_library("libweston") { + sources = [ + "libweston/animation.c", + "libweston/bindings.c", + "libweston/clipboard.c", + "libweston/compositor.c", + "libweston/content-protection.c", + "libweston/data-device.c", + "libweston/input.c", + "libweston/launcher-direct.c", + "libweston/launcher-util.c", + "libweston/launcher-weston-launch.c", + "libweston/linux-dmabuf.c", + "libweston/linux-explicit-synchronization.c", + "libweston/linux-sync-file.c", + "libweston/pixel-formats.c", + "libweston/pixman-renderer.c", + "libweston/plugin-registry.c", + "libweston/screenshooter.c", + "libweston/tde-render-part.c", + "libweston/touch-calibration.c", + "libweston/vertex-clipping.c", + "libweston/weston-direct-display.c", + "libweston/zoom.c", + ] + + configs = [ + ":libweston_config", + ":display_gfx_public_config", + ] + + public_configs = [ + ":libweston_public_config", + ":drm-backend_public_config", + ] + + deps = [ + ":libdisplay_gfx", + "//foundation/graphic/standard/prebuilts/librarys/display_gralloc:libdisplay_gralloc", + "//third_party/libdrm:libdrm", + "//third_party/libinput:libinput-third", + "//utils/native/base:utils", + ] + + public_deps = [ + ":libshared", + ":text_cursor_position_protocol", + ":weston_content_protection_protocol", + ":weston_direct_display_protocol", + ":weston_touch_calibration_protocol", + "//base/hiviewdfx/hilog/interfaces/native/innerkits:libhilog", + "//third_party/libxkbcommon:libxkbcommon", + "//third_party/pixman:libpixman", + "//third_party/wayland-protocols_standard:input_timestamps_unstable_v1_protocol", + "//third_party/wayland-protocols_standard:linux_dmabuf_unstable_v1_protocol", + "//third_party/wayland-protocols_standard:linux_explicit_synchronization_unstable_v1_protocol", + "//third_party/wayland-protocols_standard:pointer_constraints_unstable_v1_protocol", + "//third_party/wayland-protocols_standard:presentation_time_protocol", + "//third_party/wayland-protocols_standard:relative_pointer_unstable_v1_protocol", + "//third_party/wayland-protocols_standard:viewporter_protocol", + "//third_party/wayland-protocols_standard:xdg_output_unstable_v1_protocol", + ] + + part_name = "graphic_standard" + subsystem_name = "graphic" +} + +## Build libweston.so }}} + +## Build libexec_weston.a {{{ +config("libexec_weston_config") { + visibility = [ ":*" ] + + include_dirs = [ + "libweston", + ".", + ] + + cflags = [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wno-missing-field-initializers", + "-Wno-unused-variable", + ] +} + +config("libexec_weston_public_config") { + include_dirs = [] + + cflags = [] +} + +ohos_static_library("libexec_weston") { + sources = [ + "compositor/main.c", + "compositor/text-backend.c", + "compositor/weston-screenshooter.c", + ] + + configs = [ ":libexec_weston_config" ] + + public_configs = [ ":libexec_weston_public_config" ] + + deps = [ + ":weston_screenshooter_protocol", + "//third_party/libinput:libinput-third", + "//third_party/wayland-protocols_standard:input_method_unstable_v1_protocol", + "//third_party/wayland-protocols_standard:text_input_unstable_v1_protocol", + ] + + public_deps = [ + ":libweston", + "//third_party/wayland_standard:wayland_core_protocol", + ] +} + +## Build libexec_weston.a }}} + +## Build weston {{{ +config("weston_config") { + visibility = [ ":*" ] + + include_dirs = [] + + cflags = [ + "-Wall", + "-Werror", + ] +} + +config("weston_public_config") { + include_dirs = [] + + cflags = [] +} + +ohos_executable("weston") { + install_enable = true + + sources = [ "compositor/executable.c" ] + + configs = [ ":weston_config" ] + + public_configs = [ ":weston_public_config" ] + + deps = [ ":libexec_weston" ] + + public_deps = [] + + subsystem_name = "graphic" + part_name = "graphic_standard" +} + +## Build weston }}} + +## Build libweston-desktop.so {{{ +config("libweston-desktop_config") { + visibility = [ ":*" ] + + include_dirs = [ "." ] + + cflags = [ + "-Wall", + "-Werror", + "-Wno-unused-function", + ] +} + +config("libweston-desktop_public_config") { + include_dirs = [ "include/libweston-desktop" ] + + cflags = [] +} + +ohos_shared_library("libweston-desktop") { + sources = [ + "libweston-desktop/client.c", + "libweston-desktop/libweston-desktop.c", + "libweston-desktop/seat.c", + "libweston-desktop/surface.c", + "libweston-desktop/wl-shell.c", + "libweston-desktop/xdg-shell-v6.c", + "libweston-desktop/xdg-shell.c", + ] + + configs = [ ":libweston-desktop_config" ] + + public_configs = [ ":libweston-desktop_public_config" ] + + deps = [] + + public_deps = [ + ":libexec_weston", + "//third_party/wayland-protocols_standard:xdg_shell_protocol", + "//third_party/wayland-protocols_standard:xdg_shell_unstable_v6_protocol", + ] + + part_name = "graphic_standard" + subsystem_name = "graphic" +} + +## Build libweston-desktop.so }}} + +## Build ivi-shell.so {{{ +config("ivi-shell_config") { + visibility = [ ":*" ] + + include_dirs = [ "." ] + + cflags = [ + "-Wall", + "-Werror", + "-Wno-unused-function", + ] +} + +config("ivi-shell_public_config") { + include_dirs = [] + + cflags = [] +} + +ohos_shared_library("ivi-shell") { + sources = [ + "ivi-shell/ivi-layout-transition.c", + "ivi-shell/ivi-layout.c", + "ivi-shell/ivi-shell.c", + ] + + configs = [ ":ivi-shell_config" ] + + public_configs = [ ":ivi-shell_public_config" ] + + deps = [ + ":ivi_application_protocol", + ":libweston-desktop", + "//third_party/wayland_standard:wayland_core_protocol", + ] + + public_deps = [] + + subsystem_name = "graphic" + part_name = "graphic_standard" +} + +## Build ivi-shell.so }}} + +## Build drm-backend.so {{{ +config("drm-backend_config") { + visibility = [ ":*" ] + + include_dirs = [ + "include", + "libweston", + "libweston/backend-drm/auth", + ".", + ] + + cflags = [ + "-Wall", + "-Wno-error", + "-Wno-unused-parameter", + "-Wno-missing-field-initializers", + "-Wno-implicit-function-declaration", + "-Wno-unused-variable", + "-Wno-unused-function", + "-Wno-sometimes-uninitialized", + ] +} + +config("drm-backend_public_config") { + include_dirs = [] + + cflags = [] +} + +ohos_executable("simple-preasenation") { + sources = [ + "clients/presentation-shm.c", + # "libweston/backend-drm/drm-gbm.c", + ] + + configs = [ ":drm-backend_config" ] + + public_configs = [ ":drm-backend_public_config" ] + + deps = [ + "//third_party/wayland-protocols_standard:presentation_time_protocol", + "//third_party/wayland_standard:libwayland_client", + "//third_party/wayland_standard:libwayland_cursor", + "//third_party/wayland_standard:wayland_core_protocol", + "//third_party/weston:ivi_application_protocol", + ] + + part_name = "graphic_standard" + subsystem_name = "graphic" +} + +ohos_shared_library("drm-backend") { + sources = [ + "libweston/backend-drm/auth/wayland_drm_auth_server.c", + "libweston/backend-drm/drm.c", + "libweston/backend-drm/fb.c", + "libweston/backend-drm/kms.c", + "libweston/backend-drm/libbacklight.c", + "libweston/backend-drm/modes.c", + "libweston/backend-drm/state-helpers.c", + "libweston/backend-drm/state-propose.c", + "libweston/libinput-device.c", + "libweston/libinput-seat.c", + + # "libweston/backend-drm/drm-gbm.c", + ] + + configs = [ ":drm-backend_config" ] + + public_configs = [ ":drm-backend_public_config" ] + + deps = [ + ":drm_auth_protocol", + ":libweston", + "//third_party/libdrm:libdrm", + "//third_party/libinput:libinput-third", + "//third_party/wayland-protocols_standard:linux_dmabuf_unstable_v1_protocol", + "//third_party/wayland-protocols_standard:presentation_time_protocol", + "//third_party/wayland-protocols_standard:relative_pointer_unstable_v1_protocol", + "//third_party/wayland_standard:wayland_core_protocol", + ] + + public_deps = [] + + part_name = "graphic_standard" + subsystem_name = "graphic" +} + +## Build drm-backend.so }}} + +## Install weston.ini to /system/etc/weston.ini {{{ +ohos_prebuilt_etc("weston.ini") { + source = "weston.ini" + part_name = "graphic_standard" + subsystem_name = "graphic" +} + +## Install weston.ini to /system/etc/weston.ini }}} + +## Install weston.rc to /system/etc/init/weston.rc {{{ +ohos_prebuilt_etc("weston.rc") { + source = "weston.rc" + relative_install_dir = "init" + part_name = "graphic_standard" + subsystem_name = "graphic" +} + +## Install weston.rc to /system/etc/init/weston.rc }}} + +# gl-renderer.so waiting for libEGL & libGLESv2 +### Build gl-renderer.so {{{ +#config("gl-renderer_config") { +# visibility = [ ":*" ] +# +# include_dirs = [ +# "//third_party/weston", +# "//third_party/wayland_standard/egl", +# "//third_party/weston/libweston", +# "//third_party/wayland-ivi-extension_standard/include", +# ] +# +# cflags = [ +# "-Wno-return-type", +# "-Wno-visibility", +# "-Wno-unused-function", +# ] +#} +# +#config("gl-renderer_public_config") { +# include_dirs = [ +# ] +# +# cflags = [ +# ] +#} +# +#ohos_shared_library("gl-renderer") { +# sources = [ +# "libweston/renderer-gl/egl-glue.c", +# "libweston/renderer-gl/gl-renderer.c", +# "libweston/vertex-clipping.c", +# ] +# +# configs = [ +# ":gl-renderer_config", +# ] +# +# public_configs = [ +# ":gl-renderer_public_config", +# ] +# +# deps = [ +# ":libweston", +# "//third_party/wayland-protocols_standard:linux_dmabuf_unstable_v1_protocol", +# ] +# +# asdk_deps = [ +# "shared_library:libEGL", +# "shared_library:libGLESv2", +# ] +# +# public_deps = [ +# ] +# +# subsystem_name = "graphic" +# part_name = "graphic_standard" +#} +### Build gl-renderer.so }}} + +## Prebuilt libdisplay_gfx.so {{{ +config("display_gfx_public_config") { + include_dirs = [ "//drivers/peripheral/display/interfaces/include" ] + + libs = [ "//device/hisilicon/hardware/display/libs/hispark_taurus/ext/libdisplay_gfx.z.so" ] +} + +ohos_prebuilt_shared_library("libdisplay_gfx") { + source = "//device/hisilicon/hardware/display/libs/hispark_taurus/ext/libdisplay_gfx.z.so" + + public_configs = [ ":display_gfx_public_config" ] + + part_name = "graphic_standard" + subsystem_name = "graphic" +} +## Prebuilt libdisplay_gfx.so }}} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..98fa565 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,372 @@ +Contributing to Weston +======================= + +Finding something to work on +---------------------------- + +Weston's development is [tracked on GitLab](https://gitlab.freedesktop.org/wayland/weston). +In addition to reviewing code submissions (see below), we use the issue tracker +to discuss both bugfixes and development of new features. + +The '[good for new contributors](https://gitlab.freedesktop.org/wayland/weston/issues?label_name%5B%5D=Good+for+new+contributors)' +label is used for issues the development team thinks are a good place to begin +working on Weston. These issues cover features or bugfixes which are small, +self-contained, don't require much specific background knowledge, and aren't +blocked by more complex work. + +If you have picked an issue you would like to work on, you may want to mention +in the issue tracker that you would like to pick it up. You can also discuss +it with the developers in the issue tracker, or on the +[mailing list](https://lists.freedesktop.org/mailman/listinfo/wayland-devel). +Many developers also use IRC through [Freenode](https://freenode.net)'s +`#wayland` channel; however you may need to wait some time for a response on +IRC, which requires keeping your client connected. If you cannot stay for a +long time (potentially some hours due to timezone differences), then you +may want to send your question to the list or issue tracker instead. + + +Sending patches +--------------- + +Patches should be sent via +[GitLab merge requests](https://docs.gitlab.com/ce/gitlab-basics/add-merge-request.html). +Weston is +[hosted on freedesktop.org's GitLab](https://gitlab.freedesktop.org/wayland/weston/): +in order to submit code, you should create an account on this GitLab instance, +fork the core Weston repository, push your changes to a branch in your new +repository, and then submit these patches for review through a merge request. + +Weston formerly accepted patches via `git-send-email`, sent to +**wayland-devel\@lists.freedesktop.org**; these were +[tracked using Patchwork](https://patchwork.freedesktop.org/projects/wayland/). +Some old patches continue to be sent this way, and we may accept small new +patches sent to the list, but please send all new patches through GitLab merge +requests. + +Formatting and separating commits +--------------------------------- + +Unlike many projects using GitHub and GitLab, Weston has a +[linear, 'recipe' style history](http://www.bitsnbites.eu/git-history-work-log-vs-recipe/). +This means that every commit should be small, digestible, stand-alone, and +functional. Rather than a purely chronological commit history like this: + + doc: final docs for view transforms + fix tests when disabled, redo broken doc formatting + better transformed-view iteration (thanks Hannah!) + try to catch more cases in tests + tests: add new spline test + fix compilation on splines + doc: notes on reticulating splines + compositor: add spline reticulation for view transforms + +we aim to have a clean history which only reflects the final state, broken up +into functional groupings: + + compositor: add spline reticulation for view transforms + compositor: new iterator for view transforms + tests: add view-transform correctness tests + doc: fix Doxygen formatting for view transforms + +This ensures that the final patch series only contains the final state, +without the changes and missteps taken along the development process. + +The first line of a commit message should contain a prefix indicating +what part is affected by the patch followed by one sentence that +describes the change. For examples: + + compositor-drm: Support modifiers for drm_fb + +and + + input: do not forward unmatched touch-ups + +If in doubt what prefix to use, look at other commits that change the +same file(s) as the patch being sent. + +The body of the commit message should describe what the patch changes +and why, and also note any particular side effects. This shouldn't be +empty on most of the cases. It shouldn't take a lot of effort to write +a commit message for an obvious change, so an empty commit message +body is only acceptable if the questions "What?" and "Why?" are already +answered on the one-line summary. + +The lines of the commit message should have at most 76 characters, to +cope with the way git log presents them. + +See [notes on commit messages] for a recommended reading on writing commit +messages. + +Your patches should also include a Signed-off-by line with your name and +email address which indicates that you agree to the +[Developer's Certificate of Origin 1.1](DCO-1.1.txt). +If you're not the patch's original author, you should +also gather S-o-b's by them (and/or whomever gave the patch to you.) The +significance of this is that it certifies that you created the patch, +that it was created under an appropriate open source license, or +provided to you under those terms. This lets us indicate a chain of +responsibility for the copyright status of the code. + +We won't reject patches that lack S-o-b, but it is strongly recommended. + +When you re-send patches, revised or not, it would be very good to document the +changes compared to the previous revision in the commit message and/or the +merge request. If you have already received Reviewed-by or Acked-by tags, you +should evaluate whether they still apply and include them in the respective +commit messages. Otherwise the tags may be lost, reviewers miss the credit they +deserve, and the patches may cause redundant review effort. + + +Tracking patches and following up +--------------------------------- + +Once submitted to GitLab, your patches will be reviewed by the Weston +development team on GitLab. Review may be entirely positive and result in your +code landing instantly, in which case, great! You're done. However, we may ask +you to make some revisions: fixing some bugs we've noticed, working to a +slightly different design, or adding documentation and tests. + +If you do get asked to revise the patches, please bear in mind the notes above. +You should use `git rebase -i` to make revisions, so that your patches follow +the clear linear split documented above. Following that split makes it easier +for reviewers to understand your work, and to verify that the code you're +submitting is correct. + +A common request is to split single large patch into multiple patches. This can +happen, for example, if when adding a new feature you notice a bug in Weston's +core which you need to fix to progress. Separating these changes into separate +commits will allow us to verify and land the bugfix quickly, pushing part of +your work for the good of everyone, whilst revision and discussion continues on +the larger feature part. It also allows us to direct you towards reviewers who +best understand the different areas you are working on. + +When you have made any requested changes, please rebase the commits, verify +that they still individually look good, then force-push your new branch to +GitLab. This will update the merge request and notify everyone subscribed to +your merge request, so they can review it again. + +There are also +[many GitLab CLI clients](https://about.gitlab.com/applications/#cli-clients), +if you prefer to avoid the web interface. It may be difficult to follow review +comments without using the web interface though, so we do recommend using this +to go through the review process, even if you use other clients to track the +list of available patches. + + +Coding style +------------ + +You should follow the style of the file you're editing. In general, we +try to follow the rules below. + +**Note: this file uses spaces due to markdown rendering issues for tabs. + Code must be indented using tabs.** + +- indent with tabs, and a tab is always 8 characters wide +- opening braces are on the same line as the if statement; +- no braces in an if-body with just one statement; +- if one of the branches of an if-else condition has braces, then the + other branch should also have braces; +- there is always an empty line between variable declarations and the + code; + +```c +static int +my_function(void) +{ + int a = 0; + + if (a) + b(); + else + c(); + + if (a) { + b(); + c(); + } else { + d(); + } +} +``` + +- lines should be less than 80 characters wide; +- when breaking lines with functions calls, the parameters are aligned + with the opening parentheses; +- when assigning a variable with the result of a function call, if the + line would be longer we break it around the equal '=' sign if it makes + sense; + +```c + long_variable_name = + function_with_a_really_long_name(parameter1, parameter2, + parameter3, parameter4); + + x = function_with_a_really_long_name(parameter1, parameter2, + parameter3, parameter4); +``` + +Conduct +======= + +As a freedesktop.org project, Wayland follows the Contributor Covenant, +found at: +https://www.freedesktop.org/wiki/CodeOfConduct + +Please conduct yourself in a respectful and civilised manner when +interacting with community members on mailing lists, IRC, or bug +trackers. The community represents the project as a whole, and abusive +or bullying behaviour is not tolerated by the project. + + +Licensing +========= + +Weston is licensed with the intention to be usable anywhere X.org is. +Originally, X.org was covered under the MIT X11 license, but changed to +the MIT Expat license. Similarly, Weston was covered initially as MIT +X11 licensed, but changed to the MIT Expat license, following in X.org's +footsteps. Other than wording, the two licenses are substantially the +same, with the exception of a no-advertising clause in X11 not included +in Expat. + +New source code files should specify the MIT Expat license in their +boilerplate, as part of the copyright statement. + + +Review +====== + +All patches, even trivial ones, require at least one positive review +(Reviewed-by). Additionally, if no Reviewed-by's have been given by +people with commit access, there needs to be at least one Acked-by from +someone with commit access. A person with commit access is expected to be +able to evaluate the patch with respect to the project scope and architecture. + +The below review guidelines are intended to be interpreted in spirit, not by +the letter. There may be circumstances where some guidelines are better +ignored. We rely very much on the judgement of reviewers and commit rights +holders. + +During review, the following matters should be checked: + +- The commit message explains why the change is being made. + +- The code fits the project's scope. + +- The code license is the same MIT licence the project generally uses. + +- Stable ABI or API is not broken. + +- Stable ABI or API additions must be justified by actual use cases, not only +by speculation. They must also be documented, and it is strongly recommended to +include tests exercising the additions in the test suite. + +- The code fits the existing software architecture, e.g. no layering +violations. + +- The code is correct and does not introduce new failures for existing users, +does not add new corner-case bugs, and does not introduce new compiler +warnings. + +- The patch does what it says in the commit message and changes nothing else. + +- The patch is a single logical change. If the commit message addresses +multiple points, it is a hint that the commit might need splitting up. + +- A bug fix should target the underlying root cause instead of hiding symptoms. +If a complete fix is not practical, partial fixes are acceptable if they come +with code comments and filed Gitlab issues for the remaining bugs. + +- The bug root cause rule applies to external software components as well, e.g. +do not work around kernel driver issues in userspace. + +- The test suite passes. + +- The code does not depend on API or ABI which has no working free open source +implementation. + +- The code is not dead or untestable. E.g. if there are no free open source +software users for it then it is effectively dead code. + +- The code is written to be easy to understand, or if code cannot be clear +enough on its own there are code comments to explain it. + +- The code is minimal, i.e. prefer refactor and re-use when possible unless +clarity suffers. + +- The code adheres to the style guidelines. + +- In a patch series, every intermediate step adheres to the above guidelines. + + +Commit rights +============= + +Commit rights will be granted to anyone who requests them and fulfills the +below criteria: + +- Submitted some (10 as a rule of thumb) non-trivial (not just simple + spelling fixes and whitespace adjustment) patches that have been merged + already. + +- Are actively participating in public discussions about their work (on the + mailing list or IRC). This should not be interpreted as a requirement to + review other peoples patches but just make sure that patch submission isn't + one-way communication. Cross-review is still highly encouraged. + +- Will be regularly contributing further patches. This includes regular + contributors to other parts of the open source graphics stack who only + do the occasional development in this project. + +- Agrees to use their commit rights in accordance with the documented merge + criteria, tools, and processes. + +To apply for commit rights, create a new issue in gitlab for the respective +project and give it the "accounts" label. + +Committers are encouraged to request their commit rights get removed when they +no longer contribute to the project. Commit rights will be reinstated when they +come back to the project. + +Maintainers and committers should encourage contributors to request commit +rights, especially junior contributors tend to underestimate their skills. + + +Stabilising for releases +======================== + +A release cycle ends with a stable release which also starts a new cycle and +lifts any code freezes. Gradual code freezing towards a stable release starts +with an alpha release. The release stages of a cycle are: + +- **Alpha release**: + Signified by version number #.#.91. + Major features must have landed before this. Major features include + invasive code motion and refactoring, high risk changes, and new stable + library ABI. + +- **Beta release**: + Signified by version number #.#.92. + Minor features must have landed before this. Minor features include all + new features that are not major, low risk changes, clean-ups, and + documentation. Stable ABI that was new in the alpha release can be removed + before a beta release if necessary. + +- **Release candidates (RC)**: + Signified by version number #.#.93 and up to #.#.99. + Bug fixes that are not release critical must have landed before this. + Release critical bug fixes can still be landed after this, but they may + call for another RC. + +- **Stable release**: + Signified by version number #.#.0. + Ideally no changes since the last RC. + +Mind that version #.#.90 is never released. It is used during development when +no code freeze is in effect. Stable branches and point releases are not covered +by the above. + + +[git documentation]: http://git-scm.com/documentation +[notes on commit messages]: http://who-t.blogspot.de/2009/12/on-commit-messages.html diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..fa4a8c4 --- /dev/null +++ b/COPYING @@ -0,0 +1,26 @@ + + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +--- + +The above is the version of the MIT "Expat" License used by X.org: + + http://cgit.freedesktop.org/xorg/xserver/tree/COPYING diff --git a/DCO-1.1.txt b/DCO-1.1.txt new file mode 100644 index 0000000..1ffe05c --- /dev/null +++ b/DCO-1.1.txt @@ -0,0 +1,38 @@ +test +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/OAT.xml b/OAT.xml new file mode 100644 index 0000000..90cd95b --- /dev/null +++ b/OAT.xml @@ -0,0 +1,72 @@ + + + + + + COPYING + + + + + + + + + + + + + diff --git a/README.OpenSource b/README.OpenSource new file mode 100644 index 0000000..9d1c806 --- /dev/null +++ b/README.OpenSource @@ -0,0 +1,11 @@ +[ + { + "Name": "weston", + "License": "MIT license", + "License File": "COPYING", + "Version Number": "9.0.0", + "Owner": "lizheng2@huawei.com", + "Upstream URL": "https://wayland.freedesktop.org/releases/weston-9.0.0.tar.xz", + "Description": "Weston is the reference implementation of a Wayland compositor, as well as a useful environment in and of itself." + } +] diff --git a/README.md b/README.md index 25dc963..30da9af 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,361 @@ -# python +测试 +Weston +====== -# Python for OHOS +![screenshot of skeletal Weston desktop](doc/wayland-screenshot.jpg) -#### 介绍 -这个仓库是为了能够在OpenHarmony设备上使用 Python 进行应用程序开发而创建。 -[1. 使用Python开发OpenHarmony设备程序(0-初体验)](https://harmonyos.51cto.com/posts/1887) +Weston is the reference implementation of a Wayland compositor, as well as a +useful environment in and of itself. +Out of the box, Weston provides a very basic desktop, or a full-featured +environment for non-desktop uses such as automotive, embedded, in-flight, +industrial, kiosks, set-top boxes and TVs. It also provides a library allowing +other projects to build their own full-featured environments on top of Weston's +core. -## 以下说明针对lite-python文件夹下的代码 -#### 软件架构 -这个仓库下的 Baseline 是 [MicroPython v1.13](https://github.com/micropython/micropython/tree/v1.13),在 MicroPython 的基础上进行了必要的剪裁以满足 OHOS 上的应用开发需求。 +The core focus of Weston is correctness and reliability. Weston aims to be lean +and fast, but more importantly, to be predictable. Whilst Weston does have known +bugs and shortcomings, we avoid unknown or variable behaviour as much as +possible, including variable performance such as occasional spikes in frame +display time. -#### 编译说明 -1. 编译环境: - 1)OS - Ubuntu 16+ - 2)Make - 3.81+ - 3)Python - 3.8+ -2. 配置交叉编译器: - 1)打开源码根目录中的 Makefile - 2)对变量 CROSS_COMPILE 进行赋值,如:CROSS_COMPILE ?= /home/harmony/gcc_riscv32/bin/riscv32-unknown-elf- -3. 在源码根目录中执行 make +A small suite of example or demo clients are also provided: though they can be +useful in themselves, their main purpose is to be an example or test case for +others building compositors or clients. -#### 使用说明 -1. 将编译得到的库文件 //build/libdtpython.a 拷贝到 //vendor/hisi/hi3861/hi3861/build/libs 目录下,如图: -![输入图片说明](https://images.gitee.com/uploads/images/2020/1130/102742_bea41b9a_8048968.png "82c43e952c89d664c1513719385ef6b12ba1a7.png") -2. 在设备应用中加载 Python 并执行代码 +If you are after a more mainline desktop experience, the +[GNOME](https://www.gnome.org) and [KDE](https://www.kde.org) projects provide +full-featured desktop environments built on the Wayland protocol. Many other +projects also exist providing Wayland clients and desktop environments: you are +not limited to just what you can find in Weston. -``` -#include "dtpython.h" +Reporting issues and contributing +================================= -extern const char* c_test_py; // test.py -extern const char* c_another_py; // another.py +Weston's development is +[hosted on freedesktop.org GitLab](https://gitlab.freedesktop.org/wayland/weston/). +Please also see [the contributing document](CONTRIBUTING.md), which details how +to make code or non-technical contributions to Weston. -static void DTPython_Demo_Entry(void) -{ - printf("[DTPython_Demo] DTPython_Demo_Entry()\n"); +Building Weston +=============== - DTPython_Init(); // 初始化Python环境 +Weston is built using [Meson](https://mesonbuild.com/). Weston often depends +on the current release versions of +[Wayland](https://gitlab.freedesktop.org/wayland/wayland) and +[wayland-protocols](https://cgit.freedesktop.org/wayland/wayland-protocols). - DTPython_RunCode("print(\'Python Code Begin\')"); // 执行Python语句:print('Python Code Begin') - - DTPython_RunCode("s = \'HOS Device Development\'"); // 执行Python语句:s = 'HOS Device Development' - DTPython_RunCode("print(s)"); // 执行Python语句:print(s) +If necessary, the latest Meson can be installed as a user with: - DTPython_RunCode(c_test_py); // 模拟执行Python文件:DTPython_RunFile("test.py"); - DTPython_RunCode(c_another_py); // 模拟执行Python文件:DTPython_RunFile("another.py"); + $ pip3 install --user meson - DTPython_RunCode("print(\'Python Code End\')"); // 执行Python语句:print('Python Code End') +Weston's Meson build does not do autodetection and it defaults to all +features enabled, which means you likely hit missing dependencies on the first +try. If a dependency is avoidable through a build option, the error message +should tell you what option can be used to avoid it. You may need to disable +several features if you want to avoid certain dependencies. - DTPython_Deinit(); // 清理Python环境 -} -``` + $ git clone https://gitlab.freedesktop.org/wayland/weston.git + $ cd weston + $ meson build/ --prefix=... + $ ninja -C build/ install + $ cd .. + +The `meson` command populates the build directory. This step can +fail due to missing dependencies. Any build options you want can be added on +that line, e.g. `meson build/ --prefix=... -Ddemo-clients=false`. All the build +options can be found in the file [meson_options.txt](meson_options.txt). + +Once the build directory has been successfully populated, you can inspect the +configuration with `meson configure build/`. If you need to change an +option, you can do e.g. `meson configure build/ -Ddemo-clients=false`. + +Every push to the Weston master repository and its forks is built using GitLab +CI. [Reading the configuration](.gitlab-ci.yml) may provide a useful example of +how to build and install Weston. + +More [detailed documentation on building Weston](https://wayland.freedesktop.org/building.html) +is available on the Wayland site. There are also more details on +[how to run and write tests](https://wayland.freedesktop.org/testing.html). + +For building the documentation see [weston-doc](#weston-doc). + +Running Weston +============== + +Once Weston is installed, most users can simply run it by typing `weston`. This +will launch Weston inside whatever environment you launch it from: when launched +from a text console, it will take over that console. When launched from inside +an existing Wayland or X11 session, it will start a 'nested' instance of Weston +inside a window in that session. + +Help is available by running `weston --help`, or `man weston`, which will list +the available configuration options and display backends. It can also be +configured through a file on disk; more information on this can be found through +`man weston.ini`. + +In some special cases, such as when running remotely or without logind's session +control, Weston may not be able to run directly from a text console. In these +situations, you can instead execute the `weston-launch` helper, which will gain +privileged access to input and output devices by running as root, then granting +access to the main Weston binary running as your user. Running Weston this way +is not recommended unless necessary. + +Weston-doc +========== + +For documenting weston we use [sphinx](http://www.sphinx-doc.org/en/master/) +together with [breathe](https://breathe.readthedocs.io/en/latest/) that +understands XMLs databases generated by doxygen. So far, this is a compromise +until better tools are available in order to remove the doxygen +dependency. You should be able to install both sphinx and breathe extension +using pip3 command, or your package manager. +Doxygen should be available using your distribution package manager. + +Once those are set-up, run `meson` with `-Ddoc=true` option in order to enable +building the documentation. Installation will place the documentation in the +prefix's path under datadir (i.e., `share/doc`). + +Adding and improving documentation +---------------------------------- + +For re-generating the documentation a special `docs` target has been added. +Although first time you build (and subsequently install) weston, you'll see the +documentation being built, updates to the spinx documentation files or to the +source files will only be updated when using `docs` target! + +Example: + +~~~~ +$ ninja install # generates and installs the documentation +# time passes, hack hack, add doc in sources or rST files +$ ninja install # not sufficient, docs will not be updated +$ ninja docs && ninja install # run 'docs' then install +~~~~ + +Improving/adding documentation can be done by modifying rST files under +`doc/sphinx/` directory or by modifying the source code using doxygen +directives. + +Libweston +========= + +Libweston is an effort to separate the re-usable parts of Weston into +a library. Libweston provides most of the boring and tedious bits of +correctly implementing core Wayland protocols and interfacing with +input and output systems, so that people who just want to write a new +"Wayland window manager" (WM) or a small desktop environment (DE) can +focus on the WM part. + +Libweston was first introduced in Weston 1.12, and is expected to +continue evolving through many Weston releases before it achieves a +stable API and feature completeness. + +Libweston's primary purpose is exporting an API for creating Wayland +compositors. Libweston's secondary purpose is to export the weston_config API +so that third party plugins and helper programs can read `weston.ini` if they +want to. However, these two scopes are orthogonal and independent. At no point +will the compositor functionality use or depend on the weston_config +functionality. + + +API/ABI (in)stability and parallel installability +------------------------------------------------- + +As libweston's API surface is huge, it is impossible to get it right +in one go. Therefore developers reserve the right to break the API/ABI and bump +the major version to signify that. For git snapshots of the master branch, the +API/ABI can break any time without warning. + +Libweston major can be bumped only once during a development cycle. This should +happen on the first patch that breaks the API or ABI. Further breaks before the +next Weston major.0.0 release do not cause a bump. This means that libweston +API and ABI are allowed to break also after an alpha release, up to the final +release. However, breaks after alpha should be judged by the usual practices +for allowing minor features, fixes only, or critical fixes only. + +To make things tolerable for libweston users despite API/ABI breakages, +different libweston major versions are designed to be perfectly +parallel-installable. This way external projects can easily depend on a +particular API/ABI-version. Thus they do not have to fight over which +ABI-version is installed in a user's system. This allows a user to install many +different compositors each requiring a different libweston ABI-version without +tricks or conflicts. + +Note, that versions of Weston itself will not be parallel-installable, +only libweston is. + +For more information about parallel installability, see +http://ometer.com/parallel.html + + +Versioning scheme +----------------- + +In order to provide consistent, easy to use versioning, libweston +follows the rules in the Apache Portable Runtime Project +http://apr.apache.org/versioning.html. + +The document provides the full details, with the gist summed below: + - Major - backward incompatible changes. + - Minor - new backward compatible features. + - Patch - internal (implementation specific) fixes. + +Weston and libweston have separate version numbers in meson.build. All +releases are made by the Weston version number. Libweston version number +matches the Weston version number in all releases except maybe pre-releases. +Pre-releases have the Weston micro version 91 or greater. + +A pre-release is allowed to install a libweston version greater than the Weston +version in case libweston major was bumped. In that case, the libweston version +must be Weston major + 1. + +Pkg-config files are named after libweston major, but carry the Weston version +number. This means that Weston pre-release 2.1.91 may install libweston-3.pc +for the future libweston 3.0.0, but the .pc file says the version is still +2.1.91. When a libweston user wants to depend on the fully stable API and ABI +of a libweston major, he should use (e.g. for major 3): + + PKG_CHECK_MODULES(LIBWESTON, [libweston-3 >= 3.0.0]) + +Depending only on libweston-3 without a specific version number still allows +pre-releases which might have different API or ABI. + + +Forward compatibility +--------------------- + +Inspired by ATK, Qt and KDE programs/libraries, libjpeg-turbo, GDK, +NetworkManager, js17, lz4 and many others, libweston uses a macro to restrict +the API visible to the developer - REQUIRE_LIBWESTON_API_VERSION. + +Note that different projects focus on different aspects - upper and/or lower +version check, default to visible/hidden old/new symbols and so on. + +libweston aims to guard all newly introduced API, in order to prevent subtle +breaks that a simple recompile (against a newer version) might cause. + +The macro is of the format 0x$MAJOR$MINOR and does not include PATCH version. +As mentioned in the Versioning scheme section, the latter does not reflect any +user visible API changes, thus should be not considered part of the API version. + +All new symbols should be guarded by the macro like the example given below: + +~~~~ +#if REQUIRE_LIBWESTON_API_VERSION >= 0x0101 + +bool +weston_ham_sandwich(void); + +#endif +~~~~ + +In order to use the said symbol, the one will have a similar code in their +configure.ac: + +~~~~ +PKG_CHECK_MODULES(LIBWESTON, [libweston-1 >= 1.1]) +AC_DEFINE(REQUIRE_LIBWESTON_API_VERSION, [0x0101]) +~~~~ + +If the user is _not_ interested in forward compatibility, they can use 0xffff +or similar high value. Yet doing so is not recommended. + + +Libweston design goals +---------------------- + +The high-level goal of libweston is to decouple the compositor from +the shell implementation (what used to be shell plugins). + +Thus, instead of launching 'weston' with various arguments to choose the +shell, one would launch the shell itself, e.g. 'weston-desktop', +'weston-ivi', 'orbital', etc. The main executable (the hosting program) +will implement the shell, while libweston will be used for a fundamental +compositor implementation. + +Libweston is also intended for use by other project developers who want +to create new "Wayland WMs". + +Details: + +- All configuration and user interfaces will be outside of libweston. + This includes command line parsing, configuration files, and runtime + (graphical) UI. + +- The hosting program (main executable) will be in full control of all + libweston options. Libweston should not have user settable options + that would work behind the hosting program's back, except perhaps + debugging features and such. + +- Signal handling will be outside of libweston. + +- Child process execution and management will be outside of libweston. + +- The different backends (drm, fbdev, x11, etc) will be an internal + detail of libweston. Libweston will not support third party + backends. However, hosting programs need to handle + backend-specific configuration due to differences in behaviour and + available features. + +- Renderers will be libweston internal details too, though again the + hosting program may affect the choice of renderer if the backend + allows, and maybe set renderer-specific options. + +- plugin design ??? + +- xwayland ??? + +- weston-launch is still with libweston even though it can only launch + Weston and nothing else. We would like to allow it to launch any compositor, + but since it gives by design root access to input devices and DRM, how can + we restrict it to intended programs? + +There are still many more details to be decided. + + +For packagers +------------- + +Always build Weston with --with-cairo=image. + +The Weston project is (will be) intended to be split into several +binary packages, each with its own dependencies. The maximal split +would be roughly like this: + +- libweston (minimal dependencies): + + headless backend + + wayland backend + +- gl-renderer (depends on GL libs etc.) + +- drm-backend (depends on libdrm, libgbm, libudev, libinput, ...) + +- x11-backend (depends of X11/xcb libs) + +- xwayland (depends on X11/xcb libs) + +- fbdev-backend (depends on libudev...) + +- rdp-backend (depends on freerdp) + +- weston (the executable, not parallel-installable): + + desktop shell + + ivi-shell + + fullscreen shell + + weston-info (deprecated), weston-terminal, etc. we install by default + + screen-share + +- weston demos (not parallel-installable) + + weston-simple-* programs + + possibly all the programs we build but do not install by + default + +- and possibly more... + +Everything should be parallel-installable across libweston major +ABI-versions (libweston-1.so, libweston-2.so, etc.), except those +explicitly mentioned. + +Weston's build may not sanely allow this yet, but this is the +intention. diff --git a/clients/calibrator.c b/clients/calibrator.c new file mode 100644 index 0000000..21ca876 --- /dev/null +++ b/clients/calibrator.c @@ -0,0 +1,311 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "window.h" +#include "shared/helpers.h" +#include + +/* 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, bool enable_button) +{ + 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; + + if (enable_button) + 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); +} + +static void +help(const char *name) +{ + fprintf(stderr, "Usage: %s [args...]\n", name); + fprintf(stderr, " -m, --enable-mouse Enable mouse for testing the touchscreen\n"); + fprintf(stderr, " -h, --help Display this help message\n"); +} + +int +main(int argc, char *argv[]) +{ + struct display *display; + struct calibrator *calibrator; + int c; + bool enable_mouse = 0; + struct option opts[] = { + { "enable-mouse", no_argument, NULL, 'm' }, + { "help", no_argument, NULL, 'h' }, + { 0, 0, NULL, 0 } + }; + + while ((c = getopt_long(argc, argv, "mh", opts, NULL)) != -1) { + switch (c) { + case 'm': + enable_mouse = 1; + break; + case 'h': + help(argv[0]); + exit(EXIT_FAILURE); + default: + break; + } + } + + display = display_create(&argc, argv); + + if (display == NULL) { + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); + return -1; + } + + calibrator = calibrator_create(display, enable_mouse); + + 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 0000000..4e8a945 --- /dev/null +++ b/clients/clickdot.c @@ -0,0 +1,345 @@ +/* + * Copyright © 2010 Intel Corporation + * Copyright © 2012 Collabora, Ltd. + * Copyright © 2012 Jonas Ådahl + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "window.h" +#include "shared/helpers.h" +#include "shared/xalloc.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; + + struct input *cursor_timeout_input; + struct toytimer cursor_timeout; +}; + +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 void +cursor_timeout_reset(struct clickdot *clickdot) +{ + toytimer_arm_once_usec(&clickdot->cursor_timeout, 500 * 1000); +} + +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); + + cursor_timeout_reset(clickdot); + clickdot->cursor_timeout_input = input; + + return CURSOR_BLANK; +} + +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 void +cursor_timeout_func(struct toytimer *tt) +{ + struct clickdot *clickdot = + container_of(tt, struct clickdot, cursor_timeout); + + input_set_pointer_image(clickdot->cursor_timeout_input, + CURSOR_LEFT_PTR); +} + +static struct clickdot * +clickdot_create(struct display *display) +{ + struct clickdot *clickdot; + + clickdot = xzalloc(sizeof *clickdot); + clickdot->window = window_create(display); + clickdot->widget = window_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; + + toytimer_init(&clickdot->cursor_timeout, CLOCK_MONOTONIC, + display, cursor_timeout_func); + + return clickdot; +} + +static void +clickdot_destroy(struct clickdot *clickdot) +{ + toytimer_fini(&clickdot->cursor_timeout); + 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: %s\n", + strerror(errno)); + 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 0000000..8998385 --- /dev/null +++ b/clients/cliptest.c @@ -0,0 +1,637 @@ +/* + * Copyright © 2012 Collabora, Ltd. + * Copyright © 2012 Rob Clark + * + * 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. + */ + +/* cliptest: for debugging calculate_edges() function. + * 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 "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "libweston/vertex-clipping.h" +#include "shared/xalloc.h" +#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_view { + struct { + int enabled; + } transform; + + struct geometry *geometry; +}; + +static void +weston_view_to_global_float(struct weston_view *view, + float sx, float sy, float *x, float *y) +{ + struct geometry *g = view->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 -----------------------*/ +/* Keep this in sync with what is in gl-renderer.c! */ + +#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_view *ev, 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_view_to_global_float(ev, 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 (!ev->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; +} + + +/* ---------------------- 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_view view; +}; + +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_view *view) +{ + GLfloat x[4], y[4]; + + if (view) { + weston_view_to_global_float(view, box->x1, box->y1, &x[0], &y[0]); + weston_view_to_global_float(view, box->x2, box->y1, &x[1], &y[1]); + weston_view_to_global_float(view, box->x2, box->y2, &x[2], &y[2]); + weston_view_to_global_float(view, 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_view *view, + GLfloat *ex, GLfloat *ey, int n) +{ + struct geometry *g = view->geometry; + float cx, cy; + + draw_box(cr, &g->surf, view); + cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.4); + cairo_fill(cr); + weston_view_to_global_float(view, g->surf.x1 - 4, g->surf.y1 - 4, &cx, &cy); + cairo_arc(cr, cx, cy, 1.5, 0.0, 2.0 * M_PI); + if (view->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); + + if (n) { + 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->view.geometry; + struct rectangle allocation; + cairo_t *cr; + cairo_surface_t *surface; + GLfloat ex[8]; + GLfloat ey[8]; + int n; + + n = calculate_edges(&cliptest->view, &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->view, 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->view.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->view.transform.enabled = 1; + break; + case XKB_KEY_m: + geometry_set_phi(g, g->phi - (M_PI / 24.0)); + cliptest->view.transform.enabled = 1; + break; + case XKB_KEY_r: + geometry_set_phi(g, 0.0); + cliptest->view.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->view.geometry = &cliptest->geometry; + cliptest->view.transform.enabled = 0; + geometry_init(&cliptest->geometry); + geometry_init(&cliptest->ui.geometry); + + cliptest->window = window_create(display); + cliptest->widget = window_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_view view; + 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); + + view.transform.enabled = 1; + view.geometry = &geom; + + reset_timer(); + for (i = 0; i < N; i++) { + geometry_set_phi(&geom, (float)i / 360.0f); + calculate_edges(&view, &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; +} + +static void +cliptest_destroy(struct cliptest *cliptest) +{ + widget_destroy(cliptest->widget); + window_destroy(cliptest->window); + free(cliptest); +} + +int +main(int argc, char *argv[]) +{ + struct display *d; + struct cliptest *cliptest; + + if (argc > 1) { + if (argc == 2 && !strcmp(argv[1], "-b")) + return benchmark(); + printf("Usage: %s [OPTIONS]\n -b run benchmark\n", argv[0]); + return 1; + } + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); + return -1; + } + + cliptest = cliptest_create(d); + display_run(d); + + cliptest_destroy(cliptest); + display_destroy(d); + + return 0; +} diff --git a/clients/confine.c b/clients/confine.c new file mode 100644 index 0000000..6f38457 --- /dev/null +++ b/clients/confine.c @@ -0,0 +1,512 @@ +/* + * Copyright © 2010 Intel Corporation + * Copyright © 2012 Collabora, Ltd. + * Copyright © 2012 Jonas Ådahl + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "window.h" +#include "shared/helpers.h" +#include "shared/xalloc.h" + +#define NUM_COMPLEX_REGION_RECTS 9 + +static bool option_complex_confine_region; +static bool option_help; + +struct confine { + struct display *display; + struct window *window; + struct widget *widget; + + cairo_surface_t *buffer; + + struct { + int32_t x, y; + int32_t old_x, old_y; + } line; + + int reset; + + struct input *cursor_timeout_input; + struct toytimer cursor_timeout; + + bool pointer_confined; + + bool complex_confine_region_enabled; + bool complex_confine_region_dirty; + struct rectangle complex_confine_region[NUM_COMPLEX_REGION_RECTS]; +}; + +static void +draw_line(struct confine *confine, cairo_t *cr, + struct rectangle *allocation) +{ + cairo_t *bcr; + cairo_surface_t *tmp_buffer = NULL; + + if (confine->reset) { + tmp_buffer = confine->buffer; + confine->buffer = NULL; + confine->line.x = -1; + confine->line.y = -1; + confine->line.old_x = -1; + confine->line.old_y = -1; + confine->reset = 0; + } + + if (confine->buffer == NULL) { + confine->buffer = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + allocation->width, + allocation->height); + bcr = cairo_create(confine->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(confine->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 (confine->line.x != -1 && confine->line.y != -1) { + if (confine->line.old_x != -1 && + confine->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, + confine->line.old_x, + confine->line.old_y); + cairo_line_to(bcr, + confine->line.x, + confine->line.y); + + cairo_stroke(bcr); + } + + confine->line.old_x = confine->line.x; + confine->line.old_y = confine->line.y; + } + cairo_destroy(bcr); + + cairo_set_source_surface(cr, confine->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 +calculate_complex_confine_region(struct confine *confine) +{ + struct rectangle allocation; + int32_t x, y, w, h; + struct rectangle *rs = confine->complex_confine_region; + + if (!confine->complex_confine_region_dirty) + return; + + widget_get_allocation(confine->widget, &allocation); + x = allocation.x; + y = allocation.y; + w = allocation.width; + h = allocation.height; + + /* + * The code below constructs a region made up of rectangles that + * is then used to set up both an illustrative shaded region in the + * widget and a confine region used when confining the pointer. + */ + + rs[0].x = x + (int)round(w * 0.05); + rs[0].y = y + (int)round(h * 0.15); + rs[0].width = (int)round(w * 0.35); + rs[0].height = (int)round(h * 0.7); + + rs[1].x = rs[0].x + rs[0].width; + rs[1].y = y + (int)round(h * 0.45); + rs[1].width = (int)round(w * 0.09); + rs[1].height = (int)round(h * 0.1); + + rs[2].x = rs[1].x + rs[1].width; + rs[2].y = y + (int)round(h * 0.48); + rs[2].width = (int)round(w * 0.02); + rs[2].height = (int)round(h * 0.04); + + rs[3].x = rs[2].x + rs[2].width; + rs[3].y = y + (int)round(h * 0.45); + rs[3].width = (int)round(w * 0.09); + rs[3].height = (int)round(h * 0.1); + + rs[4].x = rs[3].x + rs[3].width; + rs[4].y = y + (int)round(h * 0.15); + rs[4].width = (int)round(w * 0.35); + rs[4].height = (int)round(h * 0.7); + + rs[5].x = x + (int)round(w * 0.05); + rs[5].y = y + (int)round(h * 0.05); + rs[5].width = rs[0].width + rs[1].width + rs[2].width + + rs[3].width + rs[4].width; + rs[5].height = (int)round(h * 0.10); + + rs[6].x = x + (int)round(w * 0.1); + rs[6].y = rs[4].y + rs[4].height + (int)round(h * 0.02); + rs[6].width = (int)round(w * 0.8); + rs[6].height = (int)round(h * 0.03); + + rs[7].x = x + (int)round(w * 0.05); + rs[7].y = rs[6].y + rs[6].height; + rs[7].width = (int)round(w * 0.9); + rs[7].height = (int)round(h * 0.03); + + rs[8].x = x + (int)round(w * 0.1); + rs[8].y = rs[7].y + rs[7].height; + rs[8].width = (int)round(w * 0.8); + rs[8].height = (int)round(h * 0.03); + + confine->complex_confine_region_dirty = false; +} + +static void +draw_complex_confine_region_mask(struct confine *confine, cairo_t *cr) +{ + int i; + + calculate_complex_confine_region(confine); + + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + + for (i = 0; i < NUM_COMPLEX_REGION_RECTS; i++) { + cairo_rectangle(cr, + confine->complex_confine_region[i].x, + confine->complex_confine_region[i].y, + confine->complex_confine_region[i].width, + confine->complex_confine_region[i].height); + cairo_set_source_rgba(cr, 0.14, 0.14, 0.14, 0.9); + cairo_fill(cr); + } +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct confine *confine = data; + cairo_surface_t *surface; + cairo_t *cr; + struct rectangle allocation; + + widget_get_allocation(confine->widget, &allocation); + + surface = window_get_surface(confine->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); + + if (confine->complex_confine_region_enabled) { + draw_complex_confine_region_mask(confine, cr); + } + + draw_line(confine, cr, &allocation); + + cairo_destroy(cr); + + cairo_surface_destroy(surface); +} + +static void +keyboard_focus_handler(struct window *window, + struct input *device, void *data) +{ + struct confine *confine = data; + + window_schedule_redraw(confine->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 confine *confine = data; + + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + switch (sym) { + case XKB_KEY_Escape: + display_exit(confine->display); + break; + case XKB_KEY_BackSpace: + cairo_surface_destroy(confine->buffer); + confine->buffer = NULL; + window_schedule_redraw(confine->window); + break; + case XKB_KEY_m: + window_set_maximized(confine->window, + !window_is_maximized(window)); + break; + } +} + +static void +toggle_pointer_confine(struct confine *confine, struct input *input) +{ + if (confine->pointer_confined) { + window_unconfine_pointer(confine->window); + } else if (confine->complex_confine_region_enabled) { + calculate_complex_confine_region(confine); + window_confine_pointer_to_rectangles( + confine->window, + input, + confine->complex_confine_region, + NUM_COMPLEX_REGION_RECTS); + + } else { + window_confine_pointer_to_widget(confine->window, + confine->widget, + input); + } + + confine->pointer_confined = !confine->pointer_confined; +} + +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 confine *confine = data; + bool is_pressed = state == WL_POINTER_BUTTON_STATE_PRESSED; + + if (is_pressed && button == BTN_LEFT) + toggle_pointer_confine(confine, input); + widget_schedule_redraw(widget); +} + +static void +cursor_timeout_reset(struct confine *confine) +{ + toytimer_arm_once_usec(&confine->cursor_timeout, 500 * 1000); +} + +static int +motion_handler(struct widget *widget, + struct input *input, uint32_t time, + float x, float y, void *data) +{ + struct confine *confine = data; + confine->line.x = x; + confine->line.y = y; + + window_schedule_redraw(confine->window); + + cursor_timeout_reset(confine); + confine->cursor_timeout_input = input; + + return CURSOR_BLANK; +} + +static void +resize_handler(struct widget *widget, + int32_t width, int32_t height, + void *data) +{ + struct confine *confine = data; + + confine->reset = 1; + + if (confine->complex_confine_region_enabled) { + confine->complex_confine_region_dirty = true; + + if (confine->pointer_confined) { + calculate_complex_confine_region(confine); + window_update_confine_rectangles( + confine->window, + confine->complex_confine_region, + NUM_COMPLEX_REGION_RECTS); + } + } +} + +static void +leave_handler(struct widget *widget, + struct input *input, void *data) +{ + struct confine *confine = data; + + confine->reset = 1; +} + +static void +cursor_timeout_func(struct toytimer *tt) +{ + struct confine *confine = + container_of(tt, struct confine, cursor_timeout); + + input_set_pointer_image(confine->cursor_timeout_input, + CURSOR_LEFT_PTR); +} + +static void +pointer_unconfined(struct window *window, struct input *input, void *data) +{ + struct confine *confine = data; + + confine->pointer_confined = false; +} + +static struct confine * +confine_create(struct display *display) +{ + struct confine *confine; + + confine = xzalloc(sizeof *confine); + confine->window = window_create(display); + confine->widget = window_frame_create(confine->window, confine); + window_set_title(confine->window, "Wayland Confine"); + confine->display = display; + confine->buffer = NULL; + + window_set_key_handler(confine->window, key_handler); + window_set_user_data(confine->window, confine); + window_set_keyboard_focus_handler(confine->window, + keyboard_focus_handler); + window_set_pointer_confined_handler(confine->window, + NULL, + pointer_unconfined); + + widget_set_redraw_handler(confine->widget, redraw_handler); + widget_set_button_handler(confine->widget, button_handler); + widget_set_motion_handler(confine->widget, motion_handler); + widget_set_resize_handler(confine->widget, resize_handler); + widget_set_leave_handler(confine->widget, leave_handler); + + widget_schedule_resize(confine->widget, 500, 400); + confine->line.x = -1; + confine->line.y = -1; + confine->line.old_x = -1; + confine->line.old_y = -1; + confine->reset = 0; + + toytimer_init(&confine->cursor_timeout, CLOCK_MONOTONIC, + display, cursor_timeout_func); + + return confine; +} + +static void +confine_destroy(struct confine *confine) +{ + toytimer_fini(&confine->cursor_timeout); + if (confine->buffer) + cairo_surface_destroy(confine->buffer); + widget_destroy(confine->widget); + window_destroy(confine->window); + free(confine); +} + +static const struct weston_option confine_options[] = { + { WESTON_OPTION_BOOLEAN, "complex-confine-region", 0, &option_complex_confine_region }, + { WESTON_OPTION_BOOLEAN, "help", 0, &option_help }, +}; + +static void +print_help(const char *argv0) +{ + printf("Usage: %s [--complex-confine-region]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct display *display; + struct confine *confine; + + if (parse_options(confine_options, + ARRAY_LENGTH(confine_options), + &argc, argv) > 1 || + option_help) { + print_help(argv[0]); + return 0; + } + + display = display_create(&argc, argv); + if (display == NULL) { + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); + return -1; + } + + confine = confine_create(display); + + if (option_complex_confine_region) { + confine->complex_confine_region_dirty = true; + confine->complex_confine_region_enabled = true; + } + + display_run(display); + + confine_destroy(confine); + display_destroy(display); + + return 0; +} diff --git a/clients/content_protection.c b/clients/content_protection.c new file mode 100644 index 0000000..27e2b8a --- /dev/null +++ b/clients/content_protection.c @@ -0,0 +1,384 @@ +/* + * Copyright © 2018 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "weston-content-protection-client-protocol.h" +#include "window.h" +#include + +#define WIDTH 500 +#define HEIGHT 400 +#define FRAME_H 18 +#define FRAME_W 5 +#define BUTTON_WIDTH 65 +#define BUTTON_HEIGHT 20 + +enum protection_mode { + RELAXED, + ENFORCED +}; + +struct protected_content_player { + struct weston_content_protection *protection; + struct weston_protected_surface *psurface; + struct display *display; + struct window *window; + struct widget *widget; + struct button_t *b0, *b1, *off, *enforced, *relaxed; + int width, height, x, y; + enum weston_protected_surface_type protection_type; + enum protection_mode mode; +}; + +struct button_t { + struct window *window; + struct widget *widget; + struct protected_content_player *pc_player; + const char *name; +}; +/** + * An event to tell the client that there is a change in protection status + * + * This event is sent whenever there is a change in content + * protection. The content protection status can be ON or OFF. ON + * in case of the desired protection type is accepted on all + * connectors, and Off in case of any of the connector + * content-protection property is changed from "enabled" + */ +static void +handle_status_changed(void *data, struct weston_protected_surface *psurface, + uint32_t status) +{ + struct protected_content_player *pc_player = data; + enum weston_protected_surface_type event_status = status; + + switch (event_status) { + case WESTON_PROTECTED_SURFACE_TYPE_HDCP_0: + pc_player->protection_type = WESTON_PROTECTED_SURFACE_TYPE_HDCP_0; + break; + case WESTON_PROTECTED_SURFACE_TYPE_HDCP_1: + pc_player->protection_type = WESTON_PROTECTED_SURFACE_TYPE_HDCP_1; + break; + case WESTON_PROTECTED_SURFACE_TYPE_UNPROTECTED: + default: + pc_player->protection_type = WESTON_PROTECTED_SURFACE_TYPE_UNPROTECTED; + } + window_schedule_redraw(pc_player->window); +} + +static const struct weston_protected_surface_listener pc_player_listener = { + handle_status_changed, +}; + +static void +draw_content(cairo_surface_t *surface, int x, int y, int width, int height, + enum weston_protected_surface_type type, enum protection_mode mode) +{ + cairo_t *cr; + cairo_text_extents_t extents; + const char *content_text; + const char *mode_text; + + cr = cairo_create(surface); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_rectangle(cr, x, y, width, height); + if (type == WESTON_PROTECTED_SURFACE_TYPE_HDCP_0) + cairo_set_source_rgba(cr, 0, 1.0, 0, 1.0); + else if (type == WESTON_PROTECTED_SURFACE_TYPE_HDCP_1) + cairo_set_source_rgba(cr, 0, 0, 1.0, 1.0); + else + cairo_set_source_rgba(cr, 1.0, 0, 0, 1.0); + cairo_fill(cr); + + cairo_set_source_rgba(cr, 0, 0, 0, 1.0); + cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cr, 15); + if (type == WESTON_PROTECTED_SURFACE_TYPE_HDCP_0) + content_text = "Content-Type : Type-0"; + else if (type == WESTON_PROTECTED_SURFACE_TYPE_HDCP_1) + content_text = "Content-Type : Type-1"; + else + content_text = "Content-Type : Unprotected"; + cairo_text_extents(cr, content_text, &extents); + cairo_move_to(cr, width/2 - (extents.width/2), + height/2 - (extents.height/2)); + cairo_show_text(cr, content_text); + + if (mode == ENFORCED) + mode_text = "Mode : Enforced"; + else + mode_text = "Mode : Relaxed"; + cairo_text_extents(cr, mode_text, &extents); + cairo_move_to(cr, width / 2 - (extents.width / 2), + 2 * height / 3 - (2 * extents.height / 3)); + cairo_show_text(cr, mode_text); + + cairo_fill(cr); + cairo_destroy(cr); +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct protected_content_player *pc_player = data; + cairo_surface_t *surface; + struct rectangle rect; + + widget_get_allocation(pc_player->widget, &rect); + surface = window_get_surface(pc_player->window); + if (surface == NULL || + cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { + fprintf(stderr, "failed to create cairo egl surface\n"); + return; + } + draw_content(surface, rect.x, rect.y, rect.width, rect.height, + pc_player->protection_type, pc_player->mode); + cairo_surface_destroy(surface); +} + +static void +resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) +{ + struct rectangle allocation; + struct protected_content_player *pc_player = data; + + widget_get_allocation(pc_player->widget, &allocation); + widget_set_allocation(pc_player->b0->widget, + allocation.x + 20, allocation.y + 30, + BUTTON_WIDTH, BUTTON_HEIGHT); + widget_set_allocation(pc_player->b1->widget, + allocation.x + 20 + BUTTON_WIDTH + 5, + allocation.y + 30, + BUTTON_WIDTH, BUTTON_HEIGHT); + widget_set_allocation(pc_player->off->widget, + allocation.x + 20 + 2 * (BUTTON_WIDTH + 5), + allocation.y + 30, + BUTTON_WIDTH, BUTTON_HEIGHT); + widget_set_allocation(pc_player->enforced->widget, + allocation.x + 20 + 3 * (BUTTON_WIDTH + 5), + allocation.y + 30, + BUTTON_WIDTH, BUTTON_HEIGHT); + widget_set_allocation(pc_player->relaxed->widget, + allocation.x + 20 + 4 * (BUTTON_WIDTH + 5), + allocation.y + 30, + BUTTON_WIDTH, BUTTON_HEIGHT); +} + +static void +buttons_handler(struct widget *widget, struct input *input, uint32_t time, + uint32_t button, enum wl_pointer_button_state state, void *data) +{ + struct button_t *b = data; + struct protected_content_player *pc_player = b->pc_player; + struct wl_surface *surface; + + if (strcmp(b->name, "ENFORCED") == 0) { + weston_protected_surface_enforce(pc_player->psurface); + pc_player->mode = ENFORCED; + window_schedule_redraw(pc_player->window); + } + else if (strcmp(b->name, "RELAXED") == 0) { + weston_protected_surface_relax(pc_player->psurface); + pc_player->mode = RELAXED; + window_schedule_redraw(pc_player->window); + } + else if (strcmp(b->name, "TYPE-0") == 0) + weston_protected_surface_set_type(pc_player->psurface, + WESTON_PROTECTED_SURFACE_TYPE_HDCP_0); + else if (strcmp(b->name, "TYPE-1") == 0) + weston_protected_surface_set_type(pc_player->psurface, + WESTON_PROTECTED_SURFACE_TYPE_HDCP_1); + else + weston_protected_surface_set_type(pc_player->psurface, + WESTON_PROTECTED_SURFACE_TYPE_UNPROTECTED); + + surface = window_get_wl_surface(pc_player->window); + wl_surface_commit(surface); +} + +static void +handle_global(struct display *display, uint32_t name, const char *interface, + uint32_t version, void *data) +{ + struct protected_content_player *pc_player = data; + + if (strcmp(interface, "weston_content_protection") == 0) { + pc_player->protection = display_bind(display, name, + &weston_content_protection_interface, + 1); + } +} + +static void +buttons_redraw_handler(struct widget *widget, void *data) +{ + struct button_t *b = data; + cairo_surface_t *surface; + struct rectangle allocation; + cairo_t *cr; + + surface = window_get_surface(b->window); + widget_get_allocation(b->widget, &allocation); + + cr = cairo_create(surface); + cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, + allocation.height); + + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + + cairo_set_source_rgba(cr, 1, 1, 1, 1); + cairo_fill(cr); + + cairo_set_source_rgba(cr, 0, 0, 0, 1.0); + cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cr, 10); + cairo_move_to(cr, allocation.x + 5, allocation.y + 15); + cairo_show_text(cr, b->name); + cairo_fill(cr); + + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +static struct button_t* +create_button(struct protected_content_player *pc_player, const char *name) +{ + struct button_t *b; + + b = zalloc(sizeof(struct button_t)); + if (b == NULL) { + fprintf(stderr, "Failed to allocate memory for button.\n"); + exit(0); + } + b->widget = widget_add_widget(pc_player->widget, b); + b->window = pc_player->window; + b->pc_player = pc_player; + b->name = name; + widget_set_redraw_handler(b->widget, buttons_redraw_handler); + widget_set_button_handler(b->widget, buttons_handler); + return b; +} + +static void +destroy_button(struct button_t *b) +{ + if (!b) + return; + widget_destroy(b->widget); + free(b); +} + +static void free_pc_player(struct protected_content_player *pc_player) +{ + if (!pc_player) + return; + + destroy_button(pc_player->b0); + destroy_button(pc_player->b1); + destroy_button(pc_player->off); + destroy_button(pc_player->enforced); + destroy_button(pc_player->relaxed); + widget_destroy(pc_player->widget); + window_destroy(pc_player->window); + free(pc_player); +} + +int main(int argc, char *argv[]) +{ + struct protected_content_player *pc_player; + struct display *d; + static const char str_type_0[] = "TYPE-0"; + static const char str_type_1[] = "TYPE-1"; + static const char str_type_off[] = "OFF"; + static const char str_type_enforced[] = "ENFORCED"; + static const char str_type_relaxed[] = "RELAXED"; + struct wl_surface *surface; + + pc_player = zalloc(sizeof(struct protected_content_player)); + if (pc_player == NULL) { + fprintf(stderr, "failed to allocate memory: %m\n"); + return -1; + } + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + pc_player->protection_type = WESTON_PROTECTED_SURFACE_TYPE_UNPROTECTED; + pc_player->mode = RELAXED; + pc_player->width = WIDTH * 2.0/4.0; + pc_player->height = HEIGHT * 2.0/4.0; + pc_player->x = WIDTH * 1.0/4.0; + pc_player->y = HEIGHT * 1.0/4.0; + pc_player->window = window_create(d); + pc_player->widget = window_frame_create(pc_player->window, pc_player); + pc_player->display = d; + display_set_user_data(d, pc_player); + + display_set_global_handler(d, handle_global); + surface = window_get_wl_surface(pc_player->window); + if (pc_player->protection == NULL) { + printf("The content-protection object is NULL\n"); + return -1; + } + pc_player->psurface = weston_content_protection_get_protection(pc_player->protection, + surface); + weston_protected_surface_add_listener(pc_player->psurface, + &pc_player_listener, + pc_player); + + pc_player->b0 = create_button(pc_player, str_type_0); + pc_player->b1 = create_button(pc_player, str_type_1); + pc_player->off = create_button(pc_player, str_type_off); + pc_player->enforced = create_button(pc_player, str_type_enforced); + pc_player->relaxed = create_button(pc_player, str_type_relaxed); + + window_set_title(pc_player->window, "Player"); + widget_set_redraw_handler(pc_player->widget, redraw_handler); + widget_set_resize_handler(pc_player->widget, resize_handler); + window_schedule_resize(pc_player->window, WIDTH, HEIGHT); + widget_schedule_redraw(pc_player->b0->widget); + widget_schedule_redraw(pc_player->b1->widget); + widget_schedule_redraw(pc_player->off->widget); + + display_run(d); + weston_protected_surface_destroy(pc_player->psurface); + weston_content_protection_destroy(pc_player->protection); + free_pc_player(pc_player); + display_destroy(d); + return 0; +} diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c new file mode 100644 index 0000000..bde5dc8 --- /dev/null +++ b/clients/desktop-shell.c @@ -0,0 +1,1559 @@ +/* + * Copyright © 2011 Kristian Høgsberg + * Copyright © 2011 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#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 +#include "shared/helpers.h" +#include "shared/xalloc.h" +#include +#include "shared/file-util.h" + +#include "weston-desktop-shell-client-protocol.h" + +#define DEFAULT_CLOCK_FORMAT CLOCK_FORMAT_MINUTES +#define DEFAULT_SPACING 10 + +extern char **environ; /* defined by libc */ + +enum clock_format { + CLOCK_FORMAT_MINUTES, + CLOCK_FORMAT_SECONDS, + CLOCK_FORMAT_NONE +}; + +struct desktop { + struct display *display; + struct weston_desktop_shell *shell; + struct unlock_dialog *unlock_dialog; + struct task unlock_task; + struct wl_list outputs; + + int want_panel; + enum weston_desktop_shell_panel_position panel_position; + enum clock_format clock_format; + + struct window *grab_window; + struct widget *grab_widget; + + struct weston_config *config; + bool locking; + + enum cursor_type grab_cursor; + + int painted; +}; + +struct surface { + void (*configure)(void *data, + struct weston_desktop_shell *desktop_shell, + uint32_t edges, struct window *window, + int32_t width, int32_t height); +}; + +struct output; + +struct panel { + struct surface base; + + struct output *owner; + + struct window *window; + struct widget *widget; + struct wl_list launcher_list; + struct panel_clock *clock; + int painted; + enum weston_desktop_shell_panel_position panel_position; + enum clock_format clock_format; + uint32_t color; +}; + +struct background { + struct surface base; + + struct output *owner; + + struct window *window; + struct widget *widget; + int painted; + + char *image; + int type; + uint32_t color; +}; + +struct output { + struct wl_output *output; + uint32_t server_output_id; + struct wl_list link; + + int x; + int y; + 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 toytimer timer; + char *format_string; + time_t refresh_timer; +}; + +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 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; + + weston_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: %s\n", strerror(errno)); + return; + } + + if (pid) + return; + + argv = widget->argv.data; + + if (setsid() == -1) + exit(EXIT_FAILURE); + + if (execve(argv[0], argv, widget->envp.data) < 0) { + fprintf(stderr, "execl '%s' failed: %s\n", argv[0], + strerror(errno)); + 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); + allocation.x += allocation.width / 2 - + cairo_image_surface_get_width(launcher->icon) / 2; + if (allocation.width > allocation.height) + allocation.x += allocation.width / 2 - allocation.height / 2; + allocation.y += allocation.height / 2 - + cairo_image_surface_get_height(launcher->icon) / 2; + if (allocation.height > allocation.width) + allocation.y += allocation.height / 2 - allocation.width / 2; + 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 toytimer *tt) +{ + struct panel_clock *clock = container_of(tt, struct panel_clock, timer); + + 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; + time_t rawtime; + struct tm * timeinfo; + char string[128]; + + time(&rawtime); + timeinfo = localtime(&rawtime); + strftime(string, sizeof string, clock->format_string, timeinfo); + + widget_get_allocation(widget, &allocation); + if (allocation.width == 0) + return; + + cr = widget_cairo_create(clock->panel->widget); + cairo_set_font_size(cr, 14); + cairo_text_extents(cr, string, &extents); + if (allocation.x > 0) + allocation.x += + allocation.width - DEFAULT_SPACING * 1.5 - extents.width; + else + allocation.x += + allocation.width / 2 - extents.width / 2; + allocation.y += allocation.height / 2 - 1 + extents.height / 2; + cairo_move_to(cr, allocation.x + 1, allocation.y + 1); + cairo_set_source_rgba(cr, 0, 0, 0, 0.85); + cairo_show_text(cr, string); + cairo_move_to(cr, allocation.x, allocation.y); + cairo_set_source_rgba(cr, 1, 1, 1, 0.85); + 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 = clock->refresh_timer; + its.it_interval.tv_nsec = 0; + its.it_value.tv_sec = clock->refresh_timer; + its.it_value.tv_nsec = 0; + toytimer_arm(&clock->timer, &its); + + return 0; +} + +static void +panel_destroy_clock(struct panel_clock *clock) +{ + widget_destroy(clock->widget); + toytimer_fini(&clock->timer); + free(clock); +} + +static void +panel_add_clock(struct panel *panel) +{ + struct panel_clock *clock; + + clock = xzalloc(sizeof *clock); + clock->panel = panel; + panel->clock = clock; + + switch (panel->clock_format) { + case CLOCK_FORMAT_MINUTES: + clock->format_string = "%a %b %d, %I:%M %p"; + clock->refresh_timer = 60; + break; + case CLOCK_FORMAT_SECONDS: + clock->format_string = "%a %b %d, %I:%M:%S %p"; + clock->refresh_timer = 1; + break; + case CLOCK_FORMAT_NONE: + assert(!"not reached"); + } + + toytimer_init(&clock->timer, CLOCK_MONOTONIC, + window_get_display(panel->window), clock_func); + 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_resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + struct panel_launcher *launcher; + struct panel *panel = data; + int x = 0; + int y = 0; + int w = height > width ? width : height; + int h = w; + int horizontal = panel->panel_position == WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP || panel->panel_position == WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM; + int first_pad_h = horizontal ? 0 : DEFAULT_SPACING / 2; + int first_pad_w = horizontal ? DEFAULT_SPACING / 2 : 0; + + wl_list_for_each(launcher, &panel->launcher_list, link) { + widget_set_allocation(launcher->widget, x, y, + w + first_pad_w + 1, h + first_pad_h + 1); + if (horizontal) + x += w + first_pad_w; + else + y += h + first_pad_h; + first_pad_h = first_pad_w = 0; + } + + if (panel->clock_format == CLOCK_FORMAT_SECONDS) + w = 170; + else /* CLOCK_FORMAT_MINUTES */ + w = 150; + + if (horizontal) + x = width - w; + else + y = height - (h = DEFAULT_SPACING * 3); + + if (panel->clock) + widget_set_allocation(panel->clock->widget, + x, y, w + 1, h + 1); +} + +static void +panel_destroy(struct panel *panel); + +static void +panel_configure(void *data, + struct weston_desktop_shell *desktop_shell, + uint32_t edges, struct window *window, + int32_t width, int32_t height) +{ + struct desktop *desktop = data; + struct surface *surface = window_get_user_data(window); + struct panel *panel = container_of(surface, struct panel, base); + struct output *owner; + + if (width < 1 || height < 1) { + /* Shell plugin configures 0x0 for redundant panel. */ + owner = panel->owner; + panel_destroy(panel); + owner->panel = NULL; + return; + } + + switch (desktop->panel_position) { + case WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP: + case WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM: + height = 32; + break; + case WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT: + case WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT: + switch (desktop->clock_format) { + case CLOCK_FORMAT_NONE: + width = 32; + break; + case CLOCK_FORMAT_MINUTES: + width = 150; + break; + case CLOCK_FORMAT_SECONDS: + width = 170; + break; + } + break; + } + window_schedule_resize(panel->window, width, height); +} + +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; + + if (panel->clock) + 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 output *output) +{ + struct panel *panel; + struct weston_config_section *s; + + panel = xzalloc(sizeof *panel); + + panel->owner = output; + 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); + + panel->panel_position = desktop->panel_position; + panel->clock_format = desktop->clock_format; + if (panel->clock_format != CLOCK_FORMAT_NONE) + panel_add_clock(panel); + + s = weston_config_get_section(desktop->config, "shell", NULL, NULL); + weston_config_section_get_color(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 = xstrdup(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, + BACKGROUND_CENTERED +}; + +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; + + surface = window_get_surface(background->window); + + cr = widget_cairo_create(background->widget); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + if (background->color == 0) + cairo_set_source_rgba(cr, 0.0, 0.0, 0.2, 1.0); + else + set_hex_color(cr, background->color); + cairo_paint(cr); + + widget_get_allocation(widget, &allocation); + image = NULL; + if (background->image) + image = load_cairo_surface(background->image); + else if (background->color == 0) { + char *name = file_name_with_datadir("pattern.png"); + + image = load_cairo_surface(name); + free(name); + } + + 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); + cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD); + 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); + cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD); + break; + case BACKGROUND_TILE: + cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); + break; + case BACKGROUND_CENTERED: + s = (sx < sy) ? sx : sy; + if (s < 1.0) + s = 1.0; + + /* 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; + } + + cairo_set_source(cr, pattern); + cairo_pattern_destroy (pattern); + cairo_surface_destroy(image); + cairo_mask(cr, pattern); + } + + cairo_destroy(cr); + cairo_surface_destroy(surface); + + background->painted = 1; + check_desktop_ready(background->window); +} + +static void +background_destroy(struct background *background); + +static void +background_configure(void *data, + struct weston_desktop_shell *desktop_shell, + uint32_t edges, struct window *window, + int32_t width, int32_t height) +{ + struct output *owner; + struct background *background = + (struct background *) window_get_user_data(window); + + if (width < 1 || height < 1) { + /* Shell plugin configures 0x0 for redundant background. */ + owner = background->owner; + background_destroy(background); + owner->background = NULL; + return; + } + + if (!background->image) { + widget_set_viewport_destination(background->widget, width, height); + width = 1; + height = 1; + } + + 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; + struct wl_surface *surface; + + dialog = xzalloc(sizeof *dialog); + + dialog->window = window_create_custom(display); + dialog->widget = window_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); + + surface = window_get_wl_surface(dialog->window); + weston_desktop_shell_set_lock_surface(desktop->shell, surface); + + 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); + + weston_desktop_shell_unlock(desktop->shell); + unlock_dialog_destroy(desktop->unlock_dialog); + desktop->unlock_dialog = NULL; +} + +static void +desktop_shell_configure(void *data, + struct weston_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 weston_desktop_shell *desktop_shell) +{ + struct desktop *desktop = data; + + if (!desktop->locking) { + weston_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 weston_desktop_shell *desktop_shell, + uint32_t cursor) +{ + struct desktop *desktop = data; + + switch (cursor) { + case WESTON_DESKTOP_SHELL_CURSOR_NONE: + desktop->grab_cursor = CURSOR_BLANK; + break; + case WESTON_DESKTOP_SHELL_CURSOR_BUSY: + desktop->grab_cursor = CURSOR_WATCH; + break; + case WESTON_DESKTOP_SHELL_CURSOR_MOVE: + desktop->grab_cursor = CURSOR_DRAGGING; + break; + case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_TOP: + desktop->grab_cursor = CURSOR_TOP; + break; + case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_BOTTOM: + desktop->grab_cursor = CURSOR_BOTTOM; + break; + case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_LEFT: + desktop->grab_cursor = CURSOR_LEFT; + break; + case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_RIGHT: + desktop->grab_cursor = CURSOR_RIGHT; + break; + case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_TOP_LEFT: + desktop->grab_cursor = CURSOR_TOP_LEFT; + break; + case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_TOP_RIGHT: + desktop->grab_cursor = CURSOR_TOP_RIGHT; + break; + case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_BOTTOM_LEFT: + desktop->grab_cursor = CURSOR_BOTTOM_LEFT; + break; + case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_BOTTOM_RIGHT: + desktop->grab_cursor = CURSOR_BOTTOM_RIGHT; + break; + case WESTON_DESKTOP_SHELL_CURSOR_ARROW: + default: + desktop->grab_cursor = CURSOR_LEFT_PTR; + } +} + +static const struct weston_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 output *output) +{ + struct background *background; + struct weston_config_section *s; + char *type; + + background = xzalloc(sizeof *background); + background->owner = output; + 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); + widget_set_transparent(background->widget, 0); + + s = weston_config_get_section(desktop->config, "shell", NULL, NULL); + weston_config_section_get_string(s, "background-image", + &background->image, NULL); + weston_config_section_get_color(s, "background-color", + &background->color, 0x00000000); + + weston_config_section_get_string(s, "background-type", + &type, "tile"); + if (type == NULL) { + fprintf(stderr, "%s: out of memory\n", program_invocation_short_name); + exit(EXIT_FAILURE); + } + + 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 if (strcmp(type, "centered") == 0) { + background->type = BACKGROUND_CENTERED; + } 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); + weston_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) +{ + if (output->background) + background_destroy(output->background); + if (output->panel) + 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; + + output->x = x; + output->y = y; + + if (output->panel) + window_set_buffer_transform(output->panel->window, transform); + if (output->background) + 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; + + if (output->panel) + window_set_buffer_scale(output->panel->window, scale); + if (output->background) + 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; + + if (desktop->want_panel) { + output->panel = panel_create(desktop, output); + surface = window_get_wl_surface(output->panel->window); + weston_desktop_shell_set_panel(desktop->shell, + output->output, surface); + } + + output->background = background_create(desktop, output); + surface = window_get_wl_surface(output->background->window); + weston_desktop_shell_set_background(desktop->shell, + output->output, surface); +} + +static void +create_output(struct desktop *desktop, uint32_t id) +{ + struct output *output; + + output = zalloc(sizeof *output); + if (!output) + return; + + output->output = + display_bind(desktop->display, id, &wl_output_interface, 2); + output->server_output_id = id; + + 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 +output_remove(struct desktop *desktop, struct output *output) +{ + struct output *cur; + struct output *rep = NULL; + + if (!output->background) { + output_destroy(output); + return; + } + + /* Find a wl_output that is a clone of the removed wl_output. + * We don't want to leave the clone without a background or panel. */ + wl_list_for_each(cur, &desktop->outputs, link) { + if (cur == output) + continue; + + /* XXX: Assumes size matches. */ + if (cur->x == output->x && cur->y == output->y) { + rep = cur; + break; + } + } + + if (rep) { + /* If found and it does not already have a background or panel, + * hand over the background and panel so they don't get + * destroyed. + * + * We never create multiple backgrounds or panels for clones, + * but if the compositor moves outputs, a pair of wl_outputs + * might become "clones". This may happen temporarily when + * an output is about to be removed and the rest are reflowed. + * In this case it is correct to let the background/panel be + * destroyed. + */ + + if (!rep->background) { + rep->background = output->background; + output->background = NULL; + rep->background->owner = rep; + } + + if (!rep->panel) { + rep->panel = output->panel; + output->panel = NULL; + if (rep->panel) + rep->panel->owner = rep; + } + } + + output_destroy(output); +} + +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, "weston_desktop_shell")) { + desktop->shell = display_bind(desktop->display, + id, + &weston_desktop_shell_interface, + 1); + weston_desktop_shell_add_listener(desktop->shell, + &listener, + desktop); + } else if (!strcmp(interface, "wl_output")) { + create_output(desktop, id); + } +} + +static void +global_handler_remove(struct display *display, uint32_t id, + const char *interface, uint32_t version, void *data) +{ + struct desktop *desktop = data; + struct output *output; + + if (!strcmp(interface, "wl_output")) { + wl_list_for_each(output, &desktop->outputs, link) { + if (output->server_output_id == id) { + output_remove(desktop, output); + break; + } + } + } +} + +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) { + char *name = file_name_with_datadir("terminal.png"); + + /* add default launcher */ + panel_add_launcher(panel, + name, + BINDIR "/weston-terminal"); + free(name); + } +} + +static void +parse_panel_position(struct desktop *desktop, struct weston_config_section *s) +{ + char *position; + + desktop->want_panel = 1; + + weston_config_section_get_string(s, "panel-position", &position, "top"); + if (strcmp(position, "top") == 0) { + desktop->panel_position = WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP; + } else if (strcmp(position, "bottom") == 0) { + desktop->panel_position = WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM; + } else if (strcmp(position, "left") == 0) { + desktop->panel_position = WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT; + } else if (strcmp(position, "right") == 0) { + desktop->panel_position = WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT; + } else { + /* 'none' is valid here */ + if (strcmp(position, "none") != 0) + fprintf(stderr, "Wrong panel position: %s\n", position); + desktop->want_panel = 0; + } + free(position); +} + +static void +parse_clock_format(struct desktop *desktop, struct weston_config_section *s) +{ + char *clock_format; + + weston_config_section_get_string(s, "clock-format", &clock_format, ""); + if (strcmp(clock_format, "minutes") == 0) + desktop->clock_format = CLOCK_FORMAT_MINUTES; + else if (strcmp(clock_format, "seconds") == 0) + desktop->clock_format = CLOCK_FORMAT_SECONDS; + else if (strcmp(clock_format, "none") == 0) + desktop->clock_format = CLOCK_FORMAT_NONE; + else + desktop->clock_format = DEFAULT_CLOCK_FORMAT; + free(clock_format); +} + +int main(int argc, char *argv[]) +{ + struct desktop desktop = { 0 }; + struct output *output; + struct weston_config_section *s; + const char *config_file; + + desktop.unlock_task.run = unlock_dialog_finish; + wl_list_init(&desktop.outputs); + + config_file = weston_config_get_name_from_env(); + desktop.config = weston_config_parse(config_file); + s = weston_config_get_section(desktop.config, "shell", NULL, NULL); + weston_config_section_get_bool(s, "locking", &desktop.locking, true); + parse_panel_position(&desktop, s); + parse_clock_format(&desktop, s); + + desktop.display = display_create(&argc, argv); + if (desktop.display == NULL) { + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); + return -1; + } + + display_set_user_data(desktop.display, &desktop); + display_set_global_handler(desktop.display, global_handler); + display_set_global_handler_remove(desktop.display, global_handler_remove); + + /* Create panel and background for outputs processed before the shell + * global interface was processed */ + if (desktop.want_panel) + weston_desktop_shell_set_panel_position(desktop.shell, desktop.panel_position); + 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); + weston_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 0000000..8323f4f --- /dev/null +++ b/clients/dnd.c @@ -0,0 +1,867 @@ +/* + * Copyright © 2010 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#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/helpers.h" +#include "shared/xalloc.h" + +struct dnd_drag; + +struct pointer { + struct input *input; + bool dragging; + struct wl_list link; +}; + +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 wl_list pointers; +}; + +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; + uint32_t dnd_action; + 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, *item_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; + + if (dnd->current_drag && dnd->items[i] == dnd->current_drag->item) + item_surface = dnd->current_drag->translucent; + else + item_surface = dnd->items[i]->surface; + + cairo_set_source_surface(cr, item_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 int +lookup_dnd_cursor(uint32_t dnd_action) +{ + if (dnd_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + return CURSOR_DND_MOVE; + else if (dnd_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + return CURSOR_DND_COPY; + + return CURSOR_DND_FORBIDDEN; +} + +static void +dnd_drag_update_cursor(struct dnd_drag *dnd_drag) +{ + int cursor; + + if (dnd_drag->mime_type == NULL) + cursor = CURSOR_DND_FORBIDDEN; + else + cursor = lookup_dnd_cursor(dnd_drag->dnd_action); + + input_set_pointer_image(dnd_drag->input, cursor); +} + +static void +dnd_drag_update_surface(struct dnd_drag *dnd_drag) +{ + struct dnd *dnd = dnd_drag->dnd; + cairo_surface_t *surface; + struct wl_buffer *buffer; + + if (dnd_drag->mime_type && dnd_drag->dnd_action) + 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_target(void *data, + struct wl_data_source *source, const char *mime_type) +{ + struct dnd_drag *dnd_drag = data; + + dnd_drag->mime_type = mime_type; + dnd_drag_update_surface(dnd_drag); + dnd_drag_update_cursor(dnd_drag); +} + +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 +dnd_drag_destroy(struct dnd_drag *dnd_drag, bool delete_item) +{ + struct dnd *dnd = dnd_drag->dnd; + unsigned int i; + + wl_data_source_destroy(dnd_drag->data_source); + + if (delete_item) { + for (i = 0; i < ARRAY_LENGTH(dnd->items); i++) { + if (dnd_drag->item == dnd->items[i]) { + dnd->items[i] = NULL; + break; + } + } + + /* Destroy the item that has been dragged out */ + cairo_surface_destroy(dnd_drag->item->surface); + free(dnd_drag->item); + } + + dnd->current_drag = NULL; + + wl_surface_destroy(dnd_drag->drag_surface); + + cairo_surface_destroy(dnd_drag->translucent); + cairo_surface_destroy(dnd_drag->opaque); + free(dnd_drag); +} + +static void +data_source_cancelled(void *data, struct wl_data_source *source) +{ + struct dnd_drag *dnd_drag = data; + struct dnd *dnd = dnd_drag->dnd; + + /* 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. */ + dnd_drag_destroy(dnd_drag, false); + window_schedule_redraw(dnd->window); +} + +static void +data_source_dnd_drop_performed(void *data, struct wl_data_source *source) +{ +} + +static void +data_source_dnd_finished(void *data, struct wl_data_source *source) +{ + struct dnd_drag *dnd_drag = data; + struct dnd *dnd = dnd_drag->dnd; + bool delete_item; + + delete_item = + dnd_drag->dnd_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + + /* The operation is already finished, we can destroy all + * related data. + */ + dnd_drag_destroy(dnd_drag, delete_item); + window_schedule_redraw(dnd->window); +} + +static void +data_source_action(void *data, struct wl_data_source *source, uint32_t dnd_action) +{ + struct dnd_drag *dnd_drag = data; + + dnd_drag->dnd_action = dnd_action; + dnd_drag_update_surface(dnd_drag); + dnd_drag_update_cursor(dnd_drag); +} + +static const struct wl_data_source_listener data_source_listener = { + data_source_target, + data_source_send, + data_source_cancelled, + data_source_dnd_drop_performed, + data_source_dnd_finished, + data_source_action, +}; + +static cairo_surface_t * +create_drag_icon(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 rectangle rectangle; + cairo_pattern_t *pattern; + cairo_t *cr; + + rectangle.width = item_width; + rectangle.height = item_height; + surface = display_create_surface(dnd->display, NULL, &rectangle, + SURFACE_SHM); + + cr = cairo_create(surface); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + 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); + + cairo_destroy(cr); + + dnd_drag->hotspot_x = x - item->x; + dnd_drag->hotspot_y = y - item->y; + dnd_drag->width = rectangle.width; + dnd_drag->height = rectangle.height; + + return surface; +} + +static int +create_drag_source(struct dnd *dnd, + struct input *input, uint32_t time, + int32_t x, int32_t 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; + uint32_t actions; + + widget_get_allocation(dnd->widget, &allocation); + item = dnd_get_item(dnd, x, y); + x -= allocation.x; + y -= allocation.y; + + if (item) { + 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; + dnd_drag->dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + dnd_drag->mime_type = NULL; + + actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | + WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + + 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); + + if (display_get_data_device_manager_version(display) < + WL_DATA_SOURCE_SET_ACTIONS_SINCE_VERSION) { + /* Data sources version < 3 will not get action + * nor dnd_finished events, as we can't honor + * the "move" action at the time of finishing + * drag-and-drop, do it preemptively here. + */ + for (i = 0; i < ARRAY_LENGTH(dnd->items); i++) { + if (item == dnd->items[i]){ + dnd->items[i] = NULL; + break; + } + } + } + + if (dnd->self_only) { + dnd_drag->data_source = NULL; + } else { + dnd_drag->data_source = + display_create_data_source(dnd->display); + if (!dnd_drag->data_source) { + fprintf(stderr, "No data device manager\n"); + abort(); + } + 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); + } + + if (display_get_data_device_manager_version(display) >= + WL_DATA_SOURCE_SET_ACTIONS_SINCE_VERSION) { + wl_data_source_set_actions(dnd_drag->data_source, actions); + } + + 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); + + dnd_drag->opaque = + create_drag_icon(dnd_drag, item, x, y, 1); + dnd_drag->translucent = + create_drag_icon(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); + + return 0; + } else + return -1; +} + +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; +} + +/* Update all the mouse pointers in the window appropriately. + * Optionally, skip one (which will be the current pointer just + * about to start a drag). This is done here to save a scan + * through the pointer list. + */ +static void +update_pointer_images_except(struct dnd *dnd, struct input *except) +{ + struct pointer *pointer; + int32_t x, y; + + wl_list_for_each(pointer, &dnd->pointers, link) { + if (pointer->input == except) { + pointer->dragging = true; + continue; + } + input_get_position(pointer->input, &x, &y); + input_set_pointer_image(pointer->input, + lookup_cursor(dnd, x, y)); + } +} + +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; + + input_get_position(input, &x, &y); + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + input_ungrab(input); + if (create_drag_source(dnd, input, time, x, y) == 0) { + input_set_pointer_image(input, CURSOR_DRAGGING); + update_pointer_images_except(dnd, input); + } + } +} + +static void +dnd_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 dnd *dnd = data; + int32_t int_x, int_y; + + if (id > 0) + return; + + int_x = (int32_t)x; + int_y = (int32_t)y; + if (create_drag_source(dnd, input, time, int_x, int_y) == 0) + touch_grab(input, 0); +} + +static int +dnd_enter_handler(struct widget *widget, + struct input *input, float x, float y, void *data) +{ + struct dnd *dnd = data; + struct pointer *new_pointer = malloc(sizeof *new_pointer); + + if (new_pointer) { + new_pointer->input = input; + new_pointer->dragging = false; + wl_list_insert(dnd->pointers.prev, &new_pointer->link); + } + + return lookup_cursor(dnd, x, y); +} + +static void +dnd_leave_handler(struct widget *widget, + struct input *input, void *data) +{ + struct dnd *dnd = data; + struct pointer *pointer, *tmp; + + wl_list_for_each_safe(pointer, tmp, &dnd->pointers, link) + if (pointer->input == input) { + wl_list_remove(&pointer->link); + free(pointer); + } +} + +static int +dnd_motion_handler(struct widget *widget, + struct input *input, uint32_t time, + float x, float y, void *data) +{ + struct dnd *dnd = data; + struct pointer *pointer; + + wl_list_for_each(pointer, &dnd->pointers, link) + if (pointer->input == input) { + if (pointer->dragging) + return CURSOR_DRAGGING; + break; + } + + 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); + update_pointer_images_except(dnd, NULL); + 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); + } 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 = window_frame_create(dnd->window, dnd); + window_set_title(dnd->window, "Wayland Drag and Drop Demo"); + + dnd->display = display; + dnd->key = 100; + + wl_list_init(&dnd->pointers); + + 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_leave_handler(dnd->widget, dnd_leave_handler); + widget_set_motion_handler(dnd->widget, dnd_motion_handler); + widget_set_button_handler(dnd->widget, dnd_button_handler); + widget_set_touch_down_handler(dnd->widget, dnd_touch_down_handler); + + width = 4 * (item_width + item_padding) + item_padding; + height = 4 * (item_height + item_padding) + item_padding; + + window_frame_set_child_size(dnd->widget, width, height); + + return dnd; +} + +static void +dnd_destroy(struct dnd *dnd) +{ + widget_destroy(dnd->widget); + window_destroy(dnd->window); + free(dnd); +} + +int +main(int argc, char *argv[]) +{ + struct display *d; + struct dnd *dnd; + int self_only = 0; + + if (argc == 2 && !strcmp(argv[1], "--self-only")) + self_only = 1; + else if (argc > 1) { + printf("Usage: %s [OPTIONS]\n --self-only\n", argv[0]); + return 1; + } + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); + return -1; + } + + dnd = dnd_create(d); + if (self_only) + dnd->self_only = 1; + + display_run(d); + + dnd_destroy(dnd); + display_destroy(d); + + return 0; +} diff --git a/clients/editor.c b/clients/editor.c new file mode 100644 index 0000000..a59f967 --- /dev/null +++ b/clients/editor.c @@ -0,0 +1,1681 @@ +/* + * Copyright © 2012 Openismus GmbH + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include "shared/helpers.h" +#include "shared/xalloc.h" +#include "window.h" +#include "text-input-unstable-v1-client-protocol.h" + +struct text_entry { + struct widget *widget; + struct window *window; + char *text; + int active; + bool panel_visible; + 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 zwp_text_input_v1 *text_input; + PangoLayout *layout; + struct { + xkb_mod_mask_t shift_mask; + } keysym; + uint32_t serial; + uint32_t reset_serial; + uint32_t content_purpose; + bool click_to_show; + char *preferred_language; + bool button_pressed; +}; + +struct editor { + struct zwp_text_input_manager_v1 *text_input_manager; + struct wl_data_source *selection; + char *selected_text; + 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 +move_up(const char *p, uint32_t *cursor) +{ + const char *posr, *posr_i; + char text[16]; + + xkb_keysym_to_utf8(XKB_KEY_Return, text, sizeof(text)); + + posr = strstr(p, text); + while (posr) { + if (*cursor > (unsigned)(posr-p)) { + posr_i = strstr(posr+1, text); + if (!posr_i || !(*cursor > (unsigned)(posr_i-p))) { + *cursor = posr-p; + break; + } + posr = posr_i; + } else { + break; + } + } +} + +static void +move_down(const char *p, uint32_t *cursor) +{ + const char *posr; + char text[16]; + + xkb_keysym_to_utf8(XKB_KEY_Return, text, sizeof(text)); + + posr = strstr(p, text); + while (posr) { + if (*cursor <= (unsigned)(posr-p)) { + *cursor = posr-p + 1; + break; + } + posr = strstr(posr+1, text); + } +} + +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 void text_entry_touch_handler(struct widget *widget, struct input *input, + uint32_t serial, uint32_t time, int32_t id, + float tx, float ty, 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 zwp_text_input_v1 *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 zwp_text_input_v1 *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 zwp_text_input_v1 *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 zwp_text_input_v1 *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 zwp_text_input_v1 *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 ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_DEFAULT: + case ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_UNDERLINE: + attr1 = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); + break; + case ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_INCORRECT: + attr1 = pango_attr_underline_new(PANGO_UNDERLINE_ERROR); + attr2 = pango_attr_underline_color_new(65535, 0, 0); + break; + case ZWP_TEXT_INPUT_V1_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 ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_HIGHLIGHT: + case ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_ACTIVE: + attr1 = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); + attr2 = pango_attr_weight_new(PANGO_WEIGHT_BOLD); + break; + case ZWP_TEXT_INPUT_V1_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 zwp_text_input_v1 *text_input, + int32_t index) +{ + struct text_entry *entry = data; + + entry->preedit_info.cursor = index; +} + +static void +text_input_modifiers_map(void *data, + struct zwp_text_input_v1 *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 zwp_text_input_v1 *text_input, + uint32_t serial, + uint32_t time, + uint32_t key, + uint32_t state, + uint32_t modifiers) +{ + struct text_entry *entry = data; + const char *new_char; + + 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_Up || + key == XKB_KEY_Down) { + if (state != WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + if (key == XKB_KEY_Up) + move_up(entry->text, &entry->cursor); + else + move_down(entry->text, &entry->cursor); + + 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; + } + + if (key == XKB_KEY_Tab || + key == XKB_KEY_KP_Enter || + key == XKB_KEY_Return) { + char text[16]; + + if (state != WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + xkb_keysym_to_utf8(key, text, sizeof(text)); + + text_entry_insert_at_cursor(entry, text, 0, 0); + + return; + } +} + +static void +text_input_enter(void *data, + struct zwp_text_input_v1 *text_input, + struct wl_surface *surface) +{ + struct text_entry *entry = data; + + if (surface != window_get_wl_surface(entry->window)) + return; + + entry->active++; + + text_entry_update(entry); + entry->reset_serial = entry->serial; + + widget_schedule_redraw(entry->widget); +} + +static void +text_input_leave(void *data, + struct zwp_text_input_v1 *text_input) +{ + struct text_entry *entry = data; + + text_entry_commit_and_reset(entry); + entry->active--; + + if (!entry->active) { + zwp_text_input_v1_hide_input_panel(text_input); + entry->panel_visible = false; + } + + widget_schedule_redraw(entry->widget); +} + +static void +text_input_input_panel_state(void *data, + struct zwp_text_input_v1 *text_input, + uint32_t state) +{ +} + +static void +text_input_language(void *data, + struct zwp_text_input_v1 *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 zwp_text_input_v1 *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 ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_LTR: + pango_direction = PANGO_DIRECTION_LTR; + break; + case ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_RTL: + pango_direction = PANGO_DIRECTION_RTL; + break; + case ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_AUTO: + default: + pango_direction = PANGO_DIRECTION_NEUTRAL; + } + + pango_context_set_base_dir(context, pango_direction); +} + +static const struct zwp_text_input_v1_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 void +data_source_target(void *data, + struct wl_data_source *source, const char *mime_type) +{ +} + +static void +data_source_send(void *data, + struct wl_data_source *source, + const char *mime_type, int32_t fd) +{ + struct editor *editor = data; + + if (write(fd, editor->selected_text, strlen(editor->selected_text) + 1) < 0) + fprintf(stderr, "write failed: %s\n", strerror(errno)); + + close(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 void +paste_func(void *buffer, size_t len, + int32_t x, int32_t y, void *data) +{ + struct editor *editor = data; + struct text_entry *entry = editor->active_entry; + char *pasted_text; + + if (!entry) + return; + + pasted_text = malloc(len + 1); + strncpy(pasted_text, buffer, len); + pasted_text[len] = '\0'; + + text_entry_insert_at_cursor(entry, pasted_text, 0, 0); + + free(pasted_text); +} + +static void +editor_copy_cut(struct editor *editor, struct input *input, bool cut) +{ + struct text_entry *entry = editor->active_entry; + + if (!entry) + return; + + if (entry->cursor != entry->anchor) { + int start_index = MIN(entry->cursor, entry->anchor); + int end_index = MAX(entry->cursor, entry->anchor); + int len = end_index - start_index; + + editor->selected_text = realloc(editor->selected_text, len + 1); + strncpy(editor->selected_text, &entry->text[start_index], len); + editor->selected_text[len] = '\0'; + + if (cut) + text_entry_delete_text(entry, start_index, len); + + editor->selection = + display_create_data_source(editor->display); + if (!editor->selection) + return; + + wl_data_source_offer(editor->selection, + "text/plain;charset=utf-8"); + wl_data_source_add_listener(editor->selection, + &data_source_listener, editor); + input_set_selection(input, editor->selection, + display_get_serial(editor->display)); + } +} + +static void +editor_paste(struct editor *editor, struct input *input) +{ + input_receive_selection_data(input, + "text/plain;charset=utf-8", + paste_func, editor); +} + +static void +menu_func(void *data, struct input *input, int index) +{ + struct window *window = data; + struct editor *editor = window_get_user_data(window); + + fprintf(stderr, "picked entry %d\n", index); + + switch (index) { + case 0: + editor_copy_cut(editor, input, true); + break; + case 1: + editor_copy_cut(editor, input, false); + break; + case 2: + editor_paste(editor, input); + break; + } +} + +static void +show_menu(struct editor *editor, struct input *input, uint32_t time) +{ + int32_t x, y; + static const char *entries[] = { + "Cut", "Copy", "Paste" + }; + + input_get_position(input, &x, &y); + window_show_menu(editor->display, input, time, editor->window, + x + 10, y + 20, menu_func, + entries, ARRAY_LENGTH(entries)); +} + +static struct text_entry* +text_entry_create(struct editor *editor, const char *text) +{ + struct text_entry *entry; + + entry = xzalloc(sizeof *entry); + + entry->widget = widget_add_widget(editor->widget, entry); + entry->window = editor->window; + entry->text = strdup(text); + entry->active = 0; + entry->panel_visible = false; + entry->cursor = strlen(text); + entry->anchor = entry->cursor; + entry->text_input = + zwp_text_input_manager_v1_create_text_input(editor->text_input_manager); + zwp_text_input_v1_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); + widget_set_touch_down_handler(entry->widget, text_entry_touch_handler); + + return entry; +} + +static void +text_entry_destroy(struct text_entry *entry) +{ + widget_destroy(entry->widget); + zwp_text_input_v1_destroy(entry->text_input); + g_clear_object(&entry->layout); + free(entry->text); + free(entry->preferred_language); + 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) { + entry->panel_visible = !entry->panel_visible; + + if (entry->panel_visible) + zwp_text_input_v1_show_input_panel(entry->text_input); + else + zwp_text_input_v1_hide_input_panel(entry->text_input); + + return; + } + + if (!entry->click_to_show) + zwp_text_input_v1_show_input_panel(entry->text_input); + + zwp_text_input_v1_activate(entry->text_input, + seat, + surface); +} + +static void +text_entry_deactivate(struct text_entry *entry, + struct wl_seat *seat) +{ + zwp_text_input_v1_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 = xmalloc(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; + + zwp_text_input_v1_set_content_type(entry->text_input, + ZWP_TEXT_INPUT_V1_CONTENT_HINT_NONE, + entry->content_purpose); + + zwp_text_input_v1_set_surrounding_text(entry->text_input, + entry->text, + entry->cursor, + entry->anchor); + + if (entry->preferred_language) + zwp_text_input_v1_set_preferred_language(entry->text_input, + entry->preferred_language); + + text_entry_get_cursor_rectangle(entry, &cursor_rectangle); + zwp_text_input_v1_set_cursor_rectangle(entry->text_input, + cursor_rectangle.x, + cursor_rectangle.y, + cursor_rectangle.width, + cursor_rectangle.height); + + zwp_text_input_v1_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 = xmalloc(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); + } + + zwp_text_input_v1_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) + zwp_text_input_v1_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 int +text_offset_left(struct rectangle *allocation) +{ + return 10; +} + +static int +text_offset_top(struct rectangle *allocation) +{ + return allocation->height / 2; +} + +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), + text_offset_top(&allocation)); + + 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; + int tx, ty; + + if (!entry->button_pressed) { + return CURSOR_IBEAM; + } + + widget_get_allocation(entry->widget, &allocation); + + tx = x - allocation.x - text_offset_left(&allocation); + ty = y - allocation.y - text_offset_top(&allocation); + + text_entry_set_cursor_position(entry, tx, ty, 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(&allocation); + y -= allocation.y + text_offset_top(&allocation); + + editor = window_get_user_data(entry->window); + + switch (button) { + case 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); + break; + case BTN_RIGHT: + if (state == WL_POINTER_BUTTON_STATE_PRESSED) + show_menu(editor, input, time); + break; + } + + 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 && + button == BTN_LEFT) { + 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 +text_entry_touch_handler(struct widget *widget, struct input *input, + uint32_t serial, uint32_t time, int32_t id, + float tx, float ty, void *data) +{ + struct text_entry *entry = data; + struct wl_seat *seat = input_get_seat(input); + struct rectangle allocation; + struct editor *editor; + int32_t x, y; + + widget_get_allocation(entry->widget, &allocation); + + x = tx - (allocation.x + text_offset_left(&allocation)); + y = ty - (allocation.y + text_offset_top(&allocation)); + + editor = window_get_user_data(entry->window); + 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 +editor_touch_handler(struct widget *widget, struct input *input, + uint32_t serial, uint32_t time, int32_t id, + float tx, float ty, void *data) +{ + struct editor *editor = data; + + 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 +keyboard_focus_handler(struct window *window, + struct input *device, void *data) +{ + struct editor *editor = data; + + window_schedule_redraw(editor->window); +} + +static int +handle_bound_key(struct editor *editor, + struct input *input, uint32_t sym, uint32_t time) +{ + switch (sym) { + case XKB_KEY_X: + editor_copy_cut(editor, input, true); + return 1; + case XKB_KEY_C: + editor_copy_cut(editor, input, false); + return 1; + case XKB_KEY_V: + editor_paste(editor, input); + 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 editor *editor = data; + struct text_entry *entry; + const char *new_char; + char text[16]; + uint32_t modifiers; + + if (!editor->active_entry) + return; + + entry = editor->active_entry; + + if (state != WL_KEYBOARD_KEY_STATE_PRESSED) + return; + + modifiers = input_get_modifiers(input); + if ((modifiers & MOD_CONTROL_MASK) && + (modifiers & MOD_SHIFT_MASK) && + handle_bound_key(editor, input, sym, time)) + 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_Up: + text_entry_commit_and_reset(entry); + + move_up(entry->text, &entry->cursor); + if (!(input_get_modifiers(input) & MOD_SHIFT_MASK)) + entry->anchor = entry->cursor; + widget_schedule_redraw(entry->widget); + break; + case XKB_KEY_Down: + text_entry_commit_and_reset(entry); + + move_down(entry->text, &entry->cursor); + 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, "zwp_text_input_manager_v1")) { + editor->text_input_manager = + display_bind(display, name, + &zwp_text_input_manager_v1_interface, 1); + } +} + +/** Display help for command line options, and exit */ +static bool opt_help = false; + +/** Require a distinct click to show the input panel (virtual keyboard) */ +static bool opt_click_to_show = false; + +/** Set a specific (RFC-3066) language. Used for the virtual keyboard, etc. */ +static const char *opt_preferred_language = NULL; + +/** + * \brief command line options for editor + */ +static const struct weston_option editor_options[] = { + { WESTON_OPTION_BOOLEAN, "help", 'h', &opt_help }, + { WESTON_OPTION_BOOLEAN, "click-to-show", 'C', &opt_click_to_show }, + { WESTON_OPTION_STRING, "preferred-language", 'L', &opt_preferred_language }, +}; + +static void +usage(const char *program_name, int exit_code) +{ + unsigned k; + + fprintf(stderr, "Usage: %s [OPTIONS] [FILENAME]\n\n", program_name); + for (k = 0; k < ARRAY_LENGTH(editor_options); k++) { + const struct weston_option *p = &editor_options[k]; + if (p->name) { + fprintf(stderr, " --%s", p->name); + if (p->type != WESTON_OPTION_BOOLEAN) + fprintf(stderr, "=VALUE"); + fprintf(stderr, "\n"); + } + if (p->short_name) { + fprintf(stderr, " -%c", p->short_name); + if (p->type != WESTON_OPTION_BOOLEAN) + fprintf(stderr, "VALUE"); + fprintf(stderr, "\n"); + } + } + exit(exit_code); +} + +/* Load the contents of a file into a UTF-8 text buffer and return it. + * + * Caller is responsible for freeing the buffer when done. + * On error, returns NULL. + */ +static char * +read_file(char *filename) +{ + char *buffer = NULL; + int buf_size, read_size; + FILE *fin; + int errsv; + + fin = fopen(filename, "r"); + if (fin == NULL) + goto error; + + /* Determine required buffer size */ + if (fseek(fin, 0, SEEK_END) != 0) + goto error; + buf_size = ftell(fin); + if (buf_size < 0) + goto error; + rewind(fin); + + /* Create buffer and read in the text */ + buffer = (char*) malloc(sizeof(char) * (buf_size + 1)); + if (buffer == NULL) + goto error; + read_size = fread(buffer, sizeof(char), buf_size, fin); + fclose(fin); + if (buf_size != read_size) + goto error; + buffer[buf_size] = '\0'; + + return buffer; + +error: + errsv = errno; + if (fin) + fclose(fin); + free(buffer); + errno = errsv ? errsv : EINVAL; + + return NULL; +} + +int +main(int argc, char *argv[]) +{ + struct editor editor; + char *text_buffer = NULL; + + parse_options(editor_options, ARRAY_LENGTH(editor_options), + &argc, argv); + if (opt_help) + usage(argv[0], EXIT_SUCCESS); + + if (argc > 1) { + if (argv[1][0] == '-') + usage(argv[0], EXIT_FAILURE); + + text_buffer = read_file(argv[1]); + if (text_buffer == NULL) { + fprintf(stderr, "could not read file '%s': %s\n", + argv[1], strerror(errno)); + return -1; + } + } + + memset(&editor, 0, sizeof editor); + + editor.display = display_create(&argc, argv); + if (editor.display == NULL) { + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); + free(text_buffer); + return -1; + } + + display_set_user_data(editor.display, &editor); + display_set_global_handler(editor.display, global_handler); + + if (editor.text_input_manager == NULL) { + fprintf(stderr, "No text input manager global\n"); + display_destroy(editor.display); + free(text_buffer); + return -1; + } + + editor.window = window_create(editor.display); + editor.widget = window_frame_create(editor.window, &editor); + + if (text_buffer) + editor.entry = text_entry_create(&editor, text_buffer); + else + editor.entry = text_entry_create(&editor, "Entry"); + editor.entry->click_to_show = opt_click_to_show; + if (opt_preferred_language) + editor.entry->preferred_language = strdup(opt_preferred_language); + editor.editor = text_entry_create(&editor, "Numeric"); + editor.editor->content_purpose = ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_NUMBER; + editor.editor->click_to_show = opt_click_to_show; + editor.selection = NULL; + editor.selected_text = NULL; + + window_set_title(editor.window, "Text Editor"); + window_set_key_handler(editor.window, key_handler); + window_set_keyboard_focus_handler(editor.window, + keyboard_focus_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); + widget_set_touch_down_handler(editor.widget, editor_touch_handler); + + window_schedule_resize(editor.window, 500, 400); + + display_run(editor.display); + + if (editor.selected_text) + free(editor.selected_text); + if (editor.selection) + wl_data_source_destroy(editor.selection); + text_entry_destroy(editor.entry); + text_entry_destroy(editor.editor); + widget_destroy(editor.widget); + window_destroy(editor.window); + display_destroy(editor.display); + free(text_buffer); + + return 0; +} diff --git a/clients/eventdemo.c b/clients/eventdemo.c new file mode 100644 index 0000000..0a1a7ca --- /dev/null +++ b/clients/eventdemo.c @@ -0,0 +1,541 @@ +/* + * Copyright © 2011 Tim Wiederhake + * + * 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. + */ + +/** + * \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 "config.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "shared/helpers.h" +#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 bool noborder = false; + +/** 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 bool log_redraw = false; + +/** set to log resizing */ +static bool log_resize = false; + +/** set to log keyboard focus */ +static bool log_focus = false; + +/** set to log key events */ +static bool log_key = false; + +/** set to log button events */ +static bool log_button = false; + +/** set to log axis events */ +static bool log_axis = false; + +/** set to log motion events */ +static bool log_motion = false; + +/** + * \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; + + bool print_pointer_frame; +}; + +/** + * \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 input input + * \param time time + * \param key keycode + * \param unicode associated character + * \param state pressed or released + * \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: %u, unicode: %u, 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) +{ + struct eventdemo *e = data; + int32_t x, y; + + if (!log_button) + return; + + e->print_pointer_frame = true; + + input_get_position(input, &x, &y); + printf("button time: %u, button: %u, 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) +{ + struct eventdemo *e = data; + + if (!log_axis) + return; + + e->print_pointer_frame = true; + + printf("axis time: %u, axis: %s, value: %f\n", + time, + axis == WL_POINTER_AXIS_VERTICAL_SCROLL ? "vertical" : + "horizontal", + wl_fixed_to_double(value)); +} + +static void +pointer_frame_handler(struct widget *widget, struct input *input, void *data) +{ + struct eventdemo *e = data; + + if (!e->print_pointer_frame) + return; + + printf("pointer frame\n"); + e->print_pointer_frame = false; +} + +static void +axis_source_handler(struct widget *widget, struct input *input, + uint32_t source, void *data) +{ + const char *axis_source; + struct eventdemo *e = data; + + if (!log_axis) + return; + + e->print_pointer_frame = true; + + switch (source) { + case WL_POINTER_AXIS_SOURCE_WHEEL: + axis_source = "wheel"; + break; + case WL_POINTER_AXIS_SOURCE_FINGER: + axis_source = "finger"; + break; + case WL_POINTER_AXIS_SOURCE_CONTINUOUS: + axis_source = "continuous"; + break; + default: + axis_source = ""; + break; + } + + printf("axis source: %s\n", axis_source); +} + +static void +axis_stop_handler(struct widget *widget, struct input *input, + uint32_t time, uint32_t axis, + void *data) +{ + struct eventdemo *e = data; + + if (!log_axis) + return; + + e->print_pointer_frame = true; + printf("axis stop time: %u, axis: %s\n", + time, + axis == WL_POINTER_AXIS_VERTICAL_SCROLL ? "vertical" : + "horizontal"); +} + +static void +axis_discrete_handler(struct widget *widget, struct input *input, + uint32_t axis, int32_t discrete, void *data) +{ + struct eventdemo *e = data; + + if (!log_axis) + return; + + e->print_pointer_frame = true; + printf("axis discrete axis: %u value: %d\n", axis, discrete); +} + +/** + * \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 x x position relative to the window + * \param y 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: %u, x: %f, y: %f\n", time, x, y); + e->print_pointer_frame = true; + } + + 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 = zalloc(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 = window_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 pointer frame handler for the window */ + widget_set_pointer_frame_handler(e->widget, pointer_frame_handler); + + /* Set the callback axis handler for the window */ + widget_set_axis_handlers(e->widget, + axis_handler, + axis_source_handler, + axis_stop_handler, + axis_discrete_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; + + if (parse_options(eventdemo_options, + ARRAY_LENGTH(eventdemo_options), &argc, argv) > 1) { + unsigned k; + printf("Usage: %s [OPTIONS]\n\n", argv[0]); + for (k = 0; k < ARRAY_LENGTH(eventdemo_options); k++) { + const struct weston_option* p = &eventdemo_options[k]; + if (p->name) { + printf(" --%s", p->name); + if (p->type != WESTON_OPTION_BOOLEAN) + printf("=VALUE"); + putchar('\n'); + } + if (p->short_name) { + printf(" -%c", p->short_name); + if (p->type != WESTON_OPTION_BOOLEAN) + printf("VALUE"); + putchar('\n'); + } + } + return 1; + } + + if (!log_redraw && !log_resize && !log_focus && !log_key && + !log_button && !log_axis && !log_motion) + log_redraw = log_resize = log_focus = log_key = + log_button = log_axis = log_motion = true; + + /* Connect to the display and have the arguments parsed */ + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); + return -1; + } + + /* Create new eventdemo window */ + e = eventdemo_create(d); + if (e == NULL) { + fprintf(stderr, "failed to create eventdemo: %s\n", + strerror(errno)); + 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 0000000..e3471ce --- /dev/null +++ b/clients/flower.c @@ -0,0 +1,206 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#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; + + /* Don't 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_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: %s\n", + strerror(errno)); + 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); + + widget_destroy(flower.widget); + window_destroy(flower.window); + display_destroy(d); + + return 0; +} diff --git a/clients/fullscreen.c b/clients/fullscreen.c new file mode 100644 index 0000000..1b44ad5 --- /dev/null +++ b/clients/fullscreen.c @@ -0,0 +1,581 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "window.h" +#include "fullscreen-shell-unstable-v1-client-protocol.h" +#include + +struct fs_output { + struct wl_list link; + struct output *output; +}; + +struct fullscreen { + struct display *display; + struct window *window; + struct widget *widget; + struct zwp_fullscreen_shell_v1 *fshell; + enum zwp_fullscreen_shell_v1_present_method present_method; + int width, height; + int fullscreen; + float pointer_x, pointer_y; + int draw_cursor; + + struct wl_list output_list; + struct fs_output *current_output; +}; + +static void +fullscreen_handler(struct window *window, void *data) +{ + struct fullscreen *fullscreen = data; + + fullscreen->fullscreen ^= 1; + window_set_fullscreen(window, fullscreen->fullscreen); +} + +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", "center", "zoom", "zoom_crop", "stretch"}; + + 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); + + if (fullscreen->fshell) { + draw_string(cr, + "Surface size: %d, %d\n" + "Scale: %d, transform: %d\n" + "Pointer: %f,%f\n" + "Output: %s, present method: %s\n" + "Keys: (s)cale, (t)ransform, si(z)e, (m)ethod,\n" + " (o)utput, modes(w)itch, (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, + method_name[fullscreen->present_method], + fullscreen->current_output ? output_get_model(fullscreen->current_output->output): "null"); + } else { + draw_string(cr, + "Surface size: %d, %d\n" + "Scale: %d, transform: %d\n" + "Pointer: %f,%f\n" + "Fullscreen: %d\n" + "Keys: (s)cale, (t)ransform, si(z)e, (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); + } + + 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 (window_has_focus(fullscreen->window) && + 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; + } + + if (window_has_focus(fullscreen->window) && fullscreen->draw_cursor) { + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_set_line_width (cr, 8); + cairo_move_to(cr, + fullscreen->pointer_x - 12, + fullscreen->pointer_y - 12); + cairo_line_to(cr, + fullscreen->pointer_x + 12, + fullscreen->pointer_y + 12); + cairo_stroke(cr); + + cairo_move_to(cr, + fullscreen->pointer_x + 12, + fullscreen->pointer_y - 12); + cairo_line_to(cr, + fullscreen->pointer_x - 12, + fullscreen->pointer_y + 12); + cairo_stroke(cr); + + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_set_line_width (cr, 4); + cairo_move_to(cr, + fullscreen->pointer_x - 10, + fullscreen->pointer_y - 10); + cairo_line_to(cr, + fullscreen->pointer_x + 10, + fullscreen->pointer_y + 10); + cairo_stroke(cr); + + cairo_move_to(cr, + fullscreen->pointer_x + 10, + fullscreen->pointer_y - 10); + cairo_line_to(cr, + fullscreen->pointer_x - 10, + fullscreen->pointer_y + 10); + cairo_stroke(cr); + } + + 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; + struct fs_output *fsout; + struct wl_output *wl_output; + 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: + if (fullscreen->fullscreen) + break; + + 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: + if (!fullscreen->fshell) + break; + + wl_output = NULL; + if (fullscreen->current_output) + wl_output = output_get_wl_output(fullscreen->current_output->output); + fullscreen->present_method = (fullscreen->present_method + 1) % 5; + zwp_fullscreen_shell_v1_present_surface(fullscreen->fshell, + window_get_wl_surface(fullscreen->window), + fullscreen->present_method, + wl_output); + window_schedule_redraw(window); + break; + + case XKB_KEY_o: + if (!fullscreen->fshell) + break; + + fsout = fullscreen->current_output; + wl_output = fsout ? output_get_wl_output(fsout->output) : NULL; + + /* Clear the current presentation */ + zwp_fullscreen_shell_v1_present_surface(fullscreen->fshell, NULL, + 0, wl_output); + + if (fullscreen->current_output) { + if (fullscreen->current_output->link.next == &fullscreen->output_list) + fsout = NULL; + else + fsout = wl_container_of(fullscreen->current_output->link.next, + fsout, link); + } else { + fsout = wl_container_of(fullscreen->output_list.next, + fsout, link); + } + + fullscreen->current_output = fsout; + wl_output = fsout ? output_get_wl_output(fsout->output) : NULL; + zwp_fullscreen_shell_v1_present_surface(fullscreen->fshell, + window_get_wl_surface(fullscreen->window), + fullscreen->present_method, + wl_output); + window_schedule_redraw(window); + break; + + case XKB_KEY_w: + if (!fullscreen->fshell || !fullscreen->current_output) + break; + + wl_output = NULL; + if (fullscreen->current_output) + wl_output = output_get_wl_output(fullscreen->current_output->output); + zwp_fullscreen_shell_mode_feedback_v1_destroy( + zwp_fullscreen_shell_v1_present_surface_for_mode(fullscreen->fshell, + window_get_wl_surface(fullscreen->window), + wl_output, 0)); + window_schedule_redraw(window); + break; + + case XKB_KEY_f: + if (fullscreen->fshell) + break; + 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 fullscreen->draw_cursor ? CURSOR_BLANK : CURSOR_LEFT_PTR; +} + +static int +enter_handler(struct widget *widget, + struct input *input, + float x, float y, void *data) +{ + struct fullscreen *fullscreen = data; + + fullscreen->pointer_x = x; + fullscreen->pointer_y = y; + + widget_schedule_redraw(widget); + + return fullscreen->draw_cursor ? CURSOR_BLANK : 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 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_move(fullscreen->window, input, display_get_serial(fullscreen->display)); +} + +static void +fshell_capability_handler(void *data, struct zwp_fullscreen_shell_v1 *fshell, + uint32_t capability) +{ + struct fullscreen *fullscreen = data; + + switch (capability) { + case ZWP_FULLSCREEN_SHELL_V1_CAPABILITY_CURSOR_PLANE: + fullscreen->draw_cursor = 0; + break; + default: + break; + } +} + +struct zwp_fullscreen_shell_v1_listener fullscreen_shell_listener = { + fshell_capability_handler +}; + +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); +} + +static void +output_handler(struct output *output, void *data) +{ + struct fullscreen *fullscreen = data; + struct fs_output *fsout; + + /* If we've already seen this one, don't add it to the list */ + wl_list_for_each(fsout, &fullscreen->output_list, link) + if (fsout->output == output) + return; + + fsout = zalloc(sizeof *fsout); + if (fsout == NULL) { + fprintf(stderr, "out of memory in output_handler\n"); + return; + } + fsout->output = output; + wl_list_insert(&fullscreen->output_list, &fsout->link); +} + +static void +global_handler(struct display *display, uint32_t id, const char *interface, + uint32_t version, void *data) +{ + struct fullscreen *fullscreen = data; + + if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { + fullscreen->fshell = display_bind(display, id, + &zwp_fullscreen_shell_v1_interface, + 1); + zwp_fullscreen_shell_v1_add_listener(fullscreen->fshell, + &fullscreen_shell_listener, + fullscreen); + } +} + +int main(int argc, char *argv[]) +{ + struct fullscreen fullscreen; + struct display *d; + int i; + + fullscreen.width = 640; + fullscreen.height = 480; + fullscreen.fullscreen = 0; + fullscreen.present_method = ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT; + wl_list_init(&fullscreen.output_list); + fullscreen.current_output = NULL; + + 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: %s\n", + strerror(errno)); + return -1; + } + + fullscreen.display = d; + fullscreen.fshell = NULL; + display_set_user_data(fullscreen.display, &fullscreen); + display_set_global_handler(fullscreen.display, global_handler); + display_set_output_configure_handler(fullscreen.display, output_handler); + + if (fullscreen.fshell) { + fullscreen.window = window_create_custom(d); + zwp_fullscreen_shell_v1_present_surface(fullscreen.fshell, + window_get_wl_surface(fullscreen.window), + fullscreen.present_method, + NULL); + /* If we get the CURSOR_PLANE capability, we'll change this */ + fullscreen.draw_cursor = 1; + } else { + fullscreen.window = window_create(d); + fullscreen.draw_cursor = 0; + } + + fullscreen.widget = + window_add_widget(fullscreen.window, &fullscreen); + + window_set_title(fullscreen.window, "Fullscreen"); + + widget_set_transparent(fullscreen.widget, 0); + + widget_set_default_cursor(fullscreen.widget, CURSOR_LEFT_PTR); + 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_enter_handler(fullscreen.widget, enter_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); + + widget_destroy(fullscreen.widget); + window_destroy(fullscreen.window); + display_destroy(d); + + return 0; +} diff --git a/clients/gears.c b/clients/gears.c new file mode 100644 index 0000000..6090a85 --- /dev/null +++ b/clients/gears.c @@ -0,0 +1,503 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#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; + static bool first_call = true; + + if (first_call) { + gears->last_fps = time; + first_call = false; + } else + 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; + int i; + + gears = zalloc(sizeof *gears); + gears->d = display; + gears->window = window_create(display); + gears->widget = window_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; + + 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; +} + +static void +gears_destroy(struct gears *gears) +{ + widget_destroy(gears->widget); + window_destroy(gears->window); + free(gears); +} + +int main(int argc, char *argv[]) +{ + struct display *d; + struct gears *gears; + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); + return -1; + } + gears = gears_create(d); + display_run(d); + + gears_destroy(gears); + display_destroy(d); + + return 0; +} diff --git a/clients/image.c b/clients/image.c new file mode 100644 index 0000000..0a8fb5b --- /dev/null +++ b/clients/image.c @@ -0,0 +1,437 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2009 Chris Wilson + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#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.height) { + 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(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) { + free(image->filename); + free(image); + return NULL; + } + + image->window = window_create(display); + image->widget = window_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; + + if (argc <= 1 || argv[1][0]=='-') { + printf("Usage: %s image...\n", argv[0]); + return 1; + } + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); + return -1; + } + + for (i = 1; i < argc; i++) + image_create(d, argv[i], &image_counter); + + if (image_counter > 0) + display_run(d); + + display_destroy(d); + + return 0; +} diff --git a/clients/ivi-shell-user-interface.c b/clients/ivi-shell-user-interface.c new file mode 100644 index 0000000..7d2d1a2 --- /dev/null +++ b/clients/ivi-shell-user-interface.c @@ -0,0 +1,1338 @@ +/* + * Copyright (C) 2013 DENSO CORPORATION + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "shared/cairo-util.h" +#include +#include "shared/helpers.h" +#include "shared/os-compatibility.h" +#include "shared/xalloc.h" +#include +#include "shared/file-util.h" +#include "ivi-application-client-protocol.h" +#include "ivi-hmi-controller-client-protocol.h" + +/** + * A reference implementation how to use ivi-hmi-controller interface to + * interact with hmi-controller. This is launched from hmi-controller by using + * hmi_client_start and create a pthread. + * + * The basic flow is as followed, + * 1/ read configuration from weston.ini. + * 2/ draw png file to surface according to configuration of weston.ini + * 3/ set up UI by using ivi-hmi-controller protocol + * 4/ Enter event loop + * 5/ If a surface receives touch/pointer event, followings are invoked + * according to type of event and surface + * 5-1/ If a surface to launch ivi_application receive touch up, it execs + * ivi-application configured in weston.ini. + * 5-2/ If a surface to switch layout mode receive touch up, it sends a request, + * ivi_hmi_controller_switch_mode, to hmi-controller. + * 5-3/ If a surface to show workspace having launchers, it sends a request, + * ivi_hmi_controller_home, to hmi-controller. + * 5-4/ If touch down events happens in workspace, + * ivi_hmi_controller_workspace_control is sent to slide workspace. + * When control finished, event: ivi_hmi_controller_workspace_end_control + * is received. + */ + +/***************************************************************************** + * structure, globals + ****************************************************************************/ +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 +}; +struct wlContextCommon { + struct wl_display *wlDisplay; + struct wl_registry *wlRegistry; + struct wl_compositor *wlCompositor; + struct wl_shm *wlShm; + uint32_t formats; + struct wl_seat *wlSeat; + struct wl_pointer *wlPointer; + struct wl_touch *wlTouch; + struct ivi_application *iviApplication; + struct ivi_hmi_controller *hmiCtrl; + struct hmi_homescreen_setting *hmi_setting; + struct wl_list list_wlContextStruct; + struct wl_surface *enterSurface; + int32_t is_home_on; + struct wl_cursor_theme *cursor_theme; + struct wl_cursor **cursors; + struct wl_surface *pointer_surface; + enum cursor_type current_cursor; + uint32_t enter_serial; +}; + +struct wlContextStruct { + struct wlContextCommon *cmm; + struct wl_surface *wlSurface; + struct wl_buffer *wlBuffer; + cairo_surface_t *ctx_image; + void *data; + uint32_t id_surface; + struct wl_list link; +}; + +struct +hmi_homescreen_srf { + uint32_t id; + char *filePath; + uint32_t color; +}; + +struct +hmi_homescreen_workspace { + struct wl_array launcher_id_array; + struct wl_list link; +}; + +struct +hmi_homescreen_launcher { + uint32_t icon_surface_id; + uint32_t workspace_id; + char *icon; + char *path; + struct wl_list link; +}; + +struct +hmi_homescreen_setting { + struct hmi_homescreen_srf background; + struct hmi_homescreen_srf panel; + struct hmi_homescreen_srf tiling; + struct hmi_homescreen_srf sidebyside; + struct hmi_homescreen_srf fullscreen; + struct hmi_homescreen_srf random; + struct hmi_homescreen_srf home; + struct hmi_homescreen_srf workspace_background; + + struct wl_list workspace_list; + struct wl_list launcher_list; + + char *cursor_theme; + int32_t cursor_size; + uint32_t transition_duration; + uint32_t surface_id_offset; + int32_t screen_num; +}; + +/***************************************************************************** + * Event Handler + ****************************************************************************/ + +static void +shm_format(void *data, struct wl_shm *pWlShm, uint32_t format) +{ + struct wlContextCommon *pCtx = data; + + pCtx->formats |= (1 << format); +} + +static struct wl_shm_listener shm_listenter = { + shm_format +}; + +static int32_t +getIdOfWlSurface(struct wlContextCommon *pCtx, struct wl_surface *wlSurface) +{ + struct wlContextStruct *pWlCtxSt = NULL; + + if (NULL == pCtx || NULL == wlSurface ) + return 0; + + wl_list_for_each(pWlCtxSt, &pCtx->list_wlContextStruct, link) { + if (pWlCtxSt->wlSurface == wlSurface) + return pWlCtxSt->id_surface; + } + + return -1; +} + +static void +set_pointer_image(struct wlContextCommon *pCtx, uint32_t index) +{ + struct wl_cursor *cursor = NULL; + struct wl_cursor_image *image = NULL; + struct wl_buffer *buffer = NULL; + + if (!pCtx->wlPointer || !pCtx->cursors) + return; + + if (CURSOR_BLANK == pCtx->current_cursor) { + wl_pointer_set_cursor(pCtx->wlPointer, pCtx->enter_serial, + NULL, 0, 0); + return; + } + + cursor = pCtx->cursors[pCtx->current_cursor]; + if (!cursor) + return; + + if (cursor->image_count <= index) { + 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(pCtx->wlPointer, pCtx->enter_serial, + pCtx->pointer_surface, + image->hotspot_x, image->hotspot_y); + + wl_surface_attach(pCtx->pointer_surface, buffer, 0, 0); + + wl_surface_damage(pCtx->pointer_surface, 0, 0, + image->width, image->height); + + wl_surface_commit(pCtx->pointer_surface); +} + +static void +PointerHandleEnter(void *data, struct wl_pointer *wlPointer, uint32_t serial, + struct wl_surface *wlSurface, wl_fixed_t sx, wl_fixed_t sy) +{ + struct wlContextCommon *pCtx = data; + + pCtx->enter_serial = serial; + pCtx->enterSurface = wlSurface; + set_pointer_image(pCtx, 0); +#ifdef _DEBUG + printf("ENTER PointerHandleEnter: x(%d), y(%d)\n", sx, sy); +#endif +} + +static void +PointerHandleLeave(void *data, struct wl_pointer *wlPointer, uint32_t serial, + struct wl_surface *wlSurface) +{ + struct wlContextCommon *pCtx = data; + + pCtx->enterSurface = NULL; + +#ifdef _DEBUG + printf("ENTER PointerHandleLeave: serial(%d)\n", serial); +#endif +} + +static void +PointerHandleMotion(void *data, struct wl_pointer *wlPointer, uint32_t time, + wl_fixed_t sx, wl_fixed_t sy) +{ +#ifdef _DEBUG + printf("ENTER PointerHandleMotion: x(%d), y(%d)\n", sx, sy); +#endif +} + +/** + * if a surface assigned as launcher receives touch-off event, invoking + * ivi-application which configured in weston.ini with path to binary. + */ +extern char **environ; /*defied by libc */ + +static pid_t +execute_process(char *path, char *argv[]) +{ + pid_t pid = fork(); + if (pid < 0) + fprintf(stderr, "Failed to fork\n"); + + if (pid) + return pid; + + if (-1 == execve(path, argv, environ)) { + fprintf(stderr, "Failed to execve %s\n", path); + exit(1); + } + + return pid; +} + +static int32_t +launcher_button(uint32_t surfaceId, struct wl_list *launcher_list) +{ + struct hmi_homescreen_launcher *launcher = NULL; + + wl_list_for_each(launcher, launcher_list, link) { + char *argv[] = { NULL }; + + if (surfaceId != launcher->icon_surface_id) + continue; + + execute_process(launcher->path, argv); + + return 1; + } + + return 0; +} + +/** + * is-method to identify a surface set as launcher in workspace or workspace + * itself. This is-method is used to decide whether request; + * ivi_hmi_controller_workspace_control is sent or not. + */ +static int32_t +isWorkspaceSurface(uint32_t id, struct hmi_homescreen_setting *hmi_setting) +{ + struct hmi_homescreen_launcher *launcher = NULL; + + if (id == hmi_setting->workspace_background.id) + return 1; + + wl_list_for_each(launcher, &hmi_setting->launcher_list, link) { + if (id == launcher->icon_surface_id) + return 1; + } + + return 0; +} + +/** + * Decide which request is sent to hmi-controller + */ +static void +touch_up(struct ivi_hmi_controller *hmi_ctrl, uint32_t id_surface, + int32_t *is_home_on, struct hmi_homescreen_setting *hmi_setting) +{ + if (launcher_button(id_surface, &hmi_setting->launcher_list)) { + *is_home_on = 0; + ivi_hmi_controller_home(hmi_ctrl, IVI_HMI_CONTROLLER_HOME_OFF); + } else if (id_surface == hmi_setting->tiling.id) { + ivi_hmi_controller_switch_mode(hmi_ctrl, + IVI_HMI_CONTROLLER_LAYOUT_MODE_TILING); + } else if (id_surface == hmi_setting->sidebyside.id) { + ivi_hmi_controller_switch_mode(hmi_ctrl, + IVI_HMI_CONTROLLER_LAYOUT_MODE_SIDE_BY_SIDE); + } else if (id_surface == hmi_setting->fullscreen.id) { + ivi_hmi_controller_switch_mode(hmi_ctrl, + IVI_HMI_CONTROLLER_LAYOUT_MODE_FULL_SCREEN); + } else if (id_surface == hmi_setting->random.id) { + ivi_hmi_controller_switch_mode(hmi_ctrl, + IVI_HMI_CONTROLLER_LAYOUT_MODE_RANDOM); + } else if (id_surface == hmi_setting->home.id) { + *is_home_on = !(*is_home_on); + if (*is_home_on) { + ivi_hmi_controller_home(hmi_ctrl, + IVI_HMI_CONTROLLER_HOME_ON); + } else { + ivi_hmi_controller_home(hmi_ctrl, + IVI_HMI_CONTROLLER_HOME_OFF); + } + } +} + +/** + * Even handler of Pointer event. IVI system is usually manipulated by touch + * screen. However, some systems also have pointer device. + * Release is the same behavior as touch off + * Pressed is the same behavior as touch on + */ +static void +PointerHandleButton(void *data, struct wl_pointer *wlPointer, uint32_t serial, + uint32_t time, uint32_t button, uint32_t state) +{ + struct wlContextCommon *pCtx = data; + struct ivi_hmi_controller *hmi_ctrl = pCtx->hmiCtrl; + const uint32_t id_surface = getIdOfWlSurface(pCtx, pCtx->enterSurface); + + if (BTN_RIGHT == button) + return; + + switch (state) { + case WL_POINTER_BUTTON_STATE_RELEASED: + touch_up(hmi_ctrl, id_surface, &pCtx->is_home_on, + pCtx->hmi_setting); + break; + + case WL_POINTER_BUTTON_STATE_PRESSED: + + if (isWorkspaceSurface(id_surface, pCtx->hmi_setting)) { + ivi_hmi_controller_workspace_control(hmi_ctrl, + pCtx->wlSeat, + serial); + } + + break; + } +#ifdef _DEBUG + printf("ENTER PointerHandleButton: button(%d), state(%d)\n", + button, state); +#endif +} + +static void +PointerHandleAxis(void *data, struct wl_pointer *wlPointer, uint32_t time, + uint32_t axis, wl_fixed_t value) +{ +#ifdef _DEBUG + printf("ENTER PointerHandleAxis: axis(%d), value(%d)\n", axis, value); +#endif +} + +static struct wl_pointer_listener pointer_listener = { + PointerHandleEnter, + PointerHandleLeave, + PointerHandleMotion, + PointerHandleButton, + PointerHandleAxis +}; + +/** + * Even handler of touch event + */ +static void +TouchHandleDown(void *data, struct wl_touch *wlTouch, uint32_t serial, + uint32_t time, struct wl_surface *surface, int32_t id, + wl_fixed_t x_w, wl_fixed_t y_w) +{ + struct wlContextCommon *pCtx = data; + struct ivi_hmi_controller *hmi_ctrl = pCtx->hmiCtrl; + uint32_t id_surface = 0; + + if (0 == id) + pCtx->enterSurface = surface; + + id_surface = getIdOfWlSurface(pCtx, pCtx->enterSurface); + + /** + * When touch down happens on surfaces of workspace, ask + * hmi-controller to start control workspace to select page of + * workspace. After sending seat to hmi-controller by + * ivi_hmi_controller_workspace_control, + * hmi-controller-homescreen doesn't receive any event till + * hmi-controller sends back it. + */ + if (isWorkspaceSurface(id_surface, pCtx->hmi_setting)) { + ivi_hmi_controller_workspace_control(hmi_ctrl, pCtx->wlSeat, + serial); + } +} + +static void +TouchHandleUp(void *data, struct wl_touch *wlTouch, uint32_t serial, + uint32_t time, int32_t id) +{ + struct wlContextCommon *pCtx = data; + struct ivi_hmi_controller *hmi_ctrl = pCtx->hmiCtrl; + + const uint32_t id_surface = getIdOfWlSurface(pCtx, pCtx->enterSurface); + + /** + * triggering event according to touch-up happening on which surface. + */ + if (id == 0){ + touch_up(hmi_ctrl, id_surface, &pCtx->is_home_on, + pCtx->hmi_setting); + } +} + +static void +TouchHandleMotion(void *data, struct wl_touch *wlTouch, uint32_t time, + int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) +{ +} + +static void +TouchHandleFrame(void *data, struct wl_touch *wlTouch) +{ +} + +static void +TouchHandleCancel(void *data, struct wl_touch *wlTouch) +{ +} + +static struct wl_touch_listener touch_listener = { + TouchHandleDown, + TouchHandleUp, + TouchHandleMotion, + TouchHandleFrame, + TouchHandleCancel, +}; + +/** + * Handler of capabilities + */ +static void +seat_handle_capabilities(void *data, struct wl_seat *seat, uint32_t caps) +{ + struct wlContextCommon *p_wlCtx = (struct wlContextCommon*)data; + struct wl_seat *wlSeat = p_wlCtx->wlSeat; + struct wl_pointer *wlPointer = p_wlCtx->wlPointer; + struct wl_touch *wlTouch = p_wlCtx->wlTouch; + + if (p_wlCtx->hmi_setting->cursor_theme) { + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !wlPointer){ + wlPointer = wl_seat_get_pointer(wlSeat); + wl_pointer_add_listener(wlPointer, + &pointer_listener, data); + } else + if (!(caps & WL_SEAT_CAPABILITY_POINTER) && wlPointer){ + wl_pointer_destroy(wlPointer); + wlPointer = NULL; + } + p_wlCtx->wlPointer = wlPointer; + } + + if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !wlTouch){ + wlTouch = wl_seat_get_touch(wlSeat); + wl_touch_add_listener(wlTouch, &touch_listener, data); + } else + if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && wlTouch){ + wl_touch_destroy(wlTouch); + wlTouch = NULL; + } + p_wlCtx->wlTouch = wlTouch; +} + +static struct wl_seat_listener seat_Listener = { + seat_handle_capabilities, +}; + +/** + * Registration of event + * This event is received when hmi-controller server finished controlling + * workspace. + */ +static void +ivi_hmi_controller_workspace_end_control(void *data, + struct ivi_hmi_controller *hmi_ctrl, + int32_t is_controlled) +{ + struct wlContextCommon *pCtx = data; + const uint32_t id_surface = getIdOfWlSurface(pCtx, pCtx->enterSurface); + + if (is_controlled) + return; + + /** + * During being controlled by hmi-controller, any input event is not + * notified. So when control ends with touch up, it invokes launcher + * if up event happens on a launcher surface. + * + */ + if (launcher_button(id_surface, &pCtx->hmi_setting->launcher_list)) { + pCtx->is_home_on = 0; + ivi_hmi_controller_home(hmi_ctrl, IVI_HMI_CONTROLLER_HOME_OFF); + } +} + +static const struct ivi_hmi_controller_listener hmi_controller_listener = { + ivi_hmi_controller_workspace_end_control +}; + +/** + * Registration of interfaces + */ +static void +registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, + const char *interface, uint32_t version) +{ + struct wlContextCommon *p_wlCtx = (struct wlContextCommon*)data; + + if (!strcmp(interface, "wl_compositor")) { + p_wlCtx->wlCompositor = + wl_registry_bind(registry, name, + &wl_compositor_interface, 1); + } else if (!strcmp(interface, "wl_shm")) { + p_wlCtx->wlShm = + wl_registry_bind(registry, name, &wl_shm_interface, 1); + wl_shm_add_listener(p_wlCtx->wlShm, &shm_listenter, p_wlCtx); + } else if (!strcmp(interface, "wl_seat")) { + /* XXX: should be handling multiple wl_seats */ + if (p_wlCtx->wlSeat) + return; + + p_wlCtx->wlSeat = + wl_registry_bind(registry, name, &wl_seat_interface, 1); + wl_seat_add_listener(p_wlCtx->wlSeat, &seat_Listener, data); + } else if (!strcmp(interface, "ivi_application")) { + p_wlCtx->iviApplication = + wl_registry_bind(registry, name, + &ivi_application_interface, 1); + } else if (!strcmp(interface, "ivi_hmi_controller")) { + p_wlCtx->hmiCtrl = + wl_registry_bind(registry, name, + &ivi_hmi_controller_interface, 1); + + ivi_hmi_controller_add_listener(p_wlCtx->hmiCtrl, + &hmi_controller_listener, p_wlCtx); + } else if (!strcmp(interface, "wl_output")) { + p_wlCtx->hmi_setting->screen_num++; + } +} + +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 +frame_listener_func(void *data, struct wl_callback *callback, uint32_t time) +{ + if (callback) + wl_callback_destroy(callback); +} + +static const struct wl_callback_listener frame_listener = { + frame_listener_func +}; + +/* + * 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 wlContextCommon *cmm) +{ + uint32_t i = 0; + uint32_t j = 0; + struct wl_cursor *cursor = NULL; + char *cursor_theme = cmm->hmi_setting->cursor_theme; + int32_t cursor_size = cmm->hmi_setting->cursor_size; + + cmm->cursor_theme = wl_cursor_theme_load(cursor_theme, cursor_size, + cmm->wlShm); + + cmm->cursors = + xzalloc(ARRAY_LENGTH(cursors) * sizeof(cmm->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( + cmm->cursor_theme, cursors[i].names[j]); + } + + if (!cursor) { + fprintf(stderr, "could not load cursor '%s'\n", + cursors[i].names[0]); + } + + cmm->cursors[i] = cursor; + } +} + +static void +destroy_cursors(struct wlContextCommon *cmm) +{ + if (cmm->cursor_theme) + wl_cursor_theme_destroy(cmm->cursor_theme); + + free(cmm->cursors); +} + +/** + * Internal method to prepare parts of UI + */ +static void +createShmBuffer(struct wlContextStruct *p_wlCtx) +{ + struct wl_shm_pool *pool; + + int fd = -1; + int size = 0; + int width = 0; + int height = 0; + int stride = 0; + + width = cairo_image_surface_get_width(p_wlCtx->ctx_image); + height = cairo_image_surface_get_height(p_wlCtx->ctx_image); + stride = cairo_image_surface_get_stride(p_wlCtx->ctx_image); + + size = stride * height; + + fd = os_create_anonymous_file(size); + if (fd < 0) { + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); + return ; + } + + p_wlCtx->data = + mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + + if (MAP_FAILED == p_wlCtx->data) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + close(fd); + return; + } + + pool = wl_shm_create_pool(p_wlCtx->cmm->wlShm, fd, size); + p_wlCtx->wlBuffer = wl_shm_pool_create_buffer(pool, 0, + width, + height, + stride, + WL_SHM_FORMAT_ARGB8888); + + if (NULL == p_wlCtx->wlBuffer) { + fprintf(stderr, "wl_shm_create_buffer failed: %s\n", + strerror(errno)); + close(fd); + return; + } + + wl_shm_pool_destroy(pool); + close(fd); +} + +static void +destroyWLContextCommon(struct wlContextCommon *p_wlCtx) +{ + destroy_cursors(p_wlCtx); + + if (p_wlCtx->pointer_surface) + wl_surface_destroy(p_wlCtx->pointer_surface); + + if (p_wlCtx->wlCompositor) + wl_compositor_destroy(p_wlCtx->wlCompositor); +} + +static void +destroyWLContextStruct(struct wlContextStruct *p_wlCtx) +{ + if (p_wlCtx->wlSurface) + wl_surface_destroy(p_wlCtx->wlSurface); + + if (p_wlCtx->ctx_image) { + cairo_surface_destroy(p_wlCtx->ctx_image); + p_wlCtx->ctx_image = NULL; + } +} + +static int +createSurface(struct wlContextStruct *p_wlCtx) +{ + p_wlCtx->wlSurface = + wl_compositor_create_surface(p_wlCtx->cmm->wlCompositor); + if (NULL == p_wlCtx->wlSurface) { + printf("Error: wl_compositor_create_surface failed.\n"); + destroyWLContextCommon(p_wlCtx->cmm); + abort(); + } + + return 0; +} + +static void +drawImage(struct wlContextStruct *p_wlCtx) +{ + struct wl_callback *callback; + + int width = 0; + int height = 0; + int stride = 0; + void *data = NULL; + + width = cairo_image_surface_get_width(p_wlCtx->ctx_image); + height = cairo_image_surface_get_height(p_wlCtx->ctx_image); + stride = cairo_image_surface_get_stride(p_wlCtx->ctx_image); + data = cairo_image_surface_get_data(p_wlCtx->ctx_image); + + memcpy(p_wlCtx->data, data, stride * height); + + wl_surface_attach(p_wlCtx->wlSurface, p_wlCtx->wlBuffer, 0, 0); + wl_surface_damage(p_wlCtx->wlSurface, 0, 0, width, height); + + callback = wl_surface_frame(p_wlCtx->wlSurface); + wl_callback_add_listener(callback, &frame_listener, NULL); + + wl_surface_commit(p_wlCtx->wlSurface); +} + +static void +create_ivisurface(struct wlContextStruct *p_wlCtx, + uint32_t id_surface, + cairo_surface_t *surface) +{ + struct ivi_surface *ivisurf = NULL; + + p_wlCtx->ctx_image = surface; + + p_wlCtx->id_surface = id_surface; + wl_list_init(&p_wlCtx->link); + wl_list_insert(&p_wlCtx->cmm->list_wlContextStruct, &p_wlCtx->link); + + createSurface(p_wlCtx); + createShmBuffer(p_wlCtx); + + ivisurf = ivi_application_surface_create(p_wlCtx->cmm->iviApplication, + id_surface, + p_wlCtx->wlSurface); + if (ivisurf == NULL) { + fprintf(stderr, "Failed to create ivi_client_surface\n"); + return; + } + + drawImage(p_wlCtx); +} + +static void +create_ivisurfaceFromFile(struct wlContextStruct *p_wlCtx, + uint32_t id_surface, + const char *imageFile) +{ + cairo_surface_t *surface = load_cairo_surface(imageFile); + + if (NULL == surface) { + fprintf(stderr, "Failed to load_cairo_surface %s\n", imageFile); + return; + } + + create_ivisurface(p_wlCtx, id_surface, surface); +} + +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 +create_ivisurfaceFromColor(struct wlContextStruct *p_wlCtx, + uint32_t id_surface, + uint32_t width, uint32_t height, + uint32_t color) +{ + cairo_surface_t *surface = NULL; + cairo_t *cr = NULL; + + surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + width, height); + + cr = cairo_create(surface); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_rectangle(cr, 0, 0, width, height); + set_hex_color(cr, color); + cairo_fill(cr); + cairo_destroy(cr); + + create_ivisurface(p_wlCtx, id_surface, surface); +} + +static void +UI_ready(struct ivi_hmi_controller *controller) +{ + ivi_hmi_controller_UI_ready(controller); +} + +/** + * Internal method to set up UI by using ivi-hmi-controller + */ +static void +create_background(struct wlContextStruct *p_wlCtx, const uint32_t id_surface, + const char *imageFile) +{ + create_ivisurfaceFromFile(p_wlCtx, id_surface, imageFile); +} + +static void +create_panel(struct wlContextStruct *p_wlCtx, const uint32_t id_surface, + const char *imageFile) +{ + create_ivisurfaceFromFile(p_wlCtx, id_surface, imageFile); +} + +static void +create_button(struct wlContextStruct *p_wlCtx, const uint32_t id_surface, + const char *imageFile, uint32_t number) +{ + create_ivisurfaceFromFile(p_wlCtx, id_surface, imageFile); +} + +static void +create_home_button(struct wlContextStruct *p_wlCtx, const uint32_t id_surface, + const char *imageFile) +{ + create_ivisurfaceFromFile(p_wlCtx, id_surface, imageFile); +} + +static void +create_workspace_background(struct wlContextStruct *p_wlCtx, + struct hmi_homescreen_srf *srf) +{ + create_ivisurfaceFromColor(p_wlCtx, srf->id, 1, 1, srf->color); +} + +static void +create_launchers(struct wlContextCommon *cmm, struct wl_list *launcher_list) +{ + struct hmi_homescreen_launcher **launchers; + struct hmi_homescreen_launcher *launcher = NULL; + + int launcher_count = wl_list_length(launcher_list); + int ii = 0; + int start = 0; + + if (0 == launcher_count) + return; + + launchers = xzalloc(launcher_count * sizeof(*launchers)); + + wl_list_for_each(launcher, launcher_list, link) { + launchers[ii] = launcher; + ii++; + } + + for (ii = 0; ii < launcher_count; ii++) { + int jj = 0; + + if (ii != launcher_count - 1 && + launchers[ii]->workspace_id == + launchers[ii + 1]->workspace_id) + continue; + + for (jj = start; jj <= ii; jj++) { + struct wlContextStruct *p_wlCtx; + + p_wlCtx = xzalloc(sizeof(*p_wlCtx)); + p_wlCtx->cmm = cmm; + create_ivisurfaceFromFile(p_wlCtx, + launchers[jj]->icon_surface_id, + launchers[jj]->icon); + } + + start = ii + 1; + } + + free(launchers); +} + +/** + * Internal method to read out weston.ini to get configuration + */ +static struct hmi_homescreen_setting * +hmi_homescreen_setting_create(void) +{ + const char *config_file; + struct weston_config *config = NULL; + struct weston_config_section *shellSection = NULL; + struct hmi_homescreen_setting *setting = xzalloc(sizeof(*setting)); + struct weston_config_section *section = NULL; + const char *name = NULL; + uint32_t workspace_layer_id; + uint32_t icon_surface_id = 0; + char *filename; + + wl_list_init(&setting->workspace_list); + wl_list_init(&setting->launcher_list); + + config_file = weston_config_get_name_from_env(); + config = weston_config_parse(config_file); + + shellSection = + weston_config_get_section(config, "ivi-shell", NULL, NULL); + + weston_config_section_get_string( + shellSection, "cursor-theme", &setting->cursor_theme, NULL); + + weston_config_section_get_int( + shellSection, "cursor-size", &setting->cursor_size, 32); + + weston_config_section_get_uint( + shellSection, "workspace-layer-id", &workspace_layer_id, 3000); + + filename = file_name_with_datadir("background.png"); + weston_config_section_get_string( + shellSection, "background-image", &setting->background.filePath, + filename); + free(filename); + + weston_config_section_get_uint( + shellSection, "background-id", &setting->background.id, 1001); + + filename = file_name_with_datadir("panel.png"); + weston_config_section_get_string( + shellSection, "panel-image", &setting->panel.filePath, + filename); + free(filename); + + weston_config_section_get_uint( + shellSection, "panel-id", &setting->panel.id, 1002); + + filename = file_name_with_datadir("tiling.png"); + weston_config_section_get_string( + shellSection, "tiling-image", &setting->tiling.filePath, + filename); + free(filename); + + weston_config_section_get_uint( + shellSection, "tiling-id", &setting->tiling.id, 1003); + + filename = file_name_with_datadir("sidebyside.png"); + weston_config_section_get_string( + shellSection, "sidebyside-image", &setting->sidebyside.filePath, + filename); + free(filename); + + weston_config_section_get_uint( + shellSection, "sidebyside-id", &setting->sidebyside.id, 1004); + + filename = file_name_with_datadir("fullscreen.png"); + weston_config_section_get_string( + shellSection, "fullscreen-image", &setting->fullscreen.filePath, + filename); + free(filename); + + weston_config_section_get_uint( + shellSection, "fullscreen-id", &setting->fullscreen.id, 1005); + + filename = file_name_with_datadir("random.png"); + weston_config_section_get_string( + shellSection, "random-image", &setting->random.filePath, + filename); + free(filename); + + weston_config_section_get_uint( + shellSection, "random-id", &setting->random.id, 1006); + + filename = file_name_with_datadir("home.png"); + weston_config_section_get_string( + shellSection, "home-image", &setting->home.filePath, + filename); + free(filename); + + weston_config_section_get_uint( + shellSection, "home-id", &setting->home.id, 1007); + + weston_config_section_get_color( + shellSection, "workspace-background-color", + &setting->workspace_background.color, 0x99000000); + + weston_config_section_get_uint( + shellSection, "workspace-background-id", + &setting->workspace_background.id, 2001); + + weston_config_section_get_uint( + shellSection, "surface-id-offset", &setting->surface_id_offset, 10); + + icon_surface_id = workspace_layer_id + 1; + + while (weston_config_next_section(config, §ion, &name)) { + struct hmi_homescreen_launcher *launcher; + + if (strcmp(name, "ivi-launcher") != 0) + continue; + + launcher = xzalloc(sizeof(*launcher)); + wl_list_init(&launcher->link); + + weston_config_section_get_string(section, "icon", + &launcher->icon, NULL); + weston_config_section_get_string(section, "path", + &launcher->path, NULL); + weston_config_section_get_uint(section, "workspace-id", + &launcher->workspace_id, 0); + weston_config_section_get_uint(section, "icon-id", + &launcher->icon_surface_id, + icon_surface_id); + icon_surface_id++; + + wl_list_insert(setting->launcher_list.prev, &launcher->link); + } + + weston_config_destroy(config); + return setting; +} + +/** + * Main thread + * + * The basic flow are as followed, + * 1/ read configuration from weston.ini by hmi_homescreen_setting_create + * 2/ draw png file to surface according to configuration of weston.ini and + * set up UI by using ivi-hmi-controller protocol by each create_* method + */ +int main(int argc, char **argv) +{ + struct wlContextCommon wlCtxCommon; + struct wlContextStruct *wlCtx_BackGround; + struct wlContextStruct *wlCtx_Panel; + struct wlContextStruct wlCtx_Button_1; + struct wlContextStruct wlCtx_Button_2; + struct wlContextStruct wlCtx_Button_3; + struct wlContextStruct wlCtx_Button_4; + struct wlContextStruct wlCtx_HomeButton; + struct wlContextStruct wlCtx_WorkSpaceBackGround; + struct wl_list launcher_wlCtxList; + int ret = 0; + struct hmi_homescreen_setting *hmi_setting; + struct wlContextStruct *pWlCtxSt = NULL; + int i = 0; + + hmi_setting = hmi_homescreen_setting_create(); + + memset(&wlCtxCommon, 0x00, sizeof(wlCtxCommon)); + memset(&wlCtx_Button_1, 0x00, sizeof(wlCtx_Button_1)); + memset(&wlCtx_Button_2, 0x00, sizeof(wlCtx_Button_2)); + memset(&wlCtx_Button_3, 0x00, sizeof(wlCtx_Button_3)); + memset(&wlCtx_Button_4, 0x00, sizeof(wlCtx_Button_4)); + memset(&wlCtx_HomeButton, 0x00, sizeof(wlCtx_HomeButton)); + memset(&wlCtx_WorkSpaceBackGround, 0x00, + sizeof(wlCtx_WorkSpaceBackGround)); + wl_list_init(&launcher_wlCtxList); + wl_list_init(&wlCtxCommon.list_wlContextStruct); + + wlCtxCommon.hmi_setting = hmi_setting; + + wlCtxCommon.wlDisplay = wl_display_connect(NULL); + if (NULL == wlCtxCommon.wlDisplay) { + printf("Error: wl_display_connect failed.\n"); + return -1; + } + + /* get wl_registry */ + wlCtxCommon.formats = 0; + wlCtxCommon.wlRegistry = wl_display_get_registry(wlCtxCommon.wlDisplay); + wl_registry_add_listener(wlCtxCommon.wlRegistry, + ®istry_listener, &wlCtxCommon); + wl_display_roundtrip(wlCtxCommon.wlDisplay); + + if (wlCtxCommon.wlShm == NULL) { + fprintf(stderr, "No wl_shm global\n"); + exit(1); + } + + wl_display_roundtrip(wlCtxCommon.wlDisplay); + + if (!(wlCtxCommon.formats & (1 << WL_SHM_FORMAT_XRGB8888))) { + fprintf(stderr, "WL_SHM_FORMAT_XRGB32 not available\n"); + exit(1); + } + + wlCtx_BackGround = xzalloc(hmi_setting->screen_num * sizeof(struct wlContextStruct)); + wlCtx_Panel= xzalloc(hmi_setting->screen_num * sizeof(struct wlContextStruct)); + + if (wlCtxCommon.hmi_setting->cursor_theme) { + create_cursors(&wlCtxCommon); + + wlCtxCommon.pointer_surface = + wl_compositor_create_surface(wlCtxCommon.wlCompositor); + + wlCtxCommon.current_cursor = CURSOR_LEFT_PTR; + } + + wlCtx_Button_1.cmm = &wlCtxCommon; + wlCtx_Button_2.cmm = &wlCtxCommon; + wlCtx_Button_3.cmm = &wlCtxCommon; + wlCtx_Button_4.cmm = &wlCtxCommon; + wlCtx_HomeButton.cmm = &wlCtxCommon; + wlCtx_WorkSpaceBackGround.cmm = &wlCtxCommon; + + /* create desktop widgets */ + for (i = 0; i < hmi_setting->screen_num; i++) { + wlCtx_BackGround[i].cmm = &wlCtxCommon; + create_background(&wlCtx_BackGround[i], + hmi_setting->background.id + + (i * hmi_setting->surface_id_offset), + hmi_setting->background.filePath); + + wlCtx_Panel[i].cmm = &wlCtxCommon; + create_panel(&wlCtx_Panel[i], + hmi_setting->panel.id + (i * hmi_setting->surface_id_offset), + hmi_setting->panel.filePath); + } + + create_button(&wlCtx_Button_1, hmi_setting->tiling.id, + hmi_setting->tiling.filePath, 0); + + create_button(&wlCtx_Button_2, hmi_setting->sidebyside.id, + hmi_setting->sidebyside.filePath, 1); + + create_button(&wlCtx_Button_3, hmi_setting->fullscreen.id, + hmi_setting->fullscreen.filePath, 2); + + create_button(&wlCtx_Button_4, hmi_setting->random.id, + hmi_setting->random.filePath, 3); + + create_workspace_background(&wlCtx_WorkSpaceBackGround, + &hmi_setting->workspace_background); + + create_launchers(&wlCtxCommon, &hmi_setting->launcher_list); + + create_home_button(&wlCtx_HomeButton, hmi_setting->home.id, + hmi_setting->home.filePath); + + UI_ready(wlCtxCommon.hmiCtrl); + + while (ret != -1) + ret = wl_display_dispatch(wlCtxCommon.wlDisplay); + + wl_list_for_each(pWlCtxSt, &wlCtxCommon.list_wlContextStruct, link) { + destroyWLContextStruct(pWlCtxSt); + } + + free(wlCtx_BackGround); + free(wlCtx_Panel); + + destroyWLContextCommon(&wlCtxCommon); + + return 0; +} diff --git a/clients/keyboard.c b/clients/keyboard.c new file mode 100644 index 0000000..e39d76d --- /dev/null +++ b/clients/keyboard.c @@ -0,0 +1,1041 @@ +/* + * Copyright © 2012 Openismus GmbH + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "window.h" +#include "input-method-unstable-v1-client-protocol.h" +#include "text-input-unstable-v1-client-protocol.h" +#include "shared/xalloc.h" + +struct keyboard; + +struct virtual_keyboard { + struct zwp_input_panel_v1 *input_panel; + struct zwp_input_method_v1 *input_method; + struct zwp_input_method_context_v1 *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; + bool toplevel; +}; + +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 *uppercase; + char *symbol; + + 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", 1}, + { keytype_default, "w", "W", "2", 1}, + { keytype_default, "e", "E", "3", 1}, + { keytype_default, "r", "R", "4", 1}, + { keytype_default, "t", "T", "5", 1}, + { keytype_default, "y", "Y", "6", 1}, + { keytype_default, "u", "U", "7", 1}, + { keytype_default, "i", "I", "8", 1}, + { keytype_default, "o", "O", "9", 1}, + { keytype_default, "p", "P", "0", 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", "Enter", 2}, + + { keytype_switch, "ABC", "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", "ABC", 1}, + + { keytype_symbols, "?123", "?123", "abc", 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", 1}, + { keytype_default, "2", "2", "2", 1}, + { keytype_default, "3", "3", "3", 1}, + { keytype_default, "4", "4", "4", 1}, + { keytype_default, "5", "5", "5", 1}, + { keytype_default, "6", "6", "6", 1}, + { keytype_default, "7", "7", "7", 1}, + { keytype_default, "8", "8", "8", 1}, + { keytype_default, "9", "9", "9", 1}, + { keytype_default, "0", "0", "0", 1}, + { keytype_backspace, "<--", "<--", "<--", 2}, + + { keytype_space, "", "", "", 4}, + { keytype_enter, "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_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, "ن", "x", "&", 1}, + { keytype_default, "م", "/", "*", 1}, + { keytype_default, "ك", ":", "_", 1}, + { keytype_default, "د", "\"", "+", 1}, + { keytype_enter, "Enter", "Enter", "Enter", 2}, + + { keytype_switch, "Shift", "Base", "Shift", 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, "Shift", "Base", "Shift", 2}, + + { keytype_symbols, "؟٣٢١", "؟٣٢١", "Base", 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", + ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_LTR +}; + +static const struct layout numeric_layout = { + numeric_keys, + sizeof(numeric_keys) / sizeof(*numeric_keys), + 12, + 2, + "en", + ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_LTR +}; + +static const struct layout arabic_layout = { + arabic_keys, + sizeof(arabic_keys) / sizeof(*arabic_keys), + 13, + 4, + "ar", + ZWP_TEXT_INPUT_V1_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 { + KEYBOARD_STATE_DEFAULT, + KEYBOARD_STATE_UPPERCASE, + KEYBOARD_STATE_SYMBOLS +}; + +struct keyboard { + struct virtual_keyboard *keyboard; + struct window *window; + struct widget *widget; + + enum keyboard_state state; +}; + +static void __attribute__ ((format (printf, 1, 2))) +dbg(const char *fmt, ...) +{ +#ifdef DEBUG + va_list argp; + + va_start(argp, fmt); + vfprintf(stderr, fmt, argp); + va_end(argp); +#endif +} + +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]; + + switch(keyboard->state) { + case KEYBOARD_STATE_DEFAULT: + return key->label; + case KEYBOARD_STATE_UPPERCASE: + return key->uppercase; + case KEYBOARD_STATE_SYMBOLS: + return key->symbol; + } + + return ""; +} + +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 ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_DIGITS: + case ZWP_TEXT_INPUT_V1_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) +{ + int tlen = strlen(text), ilen = strlen(insert); + char *new_text = xmalloc(tlen + ilen + 1); + + memcpy(new_text, text, offset); + memcpy(new_text + offset, insert, ilen); + memcpy(new_text + offset + ilen, text + offset, tlen - offset); + new_text[tlen + ilen] = '\0'; + + 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; + + zwp_input_method_context_v1_cursor_position(keyboard->context, + 0, 0); + zwp_input_method_context_v1_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) + zwp_input_method_context_v1_preedit_styling(keyboard->context, + 0, + strlen(keyboard->preedit_string), + keyboard->preedit_style); + if (cursor > 0) + index = cursor; + zwp_input_method_context_v1_preedit_cursor(keyboard->context, + index); + zwp_input_method_context_v1_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) { + dbg("delete_before_cursor: No surrounding text available\n"); + return; + } + + start = prev_utf8_char(keyboard->surrounding_text, + keyboard->surrounding_text + keyboard->surrounding_cursor); + if (!start) { + dbg("delete_before_cursor: No previous character to delete\n"); + return; + } + + end = keyboard->surrounding_text + keyboard->surrounding_cursor; + + zwp_input_method_context_v1_delete_surrounding_text(keyboard->context, + (start - keyboard->surrounding_text) - keyboard->surrounding_cursor, + end - start); + zwp_input_method_context_v1_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 char * +append(char *s1, const char *s2) +{ + int len1, len2; + char *s; + + len1 = strlen(s1); + len2 = strlen(s2); + s = xrealloc(s1, len1 + len2 + 1); + memcpy(s + len1, s2, len2); + s[len1 + len2] = '\0'; + + return s; +} + +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 = NULL; + + switch(keyboard->state) { + case KEYBOARD_STATE_DEFAULT : + label = key->label; + break; + case KEYBOARD_STATE_UPPERCASE : + label = key->uppercase; + break; + case KEYBOARD_STATE_SYMBOLS : + label = key->symbol; + break; + } + + xkb_mod_mask_t mod_mask = keyboard->state == KEYBOARD_STATE_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 = + append(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); + zwp_input_method_context_v1_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 = + append(keyboard->keyboard->preedit_string, " "); + virtual_keyboard_commit_preedit(keyboard->keyboard); + break; + case keytype_switch: + if (state != WL_POINTER_BUTTON_STATE_PRESSED) + break; + switch(keyboard->state) { + case KEYBOARD_STATE_DEFAULT: + keyboard->state = KEYBOARD_STATE_UPPERCASE; + break; + case KEYBOARD_STATE_UPPERCASE: + keyboard->state = KEYBOARD_STATE_DEFAULT; + break; + case KEYBOARD_STATE_SYMBOLS: + keyboard->state = KEYBOARD_STATE_UPPERCASE; + break; + } + break; + case keytype_symbols: + if (state != WL_POINTER_BUTTON_STATE_PRESSED) + break; + switch(keyboard->state) { + case KEYBOARD_STATE_DEFAULT: + keyboard->state = KEYBOARD_STATE_SYMBOLS; + break; + case KEYBOARD_STATE_UPPERCASE: + keyboard->state = KEYBOARD_STATE_SYMBOLS; + break; + case KEYBOARD_STATE_SYMBOLS: + keyboard->state = KEYBOARD_STATE_DEFAULT; + break; + } + break; + case keytype_tab: + virtual_keyboard_commit_preedit(keyboard->keyboard); + zwp_input_method_context_v1_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); + zwp_input_method_context_v1_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); + zwp_input_method_context_v1_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); + zwp_input_method_context_v1_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); + zwp_input_method_context_v1_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_handler(struct input *input, uint32_t time, + float x, float y, uint32_t state, 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, state); + break; + } + } + + widget_schedule_redraw(keyboard->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) +{ + touch_handler(input, time, x, y, + WL_POINTER_BUTTON_STATE_PRESSED, data); +} + +static void +touch_up_handler(struct widget *widget, struct input *input, + uint32_t serial, uint32_t time, int32_t id, + void *data) +{ + float x, y; + + input_get_touch(input, id, &x, &y); + + touch_handler(input, time, x, y, + WL_POINTER_BUTTON_STATE_RELEASED, data); +} + +static void +handle_surrounding_text(void *data, + struct zwp_input_method_context_v1 *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 zwp_input_method_context_v1 *context) +{ + struct virtual_keyboard *keyboard = data; + + dbg("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 zwp_input_method_context_v1 *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 zwp_input_method_context_v1 *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 zwp_input_method_context_v1 *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) + dbg("Surrounding text updated: %s\n", keyboard->surrounding_text); + + window_schedule_resize(keyboard->keyboard->window, + layout->columns * key_width, + layout->rows * key_height); + + zwp_input_method_context_v1_language(context, + keyboard->serial, + layout->language); + zwp_input_method_context_v1_text_direction(context, + keyboard->serial, + layout->text_direction); + + widget_schedule_redraw(keyboard->keyboard->widget); +} + +static void +handle_preferred_language(void *data, + struct zwp_input_method_context_v1 *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 zwp_input_method_context_v1_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 zwp_input_method_v1 *input_method, + struct zwp_input_method_context_v1 *context) +{ + struct virtual_keyboard *keyboard = data; + struct wl_array modifiers_map; + const struct layout *layout; + + keyboard->keyboard->state = KEYBOARD_STATE_DEFAULT; + + if (keyboard->context) + zwp_input_method_context_v1_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; + zwp_input_method_context_v1_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"); + zwp_input_method_context_v1_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); + + zwp_input_method_context_v1_language(context, + keyboard->serial, + layout->language); + zwp_input_method_context_v1_text_direction(context, + keyboard->serial, + layout->text_direction); + + widget_schedule_redraw(keyboard->keyboard->widget); +} + +static void +input_method_deactivate(void *data, + struct zwp_input_method_v1 *input_method, + struct zwp_input_method_context_v1 *context) +{ + struct virtual_keyboard *keyboard = data; + + if (!keyboard->context) + return; + + zwp_input_method_context_v1_destroy(keyboard->context); + keyboard->context = NULL; +} + +static const struct zwp_input_method_v1_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, "zwp_input_panel_v1")) { + keyboard->input_panel = + display_bind(display, name, &zwp_input_panel_v1_interface, 1); + } else if (!strcmp(interface, "zwp_input_method_v1")) { + keyboard->input_method = + display_bind(display, name, + &zwp_input_method_v1_interface, 1); + zwp_input_method_v1_add_listener(keyboard->input_method, + &input_method_listener, + keyboard); + } +} + +static void +set_toplevel(struct output *output, struct virtual_keyboard *virtual_keyboard) +{ + struct zwp_input_panel_surface_v1 *ips; + struct keyboard *keyboard = virtual_keyboard->keyboard; + + ips = zwp_input_panel_v1_get_input_panel_surface(virtual_keyboard->input_panel, + window_get_wl_surface(keyboard->window)); + + zwp_input_panel_surface_v1_set_toplevel(ips, + output_get_wl_output(output), + ZWP_INPUT_PANEL_SURFACE_V1_POSITION_CENTER_BOTTOM); + + virtual_keyboard->toplevel = true; +} + +static void +display_output_handler(struct output *output, void *data) { + struct virtual_keyboard *keyboard = data; + + if (!keyboard->toplevel) + set_toplevel(output, keyboard); +} + +static void +keyboard_create(struct virtual_keyboard *virtual_keyboard) +{ + struct keyboard *keyboard; + const struct layout *layout; + + 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); + + display_set_output_configure_handler(virtual_keyboard->display, + display_output_handler); +} + +int +main(int argc, char *argv[]) +{ + struct virtual_keyboard virtual_keyboard; + + 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: %s\n", + strerror(errno)); + return -1; + } + + display_set_user_data(virtual_keyboard.display, &virtual_keyboard); + display_set_global_handler(virtual_keyboard.display, global_handler); + + if (virtual_keyboard.input_panel == NULL) { + fprintf(stderr, "No input panel global\n"); + return -1; + } + + keyboard_create(&virtual_keyboard); + + display_run(virtual_keyboard.display); + + return 0; +} diff --git a/clients/meson.build b/clients/meson.build new file mode 100644 index 0000000..2c016b8 --- /dev/null +++ b/clients/meson.build @@ -0,0 +1,373 @@ +if get_option('resize-pool') + config_h.set('USE_RESIZE_POOL', '1') +endif + +srcs_toytoolkit = [ + 'window.c', + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, + text_cursor_position_client_protocol_h, + text_cursor_position_protocol_c, + relative_pointer_unstable_v1_client_protocol_h, + relative_pointer_unstable_v1_protocol_c, + pointer_constraints_unstable_v1_client_protocol_h, + pointer_constraints_unstable_v1_protocol_c, + ivi_application_client_protocol_h, + ivi_application_protocol_c, + viewporter_client_protocol_h, + viewporter_protocol_c, +] +deps_toytoolkit = [ + dep_wayland_client, + dep_lib_cairo_shared, + dep_xkbcommon, + dependency('wayland-cursor'), + cc.find_library('util'), +] +lib_toytoolkit = static_library( + 'toytoolkit', + srcs_toytoolkit, + include_directories: common_inc, + dependencies: deps_toytoolkit, + install: false, +) +dep_toytoolkit = declare_dependency( + link_with: lib_toytoolkit, + dependencies: deps_toytoolkit, +) + +simple_clients = [ + { + 'name': 'damage', + 'sources': [ + 'simple-damage.c', + viewporter_client_protocol_h, + viewporter_protocol_c, + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, + fullscreen_shell_unstable_v1_client_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + ], + 'dep_objs': [ dep_wayland_client, dep_libshared ] + }, + { + 'name': 'dmabuf-egl', + 'sources': [ + 'simple-dmabuf-egl.c', + linux_dmabuf_unstable_v1_client_protocol_h, + linux_dmabuf_unstable_v1_protocol_c, + linux_explicit_synchronization_unstable_v1_client_protocol_h, + linux_explicit_synchronization_unstable_v1_protocol_c, + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, + weston_direct_display_client_protocol_h, + weston_direct_display_protocol_c, + fullscreen_shell_unstable_v1_client_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + ], + 'dep_objs': [ + dep_wayland_client, + dep_libdrm, + dep_libm + ], + 'deps': [ 'egl', 'glesv2', 'gbm' ], + 'options': [ 'renderer-gl' ] + }, + { + 'name': 'dmabuf-v4l', + 'sources': [ + 'simple-dmabuf-v4l.c', + linux_dmabuf_unstable_v1_client_protocol_h, + linux_dmabuf_unstable_v1_protocol_c, + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, + weston_direct_display_client_protocol_h, + weston_direct_display_protocol_c, + fullscreen_shell_unstable_v1_client_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + ], + 'dep_objs': [ dep_wayland_client, dep_libdrm_headers ] + }, + { + 'name': 'egl', + 'sources': [ + 'simple-egl.c', + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, + ivi_application_client_protocol_h, + ivi_application_protocol_c, + ], + 'dep_objs': [ dep_wayland_client, dep_libshared, dep_libm ], + 'deps': [ 'egl', 'wayland-egl', 'glesv2', 'wayland-cursor' ], + 'options': [ 'renderer-gl' ] + }, + # weston-simple-im is handled specially separately due to install_dir and odd window.h usage + { + 'name': 'shm', + 'sources': [ + 'simple-shm.c', + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, + fullscreen_shell_unstable_v1_client_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + ivi_application_client_protocol_h, + ivi_application_protocol_c, + ], + 'dep_objs': [ dep_wayland_client, dep_libshared ] + }, + { + 'name': 'touch', + 'sources': [ + 'simple-touch.c', + ], + 'dep_objs': [ dep_wayland_client, dep_libshared ] + }, +] + +simple_clients_enabled = get_option('simple-clients') +simple_build_all = simple_clients_enabled.contains('all') +foreach t : simple_clients + if simple_build_all or simple_clients_enabled.contains(t.get('name')) + t_name = 'weston-simple-' + t.get('name') + t_deps = t.get('dep_objs', []) + foreach depname : t.get('deps', []) + dep = dependency(depname, required: false) + if not dep.found() + error('@0@ requires @1@ which was not found. If you rather not build this, drop "@2@" from simple-clients option.'.format(t_name, depname, t.get('name'))) + endif + t_deps += dep + endforeach + + foreach optname : t.get('options', []) + if not get_option(optname) + error('@0@ requires option @1@ which is not enabled. If you rather not build this, drop "@2@" from simple-clients option.'.format(t_name, optname, t.get('name'))) + endif + endforeach + + executable( + t_name, t.get('sources'), + include_directories: common_inc, + dependencies: t_deps, + install: true + ) + endif +endforeach + +if simple_build_all or simple_clients_enabled.contains('im') + executable( + 'weston-simple-im', [ + 'simple-im.c', + input_method_unstable_v1_client_protocol_h, + input_method_unstable_v1_protocol_c, + ], + include_directories: common_inc, + dependencies: [ + dep_libshared, + dep_wayland_client, + dep_xkbcommon, + dependency('wayland-cursor'), + dependency('cairo') + ], + install: true, + install_dir: dir_libexec + ) +endif + +tools_enabled = get_option('tools') +tools_list = [ + { + 'name': 'calibrator', + 'sources': [ 'calibrator.c' ], + 'deps': [ dep_toytoolkit, dep_matrix_c ], + }, + { + 'name': 'debug', + 'sources': [ + 'weston-debug.c', + weston_debug_client_protocol_h, + weston_debug_protocol_c, + ], + 'deps': [ dep_wayland_client ] + }, + { + 'name': 'info', + 'sources': [ + 'weston-info.c', + presentation_time_client_protocol_h, + presentation_time_protocol_c, + linux_dmabuf_unstable_v1_client_protocol_h, + linux_dmabuf_unstable_v1_protocol_c, + tablet_unstable_v2_client_protocol_h, + tablet_unstable_v2_protocol_c, + xdg_output_unstable_v1_client_protocol_h, + xdg_output_unstable_v1_protocol_c, + ], + 'deps': [ dep_wayland_client, dep_libshared ] + }, + { + 'name': 'terminal', + 'sources': [ 'terminal.c' ], + 'deps': [ dep_toytoolkit ], + }, + { + 'name': 'touch-calibrator', + 'sources': [ + 'touch-calibrator.c', + weston_touch_calibration_client_protocol_h, + weston_touch_calibration_protocol_c, + ], + 'deps': [ dep_toytoolkit, dep_matrix_c ], + }, +] + +foreach t : tools_list + if tools_enabled.contains(t.get('name')) + executable( + 'weston-@0@'.format(t.get('name')), + t.get('sources'), + include_directories: common_inc, + dependencies: t.get('deps', []), + install: true + ) + endif +endforeach + +demo_clients = [ + { 'basename': 'clickdot' }, + { + 'basename': 'cliptest', + 'dep_objs': dep_vertex_clipping + }, + { 'basename': 'confine' }, + { + 'basename': 'content_protection', + 'add_sources': [ + weston_content_protection_client_protocol_h, + weston_content_protection_protocol_c, + ] + }, + + { 'basename': 'dnd' }, + { + 'basename': 'editor', + 'add_sources': [ + text_input_unstable_v1_client_protocol_h, + text_input_unstable_v1_protocol_c, + ], + 'deps': [ 'pangocairo', 'gobject-2.0' ] + }, + { 'basename': 'eventdemo' }, + { 'basename': 'flower' }, + { + 'basename': 'fullscreen', + 'add_sources': [ + fullscreen_shell_unstable_v1_client_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + ] + }, + { 'basename': 'image' }, + { 'basename': 'multi-resource' }, + { + 'basename': 'presentation-shm', + 'add_sources': [ + presentation_time_client_protocol_h, + presentation_time_protocol_c, + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, + ] + }, + { 'basename': 'resizor' }, + { + 'basename': 'scaler', + 'add_sources': [ + viewporter_client_protocol_h, + viewporter_protocol_c, + ] + }, + { 'basename': 'smoke' }, + { 'basename': 'stacking' }, + { + 'basename': 'subsurfaces', + 'deps': [ 'egl', 'glesv2', 'wayland-egl' ] + }, + { 'basename': 'transformed' }, +] + +if get_option('demo-clients') + foreach t : demo_clients + t_name = 'weston-' + t.get('basename') + t_srcs = [ t.get('basename') + '.c' ] + t.get('add_sources', []) + t_deps = [ dep_toytoolkit, t.get('dep_objs', []) ] + foreach depname : t.get('deps', []) + dep = dependency(depname, required: false) + if not dep.found() + error('@0@ requires \'@1@\' which was not found. If you rather not build this, set \'-Ddemo-clients=false\'.'.format(t_name, depname)) + endif + t_deps += dep + endforeach + + executable( + t_name, t_srcs, + include_directories: common_inc, + dependencies: t_deps, + install: true + ) + endforeach +endif + +if get_option('shell-desktop') + exe_keyboard = executable( + 'weston-keyboard', + 'keyboard.c', + text_input_unstable_v1_client_protocol_h, + text_input_unstable_v1_protocol_c, + input_method_unstable_v1_client_protocol_h, + input_method_unstable_v1_protocol_c, + include_directories: common_inc, + dependencies: dep_toytoolkit, + install_dir: get_option('libexecdir'), + install: true + ) + env_modmap += 'weston-keyboard=@0@;'.format(exe_keyboard.full_path()) + + exe_shooter = executable( + 'weston-screenshooter', + 'screenshot.c', + weston_screenshooter_client_protocol_h, + weston_screenshooter_protocol_c, + include_directories: common_inc, + dependencies: dep_toytoolkit, + install_dir: get_option('bindir'), + install: true + ) + env_modmap += 'weston-screenshooter=@0@;'.format(exe_shooter.full_path()) + + exe_shell_desktop = executable( + 'weston-desktop-shell', + 'desktop-shell.c', + weston_desktop_shell_client_protocol_h, + weston_desktop_shell_protocol_c, + include_directories: common_inc, + dependencies: dep_toytoolkit, + install_dir: get_option('libexecdir'), + install: true + ) + env_modmap += 'weston-desktop-shell=@0@;'.format(exe_shell_desktop.full_path()) +endif + + +if get_option('shell-ivi') + exe_shell_ivi_ui = executable( + 'weston-ivi-shell-user-interface', + 'ivi-shell-user-interface.c', + ivi_hmi_controller_client_protocol_h, + ivi_hmi_controller_protocol_c, + ivi_application_client_protocol_h, + ivi_application_protocol_c, + include_directories: common_inc, + dependencies: dep_toytoolkit, + install: true, + install_dir: get_option('libexecdir') + ) + env_modmap += 'weston-ivi-shell-user-interface=@0@;'.format(exe_shell_ivi_ui.full_path()) +endif diff --git a/clients/multi-resource.c b/clients/multi-resource.c new file mode 100644 index 0000000..b86503d --- /dev/null +++ b/clients/multi-resource.c @@ -0,0 +1,591 @@ +/* + * Copyright © 2011 Benjamin Franzke + * Copyright © 2010, 2013 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "shared/os-compatibility.h" +#include "shared/xalloc.h" +#include + +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 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: %s\n", + size, strerror(errno)); + 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_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) +{ + /* Just so we don’t leak the keymap fd */ + close(fd); +} + +static void +keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) +{ +} + +static void +keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface) +{ +} + +static void +keyboard_handle_key(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t time, uint32_t key, + uint32_t state_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 || tail == time_desc) + goto error; + + if (*tail == ':') { + time_desc = tail + 1; + end_time = strtoul(time_desc, &tail, 10); + if (errno || tail == time_desc || *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 0000000..a9e034e --- /dev/null +++ b/clients/nested-client.c @@ -0,0 +1,374 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "shared/platform.h" + +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 = + weston_platform_get_egl_display(EGL_PLATFORM_WAYLAND_KHR, + client->display, NULL); + 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 = weston_platform_create_egl_surface(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); + + weston_platform_destroy_egl_surface(client->egl_display, + client->egl_surface); + wl_egl_window_destroy(client->native); + + wl_surface_destroy(client->surface); + + if (client->compositor) + wl_compositor_destroy(client->compositor); + + wl_registry_destroy(client->registry); + eglTerminate(client->egl_display); + eglReleaseThread(); + 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 0000000..4bbaa27 --- /dev/null +++ b/clients/nested.c @@ -0,0 +1,1137 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#define WL_HIDE_DEPRECATED +#include + +#include "shared/helpers.h" +#include "shared/xalloc.h" +#include "window.h" + +#include "shared/weston-egl-ext.h" + + +static bool option_blit; + +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; + + const struct nested_renderer *renderer; +}; + +struct nested_region { + struct wl_resource *resource; + pixman_region32_t region; +}; + +struct nested_buffer_reference { + struct nested_buffer *buffer; + struct wl_listener destroy_listener; +}; + +struct nested_buffer { + struct wl_resource *resource; + struct wl_signal destroy_signal; + struct wl_listener destroy_listener; + uint32_t busy_count; + + /* A buffer in the parent compositor representing the same + * data. This is created on-demand when the subsurface + * renderer is used */ + struct wl_buffer *parent_buffer; + /* This reference is used to mark when the parent buffer has + * been attached to the subsurface. It will be unrefenced when + * we receive a buffer release event. That way we won't inform + * the client that the buffer is free until the parent + * compositor is also finished with it */ + struct nested_buffer_reference parent_ref; +}; + +struct nested_surface { + struct wl_resource *resource; + struct nested *nested; + EGLImageKHR *image; + struct wl_list link; + + struct wl_list frame_callback_list; + + struct { + /* wl_surface.attach */ + int newly_attached; + struct nested_buffer *buffer; + struct wl_listener buffer_destroy_listener; + + /* wl_surface.frame */ + struct wl_list frame_callback_list; + + /* wl_surface.damage */ + pixman_region32_t damage; + } pending; + + void *renderer_data; +}; + +/* Data used for the blit renderer */ +struct nested_blit_surface { + struct nested_buffer_reference buffer_ref; + GLuint texture; + cairo_surface_t *cairo_surface; +}; + +/* Data used for the subsurface renderer */ +struct nested_ss_surface { + struct widget *widget; + struct wl_surface *surface; + struct wl_subsurface *subsurface; + struct wl_callback *frame_callback; +}; + +struct nested_frame_callback { + struct wl_resource *resource; + struct wl_list link; +}; + +struct nested_renderer { + void (* surface_init)(struct nested_surface *surface); + void (* surface_fini)(struct nested_surface *surface); + void (* render_clients)(struct nested *nested, cairo_t *cr); + void (* surface_attach)(struct nested_surface *surface, + struct nested_buffer *buffer); +}; + +static const struct weston_option nested_options[] = { + { WESTON_OPTION_BOOLEAN, "blit", 'b', &option_blit }, +}; + +static const struct nested_renderer nested_blit_renderer; +static const struct nested_renderer nested_ss_renderer; + +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 PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWL create_wayland_buffer_from_image; + +static void +nested_buffer_destroy_handler(struct wl_listener *listener, void *data) +{ + struct nested_buffer *buffer = + container_of(listener, struct nested_buffer, destroy_listener); + + wl_signal_emit(&buffer->destroy_signal, buffer); + + if (buffer->parent_buffer) + wl_buffer_destroy(buffer->parent_buffer); + + free(buffer); +} + +static struct nested_buffer * +nested_buffer_from_resource(struct wl_resource *resource) +{ + struct nested_buffer *buffer; + struct wl_listener *listener; + + listener = + wl_resource_get_destroy_listener(resource, + nested_buffer_destroy_handler); + + if (listener) + return container_of(listener, struct nested_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 = nested_buffer_destroy_handler; + wl_resource_add_destroy_listener(resource, &buffer->destroy_listener); + + return buffer; +} + +static void +nested_buffer_reference_handle_destroy(struct wl_listener *listener, + void *data) +{ + struct nested_buffer_reference *ref = + container_of(listener, struct nested_buffer_reference, + destroy_listener); + + assert((struct nested_buffer *)data == ref->buffer); + ref->buffer = NULL; +} + +static void +nested_buffer_reference(struct nested_buffer_reference *ref, + struct nested_buffer *buffer) +{ + if (buffer == ref->buffer) + return; + + if (ref->buffer) { + ref->buffer->busy_count--; + if (ref->buffer->busy_count == 0) { + assert(wl_resource_get_client(ref->buffer->resource)); + wl_buffer_send_release(ref->buffer->resource); + } + wl_list_remove(&ref->destroy_listener.link); + } + + if (buffer) { + buffer->busy_count++; + wl_signal_add(&buffer->destroy_signal, + &ref->destroy_listener); + + ref->destroy_listener.notify = + nested_buffer_reference_handle_destroy; + } + + ref->buffer = buffer; +} + +static void +flush_surface_frame_callback_list(struct nested_surface *surface, + uint32_t time) +{ + struct nested_frame_callback *nc, *next; + + wl_list_for_each_safe(nc, next, &surface->frame_callback_list, link) { + wl_callback_send_done(nc->resource, time); + wl_resource_destroy(nc->resource); + } + wl_list_init(&surface->frame_callback_list); + + /* FIXME: toytoolkit need a pre-block handler where we can + * call this. */ + wl_display_flush_clients(surface->nested->child_display); +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct nested *nested = data; + cairo_surface_t *surface; + cairo_t *cr; + struct rectangle allocation; + + 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); + + nested->renderer->render_clients(nested, cr); + + cairo_destroy(cr); + + cairo_surface_destroy(surface); +} + +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': %s\n", + path, strerror(errno)); + 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': %s\n", path, + strerror(errno)); + 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: %s\n", + strerror(errno)); + exit(-1); + } + + snprintf(s, sizeof s, "%d", clientfd); + setenv("WAYLAND_SOCKET", s, 1); + + execl(path, path, NULL); + + fprintf(stderr, "compositor: executing '%s' failed: %s\n", + path, strerror(errno)); + 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); + struct nested *nested = surface->nested; + struct nested_frame_callback *cb, *next; + + wl_list_for_each_safe(cb, next, + &surface->frame_callback_list, link) + wl_resource_destroy(cb->resource); + + wl_list_for_each_safe(cb, next, + &surface->pending.frame_callback_list, link) + wl_resource_destroy(cb->resource); + + pixman_region32_fini(&surface->pending.damage); + + nested->renderer->surface_fini(surface); + + wl_list_remove(&surface->link); + + 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; + struct nested_buffer *buffer = NULL; + + if (buffer_resource) { + int format; + + if (!query_buffer(nested->egl_display, (void *) buffer_resource, + EGL_TEXTURE_FORMAT, &format)) { + wl_resource_post_error(buffer_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "attaching non-egl wl_buffer"); + return; + } + + switch (format) { + case EGL_TEXTURE_RGB: + case EGL_TEXTURE_RGBA: + break; + default: + wl_resource_post_error(buffer_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "invalid format"); + return; + } + + buffer = nested_buffer_from_resource(buffer_resource); + if (buffer == NULL) { + wl_client_post_no_memory(client); + return; + } + } + + if (surface->pending.buffer) + wl_list_remove(&surface->pending.buffer_destroy_listener.link); + + surface->pending.buffer = buffer; + surface->pending.newly_attached = 1; + if (buffer) { + wl_signal_add(&buffer->destroy_signal, + &surface->pending.buffer_destroy_listener); + } +} + +static void +nested_surface_attach(struct nested_surface *surface, + struct nested_buffer *buffer) +{ + struct nested *nested = surface->nested; + + if (surface->image != EGL_NO_IMAGE_KHR) + destroy_image(nested->egl_display, surface->image); + + 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; + } + + nested->renderer->surface_attach(surface, buffer); +} + +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 nested_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 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); + + 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(surface->pending.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) +{ + struct nested_surface *surface = wl_resource_get_user_data(resource); + struct nested *nested = surface->nested; + + /* wl_surface.attach */ + if (surface->pending.newly_attached) + nested_surface_attach(surface, surface->pending.buffer); + + if (surface->pending.buffer) { + wl_list_remove(&surface->pending.buffer_destroy_listener.link); + surface->pending.buffer = NULL; + } + surface->pending.newly_attached = 0; + + /* wl_surface.damage */ + pixman_region32_clear(&surface->pending.damage); + + /* wl_surface.frame */ + wl_list_insert_list(&surface->frame_callback_list, + &surface->pending.frame_callback_list); + wl_list_init(&surface->pending.frame_callback_list); + + /* FIXME: For the subsurface renderer we don't need to + * actually redraw the window. However we do want to cause a + * commit because the subsurface is synchronized. Ideally we + * would just queue the commit */ + window_schedule_redraw(nested->window); +} + +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 +surface_handle_pending_buffer_destroy(struct wl_listener *listener, void *data) +{ + struct nested_surface *surface = + container_of(listener, struct nested_surface, + pending.buffer_destroy_listener); + + surface->pending.buffer = NULL; +} + +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; + + wl_list_init(&surface->frame_callback_list); + + wl_list_init(&surface->pending.frame_callback_list); + surface->pending.buffer_destroy_listener.notify = + surface_handle_pending_buffer_destroy; + pixman_region32_init(&surface->pending.damage); + + display_acquire_window_surface(nested->display, + nested->window, NULL); + + nested->renderer->surface_init(surface); + + 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 use_ss_renderer = 0; + int fd, ret; + + wl_list_init(&nested->surface_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 (!weston_check_egl_extension(extensions, "EGL_WL_bind_wayland_display")) { + 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; + } + + if (display_has_subcompositor(nested->display)) { + const char *func = "eglCreateWaylandBufferFromImageWL"; + const char *ext = "EGL_WL_create_wayland_buffer_from_image"; + + if (weston_check_egl_extension(extensions, ext)) { + create_wayland_buffer_from_image = + (void *) eglGetProcAddress(func); + use_ss_renderer = 1; + } + } + + if (option_blit) + use_ss_renderer = 0; + + if (use_ss_renderer) { + printf("Using subsurfaces to render client surfaces\n"); + nested->renderer = &nested_ss_renderer; + } else { + printf("Using local compositing with blits to " + "render client surfaces\n"); + nested->renderer = &nested_blit_renderer; + } + + 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 = window_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); +} + +/*** blit renderer ***/ + +static void +blit_surface_init(struct nested_surface *surface) +{ + struct nested_blit_surface *blit_surface = + xzalloc(sizeof *blit_surface); + + glGenTextures(1, &blit_surface->texture); + glBindTexture(GL_TEXTURE_2D, blit_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); + + surface->renderer_data = blit_surface; +} + +static void +blit_surface_fini(struct nested_surface *surface) +{ + struct nested_blit_surface *blit_surface = surface->renderer_data; + + nested_buffer_reference(&blit_surface->buffer_ref, NULL); + + glDeleteTextures(1, &blit_surface->texture); + + free(blit_surface); +} + +static void +blit_frame_callback(void *data, struct wl_callback *callback, uint32_t time) +{ + struct nested *nested = data; + struct nested_surface *surface; + + wl_list_for_each(surface, &nested->surface_list, link) + flush_surface_frame_callback_list(surface, time); + + if (callback) + wl_callback_destroy(callback); +} + +static const struct wl_callback_listener blit_frame_listener = { + blit_frame_callback +}; + +static void +blit_render_clients(struct nested *nested, + cairo_t *cr) +{ + struct nested_surface *s; + struct rectangle allocation; + struct wl_callback *callback; + + widget_get_allocation(nested->widget, &allocation); + + wl_list_for_each(s, &nested->surface_list, link) { + struct nested_blit_surface *blit_surface = s->renderer_data; + + display_acquire_window_surface(nested->display, + nested->window, NULL); + + glBindTexture(GL_TEXTURE_2D, blit_surface->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, blit_surface->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); + } + + callback = wl_surface_frame(window_get_wl_surface(nested->window)); + wl_callback_add_listener(callback, &blit_frame_listener, nested); +} + +static void +blit_surface_attach(struct nested_surface *surface, + struct nested_buffer *buffer) +{ + struct nested *nested = surface->nested; + struct nested_blit_surface *blit_surface = surface->renderer_data; + EGLint width, height; + cairo_device_t *device; + + nested_buffer_reference(&blit_surface->buffer_ref, buffer); + + if (blit_surface->cairo_surface) + cairo_surface_destroy(blit_surface->cairo_surface); + + 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); + blit_surface->cairo_surface = + cairo_gl_surface_create_for_texture(device, + CAIRO_CONTENT_COLOR_ALPHA, + blit_surface->texture, + width, height); +} + +static const struct nested_renderer +nested_blit_renderer = { + .surface_init = blit_surface_init, + .surface_fini = blit_surface_fini, + .render_clients = blit_render_clients, + .surface_attach = blit_surface_attach +}; + +/*** subsurface renderer ***/ + +static void +ss_surface_init(struct nested_surface *surface) +{ + struct nested *nested = surface->nested; + struct wl_compositor *compositor = + display_get_compositor(nested->display); + struct nested_ss_surface *ss_surface = + xzalloc(sizeof *ss_surface); + struct rectangle allocation; + struct wl_region *region; + + ss_surface->widget = + window_add_subsurface(nested->window, + nested, + SUBSURFACE_SYNCHRONIZED); + + widget_set_use_cairo(ss_surface->widget, 0); + + ss_surface->surface = widget_get_wl_surface(ss_surface->widget); + ss_surface->subsurface = widget_get_wl_subsurface(ss_surface->widget); + + /* The toy toolkit gets confused about the pointer position + * when it gets motion events for a subsurface so we'll just + * disable input on it */ + region = wl_compositor_create_region(compositor); + wl_surface_set_input_region(ss_surface->surface, region); + wl_region_destroy(region); + + widget_get_allocation(nested->widget, &allocation); + wl_subsurface_set_position(ss_surface->subsurface, + allocation.x + 10, + allocation.y + 10); + + surface->renderer_data = ss_surface; +} + +static void +ss_surface_fini(struct nested_surface *surface) +{ + struct nested_ss_surface *ss_surface = surface->renderer_data; + + widget_destroy(ss_surface->widget); + + if (ss_surface->frame_callback) + wl_callback_destroy(ss_surface->frame_callback); + + free(ss_surface); +} + +static void +ss_render_clients(struct nested *nested, + cairo_t *cr) +{ + /* The clients are composited by the parent compositor so we + * don't need to do anything here */ +} + +static void +ss_buffer_release(void *data, struct wl_buffer *wl_buffer) +{ + struct nested_buffer *buffer = data; + + nested_buffer_reference(&buffer->parent_ref, NULL); +} + +static struct wl_buffer_listener ss_buffer_listener = { + ss_buffer_release +}; + +static void +ss_frame_callback(void *data, struct wl_callback *callback, uint32_t time) +{ + struct nested_surface *surface = data; + struct nested_ss_surface *ss_surface = surface->renderer_data; + + flush_surface_frame_callback_list(surface, time); + + if (callback) + wl_callback_destroy(callback); + + ss_surface->frame_callback = NULL; +} + +static const struct wl_callback_listener ss_frame_listener = { + ss_frame_callback +}; + +static void +ss_surface_attach(struct nested_surface *surface, + struct nested_buffer *buffer) +{ + struct nested *nested = surface->nested; + struct nested_ss_surface *ss_surface = surface->renderer_data; + struct wl_buffer *parent_buffer; + const pixman_box32_t *rects; + int n_rects, i; + + if (buffer) { + /* Create a representation of the buffer in the parent + * compositor if we haven't already */ + if (buffer->parent_buffer == NULL) { + EGLDisplay *edpy = nested->egl_display; + EGLImageKHR image = surface->image; + + buffer->parent_buffer = + create_wayland_buffer_from_image(edpy, image); + + wl_buffer_add_listener(buffer->parent_buffer, + &ss_buffer_listener, + buffer); + } + + parent_buffer = buffer->parent_buffer; + + /* We'll take a reference to the buffer while the parent + * compositor is using it so that we won't report the release + * event until the parent has also finished with it */ + nested_buffer_reference(&buffer->parent_ref, buffer); + } else { + parent_buffer = NULL; + } + + wl_surface_attach(ss_surface->surface, parent_buffer, 0, 0); + + rects = pixman_region32_rectangles(&surface->pending.damage, &n_rects); + + for (i = 0; i < n_rects; i++) { + const pixman_box32_t *rect = rects + i; + wl_surface_damage(ss_surface->surface, + rect->x1, + rect->y1, + rect->x2 - rect->x1, + rect->y2 - rect->y1); + } + + if (ss_surface->frame_callback) + wl_callback_destroy(ss_surface->frame_callback); + + ss_surface->frame_callback = wl_surface_frame(ss_surface->surface); + wl_callback_add_listener(ss_surface->frame_callback, + &ss_frame_listener, + surface); + + wl_surface_commit(ss_surface->surface); +} + +static const struct nested_renderer +nested_ss_renderer = { + .surface_init = ss_surface_init, + .surface_fini = ss_surface_fini, + .render_clients = ss_render_clients, + .surface_attach = ss_surface_attach +}; + +int +main(int argc, char *argv[]) +{ + struct display *display; + struct nested *nested; + + if (parse_options(nested_options, + ARRAY_LENGTH(nested_options), &argc, argv) > 1) { + printf("Usage: %s [OPTIONS]\n --blit or -b\n", argv[0]); + exit(1); + } + + display = display_create(&argc, argv); + if (display == NULL) { + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); + 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/presentation-shm.c b/clients/presentation-shm.c new file mode 100644 index 0000000..d5d73a2 --- /dev/null +++ b/clients/presentation-shm.c @@ -0,0 +1,956 @@ +/* + * Copyright © 2011 Benjamin Franzke + * Copyright © 2010 Intel Corporation + * Copyright © 2014 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "shared/helpers.h" +#include +#include "shared/timespec-util.h" +#include "shared/os-compatibility.h" +#include "presentation-time-client-protocol.h" +#include "xdg-shell-client-protocol.h" + +enum run_mode { + RUN_MODE_FEEDBACK, + RUN_MODE_FEEDBACK_IDLE, + RUN_MODE_PRESENT, +}; + +static const char * const run_mode_name[] = { + [RUN_MODE_FEEDBACK] = "feedback", + [RUN_MODE_FEEDBACK_IDLE] = "feedback-idle", + [RUN_MODE_PRESENT] = "low-lat present", +}; + +struct output { + struct wl_output *output; + uint32_t name; + struct wl_list link; +}; + +struct display { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct xdg_wm_base *wm_base; + + struct wl_shm *shm; + uint32_t formats; + + struct wp_presentation *presentation; + clockid_t clk_id; + + struct wl_list output_list; /* struct output::link */ +}; + +struct feedback { + struct window *window; + unsigned frame_no; + struct wp_presentation_feedback *feedback; + struct timespec commit; + struct timespec target; + uint32_t frame_stamp; + struct wl_list link; + struct timespec present; +}; + +struct buffer { + struct wl_buffer *buffer; + void *shm_data; + int busy; +}; + +struct window { + struct display *display; + int width, height; + enum run_mode mode; + struct wl_surface *surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + uint32_t configure_serial; + + struct buffer *buffers; + int num_buffers; + int next; + int refresh_nsec; + int commit_delay_msecs; + + struct wl_callback *callback; + struct wl_list feedback_list; + + struct feedback *received_feedback; +}; + +#define NSEC_PER_SEC 1000000000 + +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_buffers(struct display *display, struct buffer **buffers, + int num_buffers, int width, int height, uint32_t format) +{ + struct buffer *bufs; + struct wl_shm_pool *pool; + int fd, size, stride, offset; + void *data; + int i; + + stride = width * 4; + size = stride * height * num_buffers; + + fd = os_create_anonymous_file(size); + if (fd < 0) { + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); + return -1; + } + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + close(fd); + return -1; + } + + pool = wl_shm_create_pool(display->shm, fd, size); + offset = 0; + + bufs = calloc(num_buffers, sizeof(*bufs)); + assert(bufs); + + for (i = 0; i < num_buffers; i++) { + bufs[i].buffer = wl_shm_pool_create_buffer(pool, offset, + width, height, + stride, format); + assert(bufs[i].buffer); + wl_buffer_add_listener(bufs[i].buffer, + &buffer_listener, &bufs[i]); + + bufs[i].shm_data = (char *)data + offset; + offset += stride * height; + } + + wl_shm_pool_destroy(pool); + close(fd); + + *buffers = bufs; + + return 0; +} + +static void +xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *xdg_wm_base, + uint32_t serial) +{ + xdg_wm_base_pong(xdg_wm_base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = xdg_wm_base_handle_ping, +}; + +static void +xdg_surface_handle_configure(void *data, struct xdg_surface *xdg_surface, + uint32_t serial) +{ + struct window *window = data; + + window->configure_serial = serial; +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_handle_configure, +}; + + +static void +xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *xdg_toplevel, + int32_t width, int32_t height, + struct wl_array *states) +{ + /* noop */ +} + +static void +xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) +{ + fprintf(stderr, "presentation-shm exiting\n"); + exit(0); +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_handle_configure, + .close = xdg_toplevel_handle_close, +}; + +static struct window * +create_window(struct display *display, int width, int height, + enum run_mode mode, int commit_delay_msecs) +{ + struct window *window; + char title[128]; + int ret; + + snprintf(title, sizeof(title), + "presentation-shm: %s [Delay %i msecs]", run_mode_name[mode], + commit_delay_msecs); + + window = zalloc(sizeof *window); + if (!window) + return NULL; + + window->commit_delay_msecs = commit_delay_msecs; + window->mode = mode; + window->callback = NULL; + wl_list_init(&window->feedback_list); + window->display = display; + window->width = width; + window->height = height; + window->surface = wl_compositor_create_surface(display->compositor); + window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, + window->surface); + + if (!window->xdg_surface) + return NULL; + + window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); + + if (!window->xdg_toplevel) + return NULL; + + xdg_wm_base_add_listener(display->wm_base, &xdg_wm_base_listener, + NULL); + xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, + window); + xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, + window); + + xdg_toplevel_set_title(window->xdg_toplevel, title); + xdg_toplevel_set_min_size(window->xdg_toplevel, width, height); + xdg_toplevel_set_max_size(window->xdg_toplevel, width, height); + + wl_surface_commit(window->surface); + wl_display_roundtrip(window->display->display); + + window->num_buffers = 60; + window->refresh_nsec = NSEC_PER_SEC / 60; /* 60 Hz guess */ + window->next = 0; + ret = create_shm_buffers(window->display, + &window->buffers, window->num_buffers, + window->width, window->height, + WL_SHM_FORMAT_XRGB8888); + assert(ret == 0); + + return window; +} + +static void +destroy_feedback(struct feedback *feedback) +{ + if (feedback->feedback) + wp_presentation_feedback_destroy(feedback->feedback); + + wl_list_remove(&feedback->link); + free(feedback); +} + +static void +destroy_window(struct window *window) +{ + int i; + + while (!wl_list_empty(&window->feedback_list)) { + struct feedback *f; + + f = wl_container_of(window->feedback_list.next, f, link); + printf("clean up feedback %u\n", f->frame_no); + destroy_feedback(f); + } + + if (window->callback) + wl_callback_destroy(window->callback); + + xdg_surface_destroy(window->xdg_surface); + wl_surface_destroy(window->surface); + + for (i = 0; i < window->num_buffers; i++) + wl_buffer_destroy(window->buffers[i].buffer); + /* munmap(window->buffers[0].shm_data, size); */ + free(window->buffers); + + free(window); +} + +static struct buffer * +window_next_buffer(struct window *window) +{ + struct buffer *buf = &window->buffers[window->next]; + + window->next = (window->next + 1) % window->num_buffers; + + return buf; +} + +static void +paint_pixels(void *image, int width, int height, uint32_t phase) +{ + const int halfh = height / 2; + const int halfw = width / 2; + uint32_t *pixel = image; + int y, or; + double ang = M_PI * 2.0 / 1000000.0 * phase; + double s = sin(ang); + double c = cos(ang); + + /* squared radii thresholds */ + or = (halfw < halfh ? halfw : halfh) - 16; + or *= or; + + for (y = 0; y < height; y++) { + int x; + int oy = y - halfh; + int y2 = oy * oy; + + for (x = 0; x < width; x++) { + int ox = x - halfw; + uint32_t v = 0xff000000; + double rx, ry; + + if (ox * ox + y2 > or) { + if (ox * oy > 0) + *pixel++ = 0xff000000; + else + *pixel++ = 0xffffffff; + continue; + } + + rx = c * ox + s * oy; + ry = -s * ox + c * oy; + + if (rx < 0.0) + v |= 0x00ff0000; + if (ry < 0.0) + v |= 0x0000ff00; + if ((rx < 0.0) == (ry < 0.0)) + v |= 0x000000ff; + + *pixel++ = v; + } + } +} + +static void +feedback_sync_output(void *data, + struct wp_presentation_feedback *presentation_feedback, + struct wl_output *output) +{ + /* not interested */ +} + +static char * +pflags_to_str(uint32_t flags, char *str, unsigned len) +{ + static const struct { + uint32_t flag; + char sym; + } desc[] = { + { WP_PRESENTATION_FEEDBACK_KIND_VSYNC, 's' }, + { WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK, 'c' }, + { WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION, 'e' }, + { WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY, 'z' }, + }; + unsigned i; + + *str = '\0'; + if (len < ARRAY_LENGTH(desc) + 1) + return str; + + for (i = 0; i < ARRAY_LENGTH(desc); i++) + str[i] = flags & desc[i].flag ? desc[i].sym : '_'; + str[ARRAY_LENGTH(desc)] = '\0'; + + return str; +} + +static uint32_t +timespec_to_ms(const struct timespec *ts) +{ + return (uint32_t)ts->tv_sec * 1000 + ts->tv_nsec / 1000000; +} + +static int +timespec_diff_to_usec(const struct timespec *a, const struct timespec *b) +{ + time_t secs = a->tv_sec - b->tv_sec; + long nsec = a->tv_nsec - b->tv_nsec; + + return secs * 1000000 + nsec / 1000; +} + +static void +feedback_presented(void *data, + struct wp_presentation_feedback *presentation_feedback, + uint32_t tv_sec_hi, + uint32_t tv_sec_lo, + uint32_t tv_nsec, + uint32_t refresh_nsec, + uint32_t seq_hi, + uint32_t seq_lo, + uint32_t flags) +{ + struct feedback *feedback = data; + struct window *window = feedback->window; + struct feedback *prev_feedback = window->received_feedback; + uint64_t seq = ((uint64_t)seq_hi << 32) + seq_lo; + const struct timespec *prevpresent; + uint32_t commit, present; + uint32_t f2c, c2p, f2p; + int p2p, t2p; + char flagstr[10]; + + timespec_from_proto(&feedback->present, tv_sec_hi, tv_sec_lo, tv_nsec); + commit = timespec_to_ms(&feedback->commit); + present = timespec_to_ms(&feedback->present); + + if (prev_feedback) + prevpresent = &prev_feedback->present; + else + prevpresent = &feedback->present; + + f2c = commit - feedback->frame_stamp; + c2p = present - commit; + f2p = present - feedback->frame_stamp; + p2p = timespec_diff_to_usec(&feedback->present, prevpresent); + t2p = timespec_diff_to_usec(&feedback->present, &feedback->target); + + switch (window->mode) { + case RUN_MODE_PRESENT: + printf("%6u: c2p %4u ms, p2p %5d us, t2p %6d us, [%s] " + "seq %" PRIu64 "\n", feedback->frame_no, c2p, + p2p, t2p, + pflags_to_str(flags, flagstr, sizeof(flagstr)), seq); + break; + case RUN_MODE_FEEDBACK: + case RUN_MODE_FEEDBACK_IDLE: + printf("%6u: f2c %2u ms, c2p %2u ms, f2p %2u ms, p2p %5d us, " + "t2p %6d, [%s], seq %" PRIu64 "\n", feedback->frame_no, + f2c, c2p, f2p, p2p, t2p, + pflags_to_str(flags, flagstr, sizeof(flagstr)), seq); + } + + if (window->received_feedback) + destroy_feedback(window->received_feedback); + window->received_feedback = feedback; +} + +static void +feedback_discarded(void *data, + struct wp_presentation_feedback *presentation_feedback) +{ + struct feedback *feedback = data; + + printf("discarded %u\n", feedback->frame_no); + + destroy_feedback(feedback); +} + +static const struct wp_presentation_feedback_listener feedback_listener = { + feedback_sync_output, + feedback_presented, + feedback_discarded +}; + +static void +window_emulate_rendering(struct window *window) +{ + struct timespec delay; + int ret; + + if (window->commit_delay_msecs <= 0) + return; + + delay.tv_sec = window->commit_delay_msecs / 1000; + delay.tv_nsec = (window->commit_delay_msecs % 1000) * 1000000; + + ret = nanosleep(&delay, NULL); + if (ret) + printf("nanosleep failed: %s\n", strerror(errno)); +} + +static void +window_create_feedback(struct window *window, uint32_t frame_stamp) +{ + static unsigned seq; + struct wp_presentation *pres = window->display->presentation; + struct feedback *feedback; + + seq++; + + if (!pres) + return; + + feedback = zalloc(sizeof *feedback); + if (!feedback) + return; + + feedback->window = window; + feedback->feedback = wp_presentation_feedback(pres, window->surface); + wp_presentation_feedback_add_listener(feedback->feedback, + &feedback_listener, feedback); + + feedback->frame_no = seq; + + clock_gettime(window->display->clk_id, &feedback->commit); + feedback->frame_stamp = frame_stamp; + feedback->target = feedback->commit; + + wl_list_insert(&window->feedback_list, &feedback->link); +} + +static void +window_commit_next(struct window *window) +{ + struct buffer *buffer; + + buffer = window_next_buffer(window); + assert(buffer); + + if (window->configure_serial) { + xdg_surface_ack_configure(window->xdg_surface, + window->configure_serial); + window->configure_serial = 0; + } + + wl_surface_attach(window->surface, buffer->buffer, 0, 0); + wl_surface_damage(window->surface, 0, 0, window->width, window->height); + wl_surface_commit(window->surface); + buffer->busy = 1; +} + +static const struct wl_callback_listener frame_listener_mode_feedback; + +static void +redraw_mode_feedback(void *data, struct wl_callback *callback, uint32_t time) +{ + struct window *window = data; + + if (callback && window->mode == RUN_MODE_FEEDBACK_IDLE) + sleep(1); + + if (callback) + wl_callback_destroy(callback); + + window_emulate_rendering(window); + + window->callback = wl_surface_frame(window->surface); + wl_callback_add_listener(window->callback, + &frame_listener_mode_feedback, window); + + window_create_feedback(window, time); + window_commit_next(window); +} + +static const struct wl_callback_listener frame_listener_mode_feedback = { + redraw_mode_feedback +}; + +static const struct wp_presentation_feedback_listener feedkick_listener; + +static void +window_feedkick(struct window *window) +{ + struct wp_presentation *pres = window->display->presentation; + struct wp_presentation_feedback *fback; + + fback = wp_presentation_feedback(pres, window->surface); + wp_presentation_feedback_add_listener(fback, &feedkick_listener, + window); +} + +static void +feedkick_presented(void *data, + struct wp_presentation_feedback *presentation_feedback, + uint32_t tv_sec_hi, + uint32_t tv_sec_lo, + uint32_t tv_nsec, + uint32_t refresh_nsec, + uint32_t seq_hi, + uint32_t seq_lo, + uint32_t flags) +{ + struct window *window = data; + + wp_presentation_feedback_destroy(presentation_feedback); + window->refresh_nsec = refresh_nsec; + + switch (window->mode) { + case RUN_MODE_PRESENT: + window_emulate_rendering(window); + window_create_feedback(window, 0); + window_feedkick(window); + window_commit_next(window); + break; + case RUN_MODE_FEEDBACK: + case RUN_MODE_FEEDBACK_IDLE: + assert(0 && "bad mode"); + } +} + +static void +feedkick_discarded(void *data, + struct wp_presentation_feedback *presentation_feedback) +{ + struct window *window = data; + + wp_presentation_feedback_destroy(presentation_feedback); + + switch (window->mode) { + case RUN_MODE_PRESENT: + window_emulate_rendering(window); + window_create_feedback(window, 0); + window_feedkick(window); + window_commit_next(window); + break; + case RUN_MODE_FEEDBACK: + case RUN_MODE_FEEDBACK_IDLE: + assert(0 && "bad mode"); + } +} + +static const struct wp_presentation_feedback_listener feedkick_listener = { + feedback_sync_output, + feedkick_presented, + feedkick_discarded +}; + +static void +firstdraw_mode_burst(struct window *window) +{ + window_emulate_rendering(window); + + switch (window->mode) { + case RUN_MODE_PRESENT: + window_create_feedback(window, 0); + break; + case RUN_MODE_FEEDBACK: + case RUN_MODE_FEEDBACK_IDLE: + assert(0 && "bad mode"); + } + + window_feedkick(window); + window_commit_next(window); +} + +static void +window_prerender(struct window *window) +{ + int i; + int timefactor = 1000000 / window->num_buffers; + + for (i = 0; i < window->num_buffers; i++) { + struct buffer *buf = &window->buffers[i]; + + if (buf->busy) + fprintf(stderr, "wl_buffer id %u) busy\n", + wl_proxy_get_id( + (struct wl_proxy *)buf->buffer)); + + paint_pixels(buf->shm_data, window->width, window->height, + i * timefactor); + } +} + +static void +output_destroy(struct output *o) +{ + wl_output_destroy(o->output); + wl_list_remove(&o->link); + free(o); +} + +static void +display_add_output(struct display *d, uint32_t name, uint32_t version) +{ + struct output *o; + + o = zalloc(sizeof(*o)); + assert(o); + + o->output = wl_registry_bind(d->registry, name, + &wl_output_interface, 1); + o->name = name; + wl_list_insert(&d->output_list, &o->link); +} + +static void +presentation_clock_id(void *data, struct wp_presentation *presentation, + uint32_t clk_id) +{ + struct display *d = data; + + d->clk_id = clk_id; +} + +static const struct wp_presentation_listener presentation_listener = { + presentation_clock_id +}; + +static void +shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) +{ + struct display *d = data; + + d->formats |= (1 << format); +} + +static const struct wl_shm_listener shm_listener = { + shm_format +}; + +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, "xdg_wm_base") == 0) { + d->wm_base = + wl_registry_bind(registry, name, + &xdg_wm_base_interface, version); + } else if (strcmp(interface, "wl_shm") == 0) { + d->shm = wl_registry_bind(registry, + name, &wl_shm_interface, 1); + wl_shm_add_listener(d->shm, &shm_listener, d); + } else if (strcmp(interface, "wl_output") == 0) { + display_add_output(d, name, version); + } else if (strcmp(interface, wp_presentation_interface.name) == 0) { + d->presentation = + wl_registry_bind(registry, + name, &wp_presentation_interface, 1); + wp_presentation_add_listener(d->presentation, + &presentation_listener, d); + } +} + +static void +registry_handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ + struct display *d = data; + struct output *output, *otmp; + + wl_list_for_each_safe(output, otmp, &d->output_list, link) { + if (output->name != name) + continue; + + output_destroy(output); + } +} + +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->clk_id = -1; + wl_list_init(&display->output_list); + 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) +{ + while (!wl_list_empty(&display->output_list)) { + struct output *o; + + o = wl_container_of(display->output_list.next, o, link); + output_destroy(o); + } + + if (display->shm) + wl_shm_destroy(display->shm); + + if (display->wm_base) + xdg_wm_base_destroy(display->wm_base); + + if (display->compositor) + wl_compositor_destroy(display->compositor); + + wl_registry_destroy(display->registry); + wl_display_flush(display->display); + wl_display_disconnect(display->display); + free(display); +} + +static int running = 1; + +static void +signal_int(int signum) +{ + running = 0; +} + +static void +usage(const char *prog, int exit_code) +{ + fprintf(stderr, "Usage: %s [mode] [options]\n" + "where 'mode' is one of\n" + " -f\t\trun in feedback mode (default)\n" + " -i\t\trun in feedback-idle mode; sleep 1s between frames\n" + " -p\t\trun in low-latency presentation mode\n" + "and 'options' may include\n" + " -d msecs\temulate the time used for rendering by a delay \n" + "\t\tof the given milliseconds before commit\n\n", + prog); + + fprintf(stderr, "Printed timing statistics, depending on mode:\n" + " commit sequence number\n" + " f2c: time from frame callback timestamp to commit\n" + " c2p: time from commit to presentation\n" + " f2p: time from frame callback timestamp to presentation\n" + " p2p: time from previous presentation to this one\n" + " t2p: time from target timestamp to presentation\n" + " seq: MSC\n"); + + + exit(exit_code); +} + +int +main(int argc, char **argv) +{ + struct sigaction sigint; + struct display *display; + struct window *window; + int ret = 0; + enum run_mode mode = RUN_MODE_FEEDBACK; + int i; + int commit_delay_msecs = 0; + + for (i = 1; i < argc; i++) { + if (strcmp("-f", argv[i]) == 0) + mode = RUN_MODE_FEEDBACK; + else if (strcmp("-i", argv[i]) == 0) + mode = RUN_MODE_FEEDBACK_IDLE; + else if (strcmp("-p", argv[i]) == 0) + mode = RUN_MODE_PRESENT; + else if ((strcmp("-d", argv[i]) == 0) && (i + 1 < argc)) { + i++; + commit_delay_msecs = atoi(argv[i]); + } + else + usage(argv[0], EXIT_FAILURE); + } + + display = create_display(); + window = create_window(display, 250, 250, mode, commit_delay_msecs); + if (!window) + return 1; + + sigint.sa_handler = signal_int; + sigemptyset(&sigint.sa_mask); + sigint.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &sigint, NULL); + + window_prerender(window); + + switch (mode) { + case RUN_MODE_FEEDBACK: + case RUN_MODE_FEEDBACK_IDLE: + redraw_mode_feedback(window, NULL, 0); + break; + case RUN_MODE_PRESENT: + firstdraw_mode_burst(window); + break; + } + + while (running && ret != -1) + ret = wl_display_dispatch(display->display); + + fprintf(stderr, "presentation-shm exiting\n"); + destroy_window(window); + destroy_display(display); + + return 0; +} diff --git a/clients/resizor.c b/clients/resizor.c new file mode 100644 index 0000000..cfc5d41 --- /dev/null +++ b/clients/resizor.c @@ -0,0 +1,456 @@ +/* + * Copyright © 2010 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "window.h" +#include "shared/xalloc.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; + bool pointer_locked; + bool locked_frame_callback_registered; + struct input *locked_input; + float pointer_x; + float pointer_y; +}; + +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); + + if (resizor->frame_callback) { + wl_callback_destroy(resizor->frame_callback); + resizor->frame_callback = NULL; + } + + if (window_is_maximized(resizor->window)) + return; + + spring_update(&resizor->width); + spring_update(&resizor->height); + + widget_schedule_resize(resizor->widget, + resizor->width.current + 0.5, + resizor->height.current + 0.5); + + 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(void *data, struct input *input, int index) +{ + 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 +locked_pointer_handle_motion(struct window *window, + struct input *input, + uint32_t time, + float dx, + float dy, + void *data) +{ + struct resizor *resizor = data; + + resizor->width.current += dx; + resizor->width.previous = resizor->width.current; + resizor->width.target = resizor->width.current; + + resizor->height.current += dy; + resizor->height.previous = resizor->height.current; + resizor->height.target = resizor->height.current; + + widget_schedule_resize(resizor->widget, + resizor->width.current, + resizor->height.current); +} + +static void +handle_pointer_locked(struct window *window, struct input *input, void *data) +{ + struct resizor *resizor = data; + + resizor->pointer_locked = true; + input_set_pointer_image(input, CURSOR_BLANK); +} + +static void +handle_pointer_unlocked(struct window *window, struct input *input, void *data) +{ + struct resizor *resizor = data; + + resizor->pointer_locked = false; + input_set_pointer_image(input, CURSOR_LEFT_PTR); +} + +static const struct wl_callback_listener locked_pointer_frame_listener; + +static void +locked_pointer_frame_callback(void *data, + struct wl_callback *callback, + uint32_t time) +{ + struct resizor *resizor = data; + struct wl_surface *surface; + struct rectangle allocation; + float x, y; + + if (resizor->pointer_locked) { + widget_get_allocation(resizor->widget, &allocation); + + x = resizor->pointer_x + (allocation.width - allocation.x); + y = resizor->pointer_y + (allocation.height - allocation.y); + + widget_set_locked_pointer_cursor_hint(resizor->widget, x, y); + } + + wl_callback_destroy(callback); + + surface = window_get_wl_surface(resizor->window); + callback = wl_surface_frame(surface); + wl_callback_add_listener(callback, + &locked_pointer_frame_listener, + resizor); +} + +static const struct wl_callback_listener locked_pointer_frame_listener = { + locked_pointer_frame_callback +}; + +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; + struct rectangle allocation; + struct wl_surface *surface; + struct wl_callback *callback; + + if (button == BTN_RIGHT && state == WL_POINTER_BUTTON_STATE_PRESSED) { + show_menu(resizor, input, time); + } else if (button == BTN_LEFT && + state == WL_POINTER_BUTTON_STATE_PRESSED) { + window_get_allocation(resizor->window, &allocation); + + resizor->width.current = allocation.width; + resizor->width.previous = allocation.width; + resizor->width.target = allocation.width; + + resizor->height.current = allocation.height; + resizor->height.previous = allocation.height; + resizor->height.target = allocation.height; + + window_lock_pointer(resizor->window, input); + window_set_pointer_locked_handler(resizor->window, + handle_pointer_locked, + handle_pointer_unlocked); + resizor->locked_input = input; + + if (resizor->locked_frame_callback_registered) + return; + + surface = window_get_wl_surface(resizor->window); + callback = wl_surface_frame(surface); + wl_callback_add_listener(callback, + &locked_pointer_frame_listener, + resizor); + resizor->locked_frame_callback_registered = true; + } else if (button == BTN_LEFT && + state == WL_POINTER_BUTTON_STATE_RELEASED) { + input_set_pointer_image(input, CURSOR_LEFT_PTR); + window_unlock_pointer(resizor->window); + } +} + +static void +set_cursor_inv_offset(struct resizor *resizor, float x, float y) +{ + struct rectangle allocation; + + widget_get_allocation(resizor->widget, &allocation); + + resizor->pointer_x = x - (allocation.width - allocation.x); + resizor->pointer_y = y - (allocation.height - allocation.y); +} + +static int +enter_handler(struct widget *widget, + struct input *input, + float x, float y, void *data) +{ + struct resizor *resizor = data; + + set_cursor_inv_offset(resizor, x , y); + + return CURSOR_LEFT_PTR; +} + +static int +motion_handler(struct widget *widget, + struct input *input, uint32_t time, + float x, float y, void *data) +{ + struct resizor *resizor = data; + + set_cursor_inv_offset(resizor, x , y); + + return CURSOR_LEFT_PTR; +} + +static struct resizor * +resizor_create(struct display *display) +{ + struct resizor *resizor; + + resizor = xzalloc(sizeof *resizor); + resizor->window = window_create(display); + resizor->widget = window_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_enter_handler(resizor->widget, enter_handler); + widget_set_motion_handler(resizor->widget, motion_handler); + + window_set_locked_pointer_motion_handler( + resizor->window, locked_pointer_handle_motion); + + 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: %s\n", + strerror(errno)); + return -1; + } + + resizor = resizor_create(display); + + display_run(display); + + resizor_destroy(resizor); + display_destroy(display); + + return 0; +} diff --git a/clients/scaler.c b/clients/scaler.c new file mode 100644 index 0000000..91736fb --- /dev/null +++ b/clients/scaler.c @@ -0,0 +1,326 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2013 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "window.h" +#include "viewporter-client-protocol.h" + +#define BUFFER_SCALE 2 +static const int BUFFER_WIDTH = 421 * BUFFER_SCALE; +static const int BUFFER_HEIGHT = 337 * BUFFER_SCALE; +static const int SURFACE_WIDTH = 55 * 4; +static const int SURFACE_HEIGHT = 77 * 4; +static const double RECT_X = 21 * BUFFER_SCALE; /* buffer coords */ +static const double RECT_Y = 25 * BUFFER_SCALE; +static const double RECT_W = 55 * BUFFER_SCALE; +static const double RECT_H = 77 * BUFFER_SCALE; + +struct box { + struct display *display; + struct window *window; + struct widget *widget; + int width, height; + + struct wp_viewporter *viewporter; + struct wp_viewport *viewport; + + enum { + MODE_NO_VIEWPORT, + MODE_SRC_ONLY, + MODE_DST_ONLY, + MODE_SRC_DST + } mode; +}; + +static void +set_my_viewport(struct box *box) +{ + wl_fixed_t src_x, src_y, src_width, src_height; + int32_t dst_width = SURFACE_WIDTH; + int32_t dst_height = SURFACE_HEIGHT; + + if (box->mode == MODE_NO_VIEWPORT) + return; + + /* Cut the green border in half, take white border fully in, + * and black border fully out. The borders are 1px wide in buffer. + * + * The gl-renderer uses linear texture sampling, this means the + * top and left edges go to 100% green, bottom goes to 50% blue/black, + * right edge has thick white sliding to 50% red. + */ + src_x = wl_fixed_from_double((RECT_X + 0.5) / BUFFER_SCALE); + src_y = wl_fixed_from_double((RECT_Y + 0.5) / BUFFER_SCALE); + src_width = wl_fixed_from_double((RECT_W - 0.5) / BUFFER_SCALE); + src_height = wl_fixed_from_double((RECT_H - 0.5) / BUFFER_SCALE); + + switch (box->mode){ + case MODE_SRC_ONLY: + /* In SRC_ONLY mode we're just cropping - in order + * for the surface size to remain an integer, the + * compositor will generate an error if we use a + * fractional width or height. + * + * We use fractional width/height for the other cases + * to ensure fractional values are still tested. + */ + src_width = wl_fixed_from_int(RECT_W / BUFFER_SCALE); + src_height = wl_fixed_from_int(RECT_H / BUFFER_SCALE); + wp_viewport_set_source(box->viewport, src_x, src_y, + src_width, src_height); + break; + case MODE_DST_ONLY: + wp_viewport_set_destination(box->viewport, + dst_width, dst_height); + break; + case MODE_SRC_DST: + wp_viewport_set_source(box->viewport, src_x, src_y, + src_width, src_height); + wp_viewport_set_destination(box->viewport, + dst_width, dst_height); + break; + default: + assert(!"not reached"); + } +} + +static void +resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + struct box *box = data; + + /* Don't resize me */ + widget_set_size(box->widget, box->width, box->height); +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct box *box = data; + cairo_surface_t *surface; + cairo_t *cr; + + surface = window_get_surface(box->window); + if (surface == NULL || + cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { + fprintf(stderr, "failed to create cairo egl surface\n"); + return; + } + + cr = cairo_create(surface); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_line_width(cr, 1.0); + cairo_translate(cr, RECT_X, RECT_Y); + + /* red background */ + cairo_set_source_rgba(cr, 255, 0, 0, 255); + cairo_paint(cr); + + /* blue box */ + cairo_set_source_rgba(cr, 0, 0, 255, 255); + cairo_rectangle(cr, 0, 0, RECT_W, RECT_H); + cairo_fill(cr); + + /* black border outside the box */ + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_move_to(cr, 0, RECT_H + 0.5); + cairo_line_to(cr, RECT_W, RECT_H + 0.5); + cairo_stroke(cr); + + /* white border inside the box */ + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_move_to(cr, RECT_W - 0.5, 0); + cairo_line_to(cr, RECT_W - 0.5, RECT_H); + cairo_stroke(cr); + + /* the green border on inside the box, to be split half by crop */ + cairo_set_source_rgb(cr, 0, 1, 0); + cairo_move_to(cr, 0.5, RECT_H); + cairo_line_to(cr, 0.5, 0); + cairo_move_to(cr, 0, 0.5); + cairo_line_to(cr, RECT_W, 0.5); + cairo_stroke(cr); + + cairo_destroy(cr); + + /* TODO: buffer_transform */ + + cairo_surface_destroy(surface); +} + +static void +global_handler(struct display *display, uint32_t name, + const char *interface, uint32_t version, void *data) +{ + struct box *box = data; + + if (strcmp(interface, "wp_viewporter") == 0) { + box->viewporter = display_bind(display, name, + &wp_viewporter_interface, 1); + + box->viewport = wp_viewporter_get_viewport(box->viewporter, + widget_get_wl_surface(box->widget)); + + set_my_viewport(box); + } +} + +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 box *box = data; + + if (button != BTN_LEFT) + return; + + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + window_move(box->window, input, + display_get_serial(box->display)); + } +} + +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 box *box = data; + window_move(box->window, input, + display_get_serial(box->display)); +} + +static void +usage(const char *progname) +{ + fprintf(stderr, "Usage: %s [mode]\n" + "where 'mode' is one of\n" + " -b\tset both src and dst in viewport (default)\n" + " -d\tset only dst in viewport\n" + " -s\tset only src in viewport\n" + " -n\tdo not set viewport at all\n\n", + progname); + + fprintf(stderr, "Expected output with output_scale=1:\n"); + + fprintf(stderr, "Mode -n:\n" + " window size %dx%d px\n" + " Red box with a blue box in the upper left part.\n" + " The blue box has white right edge, black bottom edge,\n" + " and thin green left and top edges that can really\n" + " be seen only when zoomed in.\n\n", + BUFFER_WIDTH / BUFFER_SCALE, BUFFER_HEIGHT / BUFFER_SCALE); + + fprintf(stderr, "Mode -b:\n" + " window size %dx%d px\n" + " Blue box with green top and left edge,\n" + " thick white right edge with a hint of red,\n" + " and a hint of black in bottom edge.\n\n", + SURFACE_WIDTH, SURFACE_HEIGHT); + + fprintf(stderr, "Mode -s:\n" + " window size %.0fx%.0f px\n" + " The same as mode -b, but scaled a lot smaller.\n\n", + RECT_W / BUFFER_SCALE, RECT_H / BUFFER_SCALE); + + fprintf(stderr, "Mode -d:\n" + " window size %dx%d px\n" + " This is horizontally squashed version of the -n mode.\n\n", + SURFACE_WIDTH, SURFACE_HEIGHT); +} + +int +main(int argc, char *argv[]) +{ + struct box box; + struct display *d; + struct timeval tv; + int i; + + box.mode = MODE_SRC_DST; + + for (i = 1; i < argc; i++) { + if (strcmp("-s", argv[i]) == 0) + box.mode = MODE_SRC_ONLY; + else if (strcmp("-d", argv[i]) == 0) + box.mode = MODE_DST_ONLY; + else if (strcmp("-b", argv[i]) == 0) + box.mode = MODE_SRC_DST; + else if (strcmp("-n", argv[i]) == 0) + box.mode = MODE_NO_VIEWPORT; + else { + usage(argv[0]); + exit(1); + } + } + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); + return -1; + } + + gettimeofday(&tv, NULL); + srandom(tv.tv_usec); + + box.width = BUFFER_WIDTH / BUFFER_SCALE; + box.height = BUFFER_HEIGHT / BUFFER_SCALE; + box.display = d; + box.window = window_create(d); + box.widget = window_add_widget(box.window, &box); + window_set_title(box.window, "Scaler Test Box"); + window_set_buffer_scale(box.window, BUFFER_SCALE); + + widget_set_resize_handler(box.widget, resize_handler); + widget_set_redraw_handler(box.widget, redraw_handler); + widget_set_button_handler(box.widget, button_handler); + widget_set_default_cursor(box.widget, CURSOR_HAND1); + widget_set_touch_down_handler(box.widget, touch_down_handler); + + window_schedule_resize(box.window, box.width, box.height); + + display_set_user_data(box.display, &box); + display_set_global_handler(box.display, global_handler); + + display_run(d); + + widget_destroy(box.widget); + window_destroy(box.window); + display_destroy(d); + + return 0; +} diff --git a/clients/screenshot.c b/clients/screenshot.c new file mode 100644 index 0000000..bbf2e6b --- /dev/null +++ b/clients/screenshot.c @@ -0,0 +1,329 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "weston-screenshooter-client-protocol.h" +#include "shared/os-compatibility.h" +#include "shared/xalloc.h" +#include "shared/file-util.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 */ + + +struct screenshooter_output { + struct wl_output *output; + struct wl_buffer *buffer; + int width, height, offset_x, offset_y; + void *data; + struct wl_list link; +}; + +struct buffer_size { + int width, height; + + int min_x, min_y; + int max_x, max_y; +}; + +struct screenshooter_data { + struct wl_shm *shm; + struct wl_list output_list; + + struct weston_screenshooter *screenshooter; + int buffer_copy_done; +}; + + +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 +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 weston_screenshooter *screenshooter) +{ + struct screenshooter_data *sh_data = data; + sh_data->buffer_copy_done = 1; +} + +static const struct weston_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; + struct screenshooter_data *sh_data = data; + + if (strcmp(interface, "wl_output") == 0) { + output = xmalloc(sizeof *output); + output->output = wl_registry_bind(registry, name, + &wl_output_interface, 1); + wl_list_insert(&sh_data->output_list, &output->link); + wl_output_add_listener(output->output, &output_listener, output); + } else if (strcmp(interface, "wl_shm") == 0) { + sh_data->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + } else if (strcmp(interface, "weston_screenshooter") == 0) { + sh_data->screenshooter = wl_registry_bind(registry, name, + &weston_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 * +screenshot_create_shm_buffer(int width, int height, void **data_out, + struct wl_shm *shm) +{ + 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: %s\n", + size, strerror(errno)); + return NULL; + } + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + close(fd); + return 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 +screenshot_write_png(const struct buffer_size *buff_size, + struct wl_list *output_list) +{ + int output_stride, buffer_stride, i; + cairo_surface_t *surface; + void *data, *d, *s; + struct screenshooter_output *output, *next; + FILE *fp; + char filepath[PATH_MAX]; + + buffer_stride = buff_size->width * 4; + + data = xmalloc(buffer_stride * buff_size->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 - buff_size->min_y) * buffer_stride + + (output->offset_x - buff_size->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, + buff_size->width, + buff_size->height, + buffer_stride); + + fp = file_create_dated(getenv("XDG_PICTURES_DIR"), "wayland-screenshot-", + ".png", filepath, sizeof(filepath)); + if (fp) { + fclose (fp); + cairo_surface_write_to_png(surface, filepath); + } + cairo_surface_destroy(surface); + free(data); +} + +static int +screenshot_set_buffer_size(struct buffer_size *buff_size, struct wl_list *output_list) +{ + struct screenshooter_output *output; + buff_size->min_x = buff_size->min_y = INT_MAX; + buff_size->max_x = buff_size->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) { + buff_size->min_x = MIN(buff_size->min_x, output->offset_x); + buff_size->min_y = MIN(buff_size->min_y, output->offset_y); + buff_size->max_x = + MAX(buff_size->max_x, output->offset_x + output->width); + buff_size->max_y = + MAX(buff_size->max_y, output->offset_y + output->height); + } + + if (buff_size->max_x <= buff_size->min_x || + buff_size->max_y <= buff_size->min_y) + return -1; + + buff_size->width = buff_size->max_x - buff_size->min_x; + buff_size->height = buff_size->max_y - buff_size->min_y; + + return 0; +} + +int main(int argc, char *argv[]) +{ + struct wl_display *display; + struct wl_registry *registry; + struct screenshooter_output *output; + struct buffer_size buff_size = {}; + struct screenshooter_data sh_data = {}; + + display = wl_display_connect(NULL); + if (display == NULL) { + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); + return -1; + } + + wl_list_init(&sh_data.output_list); + registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, &sh_data); + wl_display_dispatch(display); + wl_display_roundtrip(display); + if (sh_data.screenshooter == NULL) { + fprintf(stderr, "display doesn't support screenshooter\n"); + return -1; + } + + weston_screenshooter_add_listener(sh_data.screenshooter, + &screenshooter_listener, + &sh_data); + + if (screenshot_set_buffer_size(&buff_size, &sh_data.output_list)) + return -1; + + + wl_list_for_each(output, &sh_data.output_list, link) { + output->buffer = + screenshot_create_shm_buffer(output->width, + output->height, + &output->data, + sh_data.shm); + weston_screenshooter_shoot(sh_data.screenshooter, + output->output, + output->buffer); + sh_data.buffer_copy_done = 0; + while (!sh_data.buffer_copy_done) + wl_display_roundtrip(display); + } + + screenshot_write_png(&buff_size, &sh_data.output_list); + + return 0; +} diff --git a/clients/simple-damage.c b/clients/simple-damage.c new file mode 100644 index 0000000..821b42b --- /dev/null +++ b/clients/simple-damage.c @@ -0,0 +1,957 @@ +/* + * Copyright © 2014 Jason Ekstrand + * Copyright © 2011 Benjamin Franzke + * Copyright © 2010 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "shared/os-compatibility.h" +#include +#include "xdg-shell-client-protocol.h" +#include "fullscreen-shell-unstable-v1-client-protocol.h" +#include "viewporter-client-protocol.h" + +int print_debug = 0; + +struct display { + struct wl_display *display; + struct wl_registry *registry; + int compositor_version; + struct wl_compositor *compositor; + struct wp_viewporter *viewporter; + struct xdg_wm_base *wm_base; + struct zwp_fullscreen_shell_v1 *fshell; + struct wl_shm *shm; + uint32_t formats; +}; + +struct buffer { + struct wl_buffer *buffer; + uint32_t *shm_data; + int busy; +}; + +enum window_flags { + WINDOW_FLAG_USE_VIEWPORT = 0x1, + WINDOW_FLAG_ROTATING_TRANSFORM = 0x2, + WINDOW_FLAG_USE_DAMAGE_BUFFER = 0x4, +}; + +struct window { + struct display *display; + int width, height, border; + struct wl_surface *surface; + struct wp_viewport *viewport; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + struct wl_callback *callback; + struct buffer buffers[2]; + struct buffer *prev_buffer; + bool wait_for_configure; + + enum window_flags flags; + int scale; + enum wl_output_transform transform; + + struct { + float x, y; /* position in pixels */ + float dx, dy; /* velocity in pixels/second */ + int radius; /* radius in pixels */ + uint32_t prev_time; + } ball; +}; + +static int running = 1; + +static void +redraw(void *data, struct wl_callback *callback, uint32_t time); + +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, pitch; + void *data; + + pitch = width * 4; + size = pitch * height; + + fd = os_create_anonymous_file(size); + if (fd < 0) { + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); + return -1; + } + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + close(fd); + return -1; + } + + pool = wl_shm_create_pool(display->shm, fd, size); + buffer->buffer = wl_shm_pool_create_buffer(pool, 0, + width, height, + pitch, 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 +xdg_surface_handle_configure(void *data, struct xdg_surface *surface, + uint32_t serial) +{ + struct window *window = data; + + xdg_surface_ack_configure(surface, serial); + + if (window->wait_for_configure) { + redraw(window, NULL, 0); + window->wait_for_configure = false; + } +} + +static const struct xdg_surface_listener xdg_surface_listener = { + xdg_surface_handle_configure, +}; + +static void +xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, + int32_t width, int32_t height, + struct wl_array *states) +{ +} + +static void +xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) +{ + running = 0; +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + xdg_toplevel_handle_configure, + xdg_toplevel_handle_close, +}; + +static float +bounded_randf(float a, float b) +{ + return a + ((float)rand() / (float)RAND_MAX) * (b - a); +} + +static void +window_init_game(struct window *window) +{ + int ax1, ay1, ax2, ay2; /* playable arena size */ + struct timeval tv; + + gettimeofday(&tv, NULL); + srand(tv.tv_usec); + + window->ball.radius = 10; + + ax1 = window->border + window->ball.radius; + ay1 = window->border + window->ball.radius; + ax2 = window->width - window->border - window->ball.radius; + ay2 = window->height - window->border - window->ball.radius; + + window->ball.x = bounded_randf(ax1, ax2); + window->ball.y = bounded_randf(ay1, ay2); + + window->ball.dx = bounded_randf(0, window->width); + window->ball.dy = bounded_randf(0, window->height); + + window->ball.prev_time = 0; +} + +static void +window_advance_game(struct window *window, uint32_t timestamp) +{ + int ax1, ay1, ax2, ay2; /* Arena size */ + float dt; + + if (window->ball.prev_time == 0) { + /* first pass, don't do anything */ + window->ball.prev_time = timestamp; + return; + } + + /* dt in seconds */ + dt = (float)(timestamp - window->ball.prev_time) / 1000.0f; + + ax1 = window->border + window->ball.radius; + ay1 = window->border + window->ball.radius; + ax2 = window->width - window->border - window->ball.radius; + ay2 = window->height - window->border - window->ball.radius; + + window->ball.x += window->ball.dx * dt; + while (window->ball.x < ax1 || ax2 < window->ball.x) { + if (window->ball.x < ax1) + window->ball.x = 2 * ax1 - window->ball.x; + if (ax2 <= window->ball.x) + window->ball.x = 2 * ax2 - window->ball.x; + + window->ball.dx *= -1.0f; + } + + window->ball.y += window->ball.dy * dt; + while (window->ball.y < ay1 || ay2 < window->ball.y) { + if (window->ball.y < ay1) + window->ball.y = 2 * ay1 - window->ball.y; + if (ay2 <= window->ball.y) + window->ball.y = 2 * ay2 - window->ball.y; + + window->ball.dy *= -1.0f; + } + + window->ball.prev_time = timestamp; +} + +static struct window * +create_window(struct display *display, int width, int height, + enum wl_output_transform transform, int scale, + enum window_flags flags) +{ + struct window *window; + + if (display->compositor_version < 2 && + (transform != WL_OUTPUT_TRANSFORM_NORMAL || + flags & WINDOW_FLAG_ROTATING_TRANSFORM)) { + fprintf(stderr, "wl_surface.buffer_transform unsupported in " + "wl_surface version %d\n", + display->compositor_version); + exit(1); + } + + if (display->compositor_version < 3 && + (! (flags & WINDOW_FLAG_USE_VIEWPORT)) && scale != 1) { + fprintf(stderr, "wl_surface.buffer_scale unsupported in " + "wl_surface version %d\n", + display->compositor_version); + exit(1); + } + + if (display->viewporter == NULL && (flags & WINDOW_FLAG_USE_VIEWPORT)) { + fprintf(stderr, "Compositor does not support wp_viewport"); + exit(1); + } + + if (display->compositor_version < + WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION && + (flags & WINDOW_FLAG_USE_DAMAGE_BUFFER)) { + fprintf(stderr, "wl_surface.damage_buffer unsupported in " + "wl_surface version %d\n", + display->compositor_version); + exit(1); + } + + window = zalloc(sizeof *window); + if (!window) + return NULL; + + window->callback = NULL; + window->display = display; + window->width = width; + window->height = height; + window->border = 10; + window->flags = flags; + window->transform = transform; + window->scale = scale; + + window_init_game(window); + + window->surface = wl_compositor_create_surface(display->compositor); + + if (window->flags & WINDOW_FLAG_USE_VIEWPORT) + window->viewport = wp_viewporter_get_viewport(display->viewporter, + window->surface); + + if (display->wm_base) { + window->xdg_surface = + xdg_wm_base_get_xdg_surface(display->wm_base, + window->surface); + + assert(window->xdg_surface); + + xdg_surface_add_listener(window->xdg_surface, + &xdg_surface_listener, window); + + window->xdg_toplevel = + xdg_surface_get_toplevel(window->xdg_surface); + + assert(window->xdg_toplevel); + + xdg_toplevel_add_listener(window->xdg_toplevel, + &xdg_toplevel_listener, window); + + xdg_toplevel_set_title(window->xdg_toplevel, "simple-damage"); + + window->wait_for_configure = true; + wl_surface_commit(window->surface); + } else if (display->fshell) { + zwp_fullscreen_shell_v1_present_surface(display->fshell, + window->surface, + ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT, + NULL); + } else { + assert(0); + } + + /* Initialise damage to full surface, so the padding gets painted */ + if (window->flags & WINDOW_FLAG_USE_DAMAGE_BUFFER) { + wl_surface_damage_buffer(window->surface, 0, 0, + INT32_MAX, INT32_MAX); + } else { + wl_surface_damage(window->surface, 0, 0, INT32_MAX, INT32_MAX); + } + return window; +} + +static void +destroy_window(struct window *window) +{ + if (window->callback) + wl_callback_destroy(window->callback); + + if (window->buffers[0].buffer) + wl_buffer_destroy(window->buffers[0].buffer); + if (window->buffers[1].buffer) + wl_buffer_destroy(window->buffers[1].buffer); + + if (window->xdg_toplevel) + xdg_toplevel_destroy(window->xdg_toplevel); + if (window->xdg_surface) + xdg_surface_destroy(window->xdg_surface); + if (window->viewport) + wp_viewport_destroy(window->viewport); + wl_surface_destroy(window->surface); + free(window); +} + +static struct buffer * +window_next_buffer(struct window *window) +{ + struct buffer *buffer; + int ret = 0, bwidth, bheight; + + if (!window->buffers[0].busy) + buffer = &window->buffers[0]; + else if (!window->buffers[1].busy) + buffer = &window->buffers[1]; + else + return NULL; + + switch (window->transform) { + default: + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + bwidth = window->width * window->scale; + bheight = window->height * window->scale; + break; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + bwidth = window->height * window->scale; + bheight = window->width * window->scale; + break; + } + + if (!buffer->buffer) { + ret = create_shm_buffer(window->display, buffer, + bwidth, bheight, + WL_SHM_FORMAT_ARGB8888); + + if (ret < 0) + return NULL; + } + + return buffer; +} + +static void +paint_box(uint32_t *pixels, int pitch, int x, int y, int width, int height, + uint32_t color) +{ + int i, j; + + for (j = y; j < y + height; ++j) + for (i = x; i < x + width; ++i) + pixels[i + j * pitch] = color; +} + +static void +paint_circle(uint32_t *pixels, int pitch, float x, float y, int radius, + uint32_t color) +{ + int i, j; + + for (j = y - radius; j <= (int)(y + radius); ++j) + for (i = x - radius; i <= (int)(x + radius); ++i) + if ((j+0.5f-y)*(j+0.5f-y) + (i+0.5f-x)*(i+0.5f-x) <= radius * radius) + pixels[i + j * pitch] = color; +} + +static void +window_get_transformed_ball(struct window *window, float *bx, float *by) +{ + float wx, wy; + + wx = window->ball.x; + wy = window->ball.y; + + switch (window->transform) { + default: + case WL_OUTPUT_TRANSFORM_NORMAL: + *bx = wx; + *by = wy; + break; + case WL_OUTPUT_TRANSFORM_90: + *bx = wy; + *by = window->width - wx; + break; + case WL_OUTPUT_TRANSFORM_180: + *bx = window->width - wx; + *by = window->height - wy; + break; + case WL_OUTPUT_TRANSFORM_270: + *bx = window->height - wy; + *by = wx; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + *bx = window->width - wx; + *by = wy; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + *bx = wy; + *by = wx; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + *bx = wx; + *by = window->height - wy; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + *bx = window->height - wy; + *by = window->width - wx; + break; + } + + *bx *= window->scale; + *by *= window->scale; + + if (window->viewport) { + /* We're drawing half-size because of the viewport */ + *bx /= 2; + *by /= 2; + } +} + +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; + int off_x = 0, off_y = 0; + int bwidth, bheight, bborder, bpitch, bradius; + float bx, by; + + 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(); + } + + /* Rotate the damage, but keep the even/odd parity so the + * dimensions of the buffers don't change */ + if (window->flags & WINDOW_FLAG_ROTATING_TRANSFORM) + window->transform = (window->transform + 2) % 8; + + switch (window->transform) { + default: + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + bwidth = window->width * window->scale; + bheight = window->height * window->scale; + break; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + bwidth = window->height * window->scale; + bheight = window->width * window->scale; + break; + } + + bpitch = bwidth; + + bborder = window->border * window->scale; + bradius = window->ball.radius * window->scale; + + if (window->viewport) { + int tx, ty; + /* Fill the whole thing with red to detect viewport errors */ + paint_box(buffer->shm_data, bpitch, 0, 0, bwidth, bheight, + 0xffff0000); + + /* The buffer is the same size. However, we crop it + * and scale it up by a factor of 2 */ + bborder /= 2; + bradius /= 2; + bwidth /= 2; + bheight /= 2; + + /* Offset the drawing region */ + tx = (window->width / 3) * window->scale; + ty = (window->height / 5) * window->scale; + switch (window->transform) { + default: + case WL_OUTPUT_TRANSFORM_NORMAL: + off_y = ty; + off_x = tx; + break; + case WL_OUTPUT_TRANSFORM_90: + off_y = bheight - tx; + off_x = ty; + break; + case WL_OUTPUT_TRANSFORM_180: + off_y = bheight - ty; + off_x = bwidth - tx; + break; + case WL_OUTPUT_TRANSFORM_270: + off_y = tx; + off_x = bwidth - ty; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + off_y = ty; + off_x = bwidth - tx; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + off_y = tx; + off_x = ty; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + off_y = bheight - ty; + off_x = tx; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + off_y = bheight - tx; + off_x = bwidth - ty; + break; + } + wp_viewport_set_source(window->viewport, + wl_fixed_from_int(window->width / 3), + wl_fixed_from_int(window->height / 5), + wl_fixed_from_int(window->width / 2), + wl_fixed_from_int(window->height / 2)); + } + + /* Paint the border */ + paint_box(buffer->shm_data, bpitch, off_x, off_y, + bwidth, bborder, 0xffffffff); + paint_box(buffer->shm_data, bpitch, off_x, off_y, + bborder, bheight, 0xffffffff); + paint_box(buffer->shm_data, bpitch, off_x + bwidth - bborder, off_y, + bborder, bheight, 0xffffffff); + paint_box(buffer->shm_data, bpitch, off_x, off_y + bheight - bborder, + bwidth, bborder, 0xffffffff); + + /* fill with translucent */ + paint_box(buffer->shm_data, bpitch, off_x + bborder, off_y + bborder, + bwidth - 2 * bborder, bheight - 2 * bborder, 0x80000000); + + /* Damage where the ball was */ + if (window->flags & WINDOW_FLAG_USE_DAMAGE_BUFFER) { + window_get_transformed_ball(window, &bx, &by); + wl_surface_damage_buffer(window->surface, + bx - bradius + off_x, + by - bradius + off_y, + bradius * 2 + 1, + bradius * 2 + 1); + } else { + wl_surface_damage(window->surface, + window->ball.x - window->ball.radius, + window->ball.y - window->ball.radius, + window->ball.radius * 2 + 1, + window->ball.radius * 2 + 1); + } + window_advance_game(window, time); + + window_get_transformed_ball(window, &bx, &by); + + /* Paint the ball */ + paint_circle(buffer->shm_data, bpitch, off_x + bx, off_y + by, + bradius, 0xff00ff00); + + if (print_debug) { + printf("Ball now located at (%f, %f)\n", + window->ball.x, window->ball.y); + + printf("Circle painted at (%f, %f), radius %d\n", bx, by, + bradius); + + printf("Buffer damage rectangle: (%d, %d) @ %dx%d\n", + (int)(bx - bradius) + off_x, + (int)(by - bradius) + off_y, + bradius * 2 + 1, bradius * 2 + 1); + } + + /* Damage where the ball is now */ + if (window->flags & WINDOW_FLAG_USE_DAMAGE_BUFFER) { + wl_surface_damage_buffer(window->surface, + bx - bradius + off_x, + by - bradius + off_y, + bradius * 2 + 1, + bradius * 2 + 1); + } else { + wl_surface_damage(window->surface, + window->ball.x - window->ball.radius, + window->ball.y - window->ball.radius, + window->ball.radius * 2 + 1, + window->ball.radius * 2 + 1); + } + wl_surface_attach(window->surface, buffer->buffer, 0, 0); + + if (window->display->compositor_version >= 2 && + (window->transform != WL_OUTPUT_TRANSFORM_NORMAL || + window->flags & WINDOW_FLAG_ROTATING_TRANSFORM)) + wl_surface_set_buffer_transform(window->surface, + window->transform); + + if (window->viewport) + wp_viewport_set_destination(window->viewport, + window->width, + window->height); + + if (window->scale != 1) + wl_surface_set_buffer_scale(window->surface, + window->scale); + + 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 +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) +{ + xdg_wm_base_pong(shell, serial); +} + +static const struct xdg_wm_base_listener wm_base_listener = { + xdg_wm_base_ping, +}; + +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) { + if (d->compositor_version > (int)version) { + fprintf(stderr, "Compositor does not support " + "wl_surface version %d\n", d->compositor_version); + exit(1); + } + + if (d->compositor_version < 0) + d->compositor_version = version; + + d->compositor = + wl_registry_bind(registry, + id, &wl_compositor_interface, + d->compositor_version); + } else if (strcmp(interface, "wp_viewporter") == 0) { + d->viewporter = wl_registry_bind(registry, id, + &wp_viewporter_interface, 1); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + d->wm_base = wl_registry_bind(registry, + id, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d); + } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { + d->fshell = wl_registry_bind(registry, + id, &zwp_fullscreen_shell_v1_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(int version) +{ + 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->compositor_version = version; + 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); + } + + return display; +} + +static void +destroy_display(struct display *display) +{ + if (display->shm) + wl_shm_destroy(display->shm); + + if (display->wm_base) + xdg_wm_base_destroy(display->wm_base); + + if (display->fshell) + zwp_fullscreen_shell_v1_release(display->fshell); + + if (display->viewporter) + wp_viewporter_destroy(display->viewporter); + + if (display->compositor) + wl_compositor_destroy(display->compositor); + + wl_registry_destroy(display->registry); + wl_display_flush(display->display); + wl_display_disconnect(display->display); + free(display); +} + +static void +signal_int(int signum) +{ + running = 0; +} + +static void +print_usage(int retval) +{ + printf( + "usage: weston-simple-damage [options]\n\n" + "options:\n" + " -h, --help\t\tPring this help\n" + " --verbose\t\tPrint verbose log information\n" + " --version=VERSION\tVersion of wl_surface to use\n" + " --width=WIDTH\t\tWidth of the window\n" + " --height=HEIGHT\tHeight of the window\n" + " --scale=SCALE\t\tScale factor for the surface\n" + " --transform=TRANSFORM\tTransform for the surface\n" + " --rotating-transform\tUse a different buffer_transform for each frame\n" + " --use-viewport\tUse wp_viewport\n" + " --use-damage-buffer\tUse damage_buffer to post damage\n" + ); + + exit(retval); +} + +static int +parse_transform(const char *str, enum wl_output_transform *transform) +{ + int i; + static const struct { + const char *name; + enum wl_output_transform transform; + } 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 }, + }; + + for (i = 0; i < 8; i++) { + if (strcmp(names[i].name, str) == 0) { + *transform = names[i].transform; + return 1; + } + } + + return 0; +} + +int +main(int argc, char **argv) +{ + struct sigaction sigint; + struct display *display; + struct window *window; + int i, ret = 0; + int version = -1; + int width = 300, height = 200, scale = 1; + enum wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; + enum window_flags flags = 0; + + for (i = 1; i < argc; ++i) { + if (strcmp(argv[i], "--help") == 0 || + strcmp(argv[i], "-h") == 0) { + print_usage(0); + } else if (sscanf(argv[i], "--version=%d", &version) > 0) { + if (version < 1 || version > 4) { + fprintf(stderr, "Unsupported wl_surface version: %d\n", + version); + return 1; + } + continue; + } else if (strcmp(argv[i], "--verbose") == 0) { + print_debug = 1; + continue; + } else if (sscanf(argv[i], "--width=%d", &width) > 0) { + continue; + } else if (sscanf(argv[i], "--height=%d", &height) > 0) { + continue; + } else if (strncmp(argv[i], "--transform=", 12) == 0 && + parse_transform(argv[i] + 12, &transform) > 0) { + continue; + } else if (strcmp(argv[i], "--rotating-transform") == 0) { + flags |= WINDOW_FLAG_ROTATING_TRANSFORM; + continue; + } else if (sscanf(argv[i], "--scale=%d", &scale) > 0) { + continue; + } else if (strcmp(argv[i], "--use-viewport") == 0) { + flags |= WINDOW_FLAG_USE_VIEWPORT; + continue; + } else if (strcmp(argv[i], "--use-damage-buffer") == 0) { + flags |= WINDOW_FLAG_USE_DAMAGE_BUFFER; + continue; + } else { + printf("Invalid option: %s\n", argv[i]); + print_usage(255); + } + } + + display = create_display(version); + + window = create_window(display, width, height, transform, scale, flags); + if (!window) + return 1; + + sigint.sa_handler = signal_int; + sigemptyset(&sigint.sa_mask); + sigint.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &sigint, NULL); + + if (!window->wait_for_configure) + 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-dmabuf-egl.c b/clients/simple-dmabuf-egl.c new file mode 100644 index 0000000..10e72a9 --- /dev/null +++ b/clients/simple-dmabuf-egl.c @@ -0,0 +1,1562 @@ +/* + * Copyright © 2011 Benjamin Franzke + * Copyright © 2010 Intel Corporation + * Copyright © 2014,2018 Collabora Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include "shared/helpers.h" +#include "shared/platform.h" +#include +#include "xdg-shell-client-protocol.h" +#include "fullscreen-shell-unstable-v1-client-protocol.h" +#include "linux-dmabuf-unstable-v1-client-protocol.h" +#include "weston-direct-display-client-protocol.h" +#include "linux-explicit-synchronization-unstable-v1-client-protocol.h" + +#include +#include +#include +#include + +#include "shared/weston-egl-ext.h" + +#ifndef DRM_FORMAT_MOD_INVALID +#define DRM_FORMAT_MOD_INVALID ((1ULL << 56) - 1) +#endif + +/* Possible options that affect the displayed image */ +#define OPT_IMMEDIATE (1 << 0) /* create wl_buffer immediately */ +#define OPT_IMPLICIT_SYNC (1 << 1) /* force implicit sync */ +#define OPT_MANDELBROT (1 << 2) /* render mandelbrot */ +#define OPT_DIRECT_DISPLAY (1 << 3) /* direct-display */ + +#define BUFFER_FORMAT DRM_FORMAT_XRGB8888 +#define MAX_BUFFER_PLANES 4 + +struct display { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct xdg_wm_base *wm_base; + struct zwp_fullscreen_shell_v1 *fshell; + struct zwp_linux_dmabuf_v1 *dmabuf; + struct weston_direct_display_v1 *direct_display; + struct zwp_linux_explicit_synchronization_v1 *explicit_sync; + uint64_t *modifiers; + int modifiers_count; + int req_dmabuf_immediate; + bool use_explicit_sync; + struct { + EGLDisplay display; + EGLContext context; + EGLConfig conf; + bool has_dma_buf_import_modifiers; + bool has_no_config_context; + PFNEGLQUERYDMABUFMODIFIERSEXTPROC query_dma_buf_modifiers; + PFNEGLCREATEIMAGEKHRPROC create_image; + PFNEGLDESTROYIMAGEKHRPROC destroy_image; + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d; + PFNEGLCREATESYNCKHRPROC create_sync; + PFNEGLDESTROYSYNCKHRPROC destroy_sync; + PFNEGLCLIENTWAITSYNCKHRPROC client_wait_sync; + PFNEGLDUPNATIVEFENCEFDANDROIDPROC dup_native_fence_fd; + PFNEGLWAITSYNCKHRPROC wait_sync; + } egl; + struct { + int drm_fd; + struct gbm_device *device; + } gbm; +}; + +struct buffer { + struct display *display; + struct wl_buffer *buffer; + int busy; + + struct gbm_bo *bo; + + int width; + int height; + int format; + uint64_t modifier; + int plane_count; + int dmabuf_fds[MAX_BUFFER_PLANES]; + uint32_t strides[MAX_BUFFER_PLANES]; + uint32_t offsets[MAX_BUFFER_PLANES]; + + EGLImageKHR egl_image; + GLuint gl_texture; + GLuint gl_fbo; + + struct zwp_linux_buffer_release_v1 *buffer_release; + /* The buffer owns the release_fence_fd, until it passes ownership + * to it to EGL (see wait_for_buffer_release_fence). */ + int release_fence_fd; +}; + +#define NUM_BUFFERS 3 + +struct window { + struct display *display; + int width, height; + struct wl_surface *surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + struct zwp_linux_surface_synchronization_v1 *surface_sync; + struct buffer buffers[NUM_BUFFERS]; + struct wl_callback *callback; + bool initialized; + bool wait_for_configure; + struct { + GLuint program; + GLuint pos; + GLuint color; + GLuint offset_uniform; + } gl; + bool render_mandelbrot; +}; + +static sig_atomic_t running = 1; + +static void +redraw(void *data, struct wl_callback *callback, uint32_t time); + +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 void +buffer_free(struct buffer *buf) +{ + int i; + + if (buf->release_fence_fd >= 0) + close(buf->release_fence_fd); + + if (buf->buffer_release) + zwp_linux_buffer_release_v1_destroy(buf->buffer_release); + + if (buf->gl_fbo) + glDeleteFramebuffers(1, &buf->gl_fbo); + + if (buf->gl_texture) + glDeleteTextures(1, &buf->gl_texture); + + if (buf->egl_image) { + buf->display->egl.destroy_image(buf->display->egl.display, + buf->egl_image); + } + + if (buf->buffer) + wl_buffer_destroy(buf->buffer); + + if (buf->bo) + gbm_bo_destroy(buf->bo); + + for (i = 0; i < buf->plane_count; ++i) { + if (buf->dmabuf_fds[i] >= 0) + close(buf->dmabuf_fds[i]); + } +} + +static void +create_succeeded(void *data, + struct zwp_linux_buffer_params_v1 *params, + struct wl_buffer *new_buffer) +{ + struct buffer *buffer = data; + + buffer->buffer = new_buffer; + /* When not using explicit synchronization listen to wl_buffer.release + * for release notifications, otherwise we are going to use + * zwp_linux_buffer_release_v1. */ + if (!buffer->display->use_explicit_sync) { + wl_buffer_add_listener(buffer->buffer, &buffer_listener, + buffer); + } + + zwp_linux_buffer_params_v1_destroy(params); +} + +static void +create_failed(void *data, struct zwp_linux_buffer_params_v1 *params) +{ + struct buffer *buffer = data; + + buffer->buffer = NULL; + running = 0; + + zwp_linux_buffer_params_v1_destroy(params); + + fprintf(stderr, "Error: zwp_linux_buffer_params.create failed.\n"); +} + +static const struct zwp_linux_buffer_params_v1_listener params_listener = { + create_succeeded, + create_failed +}; + +static bool +create_fbo_for_buffer(struct display *display, struct buffer *buffer) +{ + static const int general_attribs = 3; + static const int plane_attribs = 5; + static const int entries_per_attrib = 2; + EGLint attribs[(general_attribs + plane_attribs * MAX_BUFFER_PLANES) * + entries_per_attrib + 1]; + unsigned int atti = 0; + + attribs[atti++] = EGL_WIDTH; + attribs[atti++] = buffer->width; + attribs[atti++] = EGL_HEIGHT; + attribs[atti++] = buffer->height; + attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; + attribs[atti++] = buffer->format; + +#define ADD_PLANE_ATTRIBS(plane_idx) { \ + attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _FD_EXT; \ + attribs[atti++] = buffer->dmabuf_fds[plane_idx]; \ + attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _OFFSET_EXT; \ + attribs[atti++] = (int) buffer->offsets[plane_idx]; \ + attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _PITCH_EXT; \ + attribs[atti++] = (int) buffer->strides[plane_idx]; \ + if (display->egl.has_dma_buf_import_modifiers) { \ + attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _MODIFIER_LO_EXT; \ + attribs[atti++] = buffer->modifier & 0xFFFFFFFF; \ + attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _MODIFIER_HI_EXT; \ + attribs[atti++] = buffer->modifier >> 32; \ + } \ + } + + if (buffer->plane_count > 0) + ADD_PLANE_ATTRIBS(0); + + if (buffer->plane_count > 1) + ADD_PLANE_ATTRIBS(1); + + if (buffer->plane_count > 2) + ADD_PLANE_ATTRIBS(2); + + if (buffer->plane_count > 3) + ADD_PLANE_ATTRIBS(3); + +#undef ADD_PLANE_ATTRIBS + + attribs[atti] = EGL_NONE; + + assert(atti < ARRAY_LENGTH(attribs)); + + buffer->egl_image = display->egl.create_image(display->egl.display, + EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, + NULL, attribs); + if (buffer->egl_image == EGL_NO_IMAGE_KHR) { + fprintf(stderr, "EGLImageKHR creation failed\n"); + return false; + } + + eglMakeCurrent(display->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, + display->egl.context); + + glGenTextures(1, &buffer->gl_texture); + glBindTexture(GL_TEXTURE_2D, buffer->gl_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_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + display->egl.image_target_texture_2d(GL_TEXTURE_2D, buffer->egl_image); + + glGenFramebuffers(1, &buffer->gl_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, buffer->gl_texture, 0); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + fprintf(stderr, "FBO creation failed\n"); + return false; + } + + return true; +} + + +static int +create_dmabuf_buffer(struct display *display, struct buffer *buffer, + int width, int height, uint32_t opts) +{ + /* Y-Invert the buffer image, since we are going to renderer to the + * buffer through a FBO. */ + static uint32_t flags = ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; + struct zwp_linux_buffer_params_v1 *params; + int i; + + buffer->display = display; + buffer->width = width; + buffer->height = height; + buffer->format = BUFFER_FORMAT; + buffer->release_fence_fd = -1; + +#ifdef HAVE_GBM_MODIFIERS + if (display->modifiers_count > 0) { + buffer->bo = gbm_bo_create_with_modifiers(display->gbm.device, + buffer->width, + buffer->height, + buffer->format, + display->modifiers, + display->modifiers_count); + if (buffer->bo) + buffer->modifier = gbm_bo_get_modifier(buffer->bo); + } +#endif + + if (!buffer->bo) { + buffer->bo = gbm_bo_create(display->gbm.device, + buffer->width, + buffer->height, + buffer->format, + GBM_BO_USE_RENDERING); + buffer->modifier = DRM_FORMAT_MOD_INVALID; + } + + if (!buffer->bo) { + fprintf(stderr, "create_bo failed\n"); + goto error; + } + +#ifdef HAVE_GBM_MODIFIERS + buffer->plane_count = gbm_bo_get_plane_count(buffer->bo); + for (i = 0; i < buffer->plane_count; ++i) { + int ret; + union gbm_bo_handle handle; + + handle = gbm_bo_get_handle_for_plane(buffer->bo, i); + if (handle.s32 == -1) { + fprintf(stderr, "error: failed to get gbm_bo_handle\n"); + goto error; + } + + ret = drmPrimeHandleToFD(display->gbm.drm_fd, handle.u32, 0, + &buffer->dmabuf_fds[i]); + if (ret < 0 || buffer->dmabuf_fds[i] < 0) { + fprintf(stderr, "error: failed to get dmabuf_fd\n"); + goto error; + } + buffer->strides[i] = gbm_bo_get_stride_for_plane(buffer->bo, i); + buffer->offsets[i] = gbm_bo_get_offset(buffer->bo, i); + } +#else + buffer->plane_count = 1; + buffer->strides[0] = gbm_bo_get_stride(buffer->bo); + buffer->dmabuf_fds[0] = gbm_bo_get_fd(buffer->bo); + if (buffer->dmabuf_fds[0] < 0) { + fprintf(stderr, "error: failed to get dmabuf_fd\n"); + goto error; + } +#endif + + params = zwp_linux_dmabuf_v1_create_params(display->dmabuf); + + if ((opts & OPT_DIRECT_DISPLAY) && display->direct_display) { + weston_direct_display_v1_enable(display->direct_display, params); + /* turn off Y_INVERT otherwise linux-dmabuf will reject it and + * we need all dmabuf flags turned off */ + flags &= ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; + + fprintf(stdout, "image is y-inverted as direct-display flag was set, " + "dmabuf y-inverted attribute flag was removed\n"); + } + + for (i = 0; i < buffer->plane_count; ++i) { + zwp_linux_buffer_params_v1_add(params, + buffer->dmabuf_fds[i], + i, + buffer->offsets[i], + buffer->strides[i], + buffer->modifier >> 32, + buffer->modifier & 0xffffffff); + } + + zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, buffer); + if (display->req_dmabuf_immediate) { + buffer->buffer = + zwp_linux_buffer_params_v1_create_immed(params, + buffer->width, + buffer->height, + buffer->format, + flags); + /* When not using explicit synchronization listen to + * wl_buffer.release for release notifications, otherwise we + * are going to use zwp_linux_buffer_release_v1. */ + if (!buffer->display->use_explicit_sync) { + wl_buffer_add_listener(buffer->buffer, + &buffer_listener, + buffer); + } + } + else { + zwp_linux_buffer_params_v1_create(params, + buffer->width, + buffer->height, + buffer->format, + flags); + } + + if (!create_fbo_for_buffer(display, buffer)) + goto error; + + return 0; + +error: + buffer_free(buffer); + return -1; +} + +static void +xdg_surface_handle_configure(void *data, struct xdg_surface *surface, + uint32_t serial) +{ + struct window *window = data; + + xdg_surface_ack_configure(surface, serial); + + if (window->initialized && window->wait_for_configure) + redraw(window, NULL, 0); + window->wait_for_configure = false; +} + +static const struct xdg_surface_listener xdg_surface_listener = { + xdg_surface_handle_configure, +}; + +static void +xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, + int32_t width, int32_t height, + struct wl_array *states) +{ +} + +static void +xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) +{ + running = 0; +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + xdg_toplevel_handle_configure, + xdg_toplevel_handle_close, +}; + +static const char *vert_shader_text = + "uniform float offset;\n" + "attribute vec4 pos;\n" + "attribute vec4 color;\n" + "varying vec4 v_color;\n" + "void main() {\n" + " gl_Position = pos + vec4(offset, offset, 0.0, 0.0);\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 const char *vert_shader_mandelbrot_text = + "uniform float offset;\n" + "attribute vec4 pos;\n" + "varying vec2 v_pos;\n" + "void main() {\n" + " v_pos = pos.xy;\n" + " gl_Position = pos + vec4(offset, offset, 0.0, 0.0);\n" + "}\n"; + + +/* Mandelbrot set shader using the escape time algorithm. */ +static const char *frag_shader_mandelbrot_text = + "precision mediump float;\n" + "varying vec2 v_pos;\n" + "void main() {\n" + " const int max_iteration = 500;\n" + " // Scale and translate position to get a nice mandelbrot drawing for\n" + " // the used v_pos x and y range (-0.5 to 0.5).\n" + " float x0 = 3.0 * v_pos.x - 0.5;\n" + " float y0 = 3.0 * v_pos.y;\n" + " float x = 0.0;\n" + " float y = 0.0;\n" + " int iteration = 0;\n" + " while (x * x + y * y <= 4.0 && iteration < max_iteration) {\n" + " float xtemp = x * x - y * y + x0;\n" + " y = 2.0 * x * y + y0;\n" + " x = xtemp;\n" + " ++iteration;\n" + " }\n" + " float red = iteration == max_iteration ?\n" + " 0.0 : 1.0 - fract(float(iteration) / 20.0);\n" + " gl_FragColor = vec4(red, 0.0, 0.0, 1.0);\n" + "}\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); + return 0; + } + + return shader; +} + +static GLuint +create_and_link_program(GLuint vert, GLuint frag) +{ + GLint status; + GLuint program = glCreateProgram(); + + glAttachShader(program, vert); + glAttachShader(program, frag); + 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); + return 0; + } + + return program; +} + +static bool +window_set_up_gl(struct window *window) +{ + GLuint vert = create_shader( + window->render_mandelbrot ? vert_shader_mandelbrot_text : + vert_shader_text, + GL_VERTEX_SHADER); + GLuint frag = create_shader( + window->render_mandelbrot ? frag_shader_mandelbrot_text : + frag_shader_text, + GL_FRAGMENT_SHADER); + + window->gl.program = create_and_link_program(vert, frag); + + glDeleteShader(vert); + glDeleteShader(frag); + + window->gl.pos = glGetAttribLocation(window->gl.program, "pos"); + window->gl.color = glGetAttribLocation(window->gl.program, "color"); + + glUseProgram(window->gl.program); + + window->gl.offset_uniform = + glGetUniformLocation(window->gl.program, "offset"); + + return window->gl.program != 0; +} + +static void +destroy_window(struct window *window) +{ + int i; + + if (window->gl.program) + glDeleteProgram(window->gl.program); + + if (window->callback) + wl_callback_destroy(window->callback); + + for (i = 0; i < NUM_BUFFERS; i++) { + if (window->buffers[i].buffer) + buffer_free(&window->buffers[i]); + } + + if (window->xdg_toplevel) + xdg_toplevel_destroy(window->xdg_toplevel); + if (window->xdg_surface) + xdg_surface_destroy(window->xdg_surface); + if (window->surface_sync) + zwp_linux_surface_synchronization_v1_destroy(window->surface_sync); + wl_surface_destroy(window->surface); + free(window); +} + +static struct window * +create_window(struct display *display, int width, int height, int opts) +{ + struct window *window; + int i; + int ret; + + window = zalloc(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); + + if (display->wm_base) { + window->xdg_surface = + xdg_wm_base_get_xdg_surface(display->wm_base, + window->surface); + + assert(window->xdg_surface); + + xdg_surface_add_listener(window->xdg_surface, + &xdg_surface_listener, window); + + window->xdg_toplevel = + xdg_surface_get_toplevel(window->xdg_surface); + + assert(window->xdg_toplevel); + + xdg_toplevel_add_listener(window->xdg_toplevel, + &xdg_toplevel_listener, window); + + xdg_toplevel_set_title(window->xdg_toplevel, "simple-dmabuf-egl"); + + window->wait_for_configure = true; + wl_surface_commit(window->surface); + } else if (display->fshell) { + zwp_fullscreen_shell_v1_present_surface(display->fshell, + window->surface, + ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT, + NULL); + } else { + assert(0); + } + + if (display->explicit_sync) { + window->surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + display->explicit_sync, window->surface); + assert(window->surface_sync); + } + + for (i = 0; i < NUM_BUFFERS; ++i) { + int j; + for (j = 0; j < MAX_BUFFER_PLANES; ++j) + window->buffers[i].dmabuf_fds[j] = -1; + + } + + for (i = 0; i < NUM_BUFFERS; ++i) { + ret = create_dmabuf_buffer(display, &window->buffers[i], + width, height, opts); + + if (ret < 0) + goto error; + } + + window->render_mandelbrot = opts & OPT_MANDELBROT; + + if (!window_set_up_gl(window)) + goto error; + + return window; + +error: + if (window) + destroy_window(window); + + return NULL; +} + +static int +create_egl_fence_fd(struct window *window) +{ + struct display *d = window->display; + EGLSyncKHR sync = d->egl.create_sync(d->egl.display, + EGL_SYNC_NATIVE_FENCE_ANDROID, + NULL); + int fd; + + assert(sync != EGL_NO_SYNC_KHR); + /* We need to flush before we can get the fence fd. */ + glFlush(); + fd = d->egl.dup_native_fence_fd(d->egl.display, sync); + assert(fd >= 0); + + d->egl.destroy_sync(d->egl.display, sync); + + return fd; +} + +static struct buffer * +window_next_buffer(struct window *window) +{ + int i; + + for (i = 0; i < NUM_BUFFERS; i++) + if (!window->buffers[i].busy) + return &window->buffers[i]; + + return NULL; +} + +static const struct wl_callback_listener frame_listener; + +/* Renders a square moving from the lower left corner to the + * upper right corner of the window. The square's vertices have + * the following colors: + * + * green +-----+ yellow + * | | + * | | + * red +-----+ blue + */ +static void +render(struct window *window, struct buffer *buffer) +{ + /* Complete a movement iteration in 5000 ms. */ + static const uint64_t iteration_ms = 5000; + static const GLfloat verts[4][2] = { + { -0.5, -0.5 }, + { -0.5, 0.5 }, + { 0.5, -0.5 }, + { 0.5, 0.5 } + }; + static const GLfloat colors[4][3] = { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 }, + { 1, 1, 0 } + }; + GLfloat offset; + struct timeval tv; + uint64_t time_ms; + + gettimeofday(&tv, NULL); + time_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; + + /* Split time_ms in repeating windows of [0, iteration_ms) and map them + * to offsets in the [-0.5, 0.5) range. */ + offset = (time_ms % iteration_ms) / (float) iteration_ms - 0.5; + + /* Direct all GL draws to the buffer through the FBO */ + glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); + + glViewport(0, 0, window->width, window->height); + + glUniform1f(window->gl.offset_uniform, offset); + + glClearColor(0.0,0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); + glVertexAttribPointer(window->gl.color, 3, GL_FLOAT, GL_FALSE, 0, colors); + glEnableVertexAttribArray(window->gl.pos); + glEnableVertexAttribArray(window->gl.color); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(window->gl.pos); + glDisableVertexAttribArray(window->gl.color); +} + +static void +render_mandelbrot(struct window *window, struct buffer *buffer) +{ + /* Complete a movement iteration in 5000 ms. */ + static const uint64_t iteration_ms = 5000; + /* Split drawing in a square grid consisting of grid_side * grid_side + * cells. */ + static const int grid_side = 4; + GLfloat norm_cell_side = 1.0 / grid_side; + int num_cells = grid_side * grid_side; + GLfloat offset; + struct timeval tv; + uint64_t time_ms; + int i; + + gettimeofday(&tv, NULL); + time_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; + + /* Split time_ms in repeating windows of [0, iteration_ms) and map them + * to offsets in the [-0.5, 0.5) range. */ + offset = (time_ms % iteration_ms) / (float) iteration_ms - 0.5; + + /* Direct all GL draws to the buffer through the FBO */ + glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); + + glViewport(0, 0, window->width, window->height); + + glUniform1f(window->gl.offset_uniform, offset); + + glClearColor(0.6, 0.6, 0.6, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + for (i = 0; i < num_cells; ++i) { + /* Calculate the vertex coordinates of the current grid cell. */ + int row = i / grid_side; + int col = i % grid_side; + GLfloat left = -0.5 + norm_cell_side * col; + GLfloat right = left + norm_cell_side; + GLfloat top = 0.5 - norm_cell_side * row; + GLfloat bottom = top - norm_cell_side; + GLfloat verts[4][2] = { + { left, bottom }, + { left, top }, + { right, bottom }, + { right, top } + }; + + /* ... and draw it. */ + glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); + glEnableVertexAttribArray(window->gl.pos); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(window->gl.pos); + } +} + +static void +buffer_fenced_release(void *data, + struct zwp_linux_buffer_release_v1 *release, + int32_t fence) +{ + struct buffer *buffer = data; + + assert(release == buffer->buffer_release); + assert(buffer->release_fence_fd == -1); + + buffer->busy = 0; + buffer->release_fence_fd = fence; + zwp_linux_buffer_release_v1_destroy(buffer->buffer_release); + buffer->buffer_release = NULL; +} + +static void +buffer_immediate_release(void *data, + struct zwp_linux_buffer_release_v1 *release) +{ + struct buffer *buffer = data; + + assert(release == buffer->buffer_release); + assert(buffer->release_fence_fd == -1); + + buffer->busy = 0; + zwp_linux_buffer_release_v1_destroy(buffer->buffer_release); + buffer->buffer_release = NULL; +} + +static const struct zwp_linux_buffer_release_v1_listener buffer_release_listener = { + buffer_fenced_release, + buffer_immediate_release, +}; + +static void +wait_for_buffer_release_fence(struct buffer *buffer) +{ + struct display *d = buffer->display; + EGLint attrib_list[] = { + EGL_SYNC_NATIVE_FENCE_FD_ANDROID, buffer->release_fence_fd, + EGL_NONE, + }; + EGLSyncKHR sync = d->egl.create_sync(d->egl.display, + EGL_SYNC_NATIVE_FENCE_ANDROID, + attrib_list); + int ret; + + assert(sync); + + /* EGLSyncKHR takes ownership of the fence fd. */ + buffer->release_fence_fd = -1; + + if (d->egl.wait_sync) + ret = d->egl.wait_sync(d->egl.display, sync, 0); + else + ret = d->egl.client_wait_sync(d->egl.display, sync, 0, + EGL_FOREVER_KHR); + assert(ret == EGL_TRUE); + + ret = d->egl.destroy_sync(d->egl.display, sync); + assert(ret == EGL_TRUE); +} + +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" : + "All buffers busy at redraw(). Server bug?\n"); + abort(); + } + + if (buffer->release_fence_fd >= 0) + wait_for_buffer_release_fence(buffer); + + if (window->render_mandelbrot) + render_mandelbrot(window, buffer); + else + render(window, buffer); + + if (window->display->use_explicit_sync) { + int fence_fd = create_egl_fence_fd(window); + zwp_linux_surface_synchronization_v1_set_acquire_fence( + window->surface_sync, fence_fd); + close(fence_fd); + + buffer->buffer_release = + zwp_linux_surface_synchronization_v1_get_release(window->surface_sync); + zwp_linux_buffer_release_v1_add_listener( + buffer->buffer_release, &buffer_release_listener, buffer); + } else { + glFinish(); + } + + wl_surface_attach(window->surface, buffer->buffer, 0, 0); + wl_surface_damage(window->surface, 0, 0, window->width, window->height); + + if (callback) + wl_callback_destroy(callback); + + 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 +dmabuf_modifiers(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, + uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) +{ + struct display *d = data; + + switch (format) { + case BUFFER_FORMAT: + ++d->modifiers_count; + d->modifiers = realloc(d->modifiers, + d->modifiers_count * sizeof(*d->modifiers)); + d->modifiers[d->modifiers_count - 1] = + ((uint64_t)modifier_hi << 32) | modifier_lo; + break; + default: + break; + } +} + +static void +dmabuf_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format) +{ + /* XXX: deprecated */ +} + +static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { + dmabuf_format, + dmabuf_modifiers +}; + +static void +xdg_wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial) +{ + xdg_wm_base_pong(wm_base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + xdg_wm_base_ping, +}; + +static void +registry_handle_global(void *data, struct wl_registry *registry, + uint32_t id, const char *interface, uint32_t version) +{ + struct display *d = data; + + if (strcmp(interface, "wl_compositor") == 0) { + d->compositor = + wl_registry_bind(registry, + id, &wl_compositor_interface, 1); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + d->wm_base = wl_registry_bind(registry, + id, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(d->wm_base, &xdg_wm_base_listener, d); + } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { + d->fshell = wl_registry_bind(registry, + id, &zwp_fullscreen_shell_v1_interface, 1); + } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { + if (version < 3) + return; + d->dmabuf = wl_registry_bind(registry, + id, &zwp_linux_dmabuf_v1_interface, 3); + zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, d); + } else if (strcmp(interface, "zwp_linux_explicit_synchronization_v1") == 0) { + d->explicit_sync = wl_registry_bind( + registry, id, + &zwp_linux_explicit_synchronization_v1_interface, 1); + } else if (strcmp(interface, "weston_direct_display_v1") == 0) { + d->direct_display = wl_registry_bind(registry, + id, &weston_direct_display_v1_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 void +destroy_display(struct display *display) +{ + if (display->gbm.device) + gbm_device_destroy(display->gbm.device); + + if (display->gbm.drm_fd >= 0) + close(display->gbm.drm_fd); + + if (display->egl.context != EGL_NO_CONTEXT) + eglDestroyContext(display->egl.display, display->egl.context); + + if (display->egl.display != EGL_NO_DISPLAY) + eglTerminate(display->egl.display); + + free(display->modifiers); + + if (display->dmabuf) + zwp_linux_dmabuf_v1_destroy(display->dmabuf); + + if (display->wm_base) + xdg_wm_base_destroy(display->wm_base); + + if (display->fshell) + zwp_fullscreen_shell_v1_release(display->fshell); + + if (display->compositor) + wl_compositor_destroy(display->compositor); + + if (display->registry) + wl_registry_destroy(display->registry); + + if (display->display) { + wl_display_flush(display->display); + wl_display_disconnect(display->display); + } + + free(display); +} + +static bool +display_set_up_egl(struct display *display) +{ + static const EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + EGLint major, minor, ret, count; + const char *egl_extensions = NULL; + const char *gl_extensions = NULL; + + 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 + }; + + display->egl.display = + weston_platform_get_egl_display(EGL_PLATFORM_GBM_KHR, + display->gbm.device, NULL); + if (display->egl.display == EGL_NO_DISPLAY) { + fprintf(stderr, "Failed to create EGLDisplay\n"); + goto error; + } + + if (eglInitialize(display->egl.display, &major, &minor) == EGL_FALSE) { + fprintf(stderr, "Failed to initialize EGLDisplay\n"); + goto error; + } + + if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) { + fprintf(stderr, "Failed to bind OpenGL ES API\n"); + goto error; + } + + egl_extensions = eglQueryString(display->egl.display, EGL_EXTENSIONS); + assert(egl_extensions != NULL); + + if (!weston_check_egl_extension(egl_extensions, + "EGL_EXT_image_dma_buf_import")) { + fprintf(stderr, "EGL_EXT_image_dma_buf_import not supported\n"); + goto error; + } + + if (!weston_check_egl_extension(egl_extensions, + "EGL_KHR_surfaceless_context")) { + fprintf(stderr, "EGL_KHR_surfaceless_context not supported\n"); + goto error; + } + + if (weston_check_egl_extension(egl_extensions, + "EGL_KHR_no_config_context")) { + display->egl.has_no_config_context = true; + } + + if (display->egl.has_no_config_context) { + display->egl.conf = EGL_NO_CONFIG_KHR; + } else { + fprintf(stderr, + "Warning: EGL_KHR_no_config_context not supported\n"); + ret = eglChooseConfig(display->egl.display, config_attribs, + &display->egl.conf, 1, &count); + assert(ret && count >= 1); + } + + display->egl.context = eglCreateContext(display->egl.display, + display->egl.conf, + EGL_NO_CONTEXT, + context_attribs); + if (display->egl.context == EGL_NO_CONTEXT) { + fprintf(stderr, "Failed to create EGLContext\n"); + goto error; + } + + eglMakeCurrent(display->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, + display->egl.context); + + gl_extensions = (const char *) glGetString(GL_EXTENSIONS); + assert(gl_extensions != NULL); + + if (!weston_check_egl_extension(gl_extensions, + "GL_OES_EGL_image")) { + fprintf(stderr, "GL_OES_EGL_image not supported\n"); + goto error; + } + + if (weston_check_egl_extension(egl_extensions, + "EGL_EXT_image_dma_buf_import_modifiers")) { + display->egl.has_dma_buf_import_modifiers = true; + display->egl.query_dma_buf_modifiers = + (void *) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); + assert(display->egl.query_dma_buf_modifiers); + } + + display->egl.create_image = + (void *) eglGetProcAddress("eglCreateImageKHR"); + assert(display->egl.create_image); + + display->egl.destroy_image = + (void *) eglGetProcAddress("eglDestroyImageKHR"); + assert(display->egl.destroy_image); + + display->egl.image_target_texture_2d = + (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES"); + assert(display->egl.image_target_texture_2d); + + if (weston_check_egl_extension(egl_extensions, "EGL_KHR_fence_sync") && + weston_check_egl_extension(egl_extensions, + "EGL_ANDROID_native_fence_sync")) { + display->egl.create_sync = + (void *) eglGetProcAddress("eglCreateSyncKHR"); + assert(display->egl.create_sync); + + display->egl.destroy_sync = + (void *) eglGetProcAddress("eglDestroySyncKHR"); + assert(display->egl.destroy_sync); + + display->egl.client_wait_sync = + (void *) eglGetProcAddress("eglClientWaitSyncKHR"); + assert(display->egl.client_wait_sync); + + display->egl.dup_native_fence_fd = + (void *) eglGetProcAddress("eglDupNativeFenceFDANDROID"); + assert(display->egl.dup_native_fence_fd); + } + + if (weston_check_egl_extension(egl_extensions, + "EGL_KHR_wait_sync")) { + display->egl.wait_sync = + (void *) eglGetProcAddress("eglWaitSyncKHR"); + assert(display->egl.wait_sync); + } + + return true; + +error: + return false; +} + +static bool +display_update_supported_modifiers_for_egl(struct display *d) +{ + uint64_t *egl_modifiers = NULL; + int num_egl_modifiers = 0; + EGLBoolean ret; + int i; + bool try_modifiers = d->egl.has_dma_buf_import_modifiers; + + if (try_modifiers) { + ret = d->egl.query_dma_buf_modifiers(d->egl.display, + BUFFER_FORMAT, + 0, /* max_modifiers */ + NULL, /* modifiers */ + NULL, /* external_only */ + &num_egl_modifiers); + if (ret == EGL_FALSE) { + fprintf(stderr, "Failed to query num EGL modifiers for format\n"); + goto error; + } + } + + if (!num_egl_modifiers) + try_modifiers = false; + + /* If EGL doesn't support modifiers, don't use them at all. */ + if (!try_modifiers) { + d->modifiers_count = 0; + free(d->modifiers); + d->modifiers = NULL; + return true; + } + + egl_modifiers = zalloc(num_egl_modifiers * sizeof(*egl_modifiers)); + + ret = d->egl.query_dma_buf_modifiers(d->egl.display, + BUFFER_FORMAT, + num_egl_modifiers, + egl_modifiers, + NULL, /* external_only */ + &num_egl_modifiers); + if (ret == EGL_FALSE) { + fprintf(stderr, "Failed to query EGL modifiers for format\n"); + goto error; + } + + /* Poor person's set intersection: d->modifiers INTERSECT + * egl_modifiers. If a modifier is not supported, replace it with + * DRM_FORMAT_MOD_INVALID in the d->modifiers array. + */ + for (i = 0; i < d->modifiers_count; ++i) { + uint64_t mod = d->modifiers[i]; + bool egl_supported = false; + int j; + + for (j = 0; j < num_egl_modifiers; ++j) { + if (egl_modifiers[j] == mod) { + egl_supported = true; + break; + } + } + + if (!egl_supported) + d->modifiers[i] = DRM_FORMAT_MOD_INVALID; + } + + free(egl_modifiers); + + return true; + +error: + free(egl_modifiers); + + return false; +} + +static bool +display_set_up_gbm(struct display *display, char const* drm_render_node) +{ + display->gbm.drm_fd = open(drm_render_node, O_RDWR); + if (display->gbm.drm_fd < 0) { + fprintf(stderr, "Failed to open drm render node %s\n", + drm_render_node); + return false; + } + + display->gbm.device = gbm_create_device(display->gbm.drm_fd); + if (display->gbm.device == NULL) { + fprintf(stderr, "Failed to create gbm device\n"); + return false; + } + + return true; +} + +static struct display * +create_display(char const *drm_render_node, int opts) +{ + struct display *display = NULL; + + display = zalloc(sizeof *display); + if (display == NULL) { + fprintf(stderr, "out of memory\n"); + goto error; + } + + display->gbm.drm_fd = -1; + + display->display = wl_display_connect(NULL); + assert(display->display); + + display->req_dmabuf_immediate = opts & OPT_IMMEDIATE; + + display->registry = wl_display_get_registry(display->display); + wl_registry_add_listener(display->registry, + ®istry_listener, display); + wl_display_roundtrip(display->display); + if (display->dmabuf == NULL) { + fprintf(stderr, "No zwp_linux_dmabuf global\n"); + goto error; + } + + wl_display_roundtrip(display->display); + + if (!display->modifiers_count) { + fprintf(stderr, "format XRGB8888 is not available\n"); + goto error; + } + + /* GBM needs to be initialized before EGL, so that we have a valid + * render node gbm_device to create the EGL display from. */ + if (!display_set_up_gbm(display, drm_render_node)) + goto error; + + if (!display_set_up_egl(display)) + goto error; + + if (!display_update_supported_modifiers_for_egl(display)) + goto error; + + /* We use explicit synchronization only if the user hasn't disabled it, + * the compositor supports it, we can handle fence fds. */ + display->use_explicit_sync = + !(opts & OPT_IMPLICIT_SYNC) && + display->explicit_sync && + display->egl.dup_native_fence_fd; + + if (opts & OPT_IMPLICIT_SYNC) { + fprintf(stderr, "Warning: Not using explicit sync, disabled by user\n"); + } else if (!display->explicit_sync) { + fprintf(stderr, + "Warning: zwp_linux_explicit_synchronization_v1 not supported,\n" + " will not use explicit synchronization\n"); + } else if (!display->egl.dup_native_fence_fd) { + fprintf(stderr, + "Warning: EGL_ANDROID_native_fence_sync not supported,\n" + " will not use explicit synchronization\n"); + } else if (!display->egl.wait_sync) { + fprintf(stderr, + "Warning: EGL_KHR_wait_sync not supported,\n" + " will not use server-side wait\n"); + } + + return display; + +error: + if (display != NULL) + destroy_display(display); + return NULL; +} + +static void +signal_int(int signum) +{ + running = 0; +} + +static void +print_usage_and_exit(void) +{ + printf("usage flags:\n" + "\t'-i,--import-immediate=<>'" + "\n\t\t0 to import dmabuf via roundtrip, " + "\n\t\t1 to enable import without roundtrip\n" + "\t'-d,--drm-render-node=<>'" + "\n\t\tthe full path to the drm render node to use\n" + "\t'-s,--size=<>'" + "\n\t\tthe window size in pixels (default: 256)\n" + "\t'-e,--explicit-sync=<>'" + "\n\t\t0 to disable explicit sync, " + "\n\t\t1 to enable explicit sync (default: 1)\n" + "\t'-m,--mandelbrot'" + "\n\t\trender a mandelbrot set with multiple draw calls\n" + "\t'-g,--direct-display'" + "\n\t\tenables weston-direct-display extension to attempt " + "direct scan-out;\n\t\tnote this will cause the image to be " + "displayed inverted as GL uses a\n\t\tdifferent texture " + "coordinate system\n"); + exit(0); +} + +static int +is_true(const char* c) +{ + if (!strcmp(c, "1")) + return 1; + else if (!strcmp(c, "0")) + return 0; + else + print_usage_and_exit(); + + return 0; +} + +int +main(int argc, char **argv) +{ + struct sigaction sigint; + struct display *display; + struct window *window; + int opts = 0; + char const *drm_render_node = "/dev/dri/renderD128"; + int c, option_index, ret = 0; + int window_size = 256; + + static struct option long_options[] = { + {"import-immediate", required_argument, 0, 'i' }, + {"drm-render-node", required_argument, 0, 'd' }, + {"size", required_argument, 0, 's' }, + {"explicit-sync", required_argument, 0, 'e' }, + {"mandelbrot", no_argument, 0, 'm' }, + {"direct-display", no_argument, 0, 'g' }, + {"help", no_argument , 0, 'h' }, + {0, 0, 0, 0} + }; + + while ((c = getopt_long(argc, argv, "hi:d:s:e:mg", + long_options, &option_index)) != -1) { + switch (c) { + case 'i': + if (is_true(optarg)) + opts |= OPT_IMMEDIATE; + break; + case 'd': + drm_render_node = optarg; + break; + case 's': + window_size = strtol(optarg, NULL, 10); + break; + case 'e': + if (!is_true(optarg)) + opts |= OPT_IMPLICIT_SYNC; + break; + case 'm': + opts |= OPT_MANDELBROT; + break; + case 'g': + opts |= OPT_DIRECT_DISPLAY; + break; + default: + print_usage_and_exit(); + } + } + + display = create_display(drm_render_node, opts); + if (!display) + return 1; + window = create_window(display, window_size, window_size, opts); + if (!window) + return 1; + + sigint.sa_handler = signal_int; + sigemptyset(&sigint.sa_mask); + sigint.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &sigint, NULL); + + /* Here we retrieve the linux-dmabuf objects if executed without immed, + * or error */ + wl_display_roundtrip(display->display); + + if (!running) + return 1; + + window->initialized = true; + + if (!window->wait_for_configure) + redraw(window, NULL, 0); + + while (running && ret != -1) + ret = wl_display_dispatch(display->display); + + fprintf(stderr, "simple-dmabuf-egl exiting\n"); + destroy_window(window); + destroy_display(display); + + return 0; +} diff --git a/clients/simple-dmabuf-v4l.c b/clients/simple-dmabuf-v4l.c new file mode 100644 index 0000000..331f049 --- /dev/null +++ b/clients/simple-dmabuf-v4l.c @@ -0,0 +1,1080 @@ +/* + * Copyright © 2015 Collabora Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include "xdg-shell-client-protocol.h" +#include "fullscreen-shell-unstable-v1-client-protocol.h" +#include "linux-dmabuf-unstable-v1-client-protocol.h" +#include "weston-direct-display-client-protocol.h" + +#include "shared/helpers.h" + +#define CLEAR(x) memset(&(x), 0, sizeof(x)) +#define OPT_FLAG_INVERT (1 << 0) +#define OPT_FLAG_DIRECT_DISPLAY (1 << 1) + +static void +redraw(void *data, struct wl_callback *callback, uint32_t time); + +static int +xioctl(int fh, int request, void *arg) +{ + int r; + + do { + r = ioctl(fh, request, arg); + } while (r == -1 && errno == EINTR); + + return r; +} + +static uint32_t +parse_format(const char fmt[4]) +{ + return fourcc_code(fmt[0], fmt[1], fmt[2], fmt[3]); +} + +static inline const char * +dump_format(uint32_t format, char out[4]) +{ +#if BYTE_ORDER == BIG_ENDIAN + format = __builtin_bswap32(format); +#endif + memcpy(out, &format, 4); + return out; +} + +struct buffer_format { + int width; + int height; + enum v4l2_buf_type type; + uint32_t format; + + unsigned num_planes; + unsigned strides[VIDEO_MAX_PLANES]; +}; + +struct display { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_seat *seat; + struct wl_keyboard *keyboard; + struct xdg_wm_base *wm_base; + struct zwp_fullscreen_shell_v1 *fshell; + struct zwp_linux_dmabuf_v1 *dmabuf; + struct weston_direct_display_v1 *direct_display; + bool requested_format_found; + uint32_t opts; + + int v4l_fd; + struct buffer_format format; + uint32_t drm_format; +}; + +struct buffer { + struct wl_buffer *buffer; + struct display *display; + int busy; + int index; + + int dmabuf_fds[VIDEO_MAX_PLANES]; + int data_offsets[VIDEO_MAX_PLANES]; +}; + +#define NUM_BUFFERS 3 + +struct window { + struct display *display; + struct wl_surface *surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + struct buffer buffers[NUM_BUFFERS]; + struct wl_callback *callback; + bool wait_for_configure; + bool initialized; +}; + +static bool running = true; + +static int +queue(struct display *display, struct buffer *buffer) +{ + struct v4l2_buffer buf; + struct v4l2_plane planes[VIDEO_MAX_PLANES]; + unsigned i; + + CLEAR(buf); + buf.type = display->format.type; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = buffer->index; + + if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + CLEAR(planes); + buf.length = VIDEO_MAX_PLANES; + buf.m.planes = planes; + } + + if (xioctl(display->v4l_fd, VIDIOC_QUERYBUF, &buf) == -1) { + perror("VIDIOC_QUERYBUF"); + return 0; + } + + if (xioctl(display->v4l_fd, VIDIOC_QBUF, &buf) == -1) { + perror("VIDIOC_QBUF"); + return 0; + } + + if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + if (display->format.num_planes != buf.length) { + fprintf(stderr, "Wrong number of planes returned by " + "QUERYBUF\n"); + return 0; + } + + for (i = 0; i < buf.length; ++i) + buffer->data_offsets[i] = buf.m.planes[i].data_offset; + } + + return 1; +} + +static void +buffer_release(void *data, struct wl_buffer *buffer) +{ + struct buffer *mybuf = data; + + mybuf->busy = 0; + + if (!queue(mybuf->display, mybuf)) + running = false; +} + +static const struct wl_buffer_listener buffer_listener = { + buffer_release +}; + +static unsigned int +set_format(struct display *display, uint32_t format) +{ + struct v4l2_format fmt; + char buf[4]; + + CLEAR(fmt); + + fmt.type = display->format.type; + + if (xioctl(display->v4l_fd, VIDIOC_G_FMT, &fmt) == -1) { + perror("VIDIOC_G_FMT"); + return 0; + } + + /* No need to set the format if it already is the one we want */ + if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE && + fmt.fmt.pix.pixelformat == format) + return 1; + if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + fmt.fmt.pix_mp.pixelformat == format) + return fmt.fmt.pix_mp.num_planes; + + if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + fmt.fmt.pix.pixelformat = format; + else + fmt.fmt.pix_mp.pixelformat = format; + + if (xioctl(display->v4l_fd, VIDIOC_S_FMT, &fmt) == -1) { + perror("VIDIOC_S_FMT"); + return 0; + } + + if ((display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE && + fmt.fmt.pix.pixelformat != format) || + (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + fmt.fmt.pix_mp.pixelformat != format)) { + fprintf(stderr, "Failed to set format to %.4s\n", + dump_format(format, buf)); + return 0; + } + + if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return fmt.fmt.pix_mp.num_planes; + + return 1; +} + +static int +v4l_connect(struct display *display, const char *dev_name) +{ + struct v4l2_capability cap; + struct v4l2_requestbuffers req; + struct v4l2_input input; + int index_input = -1; + unsigned int num_planes; + + display->v4l_fd = open(dev_name, O_RDWR); + if (display->v4l_fd < 0) { + perror(dev_name); + return 0; + } + + if (xioctl(display->v4l_fd, VIDIOC_QUERYCAP, &cap) == -1) { + if (errno == EINVAL) { + fprintf(stderr, "%s is no V4L2 device\n", dev_name); + } else { + perror("VIDIOC_QUERYCAP"); + } + return 0; + } + + if (xioctl(display->v4l_fd, VIDIOC_G_INPUT, &index_input) == 0) { + input.index = index_input; + if (xioctl(display->v4l_fd, VIDIOC_ENUMINPUT, &input) == 0) { + if (input.status & V4L2_IN_ST_VFLIP) { + fprintf(stdout, "Found camera sensor y-flipped\n"); + display->opts |= OPT_FLAG_INVERT; + } + } + } + + if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) + display->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + else if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) + display->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + else { + fprintf(stderr, "%s is no video capture device\n", dev_name); + return 0; + } + + if (!(cap.capabilities & V4L2_CAP_STREAMING)) { + fprintf(stderr, "%s does not support dmabuf i/o\n", dev_name); + return 0; + } + + /* Select video input, video standard and tune here */ + + num_planes = set_format(display, display->format.format); + if (num_planes < 1) + return 0; + + CLEAR(req); + + req.type = display->format.type; + req.memory = V4L2_MEMORY_MMAP; + req.count = NUM_BUFFERS * num_planes; + + if (xioctl(display->v4l_fd, VIDIOC_REQBUFS, &req) == -1) { + if (errno == EINVAL) { + fprintf(stderr, "%s does not support dmabuf\n", + dev_name); + } else { + perror("VIDIOC_REQBUFS"); + } + return 0; + } + + if (req.count < NUM_BUFFERS * num_planes) { + fprintf(stderr, "Insufficient buffer memory on %s\n", dev_name); + return 0; + } + + printf("Created %d buffers\n", req.count); + + return 1; +} + +static void +v4l_shutdown(struct display *display) +{ + close(display->v4l_fd); +} + +static void +create_succeeded(void *data, + struct zwp_linux_buffer_params_v1 *params, + struct wl_buffer *new_buffer) +{ + struct buffer *buffer = data; + unsigned i; + + buffer->buffer = new_buffer; + wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); + + zwp_linux_buffer_params_v1_destroy(params); + + for (i = 0; i < buffer->display->format.num_planes; ++i) + close(buffer->dmabuf_fds[i]); +} + +static void +create_failed(void *data, struct zwp_linux_buffer_params_v1 *params) +{ + struct buffer *buffer = data; + unsigned i; + + buffer->buffer = NULL; + + zwp_linux_buffer_params_v1_destroy(params); + + for (i = 0; i < buffer->display->format.num_planes; ++i) + close(buffer->dmabuf_fds[i]); + + running = false; + + fprintf(stderr, "Error: zwp_linux_buffer_params.create failed.\n"); +} + +static const struct zwp_linux_buffer_params_v1_listener params_listener = { + create_succeeded, + create_failed +}; + +static void +create_dmabuf_buffer(struct display *display, struct buffer *buffer) +{ + struct zwp_linux_buffer_params_v1 *params; + uint64_t modifier; + uint32_t flags; + unsigned i; + + modifier = 0; + flags = 0; + + if (display->opts & OPT_FLAG_INVERT) + flags |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; + + params = zwp_linux_dmabuf_v1_create_params(display->dmabuf); + + if ((display->opts & OPT_FLAG_DIRECT_DISPLAY) && display->direct_display) { + weston_direct_display_v1_enable(display->direct_display, params); + + if (display->opts & OPT_FLAG_INVERT) { + flags &= ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; + fprintf(stdout, "dmabuf y-inverted attribute flag was removed" + ", as display-direct flag was set\n"); + } + } + + for (i = 0; i < display->format.num_planes; ++i) + zwp_linux_buffer_params_v1_add(params, + buffer->dmabuf_fds[i], + i, /* plane_idx */ + buffer->data_offsets[i], /* offset */ + display->format.strides[i], + modifier >> 32, + modifier & 0xffffffff); + zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, + buffer); + zwp_linux_buffer_params_v1_create(params, + display->format.width, + display->format.height, + display->drm_format, + flags); +} + +static int +buffer_export(struct display *display, int index, int dmafd[]) +{ + struct v4l2_exportbuffer expbuf; + unsigned i; + + CLEAR(expbuf); + + for (i = 0; i < display->format.num_planes; ++i) { + expbuf.type = display->format.type; + expbuf.index = index; + expbuf.plane = i; + if (xioctl(display->v4l_fd, VIDIOC_EXPBUF, &expbuf) == -1) { + perror("VIDIOC_EXPBUF"); + while (i) + close(dmafd[--i]); + return 0; + } + dmafd[i] = expbuf.fd; + } + + return 1; +} + +static int +queue_initial_buffers(struct display *display, + struct buffer buffers[NUM_BUFFERS]) +{ + struct buffer *buffer; + int index; + + for (index = 0; index < NUM_BUFFERS; ++index) { + buffer = &buffers[index]; + buffer->display = display; + buffer->index = index; + + if (!queue(display, buffer)) { + fprintf(stderr, "Failed to queue buffer\n"); + return 0; + } + + assert(!buffer->buffer); + if (!buffer_export(display, index, buffer->dmabuf_fds)) + return 0; + + create_dmabuf_buffer(display, buffer); + } + + return 1; +} + +static int +dequeue(struct display *display) +{ + struct v4l2_buffer buf; + struct v4l2_plane planes[VIDEO_MAX_PLANES]; + + CLEAR(buf); + buf.type = display->format.type; + buf.memory = V4L2_MEMORY_MMAP; + buf.length = VIDEO_MAX_PLANES; + buf.m.planes = planes; + + /* This ioctl is blocking until a buffer is ready to be displayed */ + if (xioctl(display->v4l_fd, VIDIOC_DQBUF, &buf) == -1) { + perror("VIDIOC_DQBUF"); + return -1; + } + + return buf.index; +} + +static int +fill_buffer_format(struct display *display) +{ + struct v4l2_format fmt; + struct v4l2_pix_format *pix; + struct v4l2_pix_format_mplane *pix_mp; + int i; + char buf[4]; + + CLEAR(fmt); + fmt.type = display->format.type; + + /* Preserve original settings as set by v4l2-ctl for example */ + if (xioctl(display->v4l_fd, VIDIOC_G_FMT, &fmt) == -1) { + perror("VIDIOC_G_FMT"); + return 0; + } + + if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pix = &fmt.fmt.pix; + + printf("%d×%d, %.4s\n", pix->width, pix->height, + dump_format(pix->pixelformat, buf)); + + display->format.num_planes = 1; + display->format.width = pix->width; + display->format.height = pix->height; + display->format.strides[0] = pix->bytesperline; + } else { + pix_mp = &fmt.fmt.pix_mp; + + display->format.num_planes = pix_mp->num_planes; + display->format.width = pix_mp->width; + display->format.height = pix_mp->height; + + for (i = 0; i < pix_mp->num_planes; ++i) + display->format.strides[i] = pix_mp->plane_fmt[i].bytesperline; + + printf("%d×%d, %.4s, %d planes\n", + pix_mp->width, pix_mp->height, + dump_format(pix_mp->pixelformat, buf), + pix_mp->num_planes); + } + + return 1; +} + +static int +v4l_init(struct display *display, struct buffer buffers[NUM_BUFFERS]) { + if (!fill_buffer_format(display)) { + fprintf(stderr, "Failed to fill buffer format\n"); + return 0; + } + + if (!queue_initial_buffers(display, buffers)) { + fprintf(stderr, "Failed to queue initial buffers\n"); + return 0; + } + + return 1; +} + +static int +start_capture(struct display *display) +{ + int type = display->format.type; + + if (xioctl(display->v4l_fd, VIDIOC_STREAMON, &type) == -1) { + perror("VIDIOC_STREAMON"); + return 0; + } + + return 1; +} + +static void +xdg_surface_handle_configure(void *data, struct xdg_surface *surface, + uint32_t serial) +{ + struct window *window = data; + + xdg_surface_ack_configure(surface, serial); + + if (window->initialized && window->wait_for_configure) + redraw(window, NULL, 0); + window->wait_for_configure = false; +} + +static const struct xdg_surface_listener xdg_surface_listener = { + xdg_surface_handle_configure, +}; + +static void +xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, + int32_t width, int32_t height, + struct wl_array *states) +{ +} + +static void +xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) +{ + running = 0; +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + xdg_toplevel_handle_configure, + xdg_toplevel_handle_close, +}; + +static struct window * +create_window(struct display *display) +{ + struct window *window; + + window = zalloc(sizeof *window); + if (!window) + return NULL; + + window->callback = NULL; + window->display = display; + window->surface = wl_compositor_create_surface(display->compositor); + + if (display->wm_base) { + window->xdg_surface = + xdg_wm_base_get_xdg_surface(display->wm_base, + window->surface); + + assert(window->xdg_surface); + + xdg_surface_add_listener(window->xdg_surface, + &xdg_surface_listener, window); + + window->xdg_toplevel = + xdg_surface_get_toplevel(window->xdg_surface); + + assert(window->xdg_toplevel); + + xdg_toplevel_add_listener(window->xdg_toplevel, + &xdg_toplevel_listener, window); + + xdg_toplevel_set_title(window->xdg_toplevel, "simple-dmabuf-v4l"); + + window->wait_for_configure = true; + wl_surface_commit(window->surface); + } else if (display->fshell) { + zwp_fullscreen_shell_v1_present_surface(display->fshell, + window->surface, + ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT, + NULL); + } else { + assert(0); + } + + return window; +} + +static void +destroy_window(struct window *window) +{ + int i; + unsigned j; + + if (window->callback) + wl_callback_destroy(window->callback); + + if (window->xdg_toplevel) + xdg_toplevel_destroy(window->xdg_toplevel); + if (window->xdg_surface) + xdg_surface_destroy(window->xdg_surface); + wl_surface_destroy(window->surface); + + for (i = 0; i < NUM_BUFFERS; i++) { + if (!window->buffers[i].buffer) + continue; + + wl_buffer_destroy(window->buffers[i].buffer); + for (j = 0; j < window->display->format.num_planes; ++j) + close(window->buffers[i].dmabuf_fds[j]); + } + + v4l_shutdown(window->display); + + free(window); +} + +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; + int index, num_busy = 0; + + /* Check for a deadlock situation where we would block forever trying + * to dequeue a buffer while all of them are locked by the compositor. + */ + for (index = 0; index < NUM_BUFFERS; ++index) + if (window->buffers[index].busy) + ++num_busy; + + /* A robust application would just postpone redraw until it has queued + * a buffer. + */ + assert(num_busy < NUM_BUFFERS); + + index = dequeue(window->display); + if (index < 0) { + /* We couldn’t get any buffer out of the camera, exiting. */ + running = false; + return; + } + + buffer = &window->buffers[index]; + assert(!buffer->busy); + + wl_surface_attach(window->surface, buffer->buffer, 0, 0); + wl_surface_damage(window->surface, 0, 0, + window->display->format.width, + window->display->format.height); + + 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 +dmabuf_modifier(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, + uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) +{ + struct display *d = data; + uint64_t modifier = ((uint64_t) modifier_hi << 32 ) | modifier_lo; + + if (format == d->drm_format && modifier == DRM_FORMAT_MOD_LINEAR) + d->requested_format_found = true; +} + + +static void +dmabuf_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, + uint32_t format) +{ + /* deprecated */ +} + +static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { + dmabuf_format, + dmabuf_modifier +}; + +static void +keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, + uint32_t format, int fd, uint32_t size) +{ + /* Just so we don’t leak the keymap fd */ + close(fd); +} + +static void +keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) +{ +} + +static void +keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface) +{ +} + +static void +keyboard_handle_key(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t time, uint32_t key, + uint32_t state) +{ + struct display *d = data; + + if (!d->wm_base) + return; + + if (key == KEY_ESC && state) + running = false; +} + +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_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; + } +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, +}; + +static void +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) +{ + xdg_wm_base_pong(shell, serial); +} + +static const struct xdg_wm_base_listener wm_base_listener = { + xdg_wm_base_ping, +}; + +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_seat") == 0) { + d->seat = wl_registry_bind(registry, + id, &wl_seat_interface, 1); + wl_seat_add_listener(d->seat, &seat_listener, d); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + d->wm_base = wl_registry_bind(registry, + id, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d); + } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { + d->fshell = wl_registry_bind(registry, + id, &zwp_fullscreen_shell_v1_interface, + 1); + } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { + d->dmabuf = wl_registry_bind(registry, + id, &zwp_linux_dmabuf_v1_interface, 3); + zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, + d); + } else if (strcmp(interface, "weston_direct_display_v1") == 0) { + d->direct_display = wl_registry_bind(registry, + id, &weston_direct_display_v1_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 display * +create_display(uint32_t requested_format, uint32_t opt_flags) +{ + struct display *display; + + display = zalloc(sizeof *display); + if (display == NULL) { + fprintf(stderr, "out of memory\n"); + exit(1); + } + display->display = wl_display_connect(NULL); + assert(display->display); + + display->drm_format = requested_format; + + display->registry = wl_display_get_registry(display->display); + wl_registry_add_listener(display->registry, + ®istry_listener, display); + wl_display_roundtrip(display->display); + if (display->dmabuf == NULL) { + fprintf(stderr, "No zwp_linux_dmabuf global\n"); + exit(1); + } + + wl_display_roundtrip(display->display); + + if (!display->requested_format_found) { + fprintf(stderr, "0x%lx requested DRM format not available\n", + (unsigned long) requested_format); + exit(1); + } + + if (opt_flags) + display->opts = opt_flags; + return display; +} + +static void +destroy_display(struct display *display) +{ + if (display->dmabuf) + zwp_linux_dmabuf_v1_destroy(display->dmabuf); + + if (display->wm_base) + xdg_wm_base_destroy(display->wm_base); + + if (display->fshell) + zwp_fullscreen_shell_v1_release(display->fshell); + + if (display->compositor) + wl_compositor_destroy(display->compositor); + + wl_registry_destroy(display->registry); + wl_display_flush(display->display); + wl_display_disconnect(display->display); + free(display); +} + +static void +usage(const char *argv0) +{ + printf("Usage: %s [-v v4l2_device] [-f v4l2_format] [-d drm_format] [-i|--y-invert] [-g|--d-display]\n" + "\n" + "The default V4L2 device is /dev/video0\n" + "\n" + "Both formats are FOURCC values (see http://fourcc.org/)\n" + "V4L2 formats are defined in \n" + "DRM formats are defined in \n" + "The default for both formats is YUYV.\n" + "If the V4L2 and DRM formats differ, the data is simply " + "reinterpreted rather than converted.\n\n" + "Flags:\n" + "- y-invert force the image to be y-flipped;\n note will be " + "automatically added if we detect if the camera sensor is " + "y-flipped\n" + "- d-display skip importing dmabuf-based buffer into the GPU\n " + "and attempt pass the buffer straight to the display controller\n", + argv0); + + printf("\n" + "How to set up Vivid the virtual video driver for testing:\n" + "- build your kernel with CONFIG_VIDEO_VIVID=m\n" + "- add this to a /etc/modprobe.d/ file:\n" + " options vivid node_types=0x1 num_inputs=1 input_types=0x00\n" + "- modprobe vivid and check which device was created,\n" + " here we assume /dev/video0\n" + "- set the pixel format:\n" + " $ v4l2-ctl -d /dev/video0 --set-fmt-video=width=640,pixelformat=XR24\n" + "- optionally could add 'allocators=0x1' to options as to create" + " the buffer in a dmabuf-contiguous way\n" + " (as some display-controllers require it)\n" + "- launch the demo:\n" + " $ %s -v /dev/video0 -f XR24 -d XR24\n" + "You should see a test pattern with color bars, and some text.\n" + "\n" + "More about vivid: https://www.kernel.org/doc/Documentation/video4linux/vivid.txt\n" + "\n", argv0); + + exit(0); +} + +static void +signal_int(int signum) +{ + running = false; +} + +int +main(int argc, char **argv) +{ + struct sigaction sigint; + struct display *display; + struct window *window; + const char *v4l_device = NULL; + uint32_t v4l_format = 0x0; + uint32_t drm_format = 0x0; + uint32_t opts_flags = 0x0; + int c, opt_index, ret = 0; + + static struct option long_options[] = { + { "v4l2-device", required_argument, NULL, 'v' }, + { "v4l2-format", required_argument, NULL, 'f' }, + { "drm-format", required_argument, NULL, 'd' }, + { "y-invert", no_argument, NULL, 'i' }, + { "d-display", no_argument, NULL, 'g' }, + { "help", no_argument, NULL, 'h' }, + { 0, 0, NULL, 0 } + }; + + while ((c = getopt_long(argc, argv, "hiv:d:f:g", long_options, + &opt_index)) != -1) { + switch (c) { + case 'v': + v4l_device = optarg; + break; + case 'f': + v4l_format = parse_format(optarg); + break; + case 'd': + drm_format = parse_format(optarg); + break; + case 'i': + opts_flags |= OPT_FLAG_INVERT; + break; + case 'g': + opts_flags |= OPT_FLAG_DIRECT_DISPLAY; + break; + default: + case 'h': + usage(argv[0]); + break; + } + } + + if (!v4l_device) + v4l_device = "/dev/video0"; + + if (v4l_format == 0x0) + v4l_format = parse_format("YUYV"); + + if (drm_format == 0x0) + drm_format = v4l_format; + + display = create_display(drm_format, opts_flags); + display->format.format = v4l_format; + + window = create_window(display); + if (!window) + return 1; + + if (!v4l_connect(display, v4l_device)) + return 1; + + if (!v4l_init(display, window->buffers)) + return 1; + + sigint.sa_handler = signal_int; + sigemptyset(&sigint.sa_mask); + sigint.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &sigint, NULL); + + /* Here we retrieve the linux-dmabuf objects, or error */ + wl_display_roundtrip(display->display); + + /* In case of error, running will be 0 */ + if (!running) + return 1; + + /* We got all of our buffers, we can start the capture! */ + if (!start_capture(display)) + return 1; + + window->initialized = true; + + if (!window->wait_for_configure) + redraw(window, NULL, 0); + + while (running && ret != -1) + ret = wl_display_dispatch(display->display); + + fprintf(stderr, "simple-dmabuf-v4l exiting\n"); + destroy_window(window); + destroy_display(display); + + return 0; +} diff --git a/clients/simple-egl.c b/clients/simple-egl.c new file mode 100644 index 0000000..cd7408e --- /dev/null +++ b/clients/simple-egl.c @@ -0,0 +1,896 @@ +/* + * Copyright © 2011 Benjamin Franzke + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include + +#include "xdg-shell-client-protocol.h" +#include +#include + +#include "shared/helpers.h" +#include "shared/platform.h" +#include "shared/weston-egl-ext.h" + +struct window; +struct seat; + +struct display { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct xdg_wm_base *wm_base; + 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; + + uint32_t benchmark_time, frames; + struct wl_egl_window *native; + struct wl_surface *surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + EGLSurface egl_surface; + struct wl_callback *callback; + int fullscreen, maximized, opaque, buffer_size, frame_sync, delay; + bool wait_for_configure; +}; + +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, struct window *window) +{ + static const struct { + char *extension, *entrypoint; + } swap_damage_ext_to_entrypoint[] = { + { + .extension = "EGL_EXT_swap_buffers_with_damage", + .entrypoint = "eglSwapBuffersWithDamageEXT", + }, + { + .extension = "EGL_KHR_swap_buffers_with_damage", + .entrypoint = "eglSwapBuffersWithDamageKHR", + }, + }; + + 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, count, i, size; + EGLConfig *configs; + EGLBoolean ret; + + if (window->opaque || window->buffer_size == 16) + config_attribs[9] = 0; + + display->egl.dpy = + weston_platform_get_egl_display(EGL_PLATFORM_WAYLAND_KHR, + display->display, NULL); + 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); + + if (!eglGetConfigs(display->egl.dpy, NULL, 0, &count) || count < 1) + assert(0); + + configs = calloc(count, sizeof *configs); + assert(configs); + + ret = eglChooseConfig(display->egl.dpy, config_attribs, + configs, count, &n); + assert(ret && n >= 1); + + for (i = 0; i < n; i++) { + eglGetConfigAttrib(display->egl.dpy, + configs[i], EGL_BUFFER_SIZE, &size); + if (window->buffer_size == size) { + display->egl.conf = configs[i]; + break; + } + } + free(configs); + if (display->egl.conf == NULL) { + fprintf(stderr, "did not find config with buffer size %d\n", + window->buffer_size); + exit(EXIT_FAILURE); + } + + 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 && + weston_check_egl_extension(extensions, "EGL_EXT_buffer_age")) { + for (i = 0; i < (int) ARRAY_LENGTH(swap_damage_ext_to_entrypoint); i++) { + if (weston_check_egl_extension(extensions, + swap_damage_ext_to_entrypoint[i].extension)) { + /* The EXTPROC is identical to the KHR one */ + display->swap_buffers_with_damage = + (PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC) + eglGetProcAddress(swap_damage_ext_to_entrypoint[i].entrypoint); + break; + } + } + } + + if (display->swap_buffers_with_damage) + printf("has EGL_EXT_buffer_age and %s\n", swap_damage_ext_to_entrypoint[i].extension); + +} + +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_surface_configure(void *data, struct xdg_surface *surface, + uint32_t serial) +{ + struct window *window = data; + + xdg_surface_ack_configure(surface, serial); + + window->wait_for_configure = false; +} + +static const struct xdg_surface_listener xdg_surface_listener = { + handle_surface_configure +}; + +static void +handle_toplevel_configure(void *data, struct xdg_toplevel *toplevel, + int32_t width, int32_t height, + struct wl_array *states) +{ + struct window *window = data; + uint32_t *p; + + window->fullscreen = 0; + window->maximized = 0; + wl_array_for_each(p, states) { + uint32_t state = *p; + switch (state) { + case XDG_TOPLEVEL_STATE_FULLSCREEN: + window->fullscreen = 1; + break; + case XDG_TOPLEVEL_STATE_MAXIMIZED: + window->maximized = 1; + break; + } + } + + if (width > 0 && height > 0) { + if (!window->fullscreen && !window->maximized) { + window->window_size.width = width; + window->window_size.height = height; + } + window->geometry.width = width; + window->geometry.height = height; + } else if (!window->fullscreen && !window->maximized) { + window->geometry = window->window_size; + } + + if (window->native) + wl_egl_window_resize(window->native, + window->geometry.width, + window->geometry.height, 0, 0); +} + +static void +handle_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) +{ + running = 0; +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + handle_toplevel_configure, + handle_toplevel_close, +}; + +static void +create_surface(struct window *window) +{ + struct display *display = window->display; + EGLBoolean ret; + + window->surface = wl_compositor_create_surface(display->compositor); + + window->native = + wl_egl_window_create(window->surface, + window->geometry.width, + window->geometry.height); + window->egl_surface = + weston_platform_create_egl_surface(display->egl.dpy, + display->egl.conf, + window->native, NULL); + + window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, + window->surface); + xdg_surface_add_listener(window->xdg_surface, + &xdg_surface_listener, window); + + window->xdg_toplevel = + xdg_surface_get_toplevel(window->xdg_surface); + xdg_toplevel_add_listener(window->xdg_toplevel, + &xdg_toplevel_listener, window); + + xdg_toplevel_set_title(window->xdg_toplevel, "simple-egl"); + + window->wait_for_configure = true; + wl_surface_commit(window->surface); + + ret = eglMakeCurrent(window->display->egl.dpy, window->egl_surface, + window->egl_surface, window->display->egl.ctx); + assert(ret == EGL_TRUE); + + if (!window->frame_sync) + eglSwapInterval(display->egl.dpy, 0); + + if (!display->wm_base) + return; + + if (window->fullscreen) + xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); +} + +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); + + weston_platform_destroy_egl_surface(window->display->egl.dpy, + window->egl_surface); + wl_egl_window_destroy(window->native); + + if (window->xdg_toplevel) + xdg_toplevel_destroy(window->xdg_toplevel); + if (window->xdg_surface) + xdg_surface_destroy(window->xdg_surface); + wl_surface_destroy(window->surface); + + if (window->callback) + wl_callback_destroy(window->callback); +} + +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 uint32_t speed_div = 5, benchmark_interval = 5; + struct wl_region *region; + EGLint rect[4]; + EGLint buffer_age = 0; + struct timeval tv; + + assert(window->callback == callback); + window->callback = NULL; + + if (callback) + wl_callback_destroy(callback); + + gettimeofday(&tv, NULL); + time = tv.tv_sec * 1000 + tv.tv_usec / 1000; + if (window->frames == 0) + window->benchmark_time = time; + if (time - window->benchmark_time > (benchmark_interval * 1000)) { + printf("%d frames in %d seconds: %f fps\n", + window->frames, + benchmark_interval, + (float) window->frames / benchmark_interval); + window->benchmark_time = time; + window->frames = 0; + } + + 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); + + 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); + + usleep(window->delay); + + 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); + } + + 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); + } + window->frames++; +} + +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); + if (!buffer) + return; + 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 (!display->window->xdg_toplevel) + return; + + if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) + xdg_toplevel_move(display->window->xdg_toplevel, + 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; + + if (!d->wm_base) + return; + + xdg_toplevel_move(d->window->xdg_toplevel, 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) +{ + /* Just so we don’t leak the keymap fd */ + close(fd); +} + +static void +keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) +{ +} + +static void +keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface) +{ +} + +static void +keyboard_handle_key(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t time, uint32_t key, + uint32_t state) +{ + struct display *d = data; + + if (!d->wm_base) + return; + + if (key == KEY_F11 && state) { + if (d->window->fullscreen) + xdg_toplevel_unset_fullscreen(d->window->xdg_toplevel); + else + xdg_toplevel_set_fullscreen(d->window->xdg_toplevel, NULL); + } 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 +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) +{ + xdg_wm_base_pong(shell, serial); +} + +static const struct xdg_wm_base_listener wm_base_listener = { + xdg_wm_base_ping, +}; + +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, + MIN(version, 4)); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + d->wm_base = wl_registry_bind(registry, name, + &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d); + } 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); + if (!d->cursor_theme) { + fprintf(stderr, "unable to load default theme\n"); + return; + } + d->default_cursor = + wl_cursor_theme_get_cursor(d->cursor_theme, "left_ptr"); + if (!d->default_cursor) { + fprintf(stderr, "unable to load default left pointer\n"); + // TODO: abort ? + } + } +} + +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" + " -d \tBuffer swap delay in microseconds\n" + " -f\tRun in fullscreen mode\n" + " -o\tCreate an opaque surface\n" + " -s\tUse a 16 bpp EGL config\n" + " -b\tDon't sync to compositor redraw (eglSwapInterval 0)\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.geometry.width = 250; + window.geometry.height = 250; + window.window_size = window.geometry; + window.buffer_size = 32; + window.frame_sync = 1; + window.delay = 0; + + for (i = 1; i < argc; i++) { + if (strcmp("-d", argv[i]) == 0 && i+1 < argc) + window.delay = atoi(argv[++i]); + else if (strcmp("-f", argv[i]) == 0) + window.fullscreen = 1; + else if (strcmp("-o", argv[i]) == 0) + window.opaque = 1; + else if (strcmp("-s", argv[i]) == 0) + window.buffer_size = 16; + else if (strcmp("-b", argv[i]) == 0) + window.frame_sync = 0; + 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_roundtrip(display.display); + + init_egl(&display, &window); + 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); + + /* The mainloop here is a little subtle. Redrawing will cause + * EGL to read events so we can just call + * wl_display_dispatch_pending() to handle any events that got + * queued up as a side effect. */ + while (running && ret != -1) { + if (window.wait_for_configure) { + ret = wl_display_dispatch(display.display); + } else { + ret = wl_display_dispatch_pending(display.display); + redraw(&window, NULL, 0); + } + } + + 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.wm_base) + xdg_wm_base_destroy(display.wm_base); + + if (display.compositor) + wl_compositor_destroy(display.compositor); + + wl_registry_destroy(display.registry); + wl_display_flush(display.display); + wl_display_disconnect(display.display); + + return 0; +} diff --git a/clients/simple-im.c b/clients/simple-im.c new file mode 100644 index 0000000..a5b834d --- /dev/null +++ b/clients/simple-im.c @@ -0,0 +1,526 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "window.h" +#include "input-method-unstable-v1-client-protocol.h" +#include "shared/helpers.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 zwp_input_method_v1 *input_method; + struct zwp_input_method_context_v1 *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 zwp_input_method_context_v1 *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 zwp_input_method_context_v1 *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 zwp_input_method_context_v1 *context, + uint32_t hint, + uint32_t purpose) +{ +} + +static void +handle_invoke_action(void *data, + struct zwp_input_method_context_v1 *context, + uint32_t button, + uint32_t index) +{ +} + +static void +handle_commit_state(void *data, + struct zwp_input_method_context_v1 *context, + uint32_t serial) +{ + struct simple_im *keyboard = data; + + keyboard->serial = serial; +} + +static void +handle_preferred_language(void *data, + struct zwp_input_method_context_v1 *context, + const char *language) +{ +} + +static const struct zwp_input_method_context_v1_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_keymap_new_from_string(keyboard->xkb_context, + map_str, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS); + + 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_keymap_unref(keyboard->keymap); + return; + } + + keyboard->control_mask = + 1 << xkb_keymap_mod_get_index(keyboard->keymap, "Control"); + keyboard->alt_mask = + 1 << xkb_keymap_mod_get_index(keyboard->keymap, "Mod1"); + keyboard->shift_mask = + 1 << xkb_keymap_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_state_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 zwp_input_method_context_v1 *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_MODS_DEPRESSED | + XKB_STATE_MODS_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; + + zwp_input_method_context_v1_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 zwp_input_method_v1 *input_method, + struct zwp_input_method_context_v1 *context) +{ + struct simple_im *keyboard = data; + + if (keyboard->context) + zwp_input_method_context_v1_destroy(keyboard->context); + + keyboard->compose_state = state_normal; + + keyboard->serial = 0; + + keyboard->context = context; + zwp_input_method_context_v1_add_listener(context, + &input_method_context_listener, + keyboard); + keyboard->keyboard = zwp_input_method_context_v1_grab_keyboard(context); + wl_keyboard_add_listener(keyboard->keyboard, + &input_method_keyboard_listener, + keyboard); +} + +static void +input_method_deactivate(void *data, + struct zwp_input_method_v1 *input_method, + struct zwp_input_method_context_v1 *context) +{ + struct simple_im *keyboard = data; + + if (!keyboard->context) + return; + + zwp_input_method_context_v1_destroy(keyboard->context); + keyboard->context = NULL; +} + +static const struct zwp_input_method_v1_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, "zwp_input_method_v1")) { + keyboard->input_method = + wl_registry_bind(registry, name, + &zwp_input_method_v1_interface, 1); + zwp_input_method_v1_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 zwp_input_method_context_v1 *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 < ARRAY_LENGTH(ignore_keys_on_compose); i++) { + if (sym == ignore_keys_on_compose[i]) { + zwp_input_method_context_v1_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, + ARRAY_LENGTH(compose_seqs), + sizeof(compose_seqs[0]), compare_compose_keys); + + if (cs) { + if (cs->keys[i + 1] == 0) { + zwp_input_method_context_v1_preedit_cursor(keyboard->context, + 0); + zwp_input_method_context_v1_preedit_string(keyboard->context, + keyboard->serial, + "", ""); + zwp_input_method_context_v1_cursor_position(keyboard->context, + 0, 0); + zwp_input_method_context_v1_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); + } + + zwp_input_method_context_v1_preedit_cursor(keyboard->context, + strlen(text)); + zwp_input_method_context_v1_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); + } + zwp_input_method_context_v1_preedit_cursor(keyboard->context, + 0); + zwp_input_method_context_v1_preedit_string(keyboard->context, + keyboard->serial, + "", ""); + zwp_input_method_context_v1_cursor_position(keyboard->context, + 0, 0); + zwp_input_method_context_v1_commit_string(keyboard->context, + keyboard->serial, + text); + keyboard->compose_state = state_normal; + } + return; + } + + if (xkb_keysym_to_utf8(sym, text, sizeof(text)) <= 0) { + zwp_input_method_context_v1_key(context, serial, time, key, state); + return; + } + + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) + return; + + zwp_input_method_context_v1_cursor_position(keyboard->context, + 0, 0); + zwp_input_method_context_v1_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: %s\n", + strerror(errno)); + 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"); + return -1; + } + + simple_im.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + 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: %s\n", strerror(errno)); + return -1; + } + + return 0; +} diff --git a/clients/simple-shm.c b/clients/simple-shm.c new file mode 100644 index 0000000..90892c8 --- /dev/null +++ b/clients/simple-shm.c @@ -0,0 +1,528 @@ +/* + * Copyright © 2011 Benjamin Franzke + * Copyright © 2010 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "shared/os-compatibility.h" +#include +#include "xdg-shell-client-protocol.h" +#include "fullscreen-shell-unstable-v1-client-protocol.h" + +struct display { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct xdg_wm_base *wm_base; + struct zwp_fullscreen_shell_v1 *fshell; + struct wl_shm *shm; + bool has_xrgb; +}; + +struct buffer { + struct wl_buffer *buffer; + void *shm_data; + int busy; +}; + +struct window { + struct display *display; + int width, height; + struct wl_surface *surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + struct buffer buffers[2]; + struct buffer *prev_buffer; + struct wl_callback *callback; + bool wait_for_configure; +}; + +static int running = 1; + +static void +redraw(void *data, struct wl_callback *callback, uint32_t time); + +static void +buffer_release(void *data, struct wl_buffer *buffer) +{ + struct buffer *mybuf = data; + + mybuf->busy = 0; +} + +static const struct wl_buffer_listener buffer_listener = { + buffer_release +}; + +static int +create_shm_buffer(struct display *display, struct buffer *buffer, + int width, int height, uint32_t format) +{ + struct wl_shm_pool *pool; + int fd, size, stride; + void *data; + + stride = width * 4; + size = stride * height; + + fd = os_create_anonymous_file(size); + if (fd < 0) { + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); + return -1; + } + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + close(fd); + return -1; + } + + pool = wl_shm_create_pool(display->shm, fd, size); + buffer->buffer = wl_shm_pool_create_buffer(pool, 0, + width, height, + stride, format); + wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); + wl_shm_pool_destroy(pool); + close(fd); + + buffer->shm_data = data; + + return 0; +} + +static void +handle_xdg_surface_configure(void *data, struct xdg_surface *surface, + uint32_t serial) +{ + struct window *window = data; + + xdg_surface_ack_configure(surface, serial); + + if (window->wait_for_configure) { + redraw(window, NULL, 0); + window->wait_for_configure = false; + } +} + +static const struct xdg_surface_listener xdg_surface_listener = { + handle_xdg_surface_configure, +}; + +static void +handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, + int32_t width, int32_t height, + struct wl_array *state) +{ +} + +static void +handle_xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) +{ + running = 0; +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + handle_xdg_toplevel_configure, + handle_xdg_toplevel_close, +}; + +static struct window * +create_window(struct display *display, int width, int height) +{ + struct window *window; + + window = zalloc(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); + + if (display->wm_base) { + window->xdg_surface = + xdg_wm_base_get_xdg_surface(display->wm_base, + window->surface); + assert(window->xdg_surface); + xdg_surface_add_listener(window->xdg_surface, + &xdg_surface_listener, window); + + window->xdg_toplevel = + xdg_surface_get_toplevel(window->xdg_surface); + assert(window->xdg_toplevel); + xdg_toplevel_add_listener(window->xdg_toplevel, + &xdg_toplevel_listener, window); + + xdg_toplevel_set_title(window->xdg_toplevel, "simple-shm"); + wl_surface_commit(window->surface); + window->wait_for_configure = true; + } else if (display->fshell) { + zwp_fullscreen_shell_v1_present_surface(display->fshell, + window->surface, + ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT, + NULL); + } else { + assert(0); + } + + return window; +} + +static void +destroy_window(struct window *window) +{ + if (window->callback) + wl_callback_destroy(window->callback); + + if (window->buffers[0].buffer) + wl_buffer_destroy(window->buffers[0].buffer); + if (window->buffers[1].buffer) + wl_buffer_destroy(window->buffers[1].buffer); + + if (window->xdg_toplevel) + xdg_toplevel_destroy(window->xdg_toplevel); + if (window->xdg_surface) + xdg_surface_destroy(window->xdg_surface); + wl_surface_destroy(window->surface); + free(window); +} + +static struct buffer * +window_next_buffer(struct window *window) +{ + struct buffer *buffer; + int ret = 0; + + if (!window->buffers[0].busy) + buffer = &window->buffers[0]; + else if (!window->buffers[1].busy) + buffer = &window->buffers[1]; + else + return NULL; + + if (!buffer->buffer) { + ret = create_shm_buffer(window->display, buffer, + window->width, window->height, + WL_SHM_FORMAT_XRGB8888); + + if (ret < 0) + return NULL; + + /* paint the padding */ + memset(buffer->shm_data, 0xff, + window->width * window->height * 4); + } + + return buffer; +} + +static void +paint_pixels(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; + + if (format == WL_SHM_FORMAT_XRGB8888) + d->has_xrgb = true; +} + +struct wl_shm_listener shm_listener = { + shm_format +}; + +static void +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) +{ + xdg_wm_base_pong(shell, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + xdg_wm_base_ping, +}; + +static void +registry_handle_global(void *data, struct wl_registry *registry, + uint32_t id, const char *interface, uint32_t version) +{ + struct display *d = data; + + if (strcmp(interface, "wl_compositor") == 0) { + d->compositor = + wl_registry_bind(registry, + id, &wl_compositor_interface, 1); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + d->wm_base = wl_registry_bind(registry, + id, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(d->wm_base, &xdg_wm_base_listener, d); + } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { + d->fshell = wl_registry_bind(registry, + id, &zwp_fullscreen_shell_v1_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->has_xrgb = false; + display->registry = wl_display_get_registry(display->display); + wl_registry_add_listener(display->registry, + ®istry_listener, display); + wl_display_roundtrip(display->display); + if (display->shm == NULL) { + fprintf(stderr, "No wl_shm global\n"); + exit(1); + } + + wl_display_roundtrip(display->display); + + /* + * Why do we need two roundtrips here? + * + * wl_display_get_registry() sends a request to the server, to which + * the server replies by emitting the wl_registry.global events. + * The first wl_display_roundtrip() sends wl_display.sync. The server + * first processes the wl_display.get_registry which includes sending + * the global events, and then processes the sync. Therefore when the + * sync (roundtrip) returns, we are guaranteed to have received and + * processed all the global events. + * + * While we are inside the first wl_display_roundtrip(), incoming + * events are dispatched, which causes registry_handle_global() to + * be called for each global. One of these globals is wl_shm. + * registry_handle_global() sends wl_registry.bind request for the + * wl_shm global. However, wl_registry.bind request is sent after + * the first wl_display.sync, so the reply to the sync comes before + * the initial events of the wl_shm object. + * + * The initial events that get sent as a reply to binding to wl_shm + * include wl_shm.format. These tell us which pixel formats are + * supported, and we need them before we can create buffers. They + * don't change at runtime, so we receive them as part of init. + * + * When the reply to the first sync comes, the server may or may not + * have sent the initial wl_shm events. Therefore we need the second + * wl_display_roundtrip() call here. + * + * The server processes the wl_registry.bind for wl_shm first, and + * the second wl_display.sync next. During our second call to + * wl_display_roundtrip() the initial wl_shm events are received and + * processed. Finally, when the reply to the second wl_display.sync + * arrives, it guarantees we have processed all wl_shm initial events. + * + * This sequence contains two examples on how wl_display_roundtrip() + * can be used to guarantee, that all reply events to a request + * have been received and processed. This is a general Wayland + * technique. + */ + + if (!display->has_xrgb) { + fprintf(stderr, "WL_SHM_FORMAT_XRGB32 not available\n"); + exit(1); + } + + return display; +} + +static void +destroy_display(struct display *display) +{ + if (display->shm) + wl_shm_destroy(display->shm); + + if (display->wm_base) + xdg_wm_base_destroy(display->wm_base); + + if (display->fshell) + zwp_fullscreen_shell_v1_release(display->fshell); + + if (display->compositor) + wl_compositor_destroy(display->compositor); + + wl_registry_destroy(display->registry); + wl_display_flush(display->display); + wl_display_disconnect(display->display); + free(display); +} + +static void +signal_int(int signum) +{ + running = 0; +} + +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); + + if (!window->wait_for_configure) + 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 0000000..385188c --- /dev/null +++ b/clients/simple-touch.c @@ -0,0 +1,360 @@ +/* + * Copyright © 2011 Benjamin Franzke + * Copyright © 2011 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "shared/helpers.h" +#include "shared/os-compatibility.h" + +struct seat { + struct touch *touch; + struct wl_seat *seat; + struct wl_touch *wl_touch; +}; + +struct touch { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_shell *shell; + struct wl_shm *shm; + 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: %s\n", + size, strerror(errno)); + exit(1); + } + + touch->data = + mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (touch->data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + 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 *wl_seat, + enum wl_seat_capability caps) +{ + struct seat *seat = data; + struct touch *touch = seat->touch; + + if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !seat->wl_touch) { + seat->wl_touch = wl_seat_get_touch(wl_seat); + wl_touch_set_user_data(seat->wl_touch, touch); + wl_touch_add_listener(seat->wl_touch, &touch_listener, touch); + } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && seat->wl_touch) { + wl_touch_destroy(seat->wl_touch); + seat->wl_touch = NULL; + } +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, +}; + +static void +add_seat(struct touch *touch, uint32_t name, uint32_t version) +{ + struct seat *seat; + + seat = malloc(sizeof *seat); + assert(seat); + + seat->touch = touch; + seat->wl_touch = NULL; + seat->seat = wl_registry_bind(touch->registry, name, + &wl_seat_interface, 1); + wl_seat_add_listener(seat->seat, &seat_listener, seat); +} + +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) { + add_seat(touch, name, version); + } +} + +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 0000000..f1b90ec --- /dev/null +++ b/clients/smoke.c @@ -0,0 +1,318 @@ +/* + * Copyright © 2010 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#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; + 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 - 1.5) + px = smoke->width - 1.5; + if (py > smoke->height - 1.5) + py = smoke->height - 1.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 +redraw_handler(struct widget *widget, void *data) +{ + struct smoke *smoke = data; + uint32_t time = widget_get_last_time(smoke->widget); + 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); + + cairo_surface_destroy(surface); + + widget_schedule_redraw(smoke->widget); +} + +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; + + /* Don't 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: %s\n", + strerror(errno)); + 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); + + widget_destroy(smoke.widget); + window_destroy(smoke.window); + display_destroy(d); + + return 0; +} diff --git a/clients/stacking.c b/clients/stacking.c new file mode 100644 index 0000000..5e9084f --- /dev/null +++ b/clients/stacking.c @@ -0,0 +1,309 @@ +/* + * Copyright © 2013 Collabora Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "shared/helpers.h" +#include "window.h" + +struct stacking { + struct display *display; + struct window *root_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); +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); +static void +keyboard_focus_handler(struct window *window, + struct input *device, void *data); +static void +fullscreen_handler(struct window *window, void *data); +static void +redraw_handler(struct widget *widget, void *data); + +/* Iff parent_window is set, the new window will be transient. */ +static struct window * +new_window(struct stacking *stacking, struct window *parent_window) +{ + struct window *new_window; + struct widget *new_widget; + + new_window = window_create(stacking->display); + window_set_parent(new_window, parent_window); + + new_widget = window_frame_create(new_window, new_window); + + window_set_title(new_window, "Stacking Test"); + window_set_key_handler(new_window, key_handler); + window_set_keyboard_focus_handler(new_window, keyboard_focus_handler); + window_set_fullscreen_handler(new_window, fullscreen_handler); + widget_set_button_handler(new_widget, button_handler); + widget_set_redraw_handler(new_widget, redraw_handler); + window_set_user_data(new_window, stacking); + + window_schedule_resize(new_window, 300, 300); + + return new_window; +} + +static void +show_popup_cb(void *data, struct input *input, int index) +{ + /* Ignore the selected menu item. */ +} + +static void +show_popup(struct stacking *stacking, struct input *input, uint32_t time, + struct window *window) +{ + int32_t x, y; + static const char *entries[] = { + "Test Entry", + "Another Test Entry", + }; + + input_get_position(input, &x, &y); + window_show_menu(stacking->display, input, time, window, x, y, + show_popup_cb, entries, ARRAY_LENGTH(entries)); +} + +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 stacking *stacking = data; + + switch (button) { + case BTN_RIGHT: + if (state == WL_POINTER_BUTTON_STATE_PRESSED) + show_popup(stacking, input, time, + widget_get_user_data(widget)); + break; + + case BTN_LEFT: + default: + break; + } +} + +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 stacking *stacking = data; + + if (state != WL_KEYBOARD_KEY_STATE_PRESSED) + return; + + switch (sym) { + case XKB_KEY_f: + fullscreen_handler(window, data); + break; + + case XKB_KEY_m: + window_set_maximized(window, !window_is_maximized(window)); + break; + + case XKB_KEY_n: + /* New top-level window. */ + new_window(stacking, NULL); + break; + + case XKB_KEY_p: + show_popup(stacking, input, time, window); + break; + + case XKB_KEY_q: + exit (0); + break; + + case XKB_KEY_t: + /* New transient window. */ + new_window(stacking, window); + break; + + default: + break; + } +} + +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) +{ + window_set_fullscreen(window, !window_is_fullscreen(window)); +} + +static void +draw_string(cairo_t *cr, + const char *fmt, ...) WL_PRINTF(2, 3); + +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 +set_window_background_colour(cairo_t *cr, struct window *window) +{ + if (window_get_parent(window)) + cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 0.4); + else if (window_is_maximized(window)) + cairo_set_source_rgba(cr, 1.0, 1.0, 0.0, 0.6); + else if (window_is_fullscreen(window)) + cairo_set_source_rgba(cr, 0.0, 1.0, 1.0, 0.6); + else + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct window *window; + struct rectangle allocation; + cairo_t *cr; + + widget_get_allocation(widget, &allocation); + window = widget_get_user_data(widget); + + cr = widget_cairo_create(widget); + cairo_translate(cr, allocation.x, allocation.y); + + /* Draw background. */ + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + set_window_background_colour(cr, window); + cairo_rectangle(cr, 0, 0, allocation.width, allocation.height); + cairo_fill(cr); + + /* Print the instructions. */ + cairo_move_to(cr, 5, 15); + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + + draw_string(cr, + "Window: %p\n" + "Fullscreen? %u\n" + "Maximized? %u\n" + "Transient? %u\n" + "Keys: (f)ullscreen, (m)aximize,\n" + " (n)ew window, (p)opup,\n" + " (q)uit, (t)ransient window\n", + window, window_is_fullscreen(window), + window_is_maximized(window), window_get_parent(window) ? 1 : 0); + + cairo_destroy(cr); +} + +int +main(int argc, char *argv[]) +{ + struct stacking stacking; + + memset(&stacking, 0, sizeof stacking); + + stacking.display = display_create(&argc, argv); + if (stacking.display == NULL) { + fprintf(stderr, "Failed to create display: %s\n", + strerror(errno)); + return -1; + } + + display_set_user_data(stacking.display, &stacking); + + stacking.root_window = new_window(&stacking, NULL); + + display_run(stacking.display); + + window_destroy(stacking.root_window); + display_destroy(stacking.display); + + return 0; +} diff --git a/clients/subsurfaces.c b/clients/subsurfaces.c new file mode 100644 index 0000000..df4372f --- /dev/null +++ b/clients/subsurfaces.c @@ -0,0 +1,811 @@ +/* + * Copyright © 2010 Intel Corporation + * Copyright © 2011 Benjamin Franzke + * Copyright © 2012-2013 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "shared/helpers.h" +#include "shared/xalloc.h" +#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 bool option_no_triangle; +static bool 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 = zalloc(sizeof *egl); + assert(egl); + + egl->dpy = + weston_platform_get_egl_display(EGL_PLATFORM_WAYLAND_KHR, + display, NULL); + 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 = weston_platform_create_egl_surface(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 = xzalloc(sizeof *tri); + + tri->egl = egl; + tri->widget = window_add_subsurface(window, tri, + int_to_mode(option_triangle_mode)); + widget_set_use_cairo(tri->widget, 0); + 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) + weston_platform_destroy_egl_surface(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 = xzalloc(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 = window_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; + + if (parse_options(options, ARRAY_LENGTH(options), &argc, argv) > 1 + || option_help) { + printf(help_text, argv[0]); + return 0; + } + + display = display_create(&argc, argv); + if (display == NULL) { + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); + 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/terminal.c b/clients/terminal.c new file mode 100644 index 0000000..66e2bf5 --- /dev/null +++ b/clients/terminal.c @@ -0,0 +1,3185 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include "shared/helpers.h" +#include "shared/xalloc.h" +#include "window.h" + +static bool option_fullscreen; +static bool option_maximize; +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; + machine->unicode = 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; + char *title; + 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, row, column, max_width; + uint32_t buffer_height; + uint32_t start, end, saved_start, log_size; + wl_fixed_t smooth_scroll; + int saved_row, saved_column; + int scrolling; + 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; + int size_in_title; + + struct wl_data_source *selection; + uint32_t click_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; + int pace_pipe; +}; + +/* 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->buffer_height - 1); + + return (void *) terminal->data + index * terminal->data_pitch; +} + +static struct attr* +terminal_get_attr_row(struct terminal *terminal, int row) +{ + int index; + + index = (row + terminal->start) & (terminal->buffer_height - 1); + + return (void *) terminal->data_attr + index * terminal->attr_pitch; +} + +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; + + terminal->start += d; + 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) +{ + union utf8_char *data; + struct attr *data_attr; + char *tab_ruler; + int data_pitch, attr_pitch; + int i, l, total_rows; + uint32_t d, uheight = height; + struct rectangle allocation; + struct winsize ws; + + if (uheight > terminal->buffer_height) + height = terminal->buffer_height; + + if (terminal->width == width && terminal->height == height) + return; + + if (terminal->data && width <= terminal->max_width) { + d = 0; + if (height < terminal->height && height <= terminal->row) + d = terminal->height - height; + else if (height > terminal->height && + terminal->height - 1 == terminal->row) { + d = terminal->height - height; + if (terminal->log_size < uheight) + d = -terminal->start; + } + + terminal->start += d; + terminal->row -= d; + } else { + terminal->max_width = width; + data_pitch = width * sizeof(union utf8_char); + data = xzalloc(data_pitch * terminal->buffer_height); + attr_pitch = width * sizeof(struct attr); + data_attr = xmalloc(attr_pitch * terminal->buffer_height); + tab_ruler = xzalloc(width); + attr_init(data_attr, terminal->curr_attr, + width * terminal->buffer_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 += i; + 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->data = data; + terminal->data_attr = data_attr; + terminal->tab_ruler = tab_ruler; + terminal->start = 0; + } + + terminal->margin_bottom = + height - (terminal->height - terminal->margin_bottom); + terminal->width = width; + terminal->height = height; + 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 +update_title(struct terminal *terminal) +{ + if (window_is_resizing(terminal->window)) { + char *p; + if (asprintf(&p, "%s — [%dx%d]", terminal->title, terminal->width, terminal->height) > 0) { + window_set_title(terminal->window, p); + free(p); + } + } else { + window_set_title(terminal->window, terminal->title); + } +} + +static void +resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + struct terminal *terminal = data; + int32_t columns, rows, m; + + if (terminal->pace_pipe >= 0) { + close(terminal->pace_pipe); + terminal->pace_pipe = -1; + } + 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); + update_title(terminal); +} + +static void +state_changed_handler(struct window *window, void *data) +{ + struct terminal *terminal = data; + update_title(terminal); +} + +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; + + window_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 = terminal->selection_start_row; 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); + } + + /* skip space glyph (RLE) we use as a placeholder of + the right half of a double-width character, + because RLE is not available in every font. */ + if (p_row[col].ch == 0x200B) + continue; + + 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*/ + free(terminal->title); + terminal->title = strdup(p); + window_set_title(terminal->window, p); + break; + case 7: /* shell cwd as uri */ + break; + case 777: /* Desktop notifications */ + 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 - Insert blank characters */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + terminal_shift_line(terminal, count); + break; + case 'A': /* CUU - Move cursor up rows */ + 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 - Move cursor down rows */ + 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 - Move cursor right by columns */ + 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 - Move cursor left columns */ + 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 - Move cursor down rows, to column 1 */ + 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 - Move cursour up rows, to column 1 */ + 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 - Move cursor to column in current row */ + y = set[0] ? args[0] : 1; + y = y <= 0 ? 1 : y > terminal->width ? terminal->width : y; + + terminal->column = y - 1; + break; + case 'f': /* HVP - Move cursor to */ + case 'H': /* CUP - Move cursor to (origin at 1,1) */ + 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 - Erase display */ + 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) { + /* Clear screen by scrolling contents out */ + terminal_scroll_buffer(terminal, + terminal->end - terminal->start); + } + break; + case 'K': /* EL - Erase line */ + 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 - Insert blank lines */ + 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 - Delete lines */ + 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 - Delete characters on current line */ + 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 - Erase characters on current line */ + 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 - Move cursor to column in current row */ + 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 - Answer "I am a VT102" */ + terminal_write(terminal, "\e[?6c", 5); + break; + case 'd': /* VPA - Move cursor to row, current column */ + x = set[0] ? args[0] : 1; + x = x <= 0 ? 1 : x > terminal->height ? terminal->height : x; + + terminal->row = x - 1; + break; + case 'g': /* TBC - Clear tab stop(s) */ + 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 - Set mode */ + for (i = 0; i < 10 && set[i]; i++) { + handle_term_parameter(terminal, args[i], 1); + } + break; + case 'l': /* RM - Reset mode */ + for (i = 0; i < 10 && set[i]; i++) { + handle_term_parameter(terminal, args[i], 0); + } + break; + case 'm': /* SGR - Set attributes */ + 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 - Status report */ + 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': /* DECSTBM - Set scrolling region */ + 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': /* Save cursor location */ + 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; + } + break; + case 'u': /* Restore cursor location */ + 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 - Reverse linefeed */ + terminal->row -= 1; + if (terminal->row < terminal->margin_top) { + terminal->row = terminal->margin_top; + terminal_scroll(terminal, -1); + } + break; + case 'E': /* NEL - Newline */ + terminal->column = 0; + // fallthrough + case 'D': /* IND - Linefeed */ + terminal->row += 1; + if (terminal->row > terminal->margin_bottom) { + terminal->row = terminal->margin_bottom; + terminal_scroll(terminal, +1); + } + break; + case 'c': /* RIS - Reset*/ + terminal_init(terminal); + break; + case 'H': /* HTS - Set tab stop at current column */ + terminal->tab_ruler[terminal->column] = 1; + break; + case '7': /* DECSC - Save current state */ + 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 - Restore state most recently saved by ESC 7 */ + 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 - Set application keypad mode */ + terminal->key_mode = KM_APPLICATION; + break; + case '>': /* DECPNM - Set numeric keypad mode */ + 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; + + if (terminal->row + terminal->start + 1 > terminal->end) + terminal->end = terminal->row + terminal->start + 1; + if (terminal->end == terminal->buffer_height) + terminal->log_size = terminal->buffer_height; + else if (terminal->log_size < terminal->buffer_height) + terminal->log_size = terminal->end; + + /* 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 void +data_source_dnd_drop_performed(void *data, struct wl_data_source *source) +{ +} + +static void +data_source_dnd_finished(void *data, struct wl_data_source *source) +{ +} + +static void +data_source_action(void *data, + struct wl_data_source *source, uint32_t dnd_action) +{ +} + +static const struct wl_data_source_listener data_source_listener = { + data_source_target, + data_source_send, + data_source_cancelled, + data_source_dnd_drop_performed, + data_source_dnd_finished, + data_source_action +}; + +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(void *data) +{ + struct terminal *terminal = data; + + terminal_destroy(terminal); +} + +static void +terminal_copy(struct terminal *terminal, struct input *input) +{ + terminal->selection = + display_create_data_source(terminal->display); + if (!terminal->selection) + return; + + 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)); +} + +static void +terminal_paste(struct terminal *terminal, struct input *input) +{ + input_receive_selection_data_to_fd(input, + "text/plain;charset=utf-8", + terminal->master); + +} + +static void +terminal_new_instance(struct terminal *terminal) +{ + struct terminal *new_terminal; + + new_terminal = terminal_create(terminal->display); + if (terminal_run(new_terminal, option_shell)) + terminal_destroy(new_terminal); +} + +static int +handle_bound_key(struct terminal *terminal, + struct input *input, uint32_t sym, uint32_t time) +{ + switch (sym) { + case XKB_KEY_X: + /* Cut selection; terminal doesn't do cut, fall + * through to copy. */ + case XKB_KEY_C: + terminal_copy(terminal, input); + return 1; + case XKB_KEY_V: + terminal_paste(terminal, input); + return 1; + case XKB_KEY_N: + terminal_new_instance(terminal); + return 1; + + case XKB_KEY_Up: + if (!terminal->scrolling) + terminal->saved_start = terminal->start; + if (terminal->start == terminal->end - terminal->log_size) + return 1; + + terminal->scrolling = 1; + terminal->start--; + terminal->row++; + terminal->selection_start_row++; + terminal->selection_end_row++; + widget_schedule_redraw(terminal->widget); + return 1; + + case XKB_KEY_Down: + if (!terminal->scrolling) + terminal->saved_start = terminal->start; + + if (terminal->start == terminal->saved_start) + return 1; + + terminal->scrolling = 1; + terminal->start++; + terminal->row--; + terminal->selection_start_row--; + terminal->selection_end_row--; + widget_schedule_redraw(terminal->widget); + 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, d; + 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) { + if (terminal->scrolling) { + d = terminal->saved_start - terminal->start; + terminal->row -= d; + terminal->selection_start_row -= d; + terminal->selection_end_row -= d; + terminal->start = terminal->saved_start; + terminal->scrolling = 0; + widget_schedule_redraw(terminal->widget); + } + + 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 || ch < 0) + 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 = NULL; + + 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 +terminal_minimize(struct terminal *terminal) +{ + window_set_minimized(terminal->window); +} + +static void +menu_func(void *data, struct input *input, int index) +{ + struct window *window = data; + struct terminal *terminal = window_get_user_data(window); + + fprintf(stderr, "picked entry %d\n", index); + + switch (index) { + case 0: + terminal_new_instance(terminal); + break; + case 1: + terminal_copy(terminal, input); + break; + case 2: + terminal_paste(terminal, input); + break; + case 3: + terminal_minimize(terminal); + break; + } +} + +static void +show_menu(struct terminal *terminal, struct input *input, uint32_t time) +{ + int32_t x, y; + static const char *entries[] = { + "Open Terminal", "Copy", "Paste", "Minimize" + }; + + input_get_position(input, &x, &y); + window_show_menu(terminal->display, input, time, terminal->window, + x - 10, y - 10, menu_func, + entries, ARRAY_LENGTH(entries)); +} + +static void +click_handler(struct widget *widget, struct terminal *terminal, + struct input *input, int32_t x, int32_t y, + uint32_t time) +{ + if (time - terminal->click_time < 500) + terminal->click_count++; + else + terminal->click_count = 1; + + terminal->click_time = time; + terminal->dragging = (terminal->click_count - 1) % 3 + SELECT_CHAR; + + terminal->selection_end_x = terminal->selection_start_x = x; + terminal->selection_end_y = terminal->selection_start_y = y; + if (recompute_selection(terminal)) + widget_schedule_redraw(widget); +} + +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; + int32_t x, y; + + switch (button) { + case BTN_LEFT: + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + input_get_position(input, &x, &y); + click_handler(widget, terminal, input, x, y, time); + } else { + terminal->dragging = SELECT_NONE; + } + break; + + case BTN_RIGHT: + if (state == WL_POINTER_BUTTON_STATE_PRESSED) + show_menu(terminal, input, time); + 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; +} + +/* This magnitude is chosen rather arbitrarily. Really, the scrolling + * should happen on a (fractional) pixel basis, not a line basis. */ +#define AXIS_UNITS_PER_LINE 256 + +static void +axis_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t axis, + wl_fixed_t value, + void *data) +{ + struct terminal *terminal = data; + int lines; + + if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) + return; + + terminal->smooth_scroll += value; + lines = terminal->smooth_scroll / AXIS_UNITS_PER_LINE; + terminal->smooth_scroll -= lines * AXIS_UNITS_PER_LINE; + + if (lines > 0) { + if (terminal->scrolling) { + if ((uint32_t)lines > terminal->saved_start - terminal->start) + lines = terminal->saved_start - terminal->start; + } else { + lines = 0; + } + } else if (lines < 0) { + uint32_t neg_lines = -lines; + + if (neg_lines > terminal->log_size + terminal->start - terminal->end) + lines = terminal->end - terminal->log_size - terminal->start; + } + + if (lines) { + if (!terminal->scrolling) + terminal->saved_start = terminal->start; + terminal->scrolling = 1; + + terminal->start += lines; + terminal->row -= lines; + terminal->selection_start_row -= lines; + terminal->selection_end_row -= lines; + + widget_schedule_redraw(widget); + } +} + +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); +} + +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 terminal *terminal = data; + + if (id == 0) + click_handler(widget, terminal, input, x, y, time); +} + +static void +touch_up_handler(struct widget *widget, struct input *input, + uint32_t serial, uint32_t time, int32_t id, void *data) +{ + struct terminal *terminal = data; + + if (id == 0) + terminal->dragging = SELECT_NONE; +} + +static void +touch_motion_handler(struct widget *widget, struct input *input, + uint32_t time, int32_t id, float x, float y, void *data) +{ + struct terminal *terminal = data; + + if (terminal->dragging && + id == 0) { + terminal->selection_end_x = (int)x; + terminal->selection_end_y = (int)y; + + if (recompute_selection(terminal)) + widget_schedule_redraw(widget); + } +} + +#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 = window_frame_create(terminal->window, terminal); + terminal->title = xstrdup("Wayland Terminal"); + window_set_title(terminal->window, terminal->title); + widget_set_transparent(terminal->widget, 0); + + init_state_machine(&terminal->state_machine); + init_color_table(terminal); + + terminal->display = display; + terminal->margin = 5; + terminal->buffer_height = 1024; + terminal->end = 1; + + 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_state_changed_handler(terminal->window, state_changed_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); + widget_set_axis_handler(terminal->widget, axis_handler); + widget_set_touch_up_handler(terminal->widget, touch_up_handler); + widget_set_touch_down_handler(terminal->widget, touch_down_handler); + widget_set_touch_motion_handler(terminal->widget, touch_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->title); + 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; + int pipes[2]; + + /* Awkwardness: There's a sticky race condition here. If + * anything prints after the forkpty() but before the window has + * a size then we'll segfault. So we make a pipe and wait on + * it before actually exec()ing the terminal. The resize + * handler closes it in the parent process and the child continues + * on to launch a shell. + * + * The reason we don't just do terminal_run() after the window + * has a size is that we'd prefer to perform the fork() before + * the process opens a wayland connection. + */ + if (pipe(pipes) == -1) { + fprintf(stderr, "Can't create pipe for pacing.\n"); + exit(EXIT_FAILURE); + } + + pid = forkpty(&master, NULL, NULL, NULL); + if (pid == 0) { + int ret; + + close(pipes[1]); + do { + char tmp; + ret = read(pipes[0], &tmp, 1); + } while (ret == -1 && errno == EINTR); + close(pipes[0]); + setenv("TERM", option_term, 1); + setenv("COLORTERM", option_term, 1); + if (execl(path, path, NULL)) { + printf("exec failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + } else if (pid < 0) { + fprintf(stderr, "failed to fork and create pty (%s).\n", + strerror(errno)); + return -1; + } + + close(pipes[0]); + terminal->master = master; + terminal->pace_pipe = pipes[1]; + fcntl(master, F_SETFL, O_NONBLOCK); + terminal->io_task.run = io_handler; + display_watch_fd(terminal->display, terminal->master, + EPOLLIN | EPOLLHUP, &terminal->io_task); + + if (option_fullscreen) + window_set_fullscreen(terminal->window, 1); + else if (option_maximize) + window_set_maximized(terminal->window, 1); + else + terminal_resize(terminal, 80, 24); + + return 0; +} + +static const struct weston_option terminal_options[] = { + { WESTON_OPTION_BOOLEAN, "fullscreen", 'f', &option_fullscreen }, + { WESTON_OPTION_BOOLEAN, "maximized", 'm', &option_maximize }, + { WESTON_OPTION_STRING, "font", 0, &option_font }, + { WESTON_OPTION_INTEGER, "font-size", 0, &option_font_size }, + { WESTON_OPTION_STRING, "shell", 0, &option_shell }, +}; + +int main(int argc, char *argv[]) +{ + struct display *d; + struct terminal *terminal; + const char *config_file; + struct sigaction sigpipe; + 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_file = weston_config_get_name_from_env(); + config = weston_config_parse(config_file); + 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); + + if (parse_options(terminal_options, + ARRAY_LENGTH(terminal_options), &argc, argv) > 1) { + printf("Usage: %s [OPTIONS]\n" + " --fullscreen or -f\n" + " --maximized or -m\n" + " --font=NAME\n" + " --font-size=SIZE\n" + " --shell=NAME\n", argv[0]); + return 1; + } + + /* Disable SIGPIPE so that paste operations do not crash the program + * when the file descriptor provided to receive data is a pipe or + * socket whose reading end has been closed */ + sigpipe.sa_handler = SIG_IGN; + sigemptyset(&sigpipe.sa_mask); + sigpipe.sa_flags = 0; + sigaction(SIGPIPE, &sigpipe, NULL); + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); + 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/touch-calibrator.c b/clients/touch-calibrator.c new file mode 100644 index 0000000..66208d1 --- /dev/null +++ b/clients/touch-calibrator.c @@ -0,0 +1,970 @@ +/* + * Copyright 2012 Intel Corporation + * Copyright 2017-2018 Collabora, Ltd. + * Copyright 2017-2018 General Electric Company + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "clients/window.h" +#include "shared/helpers.h" +#include + +#include "weston-touch-calibration-client-protocol.h" + +enum exit_code { + CAL_EXIT_SUCCESS = 0, + CAL_EXIT_ERROR = 1, + CAL_EXIT_CANCELLED = 2, +}; + +static int debug_; +static int verbose_; + +#define pr_ver(...) do { \ + if (verbose_) \ + printf(__VA_ARGS__); \ +} while (0) + +#define pr_dbg(...) do { \ + if (debug_) \ + fprintf(stderr, __VA_ARGS__); \ +} while (0) + +static void +pr_err(const char *fmt, ...) WL_PRINTF(1, 2); + +/* Our points for the calibration must be not be on a line */ +static const struct { + float x_ratio, y_ratio; +} test_ratios[] = { + { 0.15, 0.10 }, /* three points for calibration */ + { 0.85, 0.13 }, + { 0.20, 0.80 }, + { 0.70, 0.75 } /* and one for verification */ +}; + +#define NR_SAMPLES ((int)ARRAY_LENGTH(test_ratios)) + +struct point { + double x; + double y; +}; + +struct sample { + int ind; + struct point drawn; /**< drawn point, pixels */ + struct weston_touch_coordinate *pending; + struct point drawn_cal; /**< drawn point, converted */ + bool conv_done; + struct point touched; /**< touch point, normalized */ + bool touch_done; +}; + +struct poly { + struct color { + double r, g, b, a; + } color; + int n_verts; + const struct point *verts; +}; + +/** Touch event handling state machine + * + * Only a complete down->up->frame sequence should be accepted with user + * feedback "right", and anything that deviates from that (invalid_touch, + * cancel, multiple touch-downs) needs to undo the current sample and + * possibly show user feedback "wrong". + * + * \ + * - \: \ + * + * IDLE + * - touch down: sample, -> DOWN + * - touch up: no-op + * - frame: no-op + * - invalid_touch: (undo), wrong, -> WAIT + * - cancel: no-op + * DOWN (first touch down) + * - touch down: undo, wrong, -> WAIT + * - touch up: -> UP + * - frame: no-op + * - invalid_touch: undo, wrong, -> WAIT + * - cancel: undo, -> IDLE + * UP (first touch was down and up) + * - touch down: undo, wrong, -> WAIT + * - touch up: no-op + * - frame: right, touch finish, -> WAIT + * - invalid_touch: undo, wrong, -> WAIT + * - cancel: undo, -> IDLE + * WAIT (show user feedback) + * - touch down: no-op + * - touch up: no-op + * - frame, cancel, timer: if num_tp == 0 && timer_done -> IDLE + * - invalid_touch: no-op + */ +enum touch_state { + STATE_IDLE, + STATE_DOWN, + STATE_UP, + STATE_WAIT +}; + +struct calibrator { + struct sample samples[NR_SAMPLES]; + int current_sample; + + struct display *display; + struct weston_touch_calibration *calibration; + struct weston_touch_calibrator *calibrator; + struct window *window; + struct widget *widget; + + int n_devices_listed; + char *match_name; + char *device_name; + + int width; + int height; + + bool cancelled; + + const struct poly *current_poly; + bool exiting; + + struct toytimer wait_timer; + bool timer_pending; + enum touch_state state; + + int num_tp; /* touch points down count */ +}; + +static struct sample * +current_sample(struct calibrator *cal) +{ + return &cal->samples[cal->current_sample]; +} + +static void +sample_start(struct calibrator *cal, int i) +{ + struct sample *s = &cal->samples[i]; + + assert(i >= 0 && i < NR_SAMPLES); + + s->ind = i; + s->drawn.x = round(test_ratios[i].x_ratio * cal->width); + s->drawn.y = round(test_ratios[i].y_ratio * cal->height); + s->pending = NULL; + s->conv_done = false; + s->touch_done = false; + + cal->current_sample = i; +} + +static struct point +wire_to_point(uint32_t xu, uint32_t yu) +{ + struct point p = { + .x = (double)xu / 0xffffffff, + .y = (double)yu / 0xffffffff + }; + + return p; +} + +static void +sample_touch_down(struct calibrator *cal, uint32_t xu, uint32_t yu) +{ + struct sample *s = current_sample(cal); + + s->touched = wire_to_point(xu, yu); + s->touch_done = true; + + pr_dbg("Down[%d] (%f, %f)\n", s->ind, s->touched.x, s->touched.y); +} + +static void +coordinate_result_handler(void *data, struct weston_touch_coordinate *interface, + uint32_t xu, uint32_t yu) +{ + struct sample *s = data; + + weston_touch_coordinate_destroy(s->pending); + s->pending = NULL; + + s->drawn_cal = wire_to_point(xu, yu); + s->conv_done = true; + + pr_dbg("Conv[%d] (%f, %f)\n", s->ind, s->drawn_cal.x, s->drawn_cal.y); +} + +struct weston_touch_coordinate_listener coordinate_listener = { + coordinate_result_handler +}; + +static void +sample_undo(struct calibrator *cal) +{ + struct sample *s = current_sample(cal); + + pr_dbg("Undo[%d]\n", s->ind); + + s->touch_done = false; + s->conv_done = false; + if (s->pending) { + weston_touch_coordinate_destroy(s->pending); + s->pending = NULL; + } +} + +static void +sample_finish(struct calibrator *cal) +{ + struct sample *s = current_sample(cal); + + pr_dbg("Finish[%d]\n", s->ind); + + assert(!s->pending && !s->conv_done); + + s->pending = weston_touch_calibrator_convert(cal->calibrator, + (int32_t)s->drawn.x, + (int32_t)s->drawn.y); + weston_touch_coordinate_add_listener(s->pending, + &coordinate_listener, s); + + if (cal->current_sample + 1 < NR_SAMPLES) { + sample_start(cal, cal->current_sample + 1); + } else { + pr_dbg("got all touches\n"); + cal->exiting = true; + } +} + +/* + * 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 int +compute_calibration(struct calibrator *cal, float *result) +{ + struct weston_matrix m; + struct weston_matrix inverse; + struct weston_vector x_calib; + struct weston_vector y_calib; + int i; + + assert(NR_SAMPLES >= 3); + + /* + * x1 y1 1 0 + * x2 y2 1 0 + * x3 y3 1 0 + * 0 0 0 1 + */ + weston_matrix_init(&m); + for (i = 0; i < 3; i++) { + m.d[i + 0] = cal->samples[i].touched.x; + m.d[i + 4] = cal->samples[i].touched.y; + m.d[i + 8] = 1.0f; + } + m.type = WESTON_MATRIX_TRANSFORM_OTHER; + + if (weston_matrix_invert(&inverse, &m) < 0) { + pr_err("non-invertible matrix during computation\n"); + return -1; + } + + for (i = 0; i < 3; i++) { + x_calib.f[i] = cal->samples[i].drawn_cal.x; + y_calib.f[i] = cal->samples[i].drawn_cal.y; + } + x_calib.f[3] = 0.0f; + y_calib.f[3] = 0.0f; + + /* Multiples into the vector */ + weston_matrix_transform(&inverse, &x_calib); + weston_matrix_transform(&inverse, &y_calib); + + for (i = 0; i < 3; i++) + result[i] = x_calib.f[i]; + for (i = 0; i < 3; i++) + result[i + 3] = y_calib.f[i]; + + return 0; +} + +static int +verify_calibration(struct calibrator *cal, const float *r) +{ + double thr = 0.1; /* accepted error radius */ + struct point e; /* expected value; error */ + const struct sample *s = &cal->samples[3]; + + /* transform raw touches through the matrix */ + e.x = r[0] * s->touched.x + r[1] * s->touched.y + r[2]; + e.y = r[3] * s->touched.x + r[4] * s->touched.y + r[5]; + + /* compute error */ + e.x -= s->drawn_cal.x; + e.y -= s->drawn_cal.y; + + pr_dbg("calibration test error: %f, %f\n", e.x, e.y); + + if (e.x * e.x + e.y * e.y < thr * thr) + return 0; + + pr_err("Calibration verification failed, too large error.\n"); + return -1; +} + +static void +send_calibration(struct calibrator *cal, float *values) +{ + struct wl_array matrix; + float *f; + int i; + + wl_array_init(&matrix); + for (i = 0; i < 6; i++) { + f = wl_array_add(&matrix, sizeof *f); + *f = values[i]; + } + weston_touch_calibration_save(cal->calibration, + cal->device_name, &matrix); + wl_array_release(&matrix); +} + +static const struct point cross_verts[] = { + { 0.1, 0.2 }, + { 0.2, 0.1 }, + { 0.5, 0.4 }, + { 0.8, 0.1 }, + { 0.9, 0.2 }, + { 0.6, 0.5 }, + { 0.9, 0.8 }, + { 0.8, 0.9 }, + { 0.5, 0.6 }, + { 0.2, 0.9 }, + { 0.1, 0.8 }, + { 0.4, 0.5 }, +}; + +/* a red cross, for "wrong" */ +static const struct poly cross = { + .color = { 0.7, 0.0, 0.0, 1.0 }, + .n_verts = ARRAY_LENGTH(cross_verts), + .verts = cross_verts +}; + +static const struct point check_verts[] = { + { 0.5, 0.7 }, + { 0.8, 0.1 }, + { 0.9, 0.1 }, + { 0.55, 0.8 }, + { 0.45, 0.8 }, + { 0.3, 0.5 }, + { 0.4, 0.5 } +}; + +/* a green check mark, for "right" */ +static const struct poly check = { + .color = { 0.0, 0.7, 0.0, 1.0 }, + .n_verts = ARRAY_LENGTH(check_verts), + .verts = check_verts +}; + +static void +draw_poly(cairo_t *cr, const struct poly *poly) +{ + int i; + + cairo_set_source_rgba(cr, poly->color.r, poly->color.g, + poly->color.b, poly->color.a); + cairo_move_to(cr, poly->verts[0].x, poly->verts[0].y); + for (i = 1; i < poly->n_verts; i++) + cairo_line_to(cr, poly->verts[i].x, poly->verts[i].y); + cairo_close_path(cr); + cairo_fill(cr); +} + +static void +feedback_show(struct calibrator *cal, const struct poly *what) +{ + cal->current_poly = what; + widget_schedule_redraw(cal->widget); + + toytimer_arm_once_usec(&cal->wait_timer, 1000 * 1000); + cal->timer_pending = true; +} + +static void +feedback_hide(struct calibrator *cal) +{ + cal->current_poly = NULL; + widget_schedule_redraw(cal->widget); +} + +static void +try_enter_state_idle(struct calibrator *cal) +{ + if (cal->num_tp != 0) + return; + + if (cal->timer_pending) + return; + + cal->state = STATE_IDLE; + + feedback_hide(cal); + + if (cal->exiting) + display_exit(cal->display); +} + +static void +enter_state_wait(struct calibrator *cal) +{ + assert(cal->timer_pending); + cal->state = STATE_WAIT; +} + +static void +wait_timer_done(struct toytimer *tt) +{ + struct calibrator *cal = container_of(tt, struct calibrator, wait_timer); + + assert(cal->state == STATE_WAIT); + cal->timer_pending = false; + try_enter_state_idle(cal); +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct calibrator *cal = data; + struct sample *s = current_sample(cal); + struct rectangle allocation; + cairo_surface_t *surface; + cairo_t *cr; + + widget_get_allocation(cal->widget, &allocation); + assert(allocation.width == cal->width); + assert(allocation.height == cal->height); + + surface = window_get_surface(cal->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); + + if (!cal->current_poly) { + cairo_translate(cr, s->drawn.x, s->drawn.y); + cairo_set_line_width(cr, 2.0); + cairo_set_source_rgb(cr, 0.7, 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); + } else { + cairo_scale(cr, allocation.width, allocation.height); + draw_poly(cr, cal->current_poly); + } + + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +static struct calibrator * +calibrator_create(struct display *display, const char *match_name) +{ + struct calibrator *cal; + + cal = zalloc(sizeof *cal); + if (!cal) + abort(); + + cal->match_name = match_name ? strdup(match_name) : NULL; + cal->window = window_create_custom(display); + cal->widget = window_add_widget(cal->window, cal); + window_inhibit_redraw(cal->window); + window_set_title(cal->window, "Touchscreen calibrator"); + cal->display = display; + + widget_set_redraw_handler(cal->widget, redraw_handler); + + toytimer_init(&cal->wait_timer, CLOCK_MONOTONIC, + display, wait_timer_done); + + cal->state = STATE_IDLE; + cal->num_tp = 0; + + return cal; +} + +static void +configure_handler(void *data, struct weston_touch_calibrator *interface, + int32_t width, int32_t height) +{ + struct calibrator *cal = data; + + pr_dbg("Configure calibrator window to size %ix%i\n", width, height); + cal->width = width; + cal->height = height; + window_schedule_resize(cal->window, width, height); + window_uninhibit_redraw(cal->window); + + sample_start(cal, 0); + widget_schedule_redraw(cal->widget); +} + +static void +cancel_calibration_handler(void *data, struct weston_touch_calibrator *interface) +{ + struct calibrator *cal = data; + + pr_dbg("calibration cancelled by the display server, quitting.\n"); + cal->cancelled = true; + display_exit(cal->display); +} + +static void +invalid_touch_handler(void *data, struct weston_touch_calibrator *interface) +{ + struct calibrator *cal = data; + + pr_dbg("invalid touch\n"); + + switch (cal->state) { + case STATE_IDLE: + case STATE_DOWN: + case STATE_UP: + sample_undo(cal); + feedback_show(cal, &cross); + enter_state_wait(cal); + break; + case STATE_WAIT: + /* no-op */ + break; + } +} + +static void +down_handler(void *data, struct weston_touch_calibrator *interface, + uint32_t time, int32_t id, uint32_t xu, uint32_t yu) +{ + struct calibrator *cal = data; + + cal->num_tp++; + + switch (cal->state) { + case STATE_IDLE: + sample_touch_down(cal, xu, yu); + cal->state = STATE_DOWN; + break; + case STATE_DOWN: + case STATE_UP: + sample_undo(cal); + feedback_show(cal, &cross); + enter_state_wait(cal); + break; + case STATE_WAIT: + /* no-op */ + break; + } + + if (cal->current_poly) + return; +} + +static void +up_handler(void *data, struct weston_touch_calibrator *interface, + uint32_t time, int32_t id) +{ + struct calibrator *cal = data; + + cal->num_tp--; + if (cal->num_tp < 0) { + pr_dbg("Unmatched touch up.\n"); + cal->num_tp = 0; + } + + switch (cal->state) { + case STATE_DOWN: + cal->state = STATE_UP; + break; + case STATE_IDLE: + case STATE_UP: + case STATE_WAIT: + /* no-op */ + break; + } +} + +static void +motion_handler(void *data, struct weston_touch_calibrator *interface, + uint32_t time, int32_t id, uint32_t xu, uint32_t yu) +{ + /* motion is ignored */ +} + +static void +frame_handler(void *data, struct weston_touch_calibrator *interface) +{ + struct calibrator *cal = data; + + switch (cal->state) { + case STATE_IDLE: + case STATE_DOWN: + /* no-op */ + break; + case STATE_UP: + feedback_show(cal, &check); + sample_finish(cal); + enter_state_wait(cal); + break; + case STATE_WAIT: + try_enter_state_idle(cal); + break; + } +} + +static void +cancel_handler(void *data, struct weston_touch_calibrator *interface) +{ + struct calibrator *cal = data; + + cal->num_tp = 0; + + switch (cal->state) { + case STATE_IDLE: + /* no-op */ + break; + case STATE_DOWN: + case STATE_UP: + sample_undo(cal); + try_enter_state_idle(cal); + break; + case STATE_WAIT: + try_enter_state_idle(cal); + break; + } +} + +struct weston_touch_calibrator_listener calibrator_listener = { + configure_handler, + cancel_calibration_handler, + invalid_touch_handler, + down_handler, + up_handler, + motion_handler, + frame_handler, + cancel_handler +}; + +static void +calibrator_show(struct calibrator *cal) +{ + struct wl_surface *surface = window_get_wl_surface(cal->window); + + cal->calibrator = + weston_touch_calibration_create_calibrator(cal->calibration, + surface, + cal->device_name); + weston_touch_calibrator_add_listener(cal->calibrator, + &calibrator_listener, cal); +} + +static void +calibrator_destroy(struct calibrator *cal) +{ + toytimer_fini(&cal->wait_timer); + if (cal->calibrator) + weston_touch_calibrator_destroy(cal->calibrator); + if (cal->calibration) + weston_touch_calibration_destroy(cal->calibration); + if (cal->widget) + widget_destroy(cal->widget); + if (cal->window) + window_destroy(cal->window); + free(cal->match_name); + free(cal->device_name); + free(cal); +} + +static void +touch_device_handler(void *data, struct weston_touch_calibration *c, + const char *device, const char *head) +{ + struct calibrator *cal = data; + + cal->n_devices_listed++; + + if (!cal->match_name) { + printf("device \"%s\" - head \"%s\"\n", device, head); + return; + } + + if (cal->device_name) + return; + + if (strcmp(cal->match_name, device) == 0 || + strcmp(cal->match_name, head) == 0) + cal->device_name = strdup(device); +} + +struct weston_touch_calibration_listener touch_calibration_listener = { + touch_device_handler +}; + +static void +global_handler(struct display *display, uint32_t name, + const char *interface, uint32_t version, void *data) +{ + struct calibrator *cal = data; + + if (strcmp(interface, "weston_touch_calibration") == 0) { + cal->calibration = display_bind(display, name, + &weston_touch_calibration_interface, 1); + weston_touch_calibration_add_listener(cal->calibration, + &touch_calibration_listener, + cal); + } +} + +static int +calibrator_run(struct calibrator *cal) +{ + struct wl_display *dpy; + struct sample *s; + bool wait; + int i; + int ret; + float result[6]; + + calibrator_show(cal); + display_run(cal->display); + + if (cal->cancelled) + return CAL_EXIT_CANCELLED; + + /* remove the window, no more input events */ + widget_destroy(cal->widget); + cal->widget = NULL; + window_destroy(cal->window); + cal->window = NULL; + + /* wait for all conversions to return */ + dpy = display_get_display(cal->display); + do { + wait = false; + + for (i = 0; i < NR_SAMPLES; i++) + if (cal->samples[i].pending) + wait = true; + + if (wait) { + ret = wl_display_roundtrip(dpy); + if (ret < 0) + return CAL_EXIT_ERROR; + } + } while (wait); + + for (i = 0; i < NR_SAMPLES; i++) { + s = &cal->samples[i]; + if (!s->conv_done || !s->touch_done) + return CAL_EXIT_ERROR; + } + + if (compute_calibration(cal, result) < 0) + return CAL_EXIT_ERROR; + + if (verify_calibration(cal, result) < 0) + return CAL_EXIT_ERROR; + + pr_ver("Calibration values:"); + for (i = 0; i < 6; i++) + pr_ver(" %f", result[i]); + pr_ver("\n"); + + send_calibration(cal, result); + ret = wl_display_roundtrip(dpy); + if (ret < 0) + return CAL_EXIT_ERROR; + + return CAL_EXIT_SUCCESS; +} + +static void +pr_err(const char *fmt, ...) +{ + va_list argp; + + va_start(argp, fmt); + fprintf(stderr, "%s error: ", program_invocation_short_name); + vfprintf(stderr, fmt, argp); + va_end(argp); +} + +static void +help(void) +{ + fprintf(stderr, "Compute a touchscreen calibration matrix for " + "a Wayland compositor by\n" + "having the user touch points on the screen.\n\n"); + fprintf(stderr, "Usage: %s [options...] name\n\n", + program_invocation_short_name); + fprintf(stderr, + "Where 'name' can be a touch device sys path or a head name.\n" + "If 'name' is not given, all devices available for " + "calibration will be listed.\n" + "If 'name' is given, it must be exactly as listed.\n" + "Options:\n" + " --debug Print messages to help debugging.\n" + " -h, --help Display this help message\n" + " -v, --verbose Print list header and calibration result.\n"); +} + +int +main(int argc, char *argv[]) +{ + struct display *display; + struct calibrator *cal; + int c; + char *match_name = NULL; + int exit_code = CAL_EXIT_SUCCESS; + static const struct option opts[] = { + { "help", no_argument, NULL, 'h' }, + { "debug", no_argument, &debug_, 1 }, + { "verbose", no_argument, &verbose_, 1 }, + { 0, 0, NULL, 0 } + }; + + while ((c = getopt_long(argc, argv, "hv", opts, NULL)) != -1) { + switch (c) { + case 'h': + help(); + return CAL_EXIT_SUCCESS; + case 'v': + verbose_ = 1; + break; + case 0: + break; + default: + return CAL_EXIT_ERROR; + } + } + + if (optind < argc) + match_name = argv[optind++]; + + if (optind < argc) { + pr_err("extra arguments given.\n\n"); + help(); + return CAL_EXIT_ERROR; + } + + display = display_create(&argc, argv); + if (!display) + return CAL_EXIT_ERROR; + + cal = calibrator_create(display, match_name); + if (!cal) + return CAL_EXIT_ERROR; + + display_set_user_data(display, cal); + display_set_global_handler(display, global_handler); + + if (!match_name) + pr_ver("Available touch devices:\n"); + + /* Roundtrip to get list of available touch devices, + * first globals, then touch_device events */ + wl_display_roundtrip(display_get_display(display)); + wl_display_roundtrip(display_get_display(display)); + + if (!cal->calibration) { + exit_code = CAL_EXIT_ERROR; + pr_err("the Wayland server does not expose the calibration interface.\n"); + } else if (cal->device_name) { + exit_code = calibrator_run(cal); + } else if (match_name) { + exit_code = CAL_EXIT_ERROR; + pr_err("\"%s\" was not found.\n", match_name); + } else if (cal->n_devices_listed == 0) { + fprintf(stderr, "No devices listed.\n"); + } + + calibrator_destroy(cal); + display_destroy(display); + + return exit_code; +} diff --git a/clients/transformed.c b/clients/transformed.c new file mode 100644 index 0000000..59f44bc --- /dev/null +++ b/clients/transformed.c @@ -0,0 +1,303 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#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; +}; + +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 +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_move(transformed->window, input, display_get_serial(transformed->display)); +} + +static void +usage(int error_code) +{ + fprintf(stderr, "Usage: transformed [OPTIONS]\n\n" + " -w \tSet window width to \n" + " -h \tSet window height to \n" + " --help\tShow this help text\n\n"); + + fprintf(stderr, "This version has been fixed for " + "https://gitlab.freedesktop.org/wayland/weston/issues/99 .\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; + + for (i = 1; i < argc; i++) { + 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: %s\n", + strerror(errno)); + return -1; + } + + transformed.display = d; + transformed.window = window_create(d); + transformed.widget = + window_add_widget(transformed.window, &transformed); + + window_set_title(transformed.window, "Transformed"); + + widget_set_transparent(transformed.widget, 0); + widget_set_default_cursor(transformed.widget, CURSOR_BLANK); + + 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); + widget_destroy(transformed.widget); + window_destroy(transformed.window); + display_destroy(d); + + return 0; +} diff --git a/clients/weston-debug.c b/clients/weston-debug.c new file mode 100644 index 0000000..3060dec --- /dev/null +++ b/clients/weston-debug.c @@ -0,0 +1,501 @@ +/* + * Copyright © 2017 Pekka Paalanen + * Copyright © 2018 Zodiac Inflight Innovations + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "shared/helpers.h" +#include +#include "weston-debug-client-protocol.h" + +struct debug_app { + struct { + bool help; + bool list; + bool bind_all; + char *output; + char *outfd; + } opt; + + int out_fd; + struct wl_display *dpy; + struct wl_registry *registry; + struct weston_debug_v1 *debug_iface; + struct wl_list stream_list; +}; + +struct debug_stream { + struct wl_list link; + bool should_bind; + char *name; + char *desc; + struct weston_debug_stream_v1 *obj; +}; + +/** + * Called either through stream_find in response to an advertisement + * event (see comment on stream_find) when we have all the information, + * or directly from option parsing to make a placeholder entry when the + * stream was explicitly named on the command line to bind to. + */ +static struct debug_stream * +stream_alloc(struct debug_app *app, const char *name, const char *desc) +{ + struct debug_stream *stream; + + stream = zalloc(sizeof *stream); + if (!stream) + return NULL; + + stream->name = strdup(name); + if (!stream->name) { + free(stream); + return NULL; + } + + if (desc) { + stream->desc = strdup(desc); + if (!stream->desc) { + free(stream->name); + free(stream); + return NULL; + } + } + + stream->should_bind = app->opt.bind_all; + wl_list_insert(app->stream_list.prev, &stream->link); + + return stream; +} + +/** + * Called in response to a stream advertisement event. If our stream was + * manually specified on the command line, then it will already have a + * dummy entry in stream_list: we fill in its description and return. + * If there's no entry in the list, we make a new one and return that. + */ +static struct debug_stream * +stream_find(struct debug_app *app, const char *name, const char *desc) +{ + struct debug_stream *stream; + + wl_list_for_each(stream, &app->stream_list, link) { + if (strcmp(stream->name, name) == 0) { + assert(stream->desc == NULL); + if (desc) + stream->desc = strdup(desc); + return stream; + } + } + + return stream_alloc(app, name, desc); +} + +static void +stream_destroy(struct debug_stream *stream) +{ + if (stream->obj) + weston_debug_stream_v1_destroy(stream->obj); + + wl_list_remove(&stream->link); + free(stream->name); + free(stream); +} + +static void +destroy_streams(struct debug_app *app) +{ + struct debug_stream *stream; + struct debug_stream *tmp; + + wl_list_for_each_safe(stream, tmp, &app->stream_list, link) + stream_destroy(stream); +} + +static void +debug_advertise(void *data, struct weston_debug_v1 *debug, const char *name, + const char *desc) +{ + struct debug_app *app = data; + (void) stream_find(app, name, desc); +} + +static const struct weston_debug_v1_listener debug_listener = { + debug_advertise, +}; + +static void +global_handler(void *data, struct wl_registry *registry, uint32_t id, + const char *interface, uint32_t version) +{ + struct debug_app *app = data; + uint32_t myver; + + assert(app->registry == registry); + + if (!strcmp(interface, weston_debug_v1_interface.name)) { + if (app->debug_iface) + return; + + myver = MIN(1, version); + app->debug_iface = + wl_registry_bind(registry, id, + &weston_debug_v1_interface, myver); + weston_debug_v1_add_listener(app->debug_iface, &debug_listener, + app); + } +} + +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 +handle_stream_complete(void *data, struct weston_debug_stream_v1 *obj) +{ + struct debug_stream *stream = data; + + assert(stream->obj == obj); + + stream_destroy(stream); +} + +static void +handle_stream_failure(void *data, struct weston_debug_stream_v1 *obj, + const char *msg) +{ + struct debug_stream *stream = data; + + assert(stream->obj == obj); + + fprintf(stderr, "Debug stream '%s' aborted: %s\n", stream->name, msg); + + stream_destroy(stream); +} + +static const struct weston_debug_stream_v1_listener stream_listener = { + handle_stream_complete, + handle_stream_failure +}; + +static void +start_streams(struct debug_app *app) +{ + struct debug_stream *stream; + + wl_list_for_each(stream, &app->stream_list, link) { + if (!stream->should_bind) + continue; + + stream->obj = weston_debug_v1_subscribe(app->debug_iface, + stream->name, + app->out_fd); + weston_debug_stream_v1_add_listener(stream->obj, + &stream_listener, stream); + } +} + +static void +list_streams(struct debug_app *app) +{ + struct debug_stream *stream; + + fprintf(stderr, "Available debug streams:\n"); + + wl_list_for_each(stream, &app->stream_list, link) { + if (stream->should_bind && stream->desc) { + fprintf(stderr, " %s [will bind]\n", stream->name); + fprintf(stderr, " %s\n", stream->desc); + } else if (stream->should_bind) { + fprintf(stderr, " %s [wanted but not found]\n", + stream->name); + } else { + fprintf(stderr, " %s [will not bind]\n", + stream->name); + fprintf(stderr, " %s\n", stream->desc); + } + } +} + +static int +setup_out_fd(const char *output, const char *outfd) +{ + int fd = -1; + int flags; + + assert(!(output && outfd)); + + if (output) { + if (strcmp(output, "-") == 0) { + fd = STDOUT_FILENO; + } else { + fd = open(output, + O_WRONLY | O_APPEND | O_CREAT, 0644); + if (fd < 0) { + fprintf(stderr, + "Error: opening file '%s' failed: %s\n", + output, strerror(errno)); + } + return fd; + } + } else if (outfd) { + fd = atoi(outfd); + } else { + fd = STDOUT_FILENO; + } + + flags = fcntl(fd, F_GETFL); + if (flags == -1) { + fprintf(stderr, + "Error: cannot use file descriptor %d: %s\n", fd, + strerror(errno)); + return -1; + } + + if ((flags & O_ACCMODE) != O_WRONLY && + (flags & O_ACCMODE) != O_RDWR) { + fprintf(stderr, + "Error: file descriptor %d is not writable.\n", fd); + return -1; + } + + return fd; +} + +static void +print_help(void) +{ + fprintf(stderr, + "Usage: weston-debug [options] [names]\n" + "Where options may be:\n" + " -h, --help\n" + " This help text, and exit with success.\n" + " -l, --list\n" + " Print a list of available debug streams to stderr.\n" + " -a, --all-streams\n" + " Bind to all available streams.\n" + " -o FILE, --output FILE\n" + " Direct output to file named FILE. Use - for stdout.\n" + " Stdout is the default. Mutually exclusive with -f.\n" + " -f FD, --outfd FD\n" + " Direct output to the file descriptor FD.\n" + " Stdout (1) is the default. Mutually exclusive with -o.\n" + "Names are whatever debug stream names the compositor supports.\n" + ); +} + +static int +parse_cmdline(struct debug_app *app, int argc, char **argv) +{ + static const struct option opts[] = { + { "help", no_argument, NULL, 'h' }, + { "list", no_argument, NULL, 'l' }, + { "all-streams", no_argument, NULL, 'a' }, + { "output", required_argument, NULL, 'o' }, + { "outfd", required_argument, NULL, 'f' }, + { 0 } + }; + static const char optstr[] = "hlao:f:"; + int c; + bool failed = false; + + while (1) { + c = getopt_long(argc, argv, optstr, opts, NULL); + if (c == -1) + break; + + switch (c) { + case 'h': + app->opt.help = true; + break; + case 'l': + app->opt.list = true; + break; + case 'a': + app->opt.bind_all = true; + break; + case 'o': + free(app->opt.output); + app->opt.output = strdup(optarg); + break; + case 'f': + free(app->opt.outfd); + app->opt.outfd = strdup(optarg); + break; + case '?': + failed = true; + break; + default: + fprintf(stderr, "huh? getopt => %c (%d)\n", c, c); + failed = true; + } + } + + if (failed) + return -1; + + while (optind < argc) { + struct debug_stream *stream = + stream_alloc(app, argv[optind++], NULL); + stream->should_bind = true; + } + + return 0; +} + +int +main(int argc, char **argv) +{ + struct debug_app app = {}; + int ret = 0; + + wl_list_init(&app.stream_list); + app.out_fd = -1; + + if (parse_cmdline(&app, argc, argv) < 0) { + ret = 1; + goto out_parse; + } + + if (app.opt.help) { + print_help(); + goto out_parse; + } + + if (!app.opt.list && !app.opt.bind_all && + wl_list_empty(&app.stream_list)) { + fprintf(stderr, "Error: no options given.\n\n"); + ret = 1; + print_help(); + goto out_parse; + } + + if (app.opt.bind_all && !wl_list_empty(&app.stream_list)) { + fprintf(stderr, "Error: --all and specific stream names cannot be used simultaneously.\n"); + ret = 1; + goto out_parse; + } + + if (app.opt.output && app.opt.outfd) { + fprintf(stderr, "Error: options --output and --outfd cannot be used simultaneously.\n"); + ret = 1; + goto out_parse; + } + + app.out_fd = setup_out_fd(app.opt.output, app.opt.outfd); + if (app.out_fd < 0) { + ret = 1; + goto out_parse; + } + + app.dpy = wl_display_connect(NULL); + if (!app.dpy) { + fprintf(stderr, "Error: Could not connect to Wayland display: %s\n", + strerror(errno)); + ret = 1; + goto out_parse; + } + + app.registry = wl_display_get_registry(app.dpy); + wl_registry_add_listener(app.registry, ®istry_listener, &app); + wl_display_roundtrip(app.dpy); + + if (!app.debug_iface) { + ret = 1; + fprintf(stderr, + "The Wayland server does not support %s interface.\n", + weston_debug_v1_interface.name); + goto out_conn; + } + + wl_display_roundtrip(app.dpy); /* for weston_debug_v1::advertise */ + + if (app.opt.list) + list_streams(&app); + + start_streams(&app); + + weston_debug_v1_destroy(app.debug_iface); + + while (1) { + struct debug_stream *stream; + bool empty = true; + + wl_list_for_each(stream, &app.stream_list, link) { + if (stream->obj) { + empty = false; + break; + } + } + + if (empty) + break; + + if (wl_display_dispatch(app.dpy) < 0) { + ret = 1; + break; + } + } + +out_conn: + destroy_streams(&app); + + /* Wait for server to close all files */ + wl_display_roundtrip(app.dpy); + + wl_registry_destroy(app.registry); + wl_display_disconnect(app.dpy); + +out_parse: + if (app.out_fd != -1) + close(app.out_fd); + + destroy_streams(&app); + free(app.opt.output); + free(app.opt.outfd); + + return ret; +} diff --git a/clients/weston-info.c b/clients/weston-info.c new file mode 100644 index 0000000..9772527 --- /dev/null +++ b/clients/weston-info.c @@ -0,0 +1,1890 @@ +/* + * Copyright © 2012 Philipp Brüschweiler + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "shared/helpers.h" +#include "shared/os-compatibility.h" +#include "shared/xalloc.h" +#include +#include "presentation-time-client-protocol.h" +#include "linux-dmabuf-unstable-v1-client-protocol.h" +#include "tablet-unstable-v2-client-protocol.h" +#include "xdg-output-unstable-v1-client-protocol.h" + +typedef void (*print_info_t)(void *info); +typedef void (*destroy_info_t)(void *info); + +struct global_info { + struct wl_list link; + + uint32_t id; + uint32_t version; + char *interface; + + print_info_t print; + destroy_info_t destroy; +}; + +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_list global_link; + + struct wl_output *output; + + int32_t version; + + struct { + int32_t x, y; + int32_t scale; + 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 linux_dmabuf_modifier { + struct wl_list link; + + uint32_t format; + uint64_t modifier; +}; + +struct linux_dmabuf_info { + struct global_info global; + struct zwp_linux_dmabuf_v1 *dmabuf; + + struct wl_list modifiers; +}; + +struct seat_info { + struct global_info global; + struct wl_list global_link; + struct wl_seat *seat; + struct weston_info *info; + + struct wl_keyboard *keyboard; + uint32_t capabilities; + char *name; + + int32_t repeat_rate; + int32_t repeat_delay; +}; + +struct tablet_v2_path { + struct wl_list link; + char *path; +}; + +struct tablet_tool_info { + struct wl_list link; + struct zwp_tablet_tool_v2 *tool; + + uint64_t hardware_serial; + uint64_t hardware_id_wacom; + enum zwp_tablet_tool_v2_type type; + + bool has_tilt; + bool has_pressure; + bool has_distance; + bool has_rotation; + bool has_slider; + bool has_wheel; +}; + +struct tablet_pad_group_info { + struct wl_list link; + struct zwp_tablet_pad_group_v2 *group; + + uint32_t modes; + size_t button_count; + int *buttons; + size_t strips; + size_t rings; +}; + +struct tablet_pad_info { + struct wl_list link; + struct zwp_tablet_pad_v2 *pad; + + uint32_t buttons; + struct wl_list paths; + struct wl_list groups; +}; + +struct tablet_info { + struct wl_list link; + struct zwp_tablet_v2 *tablet; + + char *name; + uint32_t vid, pid; + struct wl_list paths; +}; + +struct tablet_seat_info { + struct wl_list link; + + struct zwp_tablet_seat_v2 *seat; + struct seat_info *seat_info; + + struct wl_list tablets; + struct wl_list tools; + struct wl_list pads; +}; + +struct tablet_v2_info { + struct global_info global; + struct zwp_tablet_manager_v2 *manager; + struct weston_info *info; + + struct wl_list seats; +}; + +struct xdg_output_v1_info { + struct wl_list link; + + struct zxdg_output_v1 *xdg_output; + struct output_info *output; + + struct { + int32_t x, y; + int32_t width, height; + } logical; + + char *name, *description; +}; + +struct xdg_output_manager_v1_info { + struct global_info global; + struct zxdg_output_manager_v1 *manager; + struct weston_info *info; + + struct wl_list outputs; +}; + +struct presentation_info { + struct global_info global; + struct wp_presentation *presentation; + + clockid_t clk_id; +}; + +struct weston_info { + struct wl_display *display; + struct wl_registry *registry; + + struct wl_list infos; + bool roundtrip_needed; + + /* required for tablet-unstable-v2 */ + struct wl_list seats; + struct tablet_v2_info *tablet_info; + + /* required for xdg-output-unstable-v1 */ + struct wl_list outputs; + struct xdg_output_manager_v1_info *xdg_output_manager_v1_info; +}; + +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 = xstrdup(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,", + output->geometry.x, output->geometry.y); + if (output->version >= 2) + printf(" scale: %d,", output->geometry.scale); + printf("\n"); + + 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_transform: %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: %.3f 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 char +bits2graph(uint32_t value, unsigned bitoffset) +{ + int c = (value >> bitoffset) & 0xff; + + if (isgraph(c) || isspace(c)) + return c; + + return '?'; +} + +static void +fourcc2str(uint32_t format, char *str, int len) +{ + int i; + + assert(len >= 5); + + for (i = 0; i < 4; i++) + str[i] = bits2graph(format, i * 8); + str[i] = '\0'; +} + +static void +print_shm_info(void *data) +{ + char str[5]; + struct shm_info *shm = data; + struct shm_format *format; + + print_global_info(data); + + printf("\tformats:"); + + wl_list_for_each(format, &shm->formats, link) + switch (format->format) { + case WL_SHM_FORMAT_ARGB8888: + printf(" ARGB8888"); + break; + case WL_SHM_FORMAT_XRGB8888: + printf(" XRGB8888"); + break; + case WL_SHM_FORMAT_RGB565: + printf(" RGB565"); + break; + default: + fourcc2str(format->format, str, sizeof(str)); + printf(" '%s'(0x%08x)", str, format->format); + break; + } + + printf("\n"); +} + +static void +print_linux_dmabuf_info(void *data) +{ + char str[5]; + struct linux_dmabuf_info *dmabuf = data; + struct linux_dmabuf_modifier *modifier; + + print_global_info(data); + + printf("\tformats:"); + + wl_list_for_each(modifier, &dmabuf->modifiers, link) { + fourcc2str(modifier->format, str, sizeof(str)); + printf("\n\t'%s'(0x%08x), modifier: 0x%016"PRIx64, str, modifier->format, modifier->modifier); + } + + 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"); + + if (seat->repeat_rate > 0) + printf("\tkeyboard repeat rate: %d\n", seat->repeat_rate); + if (seat->repeat_delay > 0) + printf("\tkeyboard repeat delay: %d\n", seat->repeat_delay); +} + +static void +keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, + uint32_t format, int fd, uint32_t size) +{ + /* Just so we don’t leak the keymap fd */ + close(fd); +} + +static void +keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) +{ +} + +static void +keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface) +{ +} + +static void +keyboard_handle_key(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t time, uint32_t key, + uint32_t state) +{ +} + +static void +keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) +{ +} + +static void +keyboard_handle_repeat_info(void *data, struct wl_keyboard *keyboard, + int32_t rate, int32_t delay) +{ + struct seat_info *seat = data; + + seat->repeat_rate = rate; + seat->repeat_delay = delay; +} + +static const struct wl_keyboard_listener keyboard_listener = { + keyboard_handle_keymap, + keyboard_handle_enter, + keyboard_handle_leave, + keyboard_handle_key, + keyboard_handle_modifiers, + keyboard_handle_repeat_info, +}; + +static void +seat_handle_capabilities(void *data, struct wl_seat *wl_seat, + enum wl_seat_capability caps) +{ + struct seat_info *seat = data; + + seat->capabilities = caps; + + /* we want listen for repeat_info from wl_keyboard, but only + * do so if the seat info is >= 4 and if we actually have a + * keyboard */ + if (seat->global.version < 4) + return; + + if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { + seat->keyboard = wl_seat_get_keyboard(seat->seat); + wl_keyboard_add_listener(seat->keyboard, &keyboard_listener, + seat); + + seat->info->roundtrip_needed = true; + } +} + +static void +seat_handle_name(void *data, struct wl_seat *wl_seat, + const char *name) +{ + struct seat_info *seat = data; + seat->name = xstrdup(name); +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, + seat_handle_name, +}; + +static void +destroy_seat_info(void *data) +{ + struct seat_info *seat = data; + + wl_seat_destroy(seat->seat); + + if (seat->name != NULL) + free(seat->name); + + if (seat->keyboard) + wl_keyboard_destroy(seat->keyboard); + + wl_list_remove(&seat->global_link); +} + +static const char * +tablet_tool_type_to_str(enum zwp_tablet_tool_v2_type type) +{ + switch (type) { + case ZWP_TABLET_TOOL_V2_TYPE_PEN: + return "pen"; + case ZWP_TABLET_TOOL_V2_TYPE_ERASER: + return "eraser"; + case ZWP_TABLET_TOOL_V2_TYPE_BRUSH: + return "brush"; + case ZWP_TABLET_TOOL_V2_TYPE_PENCIL: + return "pencil"; + case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH: + return "airbrush"; + case ZWP_TABLET_TOOL_V2_TYPE_FINGER: + return "finger"; + case ZWP_TABLET_TOOL_V2_TYPE_MOUSE: + return "mouse"; + case ZWP_TABLET_TOOL_V2_TYPE_LENS: + return "lens"; + } + + return "Unknown type"; +} + +static void +print_tablet_tool_info(const struct tablet_tool_info *info) +{ + printf("\t\ttablet_tool: %s\n", tablet_tool_type_to_str(info->type)); + if (info->hardware_serial) { + printf("\t\t\thardware serial: %" PRIx64 "\n", info->hardware_serial); + } + if (info->hardware_id_wacom) { + printf("\t\t\thardware wacom: %" PRIx64 "\n", info->hardware_id_wacom); + } + + printf("\t\t\tcapabilities:"); + + if (info->has_tilt) { + printf(" tilt"); + } + if (info->has_pressure) { + printf(" pressure"); + } + if (info->has_distance) { + printf(" distance"); + } + if (info->has_rotation) { + printf(" rotation"); + } + if (info->has_slider) { + printf(" slider"); + } + if (info->has_wheel) { + printf(" wheel"); + } + printf("\n"); +} + +static void +destroy_tablet_tool_info(struct tablet_tool_info *info) +{ + wl_list_remove(&info->link); + zwp_tablet_tool_v2_destroy(info->tool); + free(info); +} + +static void +print_tablet_pad_group_info(const struct tablet_pad_group_info *info) +{ + size_t i; + printf("\t\t\tgroup:\n"); + printf("\t\t\t\tmodes: %u\n", info->modes); + printf("\t\t\t\tstrips: %zu\n", info->strips); + printf("\t\t\t\trings: %zu\n", info->rings); + printf("\t\t\t\tbuttons:"); + + for (i = 0; i < info->button_count; ++i) { + printf(" %d", info->buttons[i]); + } + + printf("\n"); +} + +static void +destroy_tablet_pad_group_info(struct tablet_pad_group_info *info) +{ + wl_list_remove(&info->link); + zwp_tablet_pad_group_v2_destroy(info->group); + + if (info->buttons) { + free(info->buttons); + } + free(info); +} + +static void +print_tablet_pad_info(const struct tablet_pad_info *info) +{ + const struct tablet_v2_path *path; + const struct tablet_pad_group_info *group; + + printf("\t\tpad:\n"); + printf("\t\t\tbuttons: %u\n", info->buttons); + + wl_list_for_each(path, &info->paths, link) { + printf("\t\t\tpath: %s\n", path->path); + } + + wl_list_for_each(group, &info->groups, link) { + print_tablet_pad_group_info(group); + } +} + +static void +destroy_tablet_pad_info(struct tablet_pad_info *info) +{ + struct tablet_v2_path *path; + struct tablet_v2_path *tmp_path; + struct tablet_pad_group_info *group; + struct tablet_pad_group_info *tmp_group; + + wl_list_remove(&info->link); + zwp_tablet_pad_v2_destroy(info->pad); + + wl_list_for_each_safe(path, tmp_path, &info->paths, link) { + wl_list_remove(&path->link); + free(path->path); + free(path); + } + + wl_list_for_each_safe(group, tmp_group, &info->groups, link) { + destroy_tablet_pad_group_info(group); + } + + free(info); +} + +static void +print_tablet_info(const struct tablet_info *info) +{ + const struct tablet_v2_path *path; + + printf("\t\ttablet: %s\n", info->name); + printf("\t\t\tvendor: %u\n", info->vid); + printf("\t\t\tproduct: %u\n", info->pid); + + wl_list_for_each(path, &info->paths, link) { + printf("\t\t\tpath: %s\n", path->path); + } +} + +static void +destroy_tablet_info(struct tablet_info *info) +{ + struct tablet_v2_path *path; + struct tablet_v2_path *tmp; + + wl_list_remove(&info->link); + zwp_tablet_v2_destroy(info->tablet); + + if (info->name) { + free(info->name); + } + + wl_list_for_each_safe(path, tmp, &info->paths, link) { + wl_list_remove(&path->link); + free(path->path); + free(path); + } + + free(info); +} + +static void +print_tablet_seat_info(const struct tablet_seat_info *info) +{ + const struct tablet_info *tablet; + const struct tablet_pad_info *pad; + const struct tablet_tool_info *tool; + + printf("\ttablet_seat: %s\n", info->seat_info->name); + + wl_list_for_each(tablet, &info->tablets, link) { + print_tablet_info(tablet); + } + + wl_list_for_each(pad, &info->pads, link) { + print_tablet_pad_info(pad); + } + + wl_list_for_each(tool, &info->tools, link) { + print_tablet_tool_info(tool); + } +} + +static void +destroy_tablet_seat_info(struct tablet_seat_info *info) +{ + struct tablet_info *tablet; + struct tablet_info *tmp_tablet; + struct tablet_pad_info *pad; + struct tablet_pad_info *tmp_pad; + struct tablet_tool_info *tool; + struct tablet_tool_info *tmp_tool; + + wl_list_remove(&info->link); + zwp_tablet_seat_v2_destroy(info->seat); + + wl_list_for_each_safe(tablet, tmp_tablet, &info->tablets, link) { + destroy_tablet_info(tablet); + } + + wl_list_for_each_safe(pad, tmp_pad, &info->pads, link) { + destroy_tablet_pad_info(pad); + } + + wl_list_for_each_safe(tool, tmp_tool, &info->tools, link) { + destroy_tablet_tool_info(tool); + } + + free(info); +} + +static void +print_tablet_v2_info(void *data) +{ + struct tablet_v2_info *info = data; + struct tablet_seat_info *seat; + print_global_info(data); + + wl_list_for_each(seat, &info->seats, link) { + /* Skip tablet_seats without a tablet, they are irrelevant */ + if (wl_list_empty(&seat->pads) && + wl_list_empty(&seat->tablets) && + wl_list_empty(&seat->tools)) { + continue; + } + + print_tablet_seat_info(seat); + } +} + +static void +destroy_tablet_v2_info(void *data) +{ + struct tablet_v2_info *info = data; + struct tablet_seat_info *seat; + struct tablet_seat_info *tmp; + + zwp_tablet_manager_v2_destroy(info->manager); + + wl_list_for_each_safe(seat, tmp, &info->seats, link) { + destroy_tablet_seat_info(seat); + } +} + +static void +handle_tablet_v2_tablet_tool_done(void *data, struct zwp_tablet_tool_v2 *tool) +{ + /* don't bother waiting for this; there's no good reason a + * compositor will wait more than one roundtrip before sending + * these initial events. */ +} + +static void +handle_tablet_v2_tablet_tool_removed(void *data, struct zwp_tablet_tool_v2 *tool) +{ + /* don't bother waiting for this; we never make any request either way. */ +} + +static void +handle_tablet_v2_tablet_tool_type(void *data, struct zwp_tablet_tool_v2 *tool, + uint32_t tool_type) +{ + struct tablet_tool_info *info = data; + info->type = tool_type; +} + +static void +handle_tablet_v2_tablet_tool_hardware_serial(void *data, + struct zwp_tablet_tool_v2 *tool, + uint32_t serial_hi, + uint32_t serial_lo) +{ + struct tablet_tool_info *info = data; + + info->hardware_serial = ((uint64_t) serial_hi) << 32 | + (uint64_t) serial_lo; +} + +static void +handle_tablet_v2_tablet_tool_hardware_id_wacom(void *data, + struct zwp_tablet_tool_v2 *tool, + uint32_t id_hi, uint32_t id_lo) +{ + struct tablet_tool_info *info = data; + + info->hardware_id_wacom = ((uint64_t) id_hi) << 32 | (uint64_t) id_lo; +} + +static void +handle_tablet_v2_tablet_tool_capability(void *data, + struct zwp_tablet_tool_v2 *tool, + uint32_t capability) +{ + struct tablet_tool_info *info = data; + enum zwp_tablet_tool_v2_capability cap = capability; + + switch(cap) { + case ZWP_TABLET_TOOL_V2_CAPABILITY_TILT: + info->has_tilt = true; + break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE: + info->has_pressure = true; + break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE: + info->has_distance = true; + break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION: + info->has_rotation = true; + break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER: + info->has_slider = true; + break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL: + info->has_wheel = true; + break; + } +} + +static void +handle_tablet_v2_tablet_tool_proximity_in(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t serial, struct zwp_tablet_v2 *tablet, + struct wl_surface *surface) +{ + +} + +static void +handle_tablet_v2_tablet_tool_proximity_out(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) +{ + +} + +static void +handle_tablet_v2_tablet_tool_down(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t serial) +{ + +} + +static void +handle_tablet_v2_tablet_tool_up(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) +{ + +} + + +static void +handle_tablet_v2_tablet_tool_motion(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + wl_fixed_t x, + wl_fixed_t y) +{ + +} + +static void +handle_tablet_v2_tablet_tool_pressure(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t pressure) +{ + +} + +static void +handle_tablet_v2_tablet_tool_distance(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t distance) +{ + +} + +static void +handle_tablet_v2_tablet_tool_tilt(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + wl_fixed_t tilt_x, + wl_fixed_t tilt_y) +{ + +} + +static void +handle_tablet_v2_tablet_tool_rotation(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + wl_fixed_t degrees) +{ + +} + +static void +handle_tablet_v2_tablet_tool_slider(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + int32_t position) +{ + +} + +static void +handle_tablet_v2_tablet_tool_wheel(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + wl_fixed_t degrees, + int32_t clicks) +{ + +} + +static void +handle_tablet_v2_tablet_tool_button(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t serial, + uint32_t button, + uint32_t state) +{ + +} + +static void +handle_tablet_v2_tablet_tool_frame(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t time) +{ + +} + +static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = { + .removed = handle_tablet_v2_tablet_tool_removed, + .done = handle_tablet_v2_tablet_tool_done, + .type = handle_tablet_v2_tablet_tool_type, + .hardware_serial = handle_tablet_v2_tablet_tool_hardware_serial, + .hardware_id_wacom = handle_tablet_v2_tablet_tool_hardware_id_wacom, + .capability = handle_tablet_v2_tablet_tool_capability, + + .proximity_in = handle_tablet_v2_tablet_tool_proximity_in, + .proximity_out = handle_tablet_v2_tablet_tool_proximity_out, + .down = handle_tablet_v2_tablet_tool_down, + .up = handle_tablet_v2_tablet_tool_up, + + .motion = handle_tablet_v2_tablet_tool_motion, + .pressure = handle_tablet_v2_tablet_tool_pressure, + .distance = handle_tablet_v2_tablet_tool_distance, + .tilt = handle_tablet_v2_tablet_tool_tilt, + .rotation = handle_tablet_v2_tablet_tool_rotation, + .slider = handle_tablet_v2_tablet_tool_slider, + .wheel = handle_tablet_v2_tablet_tool_wheel, + .button = handle_tablet_v2_tablet_tool_button, + .frame = handle_tablet_v2_tablet_tool_frame, +}; + +static void add_tablet_v2_tablet_tool_info(void *data, + struct zwp_tablet_seat_v2 *tablet_seat_v2, + struct zwp_tablet_tool_v2 *tool) +{ + struct tablet_seat_info *tablet_seat = data; + struct tablet_tool_info *tool_info = xzalloc(sizeof *tool_info); + + tool_info->tool = tool; + wl_list_insert(&tablet_seat->tools, &tool_info->link); + + zwp_tablet_tool_v2_add_listener(tool, &tablet_tool_listener, tool_info); +} + +static void +handle_tablet_v2_tablet_pad_group_mode_switch(void *data, + struct zwp_tablet_pad_group_v2 *zwp_tablet_pad_group_v2, + uint32_t time, uint32_t serial, uint32_t mode) +{ + /* This shouldn't ever happen */ +} + +static void +handle_tablet_v2_tablet_pad_group_done(void *data, + struct zwp_tablet_pad_group_v2 *group) +{ + /* don't bother waiting for this; there's no good reason a + * compositor will wait more than one roundtrip before sending + * these initial events. */ +} + +static void +handle_tablet_v2_tablet_pad_group_modes(void *data, + struct zwp_tablet_pad_group_v2 *group, + uint32_t modes) +{ + struct tablet_pad_group_info *info = data; + info->modes = modes; +} + +static void +handle_tablet_v2_tablet_pad_group_buttons(void *data, + struct zwp_tablet_pad_group_v2 *group, + struct wl_array *buttons) +{ + struct tablet_pad_group_info *info = data; + + info->button_count = buttons->size / sizeof(int); + info->buttons = xzalloc(buttons->size); + memcpy(info->buttons, buttons->data, buttons->size); +} + +static void +handle_tablet_v2_tablet_pad_group_ring(void *data, + struct zwp_tablet_pad_group_v2 *group, + struct zwp_tablet_pad_ring_v2 *ring) +{ + struct tablet_pad_group_info *info = data; + ++info->rings; + + zwp_tablet_pad_ring_v2_destroy(ring); +} + +static void +handle_tablet_v2_tablet_pad_group_strip(void *data, + struct zwp_tablet_pad_group_v2 *group, + struct zwp_tablet_pad_strip_v2 *strip) +{ + struct tablet_pad_group_info *info = data; + ++info->strips; + + zwp_tablet_pad_strip_v2_destroy(strip); +} + +static const struct zwp_tablet_pad_group_v2_listener tablet_pad_group_listener = { + .buttons = handle_tablet_v2_tablet_pad_group_buttons, + .modes = handle_tablet_v2_tablet_pad_group_modes, + .ring = handle_tablet_v2_tablet_pad_group_ring, + .strip = handle_tablet_v2_tablet_pad_group_strip, + .done = handle_tablet_v2_tablet_pad_group_done, + .mode_switch = handle_tablet_v2_tablet_pad_group_mode_switch, +}; + +static void +handle_tablet_v2_tablet_pad_group(void *data, + struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2, + struct zwp_tablet_pad_group_v2 *pad_group) +{ + struct tablet_pad_info *pad_info = data; + struct tablet_pad_group_info *group = xzalloc(sizeof *group); + + wl_list_insert(&pad_info->groups, &group->link); + group->group = pad_group; + zwp_tablet_pad_group_v2_add_listener(pad_group, + &tablet_pad_group_listener, group); +} + +static void +handle_tablet_v2_tablet_pad_path(void *data, struct zwp_tablet_pad_v2 *pad, + const char *path) +{ + struct tablet_pad_info *pad_info = data; + struct tablet_v2_path *path_elem = xzalloc(sizeof *path_elem); + path_elem->path = xstrdup(path); + + wl_list_insert(&pad_info->paths, &path_elem->link); +} + +static void +handle_tablet_v2_tablet_pad_buttons(void *data, struct zwp_tablet_pad_v2 *pad, + uint32_t buttons) +{ + struct tablet_pad_info *pad_info = data; + + pad_info->buttons = buttons; +} + +static void +handle_tablet_v2_tablet_pad_done(void *data, struct zwp_tablet_pad_v2 *pad) +{ + /* don't bother waiting for this; there's no good reason a + * compositor will wait more than one roundtrip before sending + * these initial events. */ +} + +static void +handle_tablet_v2_tablet_pad_removed(void *data, struct zwp_tablet_pad_v2 *pad) +{ + /* don't bother waiting for this; We never make any request that's not + * allowed to be issued either way. */ +} + +static void +handle_tablet_v2_tablet_pad_button(void *data, struct zwp_tablet_pad_v2 *pad, + uint32_t time, uint32_t button, uint32_t state) +{ + /* we don't have a surface, so this can't ever happen */ +} + +static void +handle_tablet_v2_tablet_pad_enter(void *data, struct zwp_tablet_pad_v2 *pad, + uint32_t serial, + struct zwp_tablet_v2 *tablet, + struct wl_surface *surface) +{ + /* we don't have a surface, so this can't ever happen */ +} + +static void +handle_tablet_v2_tablet_pad_leave(void *data, struct zwp_tablet_pad_v2 *pad, + uint32_t serial, struct wl_surface *surface) +{ + /* we don't have a surface, so this can't ever happen */ +} + +static const struct zwp_tablet_pad_v2_listener tablet_pad_listener = { + .group = handle_tablet_v2_tablet_pad_group, + .path = handle_tablet_v2_tablet_pad_path, + .buttons = handle_tablet_v2_tablet_pad_buttons, + .done = handle_tablet_v2_tablet_pad_done, + .removed = handle_tablet_v2_tablet_pad_removed, + .button = handle_tablet_v2_tablet_pad_button, + .enter = handle_tablet_v2_tablet_pad_enter, + .leave = handle_tablet_v2_tablet_pad_leave, +}; + +static void add_tablet_v2_tablet_pad_info(void *data, + struct zwp_tablet_seat_v2 *tablet_seat_v2, + struct zwp_tablet_pad_v2 *pad) +{ + struct tablet_seat_info *tablet_seat = data; + struct tablet_pad_info *pad_info = xzalloc(sizeof *pad_info); + + wl_list_init(&pad_info->paths); + wl_list_init(&pad_info->groups); + pad_info->pad = pad; + wl_list_insert(&tablet_seat->pads, &pad_info->link); + + zwp_tablet_pad_v2_add_listener(pad, &tablet_pad_listener, pad_info); +} + +static void +handle_tablet_v2_tablet_name(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, + const char *name) +{ + struct tablet_info *tablet_info = data; + tablet_info->name = xstrdup(name); +} + +static void +handle_tablet_v2_tablet_path(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, + const char *path) +{ + struct tablet_info *tablet_info = data; + struct tablet_v2_path *path_elem = xzalloc(sizeof *path_elem); + path_elem->path = xstrdup(path); + + wl_list_insert(&tablet_info->paths, &path_elem->link); +} + +static void +handle_tablet_v2_tablet_id(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, + uint32_t vid, uint32_t pid) +{ + struct tablet_info *tablet_info = data; + + tablet_info->vid = vid; + tablet_info->pid = pid; +} + +static void +handle_tablet_v2_tablet_done(void *data, struct zwp_tablet_v2 *zwp_tablet_v2) +{ + /* don't bother waiting for this; there's no good reason a + * compositor will wait more than one roundtrip before sending + * these initial events. */ +} + +static void +handle_tablet_v2_tablet_removed(void *data, struct zwp_tablet_v2 *zwp_tablet_v2) +{ + /* don't bother waiting for this; We never make any request that's not + * allowed to be issued either way. */ +} + +static const struct zwp_tablet_v2_listener tablet_listener = { + .name = handle_tablet_v2_tablet_name, + .id = handle_tablet_v2_tablet_id, + .path = handle_tablet_v2_tablet_path, + .done = handle_tablet_v2_tablet_done, + .removed = handle_tablet_v2_tablet_removed +}; + +static void +add_tablet_v2_tablet_info(void *data, struct zwp_tablet_seat_v2 *tablet_seat_v2, + struct zwp_tablet_v2 *tablet) +{ + struct tablet_seat_info *tablet_seat = data; + struct tablet_info *tablet_info = xzalloc(sizeof *tablet_info); + + wl_list_init(&tablet_info->paths); + tablet_info->tablet = tablet; + wl_list_insert(&tablet_seat->tablets, &tablet_info->link); + + zwp_tablet_v2_add_listener(tablet, &tablet_listener, tablet_info); +} + +static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { + .tablet_added = add_tablet_v2_tablet_info, + .pad_added = add_tablet_v2_tablet_pad_info, + .tool_added = add_tablet_v2_tablet_tool_info, +}; + +static void +add_tablet_seat_info(struct tablet_v2_info *tablet_info, struct seat_info *seat) +{ + struct tablet_seat_info *tablet_seat = xzalloc(sizeof *tablet_seat); + + wl_list_insert(&tablet_info->seats, &tablet_seat->link); + tablet_seat->seat = zwp_tablet_manager_v2_get_tablet_seat( + tablet_info->manager, seat->seat); + zwp_tablet_seat_v2_add_listener(tablet_seat->seat, + &tablet_seat_listener, tablet_seat); + + wl_list_init(&tablet_seat->pads); + wl_list_init(&tablet_seat->tablets); + wl_list_init(&tablet_seat->tools); + tablet_seat->seat_info = seat; + + tablet_info->info->roundtrip_needed = true; +} + +static void +add_tablet_v2_info(struct weston_info *info, uint32_t id, uint32_t version) +{ + struct seat_info *seat; + struct tablet_v2_info *tablet = xzalloc(sizeof *tablet); + + wl_list_init(&tablet->seats); + tablet->info = info; + + init_global_info(info, &tablet->global, id, + zwp_tablet_manager_v2_interface.name, version); + tablet->global.print = print_tablet_v2_info; + tablet->global.destroy = destroy_tablet_v2_info; + + tablet->manager = wl_registry_bind(info->registry, + id, &zwp_tablet_manager_v2_interface, 1); + + wl_list_for_each(seat, &info->seats, global_link) { + add_tablet_seat_info(tablet, seat); + } + + info->tablet_info = tablet; +} + +static void +destroy_xdg_output_v1_info(struct xdg_output_v1_info *info) +{ + wl_list_remove(&info->link); + zxdg_output_v1_destroy(info->xdg_output); + free(info->name); + free(info->description); + free(info); +} + +static void +print_xdg_output_v1_info(const struct xdg_output_v1_info *info) +{ + printf("\txdg_output_v1\n"); + printf("\t\toutput: %d\n", info->output->global.id); + if (info->name) + printf("\t\tname: '%s'\n", info->name); + if (info->description) + printf("\t\tdescription: '%s'\n", info->description); + printf("\t\tlogical_x: %d, logical_y: %d\n", + info->logical.x, info->logical.y); + printf("\t\tlogical_width: %d, logical_height: %d\n", + info->logical.width, info->logical.height); +} + +static void +print_xdg_output_manager_v1_info(void *data) +{ + struct xdg_output_manager_v1_info *info = data; + struct xdg_output_v1_info *output; + + print_global_info(data); + + wl_list_for_each(output, &info->outputs, link) + print_xdg_output_v1_info(output); +} + +static void +destroy_xdg_output_manager_v1_info(void *data) +{ + struct xdg_output_manager_v1_info *info = data; + struct xdg_output_v1_info *output, *tmp; + + zxdg_output_manager_v1_destroy(info->manager); + + wl_list_for_each_safe(output, tmp, &info->outputs, link) + destroy_xdg_output_v1_info(output); +} + +static void +handle_xdg_output_v1_logical_position(void *data, struct zxdg_output_v1 *output, + int32_t x, int32_t y) +{ + struct xdg_output_v1_info *xdg_output = data; + xdg_output->logical.x = x; + xdg_output->logical.y = y; +} + +static void +handle_xdg_output_v1_logical_size(void *data, struct zxdg_output_v1 *output, + int32_t width, int32_t height) +{ + struct xdg_output_v1_info *xdg_output = data; + xdg_output->logical.width = width; + xdg_output->logical.height = height; +} + +static void +handle_xdg_output_v1_done(void *data, struct zxdg_output_v1 *output) +{ + /* Don't bother waiting for this; there's no good reason a + * compositor will wait more than one roundtrip before sending + * these initial events. */ +} + +static void +handle_xdg_output_v1_name(void *data, struct zxdg_output_v1 *output, + const char *name) +{ + struct xdg_output_v1_info *xdg_output = data; + xdg_output->name = strdup(name); +} + +static void +handle_xdg_output_v1_description(void *data, struct zxdg_output_v1 *output, + const char *description) +{ + struct xdg_output_v1_info *xdg_output = data; + xdg_output->description = strdup(description); +} + +static const struct zxdg_output_v1_listener xdg_output_v1_listener = { + .logical_position = handle_xdg_output_v1_logical_position, + .logical_size = handle_xdg_output_v1_logical_size, + .done = handle_xdg_output_v1_done, + .name = handle_xdg_output_v1_name, + .description = handle_xdg_output_v1_description, +}; + +static void +add_xdg_output_v1_info(struct xdg_output_manager_v1_info *manager_info, + struct output_info *output) +{ + struct xdg_output_v1_info *xdg_output = xzalloc(sizeof *xdg_output); + + wl_list_insert(&manager_info->outputs, &xdg_output->link); + xdg_output->xdg_output = zxdg_output_manager_v1_get_xdg_output( + manager_info->manager, output->output); + zxdg_output_v1_add_listener(xdg_output->xdg_output, + &xdg_output_v1_listener, xdg_output); + + xdg_output->output = output; + + manager_info->info->roundtrip_needed = true; +} + +static void +add_xdg_output_manager_v1_info(struct weston_info *info, uint32_t id, + uint32_t version) +{ + struct output_info *output; + struct xdg_output_manager_v1_info *manager = xzalloc(sizeof *manager); + + wl_list_init(&manager->outputs); + manager->info = info; + + init_global_info(info, &manager->global, id, + zxdg_output_manager_v1_interface.name, version); + manager->global.print = print_xdg_output_manager_v1_info; + manager->global.destroy = destroy_xdg_output_manager_v1_info; + + manager->manager = wl_registry_bind(info->registry, id, + &zxdg_output_manager_v1_interface, version > 2 ? 2 : version); + + wl_list_for_each(output, &info->outputs, global_link) + add_xdg_output_v1_info(manager, output); + + info->xdg_output_manager_v1_info = manager; +} + +static void +add_seat_info(struct weston_info *info, uint32_t id, uint32_t version) +{ + struct seat_info *seat = xzalloc(sizeof *seat); + + /* required to set roundtrip_needed to true in capabilities + * handler */ + seat->info = info; + + init_global_info(info, &seat->global, id, "wl_seat", version); + seat->global.print = print_seat_info; + seat->global.destroy = destroy_seat_info; + + seat->seat = wl_registry_bind(info->registry, + id, &wl_seat_interface, MIN(version, 4)); + wl_seat_add_listener(seat->seat, &seat_listener, seat); + + seat->repeat_rate = seat->repeat_delay = -1; + + info->roundtrip_needed = true; + wl_list_insert(&info->seats, &seat->global_link); + + if (info->tablet_info) { + add_tablet_seat_info(info->tablet_info, seat); + } +} + +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 = xzalloc(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 +destroy_shm_info(void *data) +{ + struct shm_info *shm = data; + struct shm_format *format, *tmp; + + wl_list_for_each_safe(format, tmp, &shm->formats, link) { + wl_list_remove(&format->link); + free(format); + } + + wl_shm_destroy(shm->shm); +} + +static void +add_shm_info(struct weston_info *info, uint32_t id, uint32_t version) +{ + struct shm_info *shm = xzalloc(sizeof *shm); + + init_global_info(info, &shm->global, id, "wl_shm", version); + shm->global.print = print_shm_info; + shm->global.destroy = destroy_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 +linux_dmabuf_handle_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format) +{ + /* This is a deprecated event, don’t use it. */ +} + +static void +linux_dmabuf_handle_modifier(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) +{ + struct linux_dmabuf_info *dmabuf = data; + struct linux_dmabuf_modifier *linux_dmabuf_modifier = xzalloc(sizeof *linux_dmabuf_modifier); + + wl_list_insert(&dmabuf->modifiers, &linux_dmabuf_modifier->link); + linux_dmabuf_modifier->format = format; + linux_dmabuf_modifier->modifier = ((uint64_t)modifier_hi) << 32 | modifier_lo; +} + +static const struct zwp_linux_dmabuf_v1_listener linux_dmabuf_listener = { + linux_dmabuf_handle_format, + linux_dmabuf_handle_modifier, +}; + +static void +destroy_linux_dmabuf_info(void *data) +{ + struct linux_dmabuf_info *dmabuf = data; + struct linux_dmabuf_modifier *modifier, *tmp; + + wl_list_for_each_safe(modifier, tmp, &dmabuf->modifiers, link) { + wl_list_remove(&modifier->link); + free(modifier); + } + + zwp_linux_dmabuf_v1_destroy(dmabuf->dmabuf); +} + +static void +add_linux_dmabuf_info(struct weston_info *info, uint32_t id, uint32_t version) +{ + struct linux_dmabuf_info *dmabuf = xzalloc(sizeof *dmabuf); + + init_global_info(info, &dmabuf->global, id, "zwp_linux_dmabuf_v1", version); + dmabuf->global.print = print_linux_dmabuf_info; + dmabuf->global.destroy = destroy_linux_dmabuf_info; + + wl_list_init(&dmabuf->modifiers); + + if (version >= 3) { + dmabuf->dmabuf = wl_registry_bind(info->registry, + id, &zwp_linux_dmabuf_v1_interface, 3); + zwp_linux_dmabuf_v1_add_listener(dmabuf->dmabuf, &linux_dmabuf_listener, dmabuf); + + 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 = xstrdup(make); + output->geometry.model = xstrdup(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 void +output_handle_done(void *data, struct wl_output *wl_output) +{ + /* don't bother waiting for this; there's no good reason a + * compositor will wait more than one roundtrip before sending + * these initial events. */ +} + +static void +output_handle_scale(void *data, struct wl_output *wl_output, + int32_t scale) +{ + struct output_info *output = data; + + output->geometry.scale = scale; +} + +static const struct wl_output_listener output_listener = { + output_handle_geometry, + output_handle_mode, + output_handle_done, + output_handle_scale, +}; + +static void +destroy_output_info(void *data) +{ + struct output_info *output = data; + struct output_mode *mode, *tmp; + + wl_output_destroy(output->output); + + if (output->geometry.make != NULL) + free(output->geometry.make); + if (output->geometry.model != NULL) + free(output->geometry.model); + + wl_list_for_each_safe(mode, tmp, &output->modes, link) { + wl_list_remove(&mode->link); + free(mode); + } +} + +static void +add_output_info(struct weston_info *info, uint32_t id, uint32_t version) +{ + struct output_info *output = xzalloc(sizeof *output); + + init_global_info(info, &output->global, id, "wl_output", version); + output->global.print = print_output_info; + output->global.destroy = destroy_output_info; + + output->version = MIN(version, 2); + output->geometry.scale = 1; + wl_list_init(&output->modes); + + output->output = wl_registry_bind(info->registry, id, + &wl_output_interface, output->version); + wl_output_add_listener(output->output, &output_listener, + output); + + info->roundtrip_needed = true; + wl_list_insert(&info->outputs, &output->global_link); + + if (info->xdg_output_manager_v1_info) + add_xdg_output_v1_info(info->xdg_output_manager_v1_info, + output); +} + +static void +destroy_presentation_info(void *info) +{ + struct presentation_info *prinfo = info; + + wp_presentation_destroy(prinfo->presentation); +} + +static const char * +clock_name(clockid_t clk_id) +{ + static const char *names[] = { + [CLOCK_REALTIME] = "CLOCK_REALTIME", + [CLOCK_MONOTONIC] = "CLOCK_MONOTONIC", + [CLOCK_MONOTONIC_RAW] = "CLOCK_MONOTONIC_RAW", + [CLOCK_REALTIME_COARSE] = "CLOCK_REALTIME_COARSE", + [CLOCK_MONOTONIC_COARSE] = "CLOCK_MONOTONIC_COARSE", +#ifdef CLOCK_BOOTTIME + [CLOCK_BOOTTIME] = "CLOCK_BOOTTIME", +#endif + }; + + if (clk_id < 0 || (unsigned)clk_id >= ARRAY_LENGTH(names)) + return "unknown"; + + return names[clk_id]; +} + +static void +print_presentation_info(void *info) +{ + struct presentation_info *prinfo = info; + + print_global_info(info); + + printf("\tpresentation clock id: %d (%s)\n", + prinfo->clk_id, clock_name(prinfo->clk_id)); +} + +static void +presentation_handle_clock_id(void *data, struct wp_presentation *presentation, + uint32_t clk_id) +{ + struct presentation_info *prinfo = data; + + prinfo->clk_id = clk_id; +} + +static const struct wp_presentation_listener presentation_listener = { + presentation_handle_clock_id +}; + +static void +add_presentation_info(struct weston_info *info, uint32_t id, uint32_t version) +{ + struct presentation_info *prinfo = xzalloc(sizeof *prinfo); + + init_global_info(info, &prinfo->global, id, + wp_presentation_interface.name, version); + prinfo->global.print = print_presentation_info; + prinfo->global.destroy = destroy_presentation_info; + + prinfo->clk_id = -1; + prinfo->presentation = wl_registry_bind(info->registry, id, + &wp_presentation_interface, 1); + wp_presentation_add_listener(prinfo->presentation, + &presentation_listener, prinfo); + + info->roundtrip_needed = true; +} + +static void +destroy_global_info(void *data) +{ +} + +static void +add_global_info(struct weston_info *info, uint32_t id, + const char *interface, uint32_t version) +{ + struct global_info *global = xzalloc(sizeof *global); + + init_global_info(info, global, id, interface, version); + global->print = print_global_info; + global->destroy = destroy_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, "zwp_linux_dmabuf_v1")) + add_linux_dmabuf_info(info, id, version); + else if (!strcmp(interface, "wl_output")) + add_output_info(info, id, version); + else if (!strcmp(interface, wp_presentation_interface.name)) + add_presentation_info(info, id, version); + else if (!strcmp(interface, zwp_tablet_manager_v2_interface.name)) + add_tablet_v2_info(info, id, version); + else if (!strcmp(interface, zxdg_output_manager_v1_interface.name)) + add_xdg_output_manager_v1_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); +} + +static void +destroy_info(void *data) +{ + struct global_info *global = data; + + global->destroy(data); + wl_list_remove(&global->link); + free(global->interface); + free(data); +} + +static void +destroy_infos(struct wl_list *infos) +{ + struct global_info *info, *tmp; + wl_list_for_each_safe(info, tmp, infos, link) + destroy_info(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: %s\n", + strerror(errno)); + return -1; + } + + fprintf(stderr, "\n"); + fprintf(stderr, "*** Please use wayland-info instead\n"); + fprintf(stderr, "*** weston-info is deprecated and will be removed in a future version\n"); + fprintf(stderr, "\n"); + + info.tablet_info = NULL; + info.xdg_output_manager_v1_info = NULL; + wl_list_init(&info.infos); + wl_list_init(&info.seats); + wl_list_init(&info.outputs); + + 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); + destroy_infos(&info.infos); + + wl_registry_destroy(info.registry); + wl_display_disconnect(info.display); + + return 0; +} diff --git a/clients/window.c b/clients/window.c new file mode 100644 index 0000000..ca7e62d --- /dev/null +++ b/clients/window.c @@ -0,0 +1,6673 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2012-2013 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#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 +#elif !defined(ENABLE_EGL) /* platform.h defines these if EGL is enabled */ +typedef void *EGLDisplay; +typedef void *EGLConfig; +typedef void *EGLContext; +#define EGL_NO_DISPLAY ((EGLDisplay)0) +#endif /* no HAVE_CAIRO_EGL */ + +#include +#ifdef HAVE_XKBCOMMON_COMPOSE +#include +#endif +#include + +#include +#include +#include "shared/cairo-util.h" +#include "shared/helpers.h" +#include "shared/xalloc.h" +#include +#include "xdg-shell-client-protocol.h" +#include "text-cursor-position-client-protocol.h" +#include "pointer-constraints-unstable-v1-client-protocol.h" +#include "relative-pointer-unstable-v1-client-protocol.h" +#include "shared/os-compatibility.h" +#include "shared/string-helpers.h" + +#include "window.h" +#include "viewporter-client-protocol.h" + +#define ZWP_RELATIVE_POINTER_MANAGER_V1_VERSION 1 +#define ZWP_POINTER_CONSTRAINTS_V1_VERSION 1 + +#define DEFAULT_XCURSOR_SIZE 32 + +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_shm *shm; + struct wl_data_device_manager *data_device_manager; + struct text_cursor_position *text_cursor_position; + struct xdg_wm_base *xdg_shell; + struct zwp_relative_pointer_manager_v1 *relative_pointer_manager; + struct zwp_pointer_constraints_v1 *pointer_constraints; + 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; + display_global_handler_t global_handler_remove; + + void *user_data; + + struct xkb_context *xkb_context; + + /* A hack to get text extents for tooltips */ + cairo_surface_t *dummy_surface; + void *dummy_surface_data; + + int data_device_manager_version; + struct wp_viewporter *viewporter; +}; + +struct window_output { + struct output *output; + struct wl_list link; +}; + +struct toysurface { + /* + * Prepare the surface for drawing. Ensure there is a surface + * of the right size available for rendering, and return 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 on 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 wp_viewport *viewport; +}; + +struct window { + struct display *display; + struct wl_list window_output_list; + char *title; + struct rectangle saved_allocation; + struct rectangle min_allocation; + struct rectangle pending_allocation; + struct rectangle last_geometry; + int x, y; + int redraw_inhibited; + int redraw_needed; + int redraw_task_scheduled; + struct task redraw_task; + int resize_needed; + int custom; + int focused; + + int resizing; + + int fullscreen; + int maximized; + + 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; + window_state_changed_handler_t state_changed_handler; + + window_locked_pointer_motion_handler_t locked_pointer_motion_handler; + + struct surface *main_surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + struct xdg_popup *xdg_popup; + + struct window *parent; + struct window *last_parent; + + struct window_frame *frame; + + /* struct surface::link, contains also main_surface */ + struct wl_list subsurface_list; + + struct zwp_relative_pointer_v1 *relative_pointer; + struct zwp_locked_pointer_v1 *locked_pointer; + bool pointer_locked; + locked_pointer_locked_handler_t pointer_locked_handler; + locked_pointer_unlocked_handler_t pointer_unlocked_handler; + confined_pointer_confined_handler_t pointer_confined_handler; + confined_pointer_unconfined_handler_t pointer_unconfined_handler; + + struct zwp_confined_pointer_v1 *confined_pointer; + struct widget *confined_widget; + bool confined; + + 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; + widget_pointer_frame_handler_t pointer_frame_handler; + widget_axis_source_handler_t axis_source_handler; + widget_axis_stop_handler_t axis_stop_handler; + widget_axis_discrete_handler_t axis_discrete_handler; + void *user_data; + int opaque; + int tooltip_count; + int default_cursor; + /* If this is set to false then no cairo surface will be + * created before redrawing the surface. This is useful if the + * redraw handler is going to do completely custom rendering + * such as using EGL directly */ + int use_cairo; + int viewport_dest_width; + int viewport_dest_height; +}; + +struct touch_point { + int32_t id; + float x, y; + 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; + struct window *locked_window; + struct window *confined_window; + int current_cursor; + uint32_t cursor_anim_start; + struct wl_callback *cursor_frame_cb; + uint32_t cursor_timer_start; + uint32_t cursor_anim_current; + struct toytimer cursor_timer; + bool cursor_timer_running; + 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; + uint32_t touch_grab; + int32_t touch_grab_id; + float drag_x, drag_y; + struct window *drag_focus; + uint32_t drag_enter_serial; + + struct { + struct xkb_keymap *keymap; + struct xkb_state *state; +#ifdef HAVE_XKBCOMMON_COMPOSE + struct xkb_compose_table *compose_table; + struct xkb_compose_state *compose_state; +#endif + xkb_mod_mask_t control_mask; + xkb_mod_mask_t alt_mask; + xkb_mod_mask_t shift_mask; + } xkb; + + int32_t repeat_rate_sec; + int32_t repeat_rate_nsec; + int32_t repeat_delay_sec; + int32_t repeat_delay_nsec; + + struct toytimer repeat_timer; + uint32_t repeat_sym; + uint32_t repeat_key; + uint32_t repeat_time; + int seat_version; +}; + +struct output { + struct display *display; + struct wl_output *output; + uint32_t server_output_id; + struct rectangle allocation; + struct wl_list link; + int transform; + int scale; + char *make; + char *model; + + display_output_handler_t destroy_handler; + void *user_data; +}; + +struct window_frame { + struct widget *widget; + struct widget *child; + struct frame *frame; + + uint32_t last_time; + uint32_t did_double, double_click; + int32_t last_id, double_id; +}; + +struct menu { + void *user_data; + struct window *window; + struct widget *widget; + struct input *input; + struct frame *frame; + const char **entries; + uint32_t time; + int current; + int count; + int release_count; + menu_func_t func; +}; + +struct tooltip { + struct widget *parent; + struct widget *widget; + char *entry; + struct toytimer timer; + 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 +}; + +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, + EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) + 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); + weston_platform_destroy_egl_surface(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 = zalloc(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 = + weston_platform_create_egl_surface(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: %s\n", + size, strerror(errno)); + return NULL; + } + + *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (*data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + close(fd); + return 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; + int stride, length, offset; + void *map; + + data = malloc(sizeof *data); + if (data == NULL) + return NULL; + + stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, + 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_ARGB32, + rectangle->width, + rectangle->height, + stride); + + cairo_surface_set_user_data(surface, &shm_surface_data_key, + data, shm_surface_data_destroy); + + 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, preferably 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); + if (!leaf->cairo_surface) + return NULL; + + 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 = xzalloc(sizeof *surface); + 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" +}; + +static const char *move_draggings[] = { + "dnd-move" +}; + +static const char *copy_draggings[] = { + "dnd-copy" +}; + +static const char *forbidden_draggings[] = { + "dnd-none", + "dnd-no-drop" +}; + +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)}, + {move_draggings, ARRAY_LENGTH(move_draggings)}, + {copy_draggings, ARRAY_LENGTH(copy_draggings)}, + {forbidden_draggings, ARRAY_LENGTH(forbidden_draggings)}, +}; + +static void +create_cursors(struct display *display) +{ + const char *config_file; + struct weston_config *config; + struct weston_config_section *s; + int size = DEFAULT_XCURSOR_SIZE; + char *theme = NULL, *size_str; + unsigned int i, j; + struct wl_cursor *cursor; + + theme = getenv("XCURSOR_THEME"); + + size_str = getenv("XCURSOR_SIZE"); + if (size_str) { + safe_strtoint(size_str, &size); + if (size <= 0) + size = DEFAULT_XCURSOR_SIZE; + } + + config_file = weston_config_get_name_from_env(); + config = weston_config_parse(config_file); + s = weston_config_get_section(config, "shell", NULL, NULL); + weston_config_section_get_string(s, "cursor-theme", &theme, theme); + weston_config_section_get_int(s, "cursor-size", &size, size); + weston_config_destroy(config); + + display->cursor_theme = wl_cursor_theme_load(theme, size, display->shm); + if (!display->cursor_theme) { + fprintf(stderr, "could not load theme '%s'\n", theme); + return; + } + 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) +{ + struct widget *widget = surface->widget; + 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; + } + + if (surface->viewport) { + wp_viewport_set_destination(surface->viewport, + widget->viewport_dest_width, + widget->viewport_dest_height); + } + + 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->focused; +} + +static void +window_close(struct window *window) +{ + if (window->close_handler) + window->close_handler(window->user_data); + else + display_exit(window->display); +} + +struct display * +window_get_display(struct window *window) +{ + return window->display; +} + +static void +surface_create_surface(struct surface *surface, 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, 0, 0, + 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; + + if (window->resizing) + flags |= SURFACE_HINT_RESIZE; + + surface_create_surface(surface, 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 window_frame_destroy(struct window_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) { + struct touch_point *tp, *tmp; + + wl_list_for_each_safe(tp, tmp, + &input->touch_point_list, + link) { + wl_list_remove(&tp->link); + free(tp); + } + + input->touch_focus = NULL; + } + if (input->pointer_focus == window) + input->pointer_focus = NULL; + if (input->keyboard_focus == window) + input->keyboard_focus = NULL; + if (input->locked_window == window) + input->locked_window = NULL; + if (input->confined_window == window) + input->confined_window = 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) + window_frame_destroy(window->frame); + + if (window->xdg_toplevel) + xdg_toplevel_destroy(window->xdg_toplevel); + if (window->xdg_popup) + xdg_popup_destroy(window->xdg_popup); + if (window->xdg_surface) + xdg_surface_destroy(window->xdg_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; + int alloc_x, alloc_y, width, height; + double scale; + + wl_list_for_each(child, &widget->child_list, link) { + target = widget_find_widget(child, x, y); + if (target) + return target; + } + + alloc_x = widget->allocation.x; + alloc_y = widget->allocation.y; + width = widget->allocation.width; + height = widget->allocation.height; + + if (widget->viewport_dest_width != -1 && + widget->viewport_dest_height != -1) { + scale = widget->viewport_dest_width / (double) width; + alloc_x = alloc_x * scale; + width = widget->viewport_dest_width; + + scale = widget->viewport_dest_height / (double) height; + alloc_y = alloc_y * scale; + height = widget->viewport_dest_height; + } + + if (alloc_x <= x && x < alloc_x + width && + alloc_y <= y && y < alloc_y + 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; + widget->use_cairo = 1; + widget->viewport_dest_width = -1; + widget->viewport_dest_height = -1; + + 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) + widget_destroy_tooltip(widget); + + 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; + + assert(widget->use_cairo); + + if (!surface->cairo_surface) { + if (surface == window->main_surface) + window_create_main_surface(window); + else + surface_create_surface(surface, 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 + M_PI_2; + translate_x = 0; + translate_y = surface_width; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + angle = M_PI + M_PI_2; + translate_x = 0; + translate_y = 0; + 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_2; + translate_x = surface_height; + translate_y = 0; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + angle = M_PI_2; + translate_x = surface_height; + translate_y = surface_width; + 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; +} + +struct wl_subsurface * +widget_get_wl_subsurface(struct widget *widget) +{ + return widget->surface->subsurface; +} + +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; +} + +void +widget_set_pointer_frame_handler(struct widget *widget, + widget_pointer_frame_handler_t handler) +{ + widget->pointer_frame_handler = handler; +} + +void +widget_set_axis_handlers(struct widget *widget, + widget_axis_handler_t axis_handler, + widget_axis_source_handler_t axis_source_handler, + widget_axis_stop_handler_t axis_stop_handler, + widget_axis_discrete_handler_t axis_discrete_handler) +{ + widget->axis_handler = axis_handler; + widget->axis_source_handler = axis_source_handler; + widget->axis_stop_handler = axis_stop_handler; + widget->axis_discrete_handler = axis_discrete_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); +} + +void +widget_set_use_cairo(struct widget *widget, + int use_cairo) +{ + widget->use_cairo = use_cairo; +} + +int +widget_set_viewport_destination(struct widget *widget, int width, int height) +{ + struct window *window = widget->window; + struct display *display = window->display; + struct surface *surface = widget->surface; + if (!display->viewporter) + return -1; + + if (width == -1 && height == -1) { + if (surface->viewport) { + wp_viewport_destroy(surface->viewport); + surface->viewport = NULL; + } + + widget->viewport_dest_width = -1; + widget->viewport_dest_height = -1; + return 0; + } + + if (!surface->viewport) { + surface->viewport = wp_viewporter_get_viewport(display->viewporter, + surface->surface); + if (!surface->viewport) + return -1; + } + + widget->viewport_dest_width = width; + widget->viewport_dest_height = height; + + return 0; +} + +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; +} + +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_translate(cr, widget->allocation.x, widget->allocation.y); + 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_rgba(cr, 1.0, 1.0, 1.0, 0.85); + cairo_move_to(cr, 10, 17); + cairo_set_font_size(cr, 14); + cairo_show_text(cr, tooltip->entry); + cairo_destroy(cr); +} + +static cairo_text_extents_t +get_text_extents(struct display *display, struct tooltip *tooltip) +{ + cairo_t *cr; + cairo_text_extents_t extents; + + /* Use the dummy_surface because the tooltip's surface was not + * created yet, and parent does not have a valid surface + * outside repaint, either. + */ + cr = cairo_create(display->dummy_surface); + cairo_set_font_size(cr, 14); + 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; + const int offset_y = 27; + const int margin = 3; + cairo_text_extents_t extents; + + if (tooltip->widget) + return 0; + + tooltip->widget = window_add_subsurface(parent->window, tooltip, SUBSURFACE_DESYNCHRONIZED); + + extents = get_text_extents(display, tooltip); + widget_set_redraw_handler(tooltip->widget, tooltip_redraw_handler); + widget_set_allocation(tooltip->widget, + tooltip->x, tooltip->y + offset_y, + 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); + tooltip->widget = NULL; + } + + toytimer_fini(&tooltip->timer); + free(tooltip->entry); + free(tooltip); + parent->tooltip = NULL; +} + +static void +tooltip_func(struct toytimer *tt) +{ + struct tooltip *tooltip = container_of(tt, struct tooltip, timer); + + window_create_tooltip(tooltip); +} + +#define TOOLTIP_TIMEOUT 500 +static int +tooltip_timer_reset(struct tooltip *tooltip) +{ + toytimer_arm_once_usec(&tooltip->timer, TOOLTIP_TIMEOUT * 1000); + + 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->x = x; + tooltip->y = y; + tooltip->entry = strdup(entry); + toytimer_init(&tooltip->timer, CLOCK_MONOTONIC, + parent->window->display, tooltip_func); + tooltip_timer_reset(tooltip); + + return 0; +} + +static void +frame_resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + struct window_frame *frame = data; + struct widget *child = frame->child; + struct rectangle interior; + struct rectangle input; + struct rectangle opaque; + + if (widget->window->fullscreen) { + interior.x = 0; + interior.y = 0; + interior.width = width; + interior.height = height; + } else { + frame_resize(frame->frame, width, height); + frame_interior(frame->frame, &interior.x, &interior.y, + &interior.width, &interior.height); + } + + widget_set_allocation(child, interior.x, interior.y, + interior.width, interior.height); + + if (child->resize_handler) { + child->resize_handler(child, interior.width, interior.height, + child->user_data); + + if (widget->window->fullscreen) { + width = child->allocation.width; + height = child->allocation.height; + } else { + frame_resize_inside(frame->frame, + child->allocation.width, + child->allocation.height); + width = frame_width(frame->frame); + height = frame_height(frame->frame); + } + } + + widget_set_allocation(widget, 0, 0, width, height); + + widget->surface->input_region = + wl_compositor_create_region(widget->window->display->compositor); + if (!widget->window->fullscreen) { + frame_input_rect(frame->frame, &input.x, &input.y, + &input.width, &input.height); + wl_region_add(widget->surface->input_region, + input.x, input.y, input.width, input.height); + } else { + wl_region_add(widget->surface->input_region, 0, 0, width, height); + } + + widget_set_allocation(widget, 0, 0, width, height); + + if (child->opaque) { + if (!widget->window->fullscreen) { + frame_opaque_rect(frame->frame, &opaque.x, &opaque.y, + &opaque.width, &opaque.height); + + wl_region_add(widget->surface->opaque_region, + opaque.x, opaque.y, + opaque.width, opaque.height); + } else { + wl_region_add(widget->surface->opaque_region, + 0, 0, width, height); + } + } + + + widget_schedule_redraw(widget); +} + +static void +frame_redraw_handler(struct widget *widget, void *data) +{ + cairo_t *cr; + struct window_frame *frame = data; + struct window *window = widget->window; + + if (window->fullscreen) + return; + + cr = widget_cairo_create(widget); + + frame_repaint(frame->frame, cr); + + cairo_destroy(cr); +} + +static int +frame_get_pointer_image_for_location(struct window_frame *frame, + enum theme_location location) +{ + struct window *window = frame->widget->window; + + if (window->custom) + return CURSOR_LEFT_PTR; + + 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(void *data, struct input *input, int index) +{ + struct window *window = data; + + switch (index) { + case 0: /* close */ + window_close(window); + break; + case 1: /* 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", + "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) +{ + struct window_frame *frame = data; + enum theme_location location; + + location = frame_pointer_enter(frame->frame, input, x, y); + if (frame_status(frame->frame) & FRAME_STATUS_REPAINT) + widget_schedule_redraw(frame->widget); + + return frame_get_pointer_image_for_location(data, location); +} + +static int +frame_motion_handler(struct widget *widget, + struct input *input, uint32_t time, + float x, float y, void *data) +{ + struct window_frame *frame = data; + enum theme_location location; + + location = frame_pointer_motion(frame->frame, input, x, y); + if (frame_status(frame->frame) & FRAME_STATUS_REPAINT) + widget_schedule_redraw(frame->widget); + + return frame_get_pointer_image_for_location(data, location); +} + +static void +frame_leave_handler(struct widget *widget, + struct input *input, void *data) +{ + struct window_frame *frame = data; + + frame_pointer_leave(frame->frame, input); + if (frame_status(frame->frame) & FRAME_STATUS_REPAINT) + widget_schedule_redraw(frame->widget); +} + +static void +frame_handle_status(struct window_frame *frame, struct input *input, + uint32_t time, enum theme_location location) +{ + struct window *window = frame->widget->window; + uint32_t status; + + status = frame_status(frame->frame); + if (status & FRAME_STATUS_REPAINT) + widget_schedule_redraw(frame->widget); + + if (status & FRAME_STATUS_MINIMIZE) { + window_set_minimized(window); + frame_status_clear(frame->frame, FRAME_STATUS_MINIMIZE); + } + + if (status & FRAME_STATUS_MENU) { + window_show_frame_menu(window, input, time); + frame_status_clear(frame->frame, FRAME_STATUS_MENU); + } + + if (status & FRAME_STATUS_MAXIMIZE) { + window_set_maximized(window, !window->maximized); + frame_status_clear(frame->frame, FRAME_STATUS_MAXIMIZE); + } + + if (status & FRAME_STATUS_CLOSE) { + window_close(window); + return; + } + + if ((status & FRAME_STATUS_MOVE) && window->xdg_toplevel) { + input_ungrab(input); + xdg_toplevel_move(window->xdg_toplevel, + input_get_seat(input), + window->display->serial); + + frame_status_clear(frame->frame, FRAME_STATUS_MOVE); + } + + if ((status & FRAME_STATUS_RESIZE) && window->xdg_toplevel) { + input_ungrab(input); + + xdg_toplevel_resize(window->xdg_toplevel, + input_get_seat(input), + window->display->serial, + location); + + frame_status_clear(frame->frame, FRAME_STATUS_RESIZE); + } +} + +#define DOUBLE_CLICK_PERIOD 250 +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 window_frame *frame = data; + enum theme_location location; + + frame->double_click = 0; + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + if (time - frame->last_time <= DOUBLE_CLICK_PERIOD) { + frame->double_click = 1; + frame->did_double = 1; + } else + frame->did_double = 0; + + frame->last_time = time; + } else if (frame->did_double == 1) { + frame->double_click = 1; + frame->did_double = 0; + } + + if (frame->double_click) + location = frame_double_click(frame->frame, input, + button, state); + else + location = frame_pointer_button(frame->frame, input, + button, state); + + frame_handle_status(frame, input, time, location); +} + +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_frame *frame = data; + + frame->double_click = 0; + if (time - frame->last_time <= DOUBLE_CLICK_PERIOD && + frame->last_id == id) { + frame->double_click = 1; + frame->did_double = 1; + frame->double_id = id; + } else + frame->did_double = 0; + + frame->last_time = time; + frame->last_id = id; + + if (frame->double_click) + frame_double_touch_down(frame->frame, input, id, x, y); + else + frame_touch_down(frame->frame, input, id, x, y); + + frame_handle_status(frame, input, time, THEME_LOCATION_CLIENT_AREA); +} + +static void +frame_touch_up_handler(struct widget *widget, + struct input *input, uint32_t serial, uint32_t time, + int32_t id, void *data) +{ + struct window_frame *frame = data; + + if (frame->double_id == id && frame->did_double) { + frame->did_double = 0; + frame->double_id = 0; + frame_double_touch_up(frame->frame, input, id); + } else + frame_touch_up(frame->frame, input, id); + frame_handle_status(frame, input, time, THEME_LOCATION_CLIENT_AREA); +} + +struct widget * +window_frame_create(struct window *window, void *data) +{ + struct window_frame *frame; + uint32_t buttons; + + if (window->custom) { + buttons = FRAME_BUTTON_NONE; + } else { + buttons = FRAME_BUTTON_ALL; + } + + frame = xzalloc(sizeof *frame); + frame->frame = frame_create(window->display->theme, 0, 0, + buttons, window->title, NULL); + if (!frame->frame) { + free(frame); + return NULL; + } + + 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_leave_handler(frame->widget, frame_leave_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); + widget_set_touch_up_handler(frame->widget, frame_touch_up_handler); + + window->frame = frame; + + return frame->child; +} + +void +window_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->maximized ? 0 : t->margin; + + if (!widget->window->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 +window_frame_destroy(struct window_frame *frame) +{ + frame_destroy(frame->frame); + + /* 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 +touch_grab(struct input *input, int32_t touch_id) +{ + input->touch_grab = 1; + input->touch_grab_id = touch_id; +} + +void +touch_ungrab(struct input *input) +{ + struct touch_point *tp, *tmp; + + input->touch_grab = 0; + + wl_list_for_each_safe(tp, tmp, + &input->touch_point_list, link) { + if (tp->id != input->touch_grab_id) + continue; + wl_list_remove(&tp->link); + free(tp); + + return; + } +} + +void +input_grab(struct input *input, struct widget *widget, uint32_t button) +{ + input->grab = widget; + input->grab_button = button; + + input_set_focus_widget(input, widget, input->sx, input->sy); +} + +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 +cursor_delay_timer_reset(struct input *input, uint32_t duration) +{ + if (!duration) + input->cursor_timer_running = false; + else + input->cursor_timer_running = true; + + toytimer_arm_once_usec(&input->cursor_timer, duration * 1000); +} + +static void cancel_pointer_image_update(struct input *input) +{ + if (input->cursor_timer_running) + cursor_delay_timer_reset(input, 0); +} + +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; + cancel_pointer_image_update(input); +} + +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; + } + + window = wl_surface_get_user_data(surface); + if (surface != window->main_surface->surface) { + DBG("Ignoring input event from subsurface %p\n", surface); + return; + } + + input->display->serial = serial; + input->pointer_enter_serial = serial; + input->pointer_focus = 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); + + if (!window) + return; + + input->sx = sx; + input->sy = sy; + + /* when making the window smaller - e.g. after an unmaximise we might + * still have a pending motion event that the compositor has picked + * based on the old surface dimensions. However, if we have an active + * grab, we expect to see input from outside the window anyway. + */ + if (!input->grab && (sx < window->main_surface->allocation.x || + sy < window->main_surface->allocation.y || + 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 void +pointer_handle_frame(void *data, struct wl_pointer *pointer) +{ + struct input *input = data; + struct widget *widget; + + widget = input->focus_widget; + if (input->grab) + widget = input->grab; + if (widget && widget->pointer_frame_handler) + (*widget->pointer_frame_handler)(widget, + input, + widget->user_data); +} + +static void +pointer_handle_axis_source(void *data, struct wl_pointer *pointer, + uint32_t source) +{ + struct input *input = data; + struct widget *widget; + + widget = input->focus_widget; + if (input->grab) + widget = input->grab; + if (widget && widget->axis_source_handler) + (*widget->axis_source_handler)(widget, + input, + source, + widget->user_data); +} + +static void +pointer_handle_axis_stop(void *data, struct wl_pointer *pointer, + uint32_t time, uint32_t axis) +{ + struct input *input = data; + struct widget *widget; + + widget = input->focus_widget; + if (input->grab) + widget = input->grab; + if (widget && widget->axis_stop_handler) + (*widget->axis_stop_handler)(widget, + input, time, + axis, + widget->user_data); +} + +static void +pointer_handle_axis_discrete(void *data, struct wl_pointer *pointer, + uint32_t axis, int32_t discrete) +{ + struct input *input = data; + struct widget *widget; + + widget = input->focus_widget; + if (input->grab) + widget = input->grab; + if (widget && widget->axis_discrete_handler) + (*widget->axis_discrete_handler)(widget, + input, + axis, + discrete, + 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, + pointer_handle_frame, + pointer_handle_axis_source, + pointer_handle_axis_stop, + pointer_handle_axis_discrete, +}; + +static void +input_remove_keyboard_focus(struct input *input) +{ + struct window *window = input->keyboard_focus; + + toytimer_disarm(&input->repeat_timer); + + if (!window) + return; + + if (window->keyboard_focus_handler) + (*window->keyboard_focus_handler)(window, NULL, + window->user_data); + + input->keyboard_focus = NULL; +} + +static void +keyboard_repeat_func(struct toytimer *tt) +{ + struct input *input = container_of(tt, struct input, repeat_timer); + struct window *window = input->keyboard_focus; + + 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; + struct xkb_keymap *keymap; + struct xkb_state *state; +#ifdef HAVE_XKBCOMMON_COMPOSE + struct xkb_compose_table *compose_table; + struct xkb_compose_state *compose_state; +#endif + char *locale; + 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_PRIVATE, fd, 0); + if (map_str == MAP_FAILED) { + close(fd); + return; + } + + /* Set up XKB keymap */ + keymap = xkb_keymap_new_from_string(input->display->xkb_context, + map_str, + XKB_KEYMAP_FORMAT_TEXT_V1, + 0); + munmap(map_str, size); + close(fd); + + if (!keymap) { + fprintf(stderr, "failed to compile keymap\n"); + return; + } + + /* Set up XKB state */ + state = xkb_state_new(keymap); + if (!state) { + fprintf(stderr, "failed to create XKB state\n"); + xkb_keymap_unref(keymap); + return; + } + + /* Look up the preferred locale, falling back to "C" as default */ + if (!(locale = getenv("LC_ALL"))) + if (!(locale = getenv("LC_CTYPE"))) + if (!(locale = getenv("LANG"))) + locale = "C"; + + /* Set up XKB compose table */ +#ifdef HAVE_XKBCOMMON_COMPOSE + compose_table = + xkb_compose_table_new_from_locale(input->display->xkb_context, + locale, + XKB_COMPOSE_COMPILE_NO_FLAGS); + if (compose_table) { + /* Set up XKB compose state */ + compose_state = xkb_compose_state_new(compose_table, + XKB_COMPOSE_STATE_NO_FLAGS); + if (compose_state) { + xkb_compose_state_unref(input->xkb.compose_state); + xkb_compose_table_unref(input->xkb.compose_table); + input->xkb.compose_state = compose_state; + input->xkb.compose_table = compose_table; + } else { + fprintf(stderr, "could not create XKB compose state. " + "Disabiling compose.\n"); + xkb_compose_table_unref(compose_table); + compose_table = NULL; + } + } else { + fprintf(stderr, "could not create XKB compose table for locale '%s'. " + "Disabiling compose\n", locale); + } +#endif + + xkb_keymap_unref(input->xkb.keymap); + xkb_state_unref(input->xkb.state); + input->xkb.keymap = keymap; + input->xkb.state = state; + + input->xkb.control_mask = + 1 << xkb_keymap_mod_get_index(input->xkb.keymap, "Control"); + input->xkb.alt_mask = + 1 << xkb_keymap_mod_get_index(input->xkb.keymap, "Mod1"); + input->xkb.shift_mask = + 1 << xkb_keymap_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; + + if (!surface) { + /* enter event for a window we've just destroyed */ + return; + } + + input->display->serial = serial; + input->keyboard_focus = wl_surface_get_user_data(surface); + + window = input->keyboard_focus; + 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); +} + +/* Translate symbols appropriately if a compose sequence is being entered */ +static xkb_keysym_t +process_key_press(xkb_keysym_t sym, struct input *input) +{ +#ifdef HAVE_XKBCOMMON_COMPOSE + if (!input->xkb.compose_state) + return sym; + if (sym == XKB_KEY_NoSymbol) + return sym; + if (xkb_compose_state_feed(input->xkb.compose_state, + sym) != XKB_COMPOSE_FEED_ACCEPTED) + return sym; + + switch (xkb_compose_state_get_status(input->xkb.compose_state)) { + case XKB_COMPOSE_COMPOSING: + return XKB_KEY_NoSymbol; + case XKB_COMPOSE_COMPOSED: + return xkb_compose_state_get_one_sym(input->xkb.compose_state); + case XKB_COMPOSE_CANCELLED: + return XKB_KEY_NoSymbol; + case XKB_COMPOSE_NOTHING: + return sym; + default: + return sym; + } +#else + return sym; +#endif +} + +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; + + /* We only use input grabs for pointer events for now, so just + * ignore key presses if a grab is active. We expand the key + * event delivery mechanism to route events to widgets to + * properly handle key grabs. In the meantime, this prevents + * key event delivery while a grab is active. */ + if (input->grab && input->grab_button == 0) + return; + + num_syms = xkb_state_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->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) { + window_close(window); + } else if (window->key_handler) { + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) + sym = process_key_press(sym, input); + + (*window->key_handler)(window, input, time, key, + sym, state, window->user_data); + } + + if (state == WL_KEYBOARD_KEY_STATE_RELEASED && + key == input->repeat_key) { + toytimer_disarm(&input->repeat_timer); + } else if (state == WL_KEYBOARD_KEY_STATE_PRESSED && + xkb_keymap_key_repeats(input->xkb.keymap, code)) { + input->repeat_sym = sym; + input->repeat_key = key; + input->repeat_time = time; + its.it_interval.tv_sec = input->repeat_rate_sec; + its.it_interval.tv_nsec = input->repeat_rate_nsec; + its.it_value.tv_sec = input->repeat_delay_sec; + its.it_value.tv_nsec = input->repeat_delay_nsec; + toytimer_arm(&input->repeat_timer, &its); + } +} + +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_MODS_DEPRESSED | + XKB_STATE_MODS_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 void +set_repeat_info(struct input *input, int32_t rate, int32_t delay) +{ + input->repeat_rate_sec = input->repeat_rate_nsec = 0; + input->repeat_delay_sec = input->repeat_delay_nsec = 0; + + /* a rate of zero disables any repeating, regardless of the delay's + * value */ + if (rate == 0) + return; + + if (rate == 1) + input->repeat_rate_sec = 1; + else + input->repeat_rate_nsec = 1000000000 / rate; + + input->repeat_delay_sec = delay / 1000; + delay -= (input->repeat_delay_sec * 1000); + input->repeat_delay_nsec = delay * 1000 * 1000; +} + +static void +keyboard_handle_repeat_info(void *data, struct wl_keyboard *keyboard, + int32_t rate, int32_t delay) +{ + struct input *input = data; + + set_repeat_info(input, rate, delay); +} + +static const struct wl_keyboard_listener keyboard_listener = { + keyboard_handle_keymap, + keyboard_handle_enter, + keyboard_handle_leave, + keyboard_handle_key, + keyboard_handle_modifiers, + keyboard_handle_repeat_info + +}; + +static void +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); + + if (!surface) { + /* down event for a window we've just destroyed */ + return; + } + + 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; + } + + if (surface != input->touch_focus->main_surface->surface) { + DBG("Ignoring input event from subsurface %p\n", surface); + input->touch_focus = NULL; + return; + } + + if (input->grab) + widget = input->grab; + else + 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; + tp->x = sx; + tp->y = sy; + 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; + + tp->x = sx; + tp->y = sy; + 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); + } +} + +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 void +touch_handle_shape(void *data, struct wl_touch *wl_touch, int32_t id, + wl_fixed_t major, wl_fixed_t minor) +{ +} + +static void +touch_handle_orientation(void *data, struct wl_touch *wl_touch, int32_t id, + wl_fixed_t orientation) +{ +} + +static const struct wl_touch_listener touch_listener = { + touch_handle_down, + touch_handle_up, + touch_handle_motion, + touch_handle_frame, + touch_handle_cancel, + touch_handle_shape, + touch_handle_orientation, +}; + +static void +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) { + if (input->seat_version >= WL_POINTER_RELEASE_SINCE_VERSION) + wl_pointer_release(input->pointer); + else + 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) { + if (input->seat_version >= WL_KEYBOARD_RELEASE_SINCE_VERSION) + wl_keyboard_release(input->keyboard); + else + 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) { + if (input->seat_version >= WL_TOUCH_RELEASE_SINCE_VERSION) + wl_touch_release(input->touch); + else + 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; +} + +int +input_get_touch(struct input *input, int32_t id, float *x, float *y) +{ + struct touch_point *tp; + + wl_list_for_each(tp, &input->touch_point_list, link) { + if (tp->id != id) + continue; + + *x = tp->x; + *y = tp->y; + return 0; + } + + return -1; +} + +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; + uint32_t dnd_action; + uint32_t source_actions; + 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 void +data_offer_source_actions(void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions) +{ + struct data_offer *offer = data; + + offer->source_actions = source_actions; +} + +static void +data_offer_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) +{ + struct data_offer *offer = data; + + offer->dnd_action = dnd_action; +} + +static const struct wl_data_offer_listener data_offer_listener = { + data_offer_offer, + data_offer_source_actions, + data_offer_action +}; + +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; + + if (!surface) { + /* enter event for a window we've just destroyed */ + return; + } + + window = wl_surface_get_user_data(surface); + input->drag_enter_serial = serial; + input->drag_focus = window, + input->drag_x = x; + input->drag_y = y; + + if (!input->touch_grab) + input->pointer_enter_serial = serial; + + 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; + + if (input->display->data_device_manager_version >= + WL_DATA_OFFER_SET_ACTIONS_SINCE_VERSION) { + wl_data_offer_set_actions(offer, + WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | + WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE, + WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE); + } + } else { + input->drag_offer = NULL; + types_data = NULL; + } + + 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->drag_focus; + float x = wl_fixed_to_double(x_w); + float y = wl_fixed_to_double(y_w); + void *types_data; + + input->drag_x = x; + input->drag_y = 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->drag_focus; + float x, y; + + x = input->drag_x; + y = input->drag_y; + + if (window->drop_handler) + window->drop_handler(window, input, + x, y, window->user_data); + + if (input->touch_grab) + touch_ungrab(input); +} + +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_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); + wl_pointer_set_cursor(input->pointer, input->pointer_enter_serial, + input->pointer_surface, + image->hotspot_x, image->hotspot_y); +} + +static const struct wl_callback_listener pointer_surface_listener; + +static bool +input_set_pointer_special(struct input *input) +{ + if (input->current_cursor == CURSOR_BLANK) { + wl_pointer_set_cursor(input->pointer, + input->pointer_enter_serial, + NULL, 0, 0); + return true; + } + + if (input->current_cursor == CURSOR_UNSET) + return true; + + return false; +} + +static void +schedule_pointer_image_update(struct input *input, + struct wl_cursor *cursor, + uint32_t duration, + bool force_frame) +{ + /* Some silly cursor sets have enormous pauses in them. In these + * cases it's better to use a timer even if it results in less + * accurate presentation, since it will save us having to set the + * same cursor image over and over again. + * + * This is really not the way we're supposed to time any kind of + * animation, but we're pretending it's OK here because we don't + * want animated cursors with long delays to needlessly hog CPU. + * + * We use force_frame to ensure we don't accumulate large timing + * errors by running off the wrong clock. + */ + if (!force_frame && duration > 100) { + struct timespec tp; + + clock_gettime(CLOCK_MONOTONIC, &tp); + input->cursor_timer_start = tp.tv_sec * 1000 + + tp.tv_nsec / 1000000; + cursor_delay_timer_reset(input, duration); + return; + } + + /* for short durations we'll just spin on frame callbacks for + * accurate timing - the way any kind of timing sensitive animation + * should really be done. */ + input->cursor_frame_cb = wl_surface_frame(input->pointer_surface); + wl_callback_add_listener(input->cursor_frame_cb, + &pointer_surface_listener, input); + +} + +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; + uint32_t duration; + bool force_frame = true; + + cancel_pointer_image_update(input); + + if (callback) { + assert(callback == input->cursor_frame_cb); + wl_callback_destroy(callback); + input->cursor_frame_cb = NULL; + force_frame = false; + } + + if (!input->pointer) + return; + + if (input_set_pointer_special(input)) + 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; + + input->cursor_anim_current = time; + + if (time == 0 || input->cursor_anim_start == 0) { + duration = 0; + i = 0; + } else + i = wl_cursor_frame_and_duration( + cursor, + time - input->cursor_anim_start, + &duration); + + if (cursor->image_count > 1) + schedule_pointer_image_update(input, cursor, duration, + force_frame); + + input_set_pointer_image_index(input, i); +} + +static void +cursor_timer_func(struct toytimer *tt) +{ + struct input *input = container_of(tt, struct input, cursor_timer); + struct timespec tp; + struct wl_cursor *cursor; + uint32_t time; + + if (!input->cursor_timer_running) + return; + + cursor = input->display->cursors[input->current_cursor]; + if (!cursor) + return; + + clock_gettime(CLOCK_MONOTONIC, &tp); + time = tp.tv_sec * 1000 + tp.tv_nsec / 1000000 - input->cursor_timer_start; + pointer_surface_frame_callback(input, NULL, input->cursor_anim_current + time); +} + +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 && !input_set_pointer_special(input)) { + /* 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) +{ + if (input->data_device) + 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->drag_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); + struct display *display = offer->input->display; + 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) { + if ((offer != offer->input->selection_offer) && + (display->data_device_manager_version >= + WL_DATA_OFFER_FINISH_SINCE_VERSION)) + wl_data_offer_finish(offer->offer); + 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->drag_x; + input->drag_offer->y = input->drag_y; +} + +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->xdg_toplevel) + return; + + xdg_toplevel_move(window->xdg_toplevel, input->seat, 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 +window_do_resize(struct window *window) +{ + struct surface *surface; + + 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); + } + + if (!window->fullscreen && !window->maximized) + window->saved_allocation = window->pending_allocation; + + if (window->confined && window->confined_widget) { + struct wl_compositor *compositor = window->display->compositor; + struct wl_region *region; + struct widget *widget = window->confined_widget; + + region = wl_compositor_create_region(compositor); + wl_region_add(region, + widget->allocation.x, + widget->allocation.y, + widget->allocation.width, + widget->allocation.height); + zwp_confined_pointer_v1_set_region(window->confined_pointer, + region); + wl_region_destroy(region); + } +} + +static void +idle_resize(struct window *window) +{ + 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); + + window_do_resize(window); +} + +static void +undo_resize(struct window *window) +{ + window->pending_allocation.width = + window->main_surface->server_allocation.width; + window->pending_allocation.height = + window->main_surface->server_allocation.height; + + DBG("back to %dx%d\n", + window->main_surface->server_allocation.width, + window->main_surface->server_allocation.height); + + window_do_resize(window); + + if (window->pending_allocation.width == 0 && + window->pending_allocation.height == 0) { + fprintf(stderr, "Error: Could not draw a surface, " + "most likely due to insufficient disk space in " + "%s (XDG_RUNTIME_DIR).\n", getenv("XDG_RUNTIME_DIR")); + exit(EXIT_FAILURE); + } +} + +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 int +window_get_shadow_margin(struct window *window) +{ + if (window->frame && !window->fullscreen) + return frame_get_shadow_margin(window->frame->frame); + else + return 0; +} + +void +window_inhibit_redraw(struct window *window) +{ + window->redraw_inhibited = 1; + wl_list_remove(&window->redraw_task.link); + wl_list_init(&window->redraw_task.link); + window->redraw_task_scheduled = 0; +} + +void +window_uninhibit_redraw(struct window *window) +{ + window->redraw_inhibited = 0; + if (window->redraw_needed || window->resize_needed) + window_schedule_redraw_task(window); +} + +static void +xdg_surface_handle_configure(void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) +{ + struct window *window = data; + + xdg_surface_ack_configure(window->xdg_surface, serial); + + if (window->state_changed_handler) + window->state_changed_handler(window, window->user_data); + + window_uninhibit_redraw(window); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + xdg_surface_handle_configure +}; + +static void +xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *xdg_toplevel, + int32_t width, int32_t height, + struct wl_array *states) +{ + struct window *window = data; + uint32_t *p; + + window->maximized = 0; + window->fullscreen = 0; + window->resizing = 0; + window->focused = 0; + + wl_array_for_each(p, states) { + uint32_t state = *p; + switch (state) { + case XDG_TOPLEVEL_STATE_MAXIMIZED: + window->maximized = 1; + break; + case XDG_TOPLEVEL_STATE_FULLSCREEN: + window->fullscreen = 1; + break; + case XDG_TOPLEVEL_STATE_RESIZING: + window->resizing = 1; + break; + case XDG_TOPLEVEL_STATE_ACTIVATED: + window->focused = 1; + break; + default: + /* Unknown state */ + break; + } + } + + if (window->frame) { + if (window->maximized) { + frame_set_flag(window->frame->frame, FRAME_FLAG_MAXIMIZED); + } else { + frame_unset_flag(window->frame->frame, FRAME_FLAG_MAXIMIZED); + } + + if (window->focused) { + frame_set_flag(window->frame->frame, FRAME_FLAG_ACTIVE); + } else { + frame_unset_flag(window->frame->frame, FRAME_FLAG_ACTIVE); + } + } + + if (width > 0 && height > 0) { + /* The width / height params are for window geometry, + * but window_schedule_resize takes allocation. Add + * on the shadow margin to get the difference. */ + int margin = window_get_shadow_margin(window); + + window_schedule_resize(window, + width + margin * 2, + height + margin * 2); + } else if (window->saved_allocation.width > 0 && + window->saved_allocation.height > 0) { + window_schedule_resize(window, + window->saved_allocation.width, + window->saved_allocation.height); + } +} + +static void +xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_surface) +{ + struct window *window = data; + window_close(window); +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + xdg_toplevel_handle_configure, + xdg_toplevel_handle_close, +}; + +static void +window_sync_parent(struct window *window) +{ + struct xdg_toplevel *parent_toplevel; + + if (!window->xdg_surface) + return; + + if (window->parent == window->last_parent) + return; + + if (window->parent) + parent_toplevel = window->parent->xdg_toplevel; + else + parent_toplevel = NULL; + + xdg_toplevel_set_parent(window->xdg_toplevel, parent_toplevel); + window->last_parent = window->parent; +} + +static void +window_get_geometry(struct window *window, struct rectangle *geometry) +{ + if (window->frame && !window->fullscreen) + frame_input_rect(window->frame->frame, + &geometry->x, + &geometry->y, + &geometry->width, + &geometry->height); + else + window_get_allocation(window, geometry); +} + +static void +window_sync_geometry(struct window *window) +{ + struct rectangle geometry; + + if (!window->xdg_surface) + return; + + window_get_geometry(window, &geometry); + if (geometry.x == window->last_geometry.x && + geometry.y == window->last_geometry.y && + geometry.width == window->last_geometry.width && + geometry.height == window->last_geometry.height) + return; + + xdg_surface_set_window_geometry(window->xdg_surface, + geometry.x, + geometry.y, + geometry.width, + geometry.height); + window->last_geometry = geometry; +} + +static void +window_flush(struct window *window) +{ + struct surface *surface; + + assert(!window->redraw_inhibited); + + if (!window->custom) { + if (window->xdg_surface) + window_sync_geometry(window); + if (window->xdg_toplevel) + window_sync_parent(window); + } + + wl_list_for_each(surface, &window->subsurface_list, link) { + if (surface == window->main_surface) + continue; + + surface_flush(surface); + } + + surface_flush(window->main_surface); +} + +static void +menu_destroy(struct menu *menu) +{ + widget_destroy(menu->widget); + window_destroy(menu->window); + frame_destroy(menu->frame); + free(menu); +} + +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 int +surface_redraw(struct surface *surface) +{ + DBG_OBJ(surface->surface, "begin\n"); + + if (!surface->window->redraw_needed && !surface->redraw_needed) + return 0; + + /* 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 0; + + DBG_OBJ(surface->frame_cb, "cancelled\n"); + wl_callback_destroy(surface->frame_cb); + } + + if (surface->widget->use_cairo && + !widget_get_cairo_surface(surface->widget)) { + DBG_OBJ(surface->surface, "cancelled due to buffer failure\n"); + return -1; + } + + 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"); + return 0; +} + +static void +idle_redraw(struct task *task, uint32_t events) +{ + struct window *window = container_of(task, struct window, redraw_task); + struct surface *surface; + int failed = 0; + int resized = 0; + + 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); + resized = 1; + } + + if (surface_redraw(window->main_surface) < 0) { + /* + * Only main_surface failure will cause us to undo the resize. + * If sub-surfaces fail, they will just be broken with old + * content. + */ + failed = 1; + } else { + wl_list_for_each(surface, &window->subsurface_list, link) { + if (surface == window->main_surface) + continue; + + surface_redraw(surface); + } + } + + window->redraw_needed = 0; + window_flush(window); + + wl_list_for_each(surface, &window->subsurface_list, link) + surface_set_synchronized_default(surface); + + if (resized && failed) { + /* Restore widget tree to correspond to what is on screen. */ + undo_resize(window); + } +} + +static void +window_schedule_redraw_task(struct window *window) +{ + if (window->redraw_inhibited) + 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->fullscreen; +} + +void +window_set_fullscreen(struct window *window, int fullscreen) +{ + if (!window->xdg_toplevel) + return; + + if (window->fullscreen == fullscreen) + return; + + if (fullscreen) + xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); + else + xdg_toplevel_unset_fullscreen(window->xdg_toplevel); +} + +int +window_is_maximized(struct window *window) +{ + return window->maximized; +} + +void +window_set_maximized(struct window *window, int maximized) +{ + if (!window->xdg_toplevel) + return; + + if (window->maximized == maximized) + return; + + if (maximized) + xdg_toplevel_set_maximized(window->xdg_toplevel); + else + xdg_toplevel_unset_maximized(window->xdg_toplevel); +} + +int +window_is_resizing(struct window *window) +{ + return window->resizing; +} + +void +window_set_minimized(struct window *window) +{ + if (!window->xdg_toplevel) + return; + + xdg_toplevel_set_minimized(window->xdg_toplevel); +} + +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_state_changed_handler(struct window *window, + window_state_changed_handler_t handler) +{ + window->state_changed_handler = handler; +} + +void +window_set_pointer_locked_handler(struct window *window, + locked_pointer_locked_handler_t locked, + locked_pointer_unlocked_handler_t unlocked) +{ + window->pointer_unlocked_handler = unlocked; + window->pointer_locked_handler = locked; +} + +void +window_set_pointer_confined_handler(struct window *window, + confined_pointer_confined_handler_t confined, + confined_pointer_unconfined_handler_t unconfined) +{ + window->pointer_confined_handler = confined; + window->pointer_unconfined_handler = unconfined; +} + +void +window_set_locked_pointer_motion_handler(struct window *window, + window_locked_pointer_motion_handler_t handler) +{ + window->locked_pointer_motion_handler = handler; +} + +void +window_set_title(struct window *window, const char *title) +{ + free(window->title); + window->title = strdup(title); + if (window->frame) { + frame_set_title(window->frame->frame, title); + widget_schedule_redraw(window->frame->widget); + } + if (window->xdg_toplevel) + xdg_toplevel_set_title(window->xdg_toplevel, 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)); +} + +static void +relative_pointer_handle_motion(void *data, struct zwp_relative_pointer_v1 *pointer, + uint32_t utime_hi, + uint32_t utime_lo, + wl_fixed_t dx, + wl_fixed_t dy, + wl_fixed_t dx_unaccel, + wl_fixed_t dy_unaccel) +{ + struct input *input = data; + struct window *window = input->pointer_focus; + uint32_t ms = (((uint64_t) utime_hi) << 32 | utime_lo) / 1000; + + if (window->locked_pointer_motion_handler && + window->pointer_locked) { + window->locked_pointer_motion_handler( + window, input, ms, + wl_fixed_to_double(dx), + wl_fixed_to_double(dy), + window->user_data); + } +} + +static const struct zwp_relative_pointer_v1_listener relative_pointer_listener = { + relative_pointer_handle_motion, +}; + +static void +locked_pointer_locked(void *data, + struct zwp_locked_pointer_v1 *locked_pointer) +{ + struct input *input = data; + struct window *window = input->locked_window; + + if (!window) + return; + + window->pointer_locked = true; + + if (window->pointer_locked_handler) { + window->pointer_locked_handler(window, + input, + window->user_data); + } +} + +static void +locked_pointer_unlocked(void *data, + struct zwp_locked_pointer_v1 *locked_pointer) +{ + struct input *input = data; + struct window *window = input->locked_window; + + if (!window) + return; + + window_unlock_pointer(window); + + input->locked_window = NULL; + + if (window->pointer_unlocked_handler) { + window->pointer_unlocked_handler(window, + input, + window->user_data); + } +} + +static const struct zwp_locked_pointer_v1_listener locked_pointer_listener = { + locked_pointer_locked, + locked_pointer_unlocked, +}; + +int +window_lock_pointer(struct window *window, struct input *input) +{ + struct zwp_relative_pointer_manager_v1 *relative_pointer_manager = + window->display->relative_pointer_manager; + struct zwp_pointer_constraints_v1 *pointer_constraints = + window->display->pointer_constraints; + struct zwp_relative_pointer_v1 *relative_pointer; + struct zwp_locked_pointer_v1 *locked_pointer; + + if (!window->display->relative_pointer_manager) + return -1; + + if (!window->display->pointer_constraints) + return -1; + + if (window->locked_pointer) + return -1; + + if (window->confined_pointer) + return -1; + + if (!input->pointer) + return -1; + + relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer( + relative_pointer_manager, input->pointer); + zwp_relative_pointer_v1_add_listener(relative_pointer, + &relative_pointer_listener, + input); + + locked_pointer = + zwp_pointer_constraints_v1_lock_pointer(pointer_constraints, + window->main_surface->surface, + input->pointer, + NULL, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); + zwp_locked_pointer_v1_add_listener(locked_pointer, + &locked_pointer_listener, + input); + + window->locked_pointer = locked_pointer; + window->relative_pointer = relative_pointer; + input->locked_window = window; + + return 0; +} + +void +window_unlock_pointer(struct window *window) +{ + if (!window->locked_pointer) + return; + + zwp_locked_pointer_v1_destroy(window->locked_pointer); + zwp_relative_pointer_v1_destroy(window->relative_pointer); + window->locked_pointer = NULL; + window->relative_pointer = NULL; + window->pointer_locked = false; +} + +void +widget_set_locked_pointer_cursor_hint(struct widget *widget, + float x, float y) +{ + struct window *window = widget->window; + + if (!window->locked_pointer) + return; + + zwp_locked_pointer_v1_set_cursor_position_hint(window->locked_pointer, + wl_fixed_from_double(x), + wl_fixed_from_double(y)); + wl_surface_commit(window->main_surface->surface); +} + +static void +confined_pointer_confined(void *data, + struct zwp_confined_pointer_v1 *confined_pointer) +{ + struct input *input = data; + struct window *window = input->confined_window; + + if (!window) + return; + + window->confined = true; + + if (window->pointer_confined_handler) { + window->pointer_confined_handler(window, + input, + window->user_data); + } +} + +static void +confined_pointer_unconfined(void *data, + struct zwp_confined_pointer_v1 *confined_pointer) +{ + struct input *input = data; + struct window *window = input->confined_window; + + if (!window) + return; + + window_unconfine_pointer(window); + + window->confined = false; + input->confined_window = NULL; + + if (window->pointer_unconfined_handler) { + window->pointer_unconfined_handler(window, + input, + window->user_data); + } +} + +static const struct zwp_confined_pointer_v1_listener confined_pointer_listener = { + confined_pointer_confined, + confined_pointer_unconfined, +}; + +int +window_confine_pointer_to_rectangles(struct window *window, + struct input *input, + struct rectangle *rectangles, + int num_rectangles) +{ + struct zwp_pointer_constraints_v1 *pointer_constraints = + window->display->pointer_constraints; + struct zwp_confined_pointer_v1 *confined_pointer; + struct wl_compositor *compositor = window->display->compositor; + struct wl_region *region = NULL; + int i; + + if (!window->display->pointer_constraints) + return -1; + + if (window->locked_pointer) + return -1; + + if (window->confined_pointer) + return -1; + + if (!input->pointer) + return -1; + + if (num_rectangles >= 1) { + region = wl_compositor_create_region(compositor); + for (i = 0; i < num_rectangles; i++) { + wl_region_add(region, + rectangles[i].x, + rectangles[i].y, + rectangles[i].width, + rectangles[i].height); + } + } + + confined_pointer = + zwp_pointer_constraints_v1_confine_pointer(pointer_constraints, + window->main_surface->surface, + input->pointer, + region, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); + if (region) + wl_region_destroy(region); + + zwp_confined_pointer_v1_add_listener(confined_pointer, + &confined_pointer_listener, + input); + + window->confined_pointer = confined_pointer; + window->confined_widget = NULL; + input->confined_window = window; + + return 0; +} + +void +window_update_confine_rectangles(struct window *window, + struct rectangle *rectangles, + int num_rectangles) +{ + struct wl_compositor *compositor = window->display->compositor; + struct wl_region *region; + int i; + + region = wl_compositor_create_region(compositor); + for (i = 0; i < num_rectangles; i++) { + wl_region_add(region, + rectangles[i].x, + rectangles[i].y, + rectangles[i].width, + rectangles[i].height); + } + + zwp_confined_pointer_v1_set_region(window->confined_pointer, region); + + wl_region_destroy(region); +} + +int +window_confine_pointer_to_widget(struct window *window, + struct widget *widget, + struct input *input) +{ + int ret; + + if (widget) { + ret = window_confine_pointer_to_rectangles(window, + input, + &widget->allocation, + 1); + window->confined_widget = widget; + return ret; + } else { + return window_confine_pointer_to_rectangles(window, + input, + NULL, + 0); + } +} + +void +window_unconfine_pointer(struct window *window) +{ + if (!window->confined_pointer) + return; + + zwp_confined_pointer_v1_destroy(window->confined_pointer); + window->confined_pointer = NULL; + window->confined = false; +} + +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 = xzalloc(sizeof *surface); + 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); + surface->viewport = NULL; + + return surface; +} + +static enum window_buffer_type +get_preferred_buffer_type(struct display *display) +{ +#ifdef HAVE_CAIRO_EGL + if (display->argb_device && !getenv("TOYTOOLKIT_NO_EGL")) + return WINDOW_BUFFER_TYPE_EGL_WINDOW; +#endif + + return WINDOW_BUFFER_TYPE_SHM; +} + +static struct window * +window_create_internal(struct display *display, int custom) +{ + struct window *window; + struct surface *surface; + + window = xzalloc(sizeof *window); + wl_list_init(&window->subsurface_list); + window->display = display; + + surface = surface_create(window); + window->main_surface = surface; + + assert(custom || display->xdg_shell); + + window->custom = custom; + + surface->buffer_type = get_preferred_buffer_type(display); + + wl_surface_set_user_data(surface->surface, window); + wl_list_insert(display->window_list.prev, &window->link); + wl_list_init(&window->redraw_task.link); + + wl_list_init (&window->window_output_list); + + return window; +} + +struct window * +window_create(struct display *display) +{ + struct window *window; + + window = window_create_internal(display, 0); + + if (window->display->xdg_shell) { + window->xdg_surface = + xdg_wm_base_get_xdg_surface(window->display->xdg_shell, + window->main_surface->surface); + fail_on_null(window->xdg_surface, 0, __FILE__, __LINE__); + + xdg_surface_add_listener(window->xdg_surface, + &xdg_surface_listener, window); + + window->xdg_toplevel = + xdg_surface_get_toplevel(window->xdg_surface); + fail_on_null(window->xdg_toplevel, 0, __FILE__, __LINE__); + + xdg_toplevel_add_listener(window->xdg_toplevel, + &xdg_toplevel_listener, window); + + window_inhibit_redraw(window); + + wl_surface_commit(window->main_surface->surface); + } + + return window; +} + +struct window * +window_create_custom(struct display *display) +{ + return window_create_internal(display, 1); +} + +void +window_set_parent(struct window *window, + struct window *parent_window) +{ + window->parent = parent_window; + window_sync_parent(window); +} + +struct window * +window_get_parent(struct window *window) +{ + return window->parent; +} + +static void +menu_set_item(struct menu *menu, int sy) +{ + int32_t x, y, width, height; + int next; + + frame_interior(menu->frame, &x, &y, &width, &height); + next = (sy - y) / 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 release after press-drag-release or + * click-motion-click. */ + menu->func(menu->user_data, input, menu->current); + input_ungrab(menu->input); + menu_destroy(menu); + } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { + menu->release_count++; + } +} + +static void +menu_touch_up_handler(struct widget *widget, + struct input *input, + uint32_t serial, + uint32_t time, + int32_t id, + void *data) +{ + struct menu *menu = data; + + input_ungrab(input); + menu_destroy(menu); +} + +static void +menu_redraw_handler(struct widget *widget, void *data) +{ + cairo_t *cr; + struct menu *menu = data; + int32_t x, y, width, height, i; + + cr = widget_cairo_create(widget); + + frame_repaint(menu->frame, cr); + frame_interior(menu->frame, &x, &y, &width, &height); + + theme_set_background_source(menu->window->display->theme, + cr, THEME_FRAME_ACTIVE); + cairo_rectangle(cr, x, y, width, height); + cairo_fill(cr); + + cairo_select_font_face(cr, "sans", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cr, 12); + + 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, x, y + i * 20, width, 20); + cairo_fill(cr); + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_move_to(cr, x + 10, y + i * 20 + 16); + cairo_show_text(cr, menu->entries[i]); + } else { + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_move_to(cr, x + 10, y + i * 20 + 16); + cairo_show_text(cr, menu->entries[i]); + } + } + + cairo_destroy(cr); +} + +static void +xdg_popup_handle_configure(void *data, + struct xdg_popup *xdg_popup, + int32_t x, + int32_t y, + int32_t width, + int32_t height) +{ +} + +static void +xdg_popup_handle_popup_done(void *data, struct xdg_popup *xdg_popup) +{ + struct window *window = data; + struct menu *menu = window->main_surface->widget->user_data; + + input_ungrab(menu->input); + menu_destroy(menu); +} + +static const struct xdg_popup_listener xdg_popup_listener = { + xdg_popup_handle_configure, + xdg_popup_handle_popup_done, +}; + +static struct menu * +create_menu(struct display *display, + struct input *input, uint32_t time, + menu_func_t func, const char **entries, int count, + void *user_data) +{ + struct window *window; + struct menu *menu; + + menu = malloc(sizeof *menu); + if (!menu) + return NULL; + + window = window_create_internal(display, 0); + if (!window) { + free(menu); + return NULL; + } + + menu->window = window; + menu->user_data = user_data; + menu->widget = window_add_widget(menu->window, menu); + menu->frame = frame_create(window->display->theme, 0, 0, + FRAME_BUTTON_NONE, NULL, NULL); + fail_on_null(menu->frame, 0, __FILE__, __LINE__); + menu->entries = entries; + menu->count = count; + menu->release_count = 0; + menu->current = -1; + menu->time = time; + menu->func = func; + menu->input = input; + + input_ungrab(input); + + 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); + widget_set_touch_up_handler(menu->widget, menu_touch_up_handler); + + input_grab(input, menu->widget, 0); + frame_resize_inside(menu->frame, 200, count * 20); + frame_set_flag(menu->frame, FRAME_FLAG_ACTIVE); + window_schedule_resize(window, frame_width(menu->frame), + frame_height(menu->frame)); + + return menu; +} + +static struct xdg_positioner * +create_simple_positioner(struct display *display, + int x, int y, int w, int h) +{ + struct xdg_positioner *positioner; + + positioner = xdg_wm_base_create_positioner(display->xdg_shell); + fail_on_null(positioner, 0, __FILE__, __LINE__); + xdg_positioner_set_anchor_rect(positioner, x, y, 1, 1); + xdg_positioner_set_size(positioner, w, h); + xdg_positioner_set_anchor(positioner, + XDG_POSITIONER_ANCHOR_TOP_LEFT); + xdg_positioner_set_gravity(positioner, + XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT); + + return positioner; +} + +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 menu *menu; + struct window *window; + int32_t ix, iy; + struct rectangle parent_geometry; + struct xdg_positioner *positioner; + + menu = create_menu(display, input, time, func, entries, count, parent); + + if (menu == NULL) + return; + + window = menu->window; + + window_set_buffer_scale (menu->window, window_get_buffer_scale (parent)); + window_set_buffer_transform (menu->window, window_get_buffer_transform (parent)); + + window->x = x; + window->y = y; + + frame_interior(menu->frame, &ix, &iy, NULL, NULL); + window_get_geometry(parent, &parent_geometry); + + if (!display->xdg_shell) + return; + + window->xdg_surface = + xdg_wm_base_get_xdg_surface(display->xdg_shell, + window->main_surface->surface); + fail_on_null(window->xdg_surface, 0, __FILE__, __LINE__); + + xdg_surface_add_listener(window->xdg_surface, + &xdg_surface_listener, window); + + positioner = create_simple_positioner(display, + window->x - (ix + parent_geometry.x), + window->y - (iy + parent_geometry.y), + frame_width(menu->frame), + frame_height(menu->frame)); + window->xdg_popup = xdg_surface_get_popup(window->xdg_surface, + parent->xdg_surface, + positioner); + fail_on_null(window->xdg_popup, 0, __FILE__, __LINE__); + xdg_positioner_destroy(positioner); + xdg_popup_grab(window->xdg_popup, input->seat, + display_get_serial(window->display)); + xdg_popup_add_listener(window->xdg_popup, + &xdg_popup_listener, window); + + window_inhibit_redraw(window); + + wl_surface_commit(window->main_surface->surface); +} + +void +window_set_buffer_type(struct window *window, enum window_buffer_type type) +{ + window->main_surface->buffer_type = type; +} + +enum window_buffer_type +window_get_buffer_type(struct window *window) +{ + return window->main_surface->buffer_type; +} + +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); + surface->buffer_type = window_get_buffer_type(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"); + } + + window->resize_needed = 1; + window_schedule_redraw(window); + + 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; + + if (output->make) + free(output->make); + output->make = strdup(make); + + if (output->model) + free(output->model); + output->model = strdup(model); +} + +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); + output->server_output_id = id; + 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); +} + +static void +display_destroy_output(struct display *d, uint32_t id) +{ + struct output *output; + + wl_list_for_each(output, &d->output_list, link) { + if (output->server_output_id == id) { + output_destroy(output); + break; + } + } +} + +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_global_handler_remove(struct display *display, + display_global_handler_t remove_handler) +{ + display->global_handler_remove = remove_handler; + if (!remove_handler) + return; +} + +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; +} + +const char * +output_get_make(struct output *output) +{ + return output->make; +} + +const char * +output_get_model(struct output *output) +{ + return output->model; +} + +static void +fini_xkb(struct input *input) +{ + xkb_state_unref(input->xkb.state); + xkb_keymap_unref(input->xkb.keymap); +} + +static void +display_add_input(struct display *d, uint32_t id, int display_seat_version) +{ + struct input *input; + int seat_version = MIN(display_seat_version, 7); + + input = xzalloc(sizeof *input); + input->display = d; + input->seat = wl_registry_bind(d->registry, id, &wl_seat_interface, + seat_version); + input->touch_focus = NULL; + input->pointer_focus = NULL; + input->keyboard_focus = NULL; + input->seat_version = seat_version; + + 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); + + if (d->data_device_manager) { + 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); + + toytimer_init(&input->cursor_timer, CLOCK_MONOTONIC, d, + cursor_timer_func); + + set_repeat_info(input, 40, 400); + toytimer_init(&input->repeat_timer, CLOCK_MONOTONIC, d, + keyboard_repeat_func); +} + +static void +display_add_data_device(struct display *d, uint32_t id, int ddm_version) +{ + struct input *input; + + d->data_device_manager_version = MIN(ddm_version, 3); + d->data_device_manager = + wl_registry_bind(d->registry, id, + &wl_data_device_manager_interface, + d->data_device_manager_version); + + wl_list_for_each(input, &d->input_list, link) { + if (!input->data_device) { + 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); + } + } +} + +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); + + if (input->data_device) { + if (input->display->data_device_manager_version >= 2) + wl_data_device_release(input->data_device); + else + wl_data_device_destroy(input->data_device); + } + if (input->seat_version >= WL_POINTER_RELEASE_SINCE_VERSION) { + if (input->touch) + wl_touch_release(input->touch); + if (input->pointer) + wl_pointer_release(input->pointer); + if (input->keyboard) + wl_keyboard_release(input->keyboard); + } else { + if (input->touch) + wl_touch_destroy(input->touch); + if (input->pointer) + wl_pointer_destroy(input->pointer); + if (input->keyboard) + wl_keyboard_destroy(input->keyboard); + } + + fini_xkb(input); + + wl_surface_destroy(input->pointer_surface); + + wl_list_remove(&input->link); + wl_seat_destroy(input->seat); + toytimer_fini(&input->repeat_timer); + toytimer_fini(&input->cursor_timer); + free(input); +} + +static void +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) +{ + xdg_wm_base_pong(shell, serial); +} + +static const struct xdg_wm_base_listener wm_base_listener = { + xdg_wm_base_ping, +}; + +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) { + display_add_input(d, id, version); + } else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0 && + version == ZWP_RELATIVE_POINTER_MANAGER_V1_VERSION) { + d->relative_pointer_manager = + wl_registry_bind(registry, id, + &zwp_relative_pointer_manager_v1_interface, + 1); + } else if (strcmp(interface, "zwp_pointer_constraints_v1") == 0 && + version == ZWP_POINTER_CONSTRAINTS_V1_VERSION) { + d->pointer_constraints = + wl_registry_bind(registry, id, + &zwp_pointer_constraints_v1_interface, + 1); + } else if (strcmp(interface, "wl_shm") == 0) { + d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); + } else if (strcmp(interface, "wl_data_device_manager") == 0) { + display_add_data_device(d, id, version); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + d->xdg_shell = wl_registry_bind(registry, id, + &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(d->xdg_shell, &wm_base_listener, d); + } 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, "wl_subcompositor") == 0) { + d->subcompositor = + wl_registry_bind(registry, id, + &wl_subcompositor_interface, 1); + } else if (!strcmp(interface, "wp_viewporter")) { + d->viewporter = + wl_registry_bind(registry, id, + &wp_viewporter_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; + + if (strcmp(global->interface, "wl_output") == 0) + display_destroy_output(d, name); + + /* XXX: Should destroy remaining bound globals */ + + if (d->global_handler_remove) + d->global_handler_remove(d, name, global->interface, + global->version, d->user_data); + + 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 = + weston_platform_get_egl_display(EGL_PLATFORM_WAYLAND_KHR, + d->display, NULL); + + 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 = xmalloc(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: %s\n", + strerror(errno)); + free(d); + return NULL; + } + + d->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + 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->registry = wl_display_get_registry(d->display); + wl_registry_add_listener(d->registry, ®istry_listener, d); + + if (wl_display_roundtrip(d->display) < 0) { + fprintf(stderr, "Failed to process Wayland connection: %s\n", + strerror(errno)); + 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->xdg_shell) + xdg_wm_base_destroy(display->xdg_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) +{ + if (wl_list_empty(&display->output_list)) + return NULL; + + 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) +{ + if (display->data_device_manager) + return wl_data_device_manager_create_data_source(display->data_device_manager); + else + return NULL; +} + +EGLConfig +display_get_argb_egl_config(struct display *d) +{ + return d->argb_config; +} + +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; +} + +int +display_get_data_device_manager_version(struct display *display) +{ + return display->data_device_manager_version; +} + +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; +} + +static void +toytimer_fire(struct task *tsk, uint32_t events) +{ + uint64_t e; + struct toytimer *tt; + + tt = container_of(tsk, struct toytimer, tsk); + + if (events != EPOLLIN) + fprintf(stderr, "unexpected timerfd events %x\n", events); + + if (!(events & EPOLLIN)) + return; + + if (read(tt->fd, &e, sizeof e) != sizeof e) { + /* If we change the timer between the fd becoming + * readable and getting here, there'll be nothing to + * read and we get EAGAIN. */ + if (errno != EAGAIN) + fprintf(stderr, "timer read failed: %s\n", + strerror(errno)); + return; + } + + tt->callback(tt); +} + +void +toytimer_init(struct toytimer *tt, clockid_t clock, struct display *display, + toytimer_cb callback) +{ + memset(tt, 0, sizeof *tt); + + tt->fd = timerfd_create(clock, TFD_CLOEXEC | TFD_NONBLOCK); + if (tt->fd == -1) { + fprintf(stderr, "creating timer failed: %s\n", + strerror(errno)); + abort(); + } + + tt->display = display; + tt->callback = callback; + tt->tsk.run = toytimer_fire; + display_watch_fd(display, tt->fd, EPOLLIN, &tt->tsk); +} + +void +toytimer_fini(struct toytimer *tt) +{ + display_unwatch_fd(tt->display, tt->fd); + close(tt->fd); + tt->fd = -1; +} + +void +toytimer_arm(struct toytimer *tt, const struct itimerspec *its) +{ + int ret; + + ret = timerfd_settime(tt->fd, 0, its, NULL); + if (ret < 0) { + fprintf(stderr, "timer setup failed: %s\n", strerror(errno)); + abort(); + } +} + +#define USEC_PER_SEC 1000000 + +void +toytimer_arm_once_usec(struct toytimer *tt, uint32_t usec) +{ + struct itimerspec its; + + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + its.it_value.tv_sec = usec / USEC_PER_SEC; + its.it_value.tv_nsec = (usec % USEC_PER_SEC) * 1000; + toytimer_arm(tt, &its); +} + +void +toytimer_disarm(struct toytimer *tt) +{ + struct itimerspec its = {}; + + toytimer_arm(tt, &its); +} diff --git a/clients/window.h b/clients/window.h new file mode 100644 index 0000000..c66dd06 --- /dev/null +++ b/clients/window.h @@ -0,0 +1,744 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef _WINDOW_H_ +#define _WINDOW_H_ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include "shared/platform.h" + +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; +}; + +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 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_set_global_handler_remove(struct display *display, + display_global_handler_t remove_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 + +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); + +int +display_get_data_device_manager_version(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_DND_MOVE, + CURSOR_DND_COPY, + CURSOR_DND_FORBIDDEN, + + 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)(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 (*window_state_changed_handler_t)(struct window *window, + void *data); + + +typedef void (*window_locked_pointer_motion_handler_t)(struct window *window, + struct input *input, + uint32_t time, + float x, float y, + void *data); + +typedef void (*locked_pointer_locked_handler_t)(struct window *window, + struct input *input, + void *data); + +typedef void (*locked_pointer_unlocked_handler_t)(struct window *window, + struct input *input, + void *data); + +typedef void (*confined_pointer_confined_handler_t)(struct window *window, + struct input *input, + void *data); + +typedef void (*confined_pointer_unconfined_handler_t)(struct window *window, + struct input *input, + 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); + +typedef void (*widget_pointer_frame_handler_t)(struct widget *widget, + struct input *input, + void *data); + +typedef void (*widget_axis_source_handler_t)(struct widget *widget, + struct input *input, + uint32_t source, + void *data); + +typedef void (*widget_axis_stop_handler_t)(struct widget *widget, + struct input *input, + uint32_t time, + uint32_t axis, + void *data); + +typedef void (*widget_axis_discrete_handler_t)(struct widget *widget, + struct input *input, + uint32_t axis, + int32_t discrete, + void *data); + +struct window * +window_create(struct display *display); +struct window * +window_create_custom(struct display *display); + +void +window_set_parent(struct window *window, struct window *parent_window); +struct window * +window_get_parent(struct window *window); + +int +window_has_focus(struct window *window); + +typedef void (*menu_func_t)(void *data, struct input *input, int index); + +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_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); + +int +window_lock_pointer(struct window *window, struct input *input); + +void +window_unlock_pointer(struct window *window); + +void +widget_set_locked_pointer_cursor_hint(struct widget *widget, + float x, float y); + +int +window_confine_pointer_to_rectangles(struct window *window, + struct input *input, + struct rectangle *rectangles, + int num_rectangles); + +void +window_update_confine_rectangles(struct window *window, + struct rectangle *rectangles, + int num_rectangles); + +int +window_confine_pointer_to_widget(struct window *window, + struct widget *widget, + struct input *input); + +void +window_unconfine_pointer(struct window *window); + +cairo_surface_t * +window_get_surface(struct window *window); + +struct wl_surface * +window_get_wl_surface(struct window *window); + +struct wl_subsurface * +widget_get_wl_subsurface(struct widget *widget); + +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); + +enum window_buffer_type +window_get_buffer_type(struct window *window); + +int +window_is_fullscreen(struct window *window); + +void +window_set_fullscreen(struct window *window, int fullscreen); + +int +window_is_maximized(struct window *window); + +void +window_set_maximized(struct window *window, int maximized); + +int +window_is_resizing(struct window *window); + +void +window_set_minimized(struct window *window); + +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_state_changed_handler(struct window *window, + window_state_changed_handler_t handler); + +void +window_set_pointer_locked_handler(struct window *window, + locked_pointer_locked_handler_t locked, + locked_pointer_unlocked_handler_t unlocked); + +void +window_set_pointer_confined_handler(struct window *window, + confined_pointer_confined_handler_t confined, + confined_pointer_unconfined_handler_t unconfined); + +void +window_set_locked_pointer_motion_handler( + struct window *window, window_locked_pointer_motion_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); + +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_set_pointer_frame_handler(struct widget *widget, + widget_pointer_frame_handler_t handler); +void +widget_set_axis_handlers(struct widget *widget, + widget_axis_handler_t axis_handler, + widget_axis_source_handler_t axis_source_handler, + widget_axis_stop_handler_t axis_stop_handler, + widget_axis_discrete_handler_t axis_discrete_handler); + +void +window_inhibit_redraw(struct window *window); +void +window_uninhibit_redraw(struct window *window); +void +widget_schedule_redraw(struct widget *widget); +void +widget_set_use_cairo(struct widget *widget, int use_cairo); + +/* + * Sets the viewport destination for the widget's surface + * return 0 on success and -1 on failure. Set width and height to + * -1 to reset the viewport. + */ +int +widget_set_viewport_destination(struct widget *widget, int width, int height); + +struct widget * +window_frame_create(struct window *window, void *data); + +void +window_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); + +int +input_get_touch(struct input *input, int32_t id, float *x, float *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 +touch_grab(struct input *input, int32_t touch_id); + +void +touch_ungrab(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); + +const char * +output_get_make(struct output *output); + +const char * +output_get_model(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); + +struct toytimer; +typedef void (*toytimer_cb)(struct toytimer *); + +struct toytimer { + struct display *display; + struct task tsk; + int fd; + toytimer_cb callback; +}; + +void +toytimer_init(struct toytimer *tt, clockid_t clock, struct display *display, + toytimer_cb callback); + +void +toytimer_fini(struct toytimer *tt); + +void +toytimer_arm(struct toytimer *tt, const struct itimerspec *its); + +void +toytimer_arm_once_usec(struct toytimer *tt, uint32_t usec); + +void +toytimer_disarm(struct toytimer *tt); + +#endif diff --git a/compositor/cms-colord.c b/compositor/cms-colord.c new file mode 100644 index 0000000..d4efdb4 --- /dev/null +++ b/compositor/cms-colord.c @@ -0,0 +1,588 @@ +/* + * Copyright © 2013 Richard Hughes + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "weston.h" +#include "cms-helper.h" +#include "shared/helpers.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 bool +edid_value_valid(const char *str) +{ + if (str == NULL) + return false; + if (str[0] == '\0') + return false; + if (strcmp(str, "unknown") == 0) + return false; + return true; +} + +static gchar * +get_output_id(struct cms_colord *cms, struct weston_output *o) +{ + struct weston_head *head; + const gchar *tmp; + GString *device_id; + + /* XXX: What to do with multiple heads? + * This is potentially unstable, if head configuration is changed + * while the output is enabled. */ + head = weston_output_get_first_head(o); + + if (wl_list_length(&o->head_list) > 1) { + weston_log("colord: WARNING: multiple heads are not supported (output %s).\n", + o->name); + } + + /* 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(head->make)) { + tmp = g_hash_table_lookup(cms->pnp_ids, head->make); + if (tmp == NULL) + tmp = head->make; + g_string_append_printf(device_id, "-%s", tmp); + } + if (edid_value_valid(head->model)) + g_string_append_printf(device_id, "-%s", head->model); + if (edid_value_valid(head->serial_number)) + g_string_append_printf(device_id, "-%s", head->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\n"); + } +} + +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_output *ocms = + container_of(listener, struct cms_output, destroy_listener); + struct weston_output *o = (struct weston_output *) data; + struct cms_colord *cms = ocms->cms; + gchar *device_id; + + device_id = get_output_id(cms, o); + 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) +{ + struct weston_head *head; + CdDevice *device; + const gchar *tmp; + gchar *device_id; + GError *error = NULL; + GHashTable *device_props; + struct cms_output *ocms; + + /* XXX: What to do with multiple heads? */ + head = weston_output_get_first_head(o); + + /* 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(head->make)) { + tmp = g_hash_table_lookup(cms->pnp_ids, head->make); + if (tmp == NULL) + tmp = head->make; + g_hash_table_insert (device_props, + g_strdup(CD_DEVICE_PROPERTY_VENDOR), + g_strdup(tmp)); + } + if (edid_value_valid(head->model)) { + g_hash_table_insert (device_props, + g_strdup(CD_DEVICE_PROPERTY_MODEL), + g_strdup(head->model)); + } + if (edid_value_valid(head->serial_number)) { + g_hash_table_insert (device_props, + g_strdup(CD_DEVICE_PROPERTY_SERIAL), + g_strdup(head->serial_number)); + } + if (head->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\n"); + 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) +{ + if (cms->loop) { + g_main_loop_quit(cms->loop); + g_main_loop_unref(cms->loop); + } + if (cms->thread) + g_thread_join(cms->thread); + + /* cms->devices must be destroyed before other resources, as + * the other resources are needed during output cleanup in + * cms->devices unref. + */ + 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); + + g_free(cms->pnp_ids_data); + g_hash_table_unref(cms->pnp_ids); + + wl_list_remove(&cms->destroy_listener.link); + 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; + struct cms_colord *cms = ocms->cms; + struct weston_output *o = ocms->o; + 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 unplugged %s\n", device_id); + + wl_list_remove(&ocms->destroy_listener.link); + 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); + } + + g_object_unref(ocms->device); + g_slice_free(struct cms_output, ocms); + g_free (device_id); +} + +WL_EXPORT int +wet_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 (!weston_compositor_add_destroy_listener_once(ec, + &cms->destroy_listener, + colord_notifier_destroy)) { + free(cms); + return 0; + } + +#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); + + /* 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/compositor/cms-helper.c b/compositor/cms-helper.c new file mode 100644 index 0000000..bc56a9d --- /dev/null +++ b/compositor/cms-helper.c @@ -0,0 +1,136 @@ +/* + * Copyright © 2013 Richard Hughes + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifdef HAVE_LCMS +#include +#endif + +#include +#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 = zalloc(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/compositor/cms-helper.h b/compositor/cms-helper.h new file mode 100644 index 0000000..4a5b711 --- /dev/null +++ b/compositor/cms-helper.h @@ -0,0 +1,75 @@ +/* + * Copyright © 2013 Richard Hughes + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _WESTON_CMS_H_ +#define _WESTON_CMS_H_ + +#include "config.h" + +#include + +/* 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 embedded 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/compositor/cms-static.c b/compositor/cms-static.c new file mode 100644 index 0000000..540d6ad --- /dev/null +++ b/compositor/cms-static.c @@ -0,0 +1,124 @@ +/* + * Copyright © 2013 Richard Hughes + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include +#include "cms-helper.h" +#include "shared/helpers.h" +#include "weston.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(wet_get_config(cms->ec), + "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 && strlen(profile) > 0) { + weston_log("cms-static: failed to load %s\n", profile); + } else { + weston_log("cms-static: loading %s for %s\n", + (p != NULL) ? profile : "identity LUT", + 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 +wet_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; + + if (!weston_compositor_add_destroy_listener_once(ec, + &cms->destroy_listener, + cms_notifier_destroy)) { + free(cms); + return 0; + } + + 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/compositor/executable.c b/compositor/executable.c new file mode 100644 index 0000000..0644077 --- /dev/null +++ b/compositor/executable.c @@ -0,0 +1,34 @@ +/* + * Copyright © 2019 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include "weston.h" + +int +main(int argc, char *argv[]) +{ + return wet_main(argc, argv); +} diff --git a/compositor/main.c b/compositor/main.c new file mode 100755 index 0000000..efaa7b2 --- /dev/null +++ b/compositor/main.c @@ -0,0 +1,3463 @@ +/* + * Copyright © 2010-2011 Intel Corporation + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2012-2018 Collabora, Ltd. + * Copyright © 2010-2011 Benjamin Franzke + * Copyright © 2013 Jason Ekstrand + * Copyright © 2017, 2018 General Electric Company + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "weston.h" +#include +#include "shared/os-compatibility.h" +#include "shared/helpers.h" +#include "shared/string-helpers.h" +#include "git-version.h" +#include +#include "weston.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../remoting/remoting-plugin.h" +#include "../pipewire/pipewire-plugin.h" + +#define WINDOW_TITLE "Weston Compositor" +/* flight recorder size (in bytes) */ +#define DEFAULT_FLIGHT_REC_SIZE (5 * 1024 * 1024) + +struct wet_output_config { + int width; + int height; + int32_t scale; + uint32_t transform; +}; + +struct wet_compositor; +struct wet_layoutput; + +struct wet_head_tracker { + struct wl_listener head_destroy_listener; +}; + +/** User data for each weston_output */ +struct wet_output { + struct weston_output *output; + struct wl_listener output_destroy_listener; + struct wet_layoutput *layoutput; + struct wl_list link; /**< in wet_layoutput::output_list */ +}; + +#define MAX_CLONE_HEADS 16 + +struct wet_head_array { + struct weston_head *heads[MAX_CLONE_HEADS]; /**< heads to add */ + unsigned n; /**< the number of heads */ +}; + +/** A layout output + * + * Contains wet_outputs that are all clones (independent CRTCs). + * Stores output layout information in the future. + */ +struct wet_layoutput { + struct wet_compositor *compositor; + struct wl_list compositor_link; /**< in wet_compositor::layoutput_list */ + struct wl_list output_list; /**< wet_output::link */ + char *name; + struct weston_config_section *section; + struct wet_head_array add; /**< tmp: heads to add as clones */ +}; + +struct wet_compositor { + struct weston_compositor *compositor; + struct weston_config *config; + struct wet_output_config *parsed_options; + bool drm_use_current_mode; + struct wl_listener heads_changed_listener; + int (*simple_output_configure)(struct weston_output *output); + bool init_failed; + struct wl_list layoutput_list; /**< wet_layoutput::compositor_link */ +}; + +static FILE *weston_logfile = NULL; +// OHOS remove logger +//static struct weston_log_scope *log_scope; +//static struct weston_log_scope *protocol_scope; +static int cached_tm_mday = -1; + +// OHOS remove logger +//static char * +//weston_log_timestamp(char *buf, size_t len) +//{ +// struct timeval tv; +// struct tm *brokendown_time; +// char datestr[128]; +// char timestr[128]; +// +// gettimeofday(&tv, NULL); +// +// brokendown_time = localtime(&tv.tv_sec); +// if (brokendown_time == NULL) { +// snprintf(buf, len, "%s", "[(NULL)localtime] "); +// return buf; +// } +// +// memset(datestr, 0, sizeof(datestr)); +// if (brokendown_time->tm_mday != cached_tm_mday) { +// strftime(datestr, sizeof(datestr), "Date: %Y-%m-%d %Z\n", +// brokendown_time); +// cached_tm_mday = brokendown_time->tm_mday; +// } +// +// strftime(timestr, sizeof(timestr), "%H:%M:%S", brokendown_time); +// /* if datestr is empty it prints only timestr*/ +// snprintf(buf, len, "%s[%s.%03li]", datestr, +// timestr, (tv.tv_usec / 1000)); +// +// return buf; +//} +// +//static void +//custom_handler(const char *fmt, va_list arg) +//{ +// char timestr[512]; +// +// weston_log_scope_printf(log_scope, "%s libwayland: ", +// weston_log_timestamp(timestr, +// sizeof(timestr))); +// weston_log_scope_vprintf(log_scope, fmt, arg); +//} +// +//static bool +//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) { +// os_fd_set_cloexec(fileno(weston_logfile)); +// } else { +// fprintf(stderr, "Failed to open %s: %s\n", filename, strerror(errno)); +// return false; +// } +// } +// +// if (weston_logfile == NULL) +// weston_logfile = stderr; +// else +// setvbuf(weston_logfile, NULL, _IOLBF, 256); +// +// return true; +//} +// +//static void +//weston_log_file_close(void) +//{ +// if ((weston_logfile != stderr) && (weston_logfile != NULL)) +// fclose(weston_logfile); +// weston_logfile = stderr; +//} +// +//static int +//vlog(const char *fmt, va_list ap) +//{ +// const char *oom = "Out of memory"; +// char timestr[128]; +// int len = 0; +// char *str; +// +// if (weston_log_scope_is_enabled(log_scope)) { +// int len_va; +// char *log_timestamp = weston_log_timestamp(timestr, +// sizeof(timestr)); +// len_va = vasprintf(&str, fmt, ap); +// if (len_va >= 0) { +// len = weston_log_scope_printf(log_scope, "%s %s", +// log_timestamp, str); +// free(str); +// } else { +// len = weston_log_scope_printf(log_scope, "%s %s", +// log_timestamp, oom); +// } +// } +// +// return len; +//} +// +//static int +//vlog_continue(const char *fmt, va_list argp) +//{ +// return weston_log_scope_vprintf(log_scope, fmt, argp); +//} +// +//static const char * +//get_next_argument(const char *signature, char* type) +//{ +// for(; *signature; ++signature) { +// switch(*signature) { +// case 'i': +// case 'u': +// case 'f': +// case 's': +// case 'o': +// case 'n': +// case 'a': +// case 'h': +// *type = *signature; +// return signature + 1; +// } +// } +// *type = '\0'; +// return signature; +//} +// +//static void +//protocol_log_fn(void *user_data, +// enum wl_protocol_logger_type direction, +// const struct wl_protocol_logger_message *message) +//{ +// FILE *fp; +// char *logstr; +// size_t logsize; +// char timestr[128]; +// struct wl_resource *res = message->resource; +// const char *signature = message->message->signature; +// int i; +// char type; +// +// if (!weston_log_scope_is_enabled(protocol_scope)) +// return; +// +// fp = open_memstream(&logstr, &logsize); +// if (!fp) +// return; +// +// weston_log_scope_timestamp(protocol_scope, +// timestr, sizeof timestr); +// fprintf(fp, "%s ", timestr); +// fprintf(fp, "client %p %s ", wl_resource_get_client(res), +// direction == WL_PROTOCOL_LOGGER_REQUEST ? "rq" : "ev"); +// fprintf(fp, "%s@%u.%s(", +// wl_resource_get_class(res), +// wl_resource_get_id(res), +// message->message->name); +// +// for (i = 0; i < message->arguments_count; i++) { +// signature = get_next_argument(signature, &type); +// +// if (i > 0) +// fprintf(fp, ", "); +// +// switch (type) { +// case 'u': +// fprintf(fp, "%u", message->arguments[i].u); +// break; +// case 'i': +// fprintf(fp, "%d", message->arguments[i].i); +// break; +// case 'f': +// fprintf(fp, "%f", +// wl_fixed_to_double(message->arguments[i].f)); +// break; +// case 's': +// fprintf(fp, "\"%s\"", message->arguments[i].s); +// break; +// case 'o': +// if (message->arguments[i].o) { +// struct wl_resource* resource; +// resource = (struct wl_resource*) message->arguments[i].o; +// fprintf(fp, "%s@%u", +// wl_resource_get_class(resource), +// wl_resource_get_id(resource)); +// } +// else +// fprintf(fp, "nil"); +// break; +// case 'n': +// fprintf(fp, "new id %s@", +// (message->message->types[i]) ? +// message->message->types[i]->name : +// "[unknown]"); +// if (message->arguments[i].n != 0) +// fprintf(fp, "%u", message->arguments[i].n); +// else +// fprintf(fp, "nil"); +// break; +// case 'a': +// fprintf(fp, "array"); +// break; +// case 'h': +// fprintf(fp, "fd %d", message->arguments[i].h); +// break; +// } +// } +// +// fprintf(fp, ")\n"); +// +// if (fclose(fp) == 0) +// weston_log_scope_write(protocol_scope, logstr, logsize); +// +// free(logstr); +//} + +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; + + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + 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"); + continue; + } + + wl_list_remove(&p->link); + p->cleanup(p, status); + } + + if (pid < 0 && errno != ECHILD) + weston_log("waitpid error %s\n", strerror(errno)); + + return 1; +} + +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 launch 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: %s\n", strerror(errno)); + return; + } + + snprintf(s, sizeof s, "%d", clientfd); + setenv("WAYLAND_SOCKET", s, 1); + + if (execl(path, path, NULL) < 0) + weston_log("compositor: executing '%s' failed: %s\n", + path, strerror(errno)); +} + +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': %s\n", + path, strerror(errno)); + return NULL; + } + + pid = fork(); + if (pid == -1) { + close(sv[0]); + close(sv[1]); + weston_log("weston_client_launch: " + "fork failed while launching '%s': %s\n", path, + strerror(errno)); + 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; +} + +WL_EXPORT void +weston_watch_process(struct weston_process *process) +{ + wl_list_insert(&child_process_list, &process->link); +} + +struct process_info { + struct weston_process proc; + char *path; +}; + +static void +process_handle_sigchld(struct weston_process *process, int status) +{ + struct process_info *pinfo = + container_of(process, struct process_info, proc); + + /* + * There are no guarantees whether this runs before or after + * the wl_client destructor. + */ + + if (WIFEXITED(status)) { + weston_log("%s exited with status %d\n", pinfo->path, + WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + weston_log("%s died on signal %d\n", pinfo->path, + WTERMSIG(status)); + } else { + weston_log("%s disappeared\n", pinfo->path); + } + + free(pinfo->path); + free(pinfo); +} + +WL_EXPORT struct wl_client * +weston_client_start(struct weston_compositor *compositor, const char *path) +{ + struct process_info *pinfo; + struct wl_client *client; + + pinfo = zalloc(sizeof *pinfo); + if (!pinfo) + return NULL; + + pinfo->path = strdup(path); + if (!pinfo->path) + goto out_free; + + client = weston_client_launch(compositor, &pinfo->proc, path, + process_handle_sigchld); + if (!client) + goto out_str; + + return client; + +out_str: + free(pinfo->path); + +out_free: + free(pinfo); + + return 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); +} + +static struct wet_compositor * +to_wet_compositor(struct weston_compositor *compositor) +{ + return weston_compositor_get_user_data(compositor); +} + +static struct wet_output_config * +wet_init_parsed_options(struct weston_compositor *ec) +{ + struct wet_compositor *compositor = to_wet_compositor(ec); + struct wet_output_config *config; + + config = zalloc(sizeof *config); + + if (!config) { + perror("out of memory"); + return NULL; + } + + config->width = 0; + config->height = 0; + config->scale = 0; + config->transform = UINT32_MAX; + + compositor->parsed_options = config; + + return config; +} + +WL_EXPORT struct weston_config * +wet_get_config(struct weston_compositor *ec) +{ + struct wet_compositor *compositor = to_wet_compositor(ec); + + return compositor->config; +} + +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 (current mode is %o),\n" + "and must be owned by the user (current owner is 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) +{ + FILE *out = error_code == EXIT_SUCCESS ? stdout : stderr; + + fprintf(out, + "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\n" +#if defined(BUILD_DRM_COMPOSITOR) + "\t\t\t\tdrm-backend.so\n" +#endif +#if defined(BUILD_FBDEV_COMPOSITOR) + "\t\t\t\tfbdev-backend.so\n" +#endif +#if defined(BUILD_HEADLESS_COMPOSITOR) + "\t\t\t\theadless-backend.so\n" +#endif +#if defined(BUILD_RDP_COMPOSITOR) + "\t\t\t\trdp-backend.so\n" +#endif +#if defined(BUILD_WAYLAND_COMPOSITOR) + "\t\t\t\twayland-backend.so\n" +#endif +#if defined(BUILD_X11_COMPOSITOR) + "\t\t\t\tx11-backend.so\n" +#endif + " --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" +#if defined(BUILD_XWAYLAND) + " --xwayland\t\tLoad the xwayland module\n" +#endif + " --modules\t\tLoad the comma-separated list of modules\n" + " --log=FILE\t\tLog to the given file\n" + " -c, --config=FILE\tConfig file to load, defaults to weston.ini\n" + " --no-config\t\tDo not read weston.ini\n" + " --wait-for-debugger\tRaise SIGSTOP on start-up\n" + " --debug\t\tEnable debug extension\n" + " -l, --logger-scopes=SCOPE\n\t\t\tSpecify log scopes to " + "subscribe to.\n\t\t\tCan specify multiple scopes, " + "each followed by comma\n" + " -f, --flight-rec-scopes=SCOPE\n\t\t\tSpecify log scopes to " + "subscribe to.\n\t\t\tCan specify multiple scopes, " + "each followed by comma\n" + " -h, --help\t\tThis help message\n\n"); + +#if defined(BUILD_DRM_COMPOSITOR) + fprintf(out, + "Options for drm-backend.so:\n\n" + " --seat=SEAT\t\tThe seat that weston should run on, instead of the seat defined in XDG_SEAT\n" + " --tty=TTY\t\tThe tty to use\n" + " --drm-device=CARD\tThe DRM device to use, e.g. \"card0\".\n" + " --use-pixman\t\tUse the pixman (CPU) renderer\n" + " --current-mode\tPrefer current KMS mode over EDID preferred mode\n" + " --continue-without-input\tAllow the compositor to start without input devices\n\n"); +#endif + +#if defined(BUILD_FBDEV_COMPOSITOR) + fprintf(out, + "Options for fbdev-backend.so:\n\n" + " --tty=TTY\t\tThe tty to use\n" + " --device=DEVICE\tThe framebuffer device to use\n" + " --seat=SEAT\t\tThe seat that weston should run on, instead of the seat defined in XDG_SEAT\n" + "\n"); +#endif + +#if defined(BUILD_HEADLESS_COMPOSITOR) + fprintf(out, + "Options for headless-backend.so:\n\n" + " --width=WIDTH\t\tWidth of memory surface\n" + " --height=HEIGHT\tHeight of memory surface\n" + " --scale=SCALE\t\tScale factor of output\n" + " --transform=TR\tThe output transformation, TR is one of:\n" + "\tnormal 90 180 270 flipped flipped-90 flipped-180 flipped-270\n" + " --use-pixman\t\tUse the pixman (CPU) renderer (default: no rendering)\n" + " --use-gl\t\tUse the GL renderer (default: no rendering)\n" + " --no-outputs\t\tDo not create any virtual outputs\n" + "\n"); +#endif + +#if defined(BUILD_RDP_COMPOSITOR) + fprintf(out, + "Options for rdp-backend.so:\n\n" + " --width=WIDTH\t\tWidth of desktop\n" + " --height=HEIGHT\tHeight of desktop\n" + " --env-socket\t\tUse socket defined in RDP_FD env variable as peer connection\n" + " --address=ADDR\tThe address to bind\n" + " --port=PORT\t\tThe port to listen on\n" + " --no-clients-resize\tThe RDP peers will be forced to the size of the desktop\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 + +#if defined(BUILD_WAYLAND_COMPOSITOR) + fprintf(out, + "Options for wayland-backend.so:\n\n" + " --width=WIDTH\t\tWidth of Wayland surface\n" + " --height=HEIGHT\tHeight of Wayland surface\n" + " --scale=SCALE\t\tScale factor of output\n" + " --fullscreen\t\tRun in fullscreen mode\n" + " --use-pixman\t\tUse the pixman (CPU) renderer\n" + " --output-count=COUNT\tCreate multiple outputs\n" + " --sprawl\t\tCreate one fullscreen output for every parent output\n" + " --display=DISPLAY\tWayland display to connect to\n\n"); +#endif + +#if defined(BUILD_X11_COMPOSITOR) + fprintf(out, + "Options for x11-backend.so:\n\n" + " --width=WIDTH\t\tWidth of X window\n" + " --height=HEIGHT\tHeight of X window\n" + " --scale=SCALE\t\tScale factor of output\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"); +#endif + + exit(error_code); +} + +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; +} + +static const char * +clock_name(clockid_t clk_id) +{ + static const char *names[] = { + [CLOCK_REALTIME] = "CLOCK_REALTIME", + [CLOCK_MONOTONIC] = "CLOCK_MONOTONIC", + [CLOCK_MONOTONIC_RAW] = "CLOCK_MONOTONIC_RAW", + [CLOCK_REALTIME_COARSE] = "CLOCK_REALTIME_COARSE", + [CLOCK_MONOTONIC_COARSE] = "CLOCK_MONOTONIC_COARSE", +#ifdef CLOCK_BOOTTIME + [CLOCK_BOOTTIME] = "CLOCK_BOOTTIME", +#endif + }; + + if (clk_id < 0 || (unsigned)clk_id >= ARRAY_LENGTH(names)) + return "unknown"; + + return names[clk_id]; +} + +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; + struct timespec res; + + 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"); + } + + weston_log_continue(STAMP_SPACE "presentation clock: %s, id %d\n", + clock_name(compositor->presentation_clock), + compositor->presentation_clock); + + if (clock_getres(compositor->presentation_clock, &res) == 0) + weston_log_continue(STAMP_SPACE + "presentation clock resolution: %d.%09ld s\n", + (int)res.tv_sec, res.tv_nsec); + else + weston_log_continue(STAMP_SPACE + "presentation clock resolution: N/A\n"); +} + +static void +handle_primary_client_destroyed(struct wl_listener *listener, void *data) +{ + struct wl_client *client = data; + + weston_log("Primary client died. Closing...\n"); + + wl_display_terminate(wl_client_get_display(client)); +} + +static int +weston_create_listening_socket(struct wl_display *display, const char *socket_name) +{ + if (socket_name) { + if (wl_display_add_socket(display, socket_name)) { + weston_log("fatal: failed to add socket: %s\n", + strerror(errno)); + return -1; + } + } else { + socket_name = wl_display_add_socket_auto(display); + if (!socket_name) { + weston_log("fatal: failed to add socket: %s\n", + strerror(errno)); + return -1; + } + } + + setenv("WAYLAND_DISPLAY", socket_name, 1); + + return 0; +} + +WL_EXPORT void * +wet_load_module_entrypoint(const char *name, const char *entrypoint) +{ + char path[PATH_MAX]; + void *module, *init; + size_t len; + + if (name == NULL) + return NULL; + + if (name[0] != '/') { + len = weston_module_path_from_env(name, path, sizeof path); + if (len == 0) + len = snprintf(path, sizeof path, "%s/%s", MODULEDIR, + name); + } else { + len = snprintf(path, sizeof path, "%s", name); + } + + /* snprintf returns the length of the string it would've written, + * _excluding_ the NUL byte. So even being equal to the size of + * our buffer is an error here. */ + if (len >= sizeof path) + return NULL; + + module = dlopen(path, RTLD_NOW | RTLD_NOLOAD); + if (module) { + weston_log("Module '%s' already loaded\n", path); + } else { + 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; +} + +WL_EXPORT int +wet_load_module(struct weston_compositor *compositor, + const char *name, int *argc, char *argv[]) +{ + int (*module_init)(struct weston_compositor *ec, + int *argc, char *argv[]); + + module_init = wet_load_module_entrypoint(name, "wet_module_init"); + if (!module_init) + return -1; + if (module_init(compositor, argc, argv) < 0) + return -1; + return 0; +} + +static int +wet_load_shell(struct weston_compositor *compositor, + const char *name, int *argc, char *argv[]) +{ + int (*shell_init)(struct weston_compositor *ec, + int *argc, char *argv[]); + + shell_init = wet_load_module_entrypoint(name, "wet_shell_init"); + if (!shell_init) + return -1; + if (shell_init(compositor, argc, argv) < 0) + return -1; + return 0; +} + +static char * +wet_get_binary_path(const char *name, const char *dir) +{ + char path[PATH_MAX]; + size_t len; + + len = weston_module_path_from_env(name, path, sizeof path); + if (len > 0) + return strdup(path); + + len = snprintf(path, sizeof path, "%s/%s", dir, name); + if (len >= sizeof path) + return NULL; + + return strdup(path); +} + +WL_EXPORT char * +wet_get_libexec_path(const char *name) +{ + return wet_get_binary_path(name, LIBEXECDIR); +} + +WL_EXPORT char * +wet_get_bindir_path(const char *name) +{ + return wet_get_binary_path(name, BINDIR); +} + +static int +load_modules(struct weston_compositor *ec, const char *modules, + int *argc, char *argv[], bool *xwayland) +{ + const char *p, *end; + char buffer[256]; + + if (modules == NULL) + return 0; + + p = modules; + while (*p) { + end = strchrnul(p, ','); + snprintf(buffer, sizeof buffer, "%.*s", (int) (end - p), p); + + if (strstr(buffer, "xwayland.so")) { + weston_log("Old Xwayland module loading detected: " + "Please use --xwayland command line option " + "or set xwayland=true in the [core] section " + "in weston.ini\n"); + *xwayland = true; + } else { + if (wet_load_module(ec, buffer, argc, argv) < 0) + return -1; + } + + p = end; + while (*p == ',') + p++; + } + + return 0; +} + +static int +save_touch_device_calibration(struct weston_compositor *compositor, + struct weston_touch_device *device, + const struct weston_touch_device_matrix *calibration) +{ + struct weston_config_section *s; + struct weston_config *config = wet_get_config(compositor); + char *helper = NULL; + char *helper_cmd = NULL; + int ret = -1; + int status; + const float *m = calibration->m; + + s = weston_config_get_section(config, + "libinput", NULL, NULL); + + weston_config_section_get_string(s, "calibration_helper", + &helper, NULL); + + if (!helper || strlen(helper) == 0) { + ret = 0; + goto out; + } + + if (asprintf(&helper_cmd, "\"%s\" '%s' %f %f %f %f %f %f", + helper, device->syspath, + m[0], m[1], m[2], + m[3], m[4], m[5]) < 0) + goto out; + + status = system(helper_cmd); + free(helper_cmd); + + if (status < 0) { + weston_log("Error: failed to run calibration helper '%s'.\n", + helper); + goto out; + } + + if (!WIFEXITED(status)) { + weston_log("Error: calibration helper '%s' possibly killed.\n", + helper); + goto out; + } + + if (WEXITSTATUS(status) == 0) { + ret = 0; + } else { + weston_log("Calibration helper '%s' exited with status %d.\n", + helper, WEXITSTATUS(status)); + } + +out: + free(helper); + + return ret; +} + +static int +weston_compositor_init_config(struct weston_compositor *ec, + struct weston_config *config) +{ + struct xkb_rule_names xkb_names; + struct weston_config_section *s; + int repaint_msec; + bool cal; + + /* weston.ini [keyboard] */ + s = weston_config_get_section(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_set_xkb_rule_names(ec, &xkb_names) < 0) + return -1; + + weston_config_section_get_int(s, "repeat-rate", + &ec->kb_repeat_rate, 40); + weston_config_section_get_int(s, "repeat-delay", + &ec->kb_repeat_delay, 400); + + weston_config_section_get_bool(s, "vt-switching", + &ec->vt_switching, true); + + /* weston.ini [core] */ + s = weston_config_get_section(config, "core", NULL, NULL); + weston_config_section_get_int(s, "repaint-window", &repaint_msec, + ec->repaint_msec); + if (repaint_msec < -10 || repaint_msec > 1000) { + weston_log("Invalid repaint_window value in config: %d\n", + repaint_msec); + } else { + ec->repaint_msec = repaint_msec; + } + weston_log("Output repaint window is %d ms maximum.\n", + ec->repaint_msec); + + /* weston.ini [libinput] */ + s = weston_config_get_section(config, "libinput", NULL, NULL); + weston_config_section_get_bool(s, "touchscreen_calibrator", &cal, 0); + if (cal) + weston_compositor_enable_touch_calibrator(ec, + save_touch_device_calibration); + + return 0; +} + +static char * +weston_choose_default_backend(void) +{ + char *backend = NULL; + + if (getenv("WAYLAND_DISPLAY") || getenv("WAYLAND_SOCKET")) + backend = strdup("wayland-backend.so"); + else if (getenv("DISPLAY")) + backend = strdup("x11-backend.so"); + else + backend = strdup(WESTON_NATIVE_BACKEND); + + return backend; +} + +static const struct { const char *name; uint32_t token; } transforms[] = { + { "normal", WL_OUTPUT_TRANSFORM_NORMAL }, + { "rotate-90", WL_OUTPUT_TRANSFORM_90 }, + { "rotate-180", WL_OUTPUT_TRANSFORM_180 }, + { "rotate-270", WL_OUTPUT_TRANSFORM_270 }, + { "flipped", WL_OUTPUT_TRANSFORM_FLIPPED }, + { "flipped-rotate-90", WL_OUTPUT_TRANSFORM_FLIPPED_90 }, + { "flipped-rotate-180", WL_OUTPUT_TRANSFORM_FLIPPED_180 }, + { "flipped-rotate-270", WL_OUTPUT_TRANSFORM_FLIPPED_270 }, +}; + +WL_EXPORT int +weston_parse_transform(const char *transform, uint32_t *out) +{ + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(transforms); i++) + if (strcmp(transforms[i].name, transform) == 0) { + *out = transforms[i].token; + return 0; + } + + *out = WL_OUTPUT_TRANSFORM_NORMAL; + return -1; +} + +WL_EXPORT const char * +weston_transform_to_string(uint32_t output_transform) +{ + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(transforms); i++) + if (transforms[i].token == output_transform) + return transforms[i].name; + + return ""; +} + +static int +load_configuration(struct weston_config **config, int32_t noconfig, + const char *config_file) +{ + const char *file = "weston.ini"; + const char *full_path; + + *config = NULL; + + if (config_file) + file = config_file; + + if (noconfig == 0) + *config = weston_config_parse(file); + + if (*config) { + full_path = weston_config_get_full_path(*config); + + weston_log("Using config file '%s'\n", full_path); + setenv(WESTON_CONFIG_FILE_ENV_VAR, full_path, 1); + + return 0; + } + + if (config_file && noconfig == 0) { + weston_log("fatal: error opening or reading config file" + " '%s'.\n", config_file); + + return -1; + } + + weston_log("Starting with no config file.\n"); + setenv(WESTON_CONFIG_FILE_ENV_VAR, "", 1); + + return 0; +} + +static void +handle_exit(struct weston_compositor *c) +{ + wl_display_terminate(c->wl_display); +} + +static void +wet_output_set_scale(struct weston_output *output, + struct weston_config_section *section, + int32_t default_scale, + int32_t parsed_scale) +{ + int32_t scale = default_scale; + + if (section) + weston_config_section_get_int(section, "scale", &scale, default_scale); + + if (parsed_scale) + scale = parsed_scale; + + weston_output_set_scale(output, scale); +} + +/* UINT32_MAX is treated as invalid because 0 is a valid + * enumeration value and the parameter is unsigned + */ +static int +wet_output_set_transform(struct weston_output *output, + struct weston_config_section *section, + uint32_t default_transform, + uint32_t parsed_transform) +{ + char *t = NULL; + uint32_t transform = default_transform; + + if (section) { + weston_config_section_get_string(section, + "transform", &t, NULL); + } + + if (t) { + if (weston_parse_transform(t, &transform) < 0) { + weston_log("Invalid transform \"%s\" for output %s\n", + t, output->name); + return -1; + } + free(t); + } + + if (parsed_transform != UINT32_MAX) + transform = parsed_transform; + + weston_output_set_transform(output, transform); + + return 0; +} + +static void +allow_content_protection(struct weston_output *output, + struct weston_config_section *section) +{ + bool allow_hdcp = true; + + if (section) + weston_config_section_get_bool(section, "allow_hdcp", + &allow_hdcp, true); + + weston_output_allow_protection(output, allow_hdcp); +} + +static int +wet_configure_windowed_output_from_config(struct weston_output *output, + struct wet_output_config *defaults) +{ + const struct weston_windowed_output_api *api = + weston_windowed_output_get_api(output->compositor); + + struct weston_config *wc = wet_get_config(output->compositor); + struct weston_config_section *section = NULL; + struct wet_compositor *compositor = to_wet_compositor(output->compositor); + struct wet_output_config *parsed_options = compositor->parsed_options; + int width = defaults->width; + int height = defaults->height; + + assert(parsed_options); + + if (!api) { + weston_log("Cannot use weston_windowed_output_api.\n"); + return -1; + } + + section = weston_config_get_section(wc, "output", "name", output->name); + + if (section) { + char *mode; + + weston_config_section_get_string(section, "mode", &mode, NULL); + if (!mode || sscanf(mode, "%dx%d", &width, + &height) != 2) { + weston_log("Invalid mode for output %s. Using defaults.\n", + output->name); + width = defaults->width; + height = defaults->height; + } + free(mode); + } + + allow_content_protection(output, section); + + if (parsed_options->width) + width = parsed_options->width; + + if (parsed_options->height) + height = parsed_options->height; + + wet_output_set_scale(output, section, defaults->scale, parsed_options->scale); + if (wet_output_set_transform(output, section, defaults->transform, + parsed_options->transform) < 0) { + return -1; + } + + if (api->output_set_size(output, width, height) < 0) { + weston_log("Cannot configure output \"%s\" using weston_windowed_output_api.\n", + output->name); + return -1; + } + + return 0; +} + +static int +count_remaining_heads(struct weston_output *output, struct weston_head *to_go) +{ + struct weston_head *iter = NULL; + int n = 0; + + while ((iter = weston_output_iterate_heads(output, iter))) { + if (iter != to_go) + n++; + } + + return n; +} + +static void +wet_head_tracker_destroy(struct wet_head_tracker *track) +{ + wl_list_remove(&track->head_destroy_listener.link); + free(track); +} + +static void +handle_head_destroy(struct wl_listener *listener, void *data) +{ + struct weston_head *head = data; + struct weston_output *output; + struct wet_head_tracker *track = + container_of(listener, struct wet_head_tracker, + head_destroy_listener); + + wet_head_tracker_destroy(track); + + output = weston_head_get_output(head); + + /* On shutdown path, the output might be already gone. */ + if (!output) + return; + + if (count_remaining_heads(output, head) > 0) + return; + + weston_output_destroy(output); +} + +static struct wet_head_tracker * +wet_head_tracker_from_head(struct weston_head *head) +{ + struct wl_listener *lis; + + lis = weston_head_get_destroy_listener(head, handle_head_destroy); + if (!lis) + return NULL; + + return container_of(lis, struct wet_head_tracker, + head_destroy_listener); +} + +/* Listen for head destroy signal. + * + * If a head is destroyed and it was the last head on the output, we + * destroy the associated output. + * + * Do not bother destroying the head trackers on shutdown, the backend will + * destroy the heads which calls our handler to destroy the trackers. + */ +static void +wet_head_tracker_create(struct wet_compositor *compositor, + struct weston_head *head) +{ + struct wet_head_tracker *track; + + track = zalloc(sizeof *track); + if (!track) + return; + + track->head_destroy_listener.notify = handle_head_destroy; + weston_head_add_destroy_listener(head, &track->head_destroy_listener); +} + +static void +simple_head_enable(struct wet_compositor *wet, struct weston_head *head) +{ + struct weston_output *output; + int ret = 0; + + output = weston_compositor_create_output_with_head(wet->compositor, + head); + if (!output) { + weston_log("Could not create an output for head \"%s\".\n", + weston_head_get_name(head)); + wet->init_failed = true; + + return; + } + + if (wet->simple_output_configure) + ret = wet->simple_output_configure(output); + if (ret < 0) { + weston_log("Cannot configure output \"%s\".\n", + weston_head_get_name(head)); + weston_output_destroy(output); + wet->init_failed = true; + + return; + } + + if (weston_output_enable(output) < 0) { + weston_log("Enabling output \"%s\" failed.\n", + weston_head_get_name(head)); + weston_output_destroy(output); + wet->init_failed = true; + + return; + } + + wet_head_tracker_create(wet, head); + + /* The weston_compositor will track and destroy the output on exit. */ +} + +static void +simple_head_disable(struct weston_head *head) +{ + struct weston_output *output; + struct wet_head_tracker *track; + + track = wet_head_tracker_from_head(head); + if (track) + wet_head_tracker_destroy(track); + + output = weston_head_get_output(head); + assert(output); + weston_output_destroy(output); +} + +static void +simple_heads_changed(struct wl_listener *listener, void *arg) +{ + struct weston_compositor *compositor = arg; + struct wet_compositor *wet = to_wet_compositor(compositor); + struct weston_head *head = NULL; + bool connected; + bool enabled; + bool changed; + bool non_desktop; + + while ((head = weston_compositor_iterate_heads(wet->compositor, head))) { + connected = weston_head_is_connected(head); + enabled = weston_head_is_enabled(head); + changed = weston_head_is_device_changed(head); + non_desktop = weston_head_is_non_desktop(head); + + if (connected && !enabled && !non_desktop) { + simple_head_enable(wet, head); + } else if (!connected && enabled) { + simple_head_disable(head); + } else if (enabled && changed) { + weston_log("Detected a monitor change on head '%s', " + "not bothering to do anything about it.\n", + weston_head_get_name(head)); + } + weston_head_reset_device_changed(head); + } +} + +static void +wet_set_simple_head_configurator(struct weston_compositor *compositor, + int (*fn)(struct weston_output *)) +{ + struct wet_compositor *wet = to_wet_compositor(compositor); + + wet->simple_output_configure = fn; + + wet->heads_changed_listener.notify = simple_heads_changed; + weston_compositor_add_heads_changed_listener(compositor, + &wet->heads_changed_listener); +} + +static void +configure_input_device_accel(struct weston_config_section *s, + struct libinput_device *device) +{ + char *profile_string = NULL; + int is_a_profile = 1; + uint32_t profiles; + enum libinput_config_accel_profile profile; + double speed; + + if (weston_config_section_get_string(s, "accel-profile", + &profile_string, NULL) == 0) { + if (strcmp(profile_string, "flat") == 0) + profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; + else if (strcmp(profile_string, "adaptive") == 0) + profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; + else { + weston_log("warning: no such accel-profile: %s\n", + profile_string); + is_a_profile = 0; + } + + profiles = libinput_device_config_accel_get_profiles(device); + if (is_a_profile && (profile & profiles) != 0) { + weston_log(" accel-profile=%s\n", + profile_string); + libinput_device_config_accel_set_profile(device, + profile); + } + } + + if (weston_config_section_get_double(s, "accel-speed", + &speed, 0) == 0 && + speed >= -1. && speed <= 1.) { + weston_log(" accel-speed=%.3f\n", speed); + libinput_device_config_accel_set_speed(device, speed); + } + + free(profile_string); +} + +static void +configure_input_device_scroll(struct weston_config_section *s, + struct libinput_device *device) +{ + bool natural; + char *method_string = NULL; + uint32_t methods; + enum libinput_config_scroll_method method; + char *button_string = NULL; + int button; + + if (libinput_device_config_scroll_has_natural_scroll(device) && + weston_config_section_get_bool(s, "natural-scroll", + &natural, false) == 0) { + weston_log(" natural-scroll=%s\n", + natural ? "true" : "false"); + libinput_device_config_scroll_set_natural_scroll_enabled( + device, natural); + } + + if (weston_config_section_get_string(s, "scroll-method", + &method_string, NULL) != 0) + goto done; + if (strcmp(method_string, "two-finger") == 0) + method = LIBINPUT_CONFIG_SCROLL_2FG; + else if (strcmp(method_string, "edge") == 0) + method = LIBINPUT_CONFIG_SCROLL_EDGE; + else if (strcmp(method_string, "button") == 0) + method = LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; + else if (strcmp(method_string, "none") == 0) + method = LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + else { + weston_log("warning: no such scroll-method: %s\n", + method_string); + goto done; + } + + methods = libinput_device_config_scroll_get_methods(device); + if (method != LIBINPUT_CONFIG_SCROLL_NO_SCROLL && + (method & methods) == 0) + goto done; + + weston_log(" scroll-method=%s\n", method_string); + libinput_device_config_scroll_set_method(device, method); + + if (method == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) { + if (weston_config_section_get_string(s, "scroll-button", + &button_string, + NULL) != 0) + goto done; + + button = libevdev_event_code_from_name(EV_KEY, button_string); + if (button == -1) { + weston_log(" Bad scroll-button: %s\n", + button_string); + goto done; + } + + weston_log(" scroll-button=%s\n", button_string); + libinput_device_config_scroll_set_button(device, button); + } + +done: + free(method_string); + free(button_string); +} + +static void +configure_input_device(struct weston_compositor *compositor, + struct libinput_device *device) +{ + struct weston_config_section *s; + struct weston_config *config = wet_get_config(compositor); + bool has_enable_tap = false; + bool enable_tap; + bool disable_while_typing; + bool middle_emulation; + bool tap_and_drag; + bool tap_and_drag_lock; + bool left_handed; + unsigned int rotation; + + weston_log("libinput: configuring device \"%s\".\n", + libinput_device_get_name(device)); + + s = weston_config_get_section(config, + "libinput", NULL, NULL); + + if (libinput_device_config_tap_get_finger_count(device) > 0) { + if (weston_config_section_get_bool(s, "enable_tap", + &enable_tap, false) == 0) { + weston_log("!!DEPRECATION WARNING!!: In weston.ini, " + "enable_tap is deprecated in favour of " + "enable-tap. Support for it may be removed " + "at any time!"); + has_enable_tap = true; + } + if (weston_config_section_get_bool(s, "enable-tap", + &enable_tap, false) == 0) + has_enable_tap = true; + if (has_enable_tap) { + weston_log(" enable-tap=%s.\n", + enable_tap ? "true" : "false"); + libinput_device_config_tap_set_enabled(device, + enable_tap); + } + if (weston_config_section_get_bool(s, "tap-and-drag", + &tap_and_drag, false) == 0) { + weston_log(" tap-and-drag=%s.\n", + tap_and_drag ? "true" : "false"); + libinput_device_config_tap_set_drag_enabled(device, + tap_and_drag); + } + if (weston_config_section_get_bool(s, "tap-and-drag-lock", + &tap_and_drag_lock, false) == 0) { + weston_log(" tap-and-drag-lock=%s.\n", + tap_and_drag_lock ? "true" : "false"); + libinput_device_config_tap_set_drag_lock_enabled( + device, tap_and_drag_lock); + } + } + + if (libinput_device_config_dwt_is_available(device) && + weston_config_section_get_bool(s, "disable-while-typing", + &disable_while_typing, false) == 0) { + weston_log(" disable-while-typing=%s.\n", + disable_while_typing ? "true" : "false"); + libinput_device_config_dwt_set_enabled(device, + disable_while_typing); + } + + if (libinput_device_config_middle_emulation_is_available(device) && + weston_config_section_get_bool(s, "middle-button-emulation", + &middle_emulation, false) == 0) { + weston_log(" middle-button-emulation=%s\n", + middle_emulation ? "true" : "false"); + libinput_device_config_middle_emulation_set_enabled( + device, middle_emulation); + } + + if (libinput_device_config_left_handed_is_available(device) && + weston_config_section_get_bool(s, "left-handed", + &left_handed, false) == 0) { + weston_log(" left-handed=%s\n", + left_handed ? "true" : "false"); + libinput_device_config_left_handed_set(device, left_handed); + } + + if (libinput_device_config_rotation_is_available(device) && + weston_config_section_get_uint(s, "rotation", + &rotation, false) == 0) { + weston_log(" rotation=%u\n", rotation); + libinput_device_config_rotation_set_angle(device, rotation); + } + + if (libinput_device_config_accel_is_available(device)) + configure_input_device_accel(s, device); + + configure_input_device_scroll(s, device); +} + +static int +drm_backend_output_configure(struct weston_output *output, + struct weston_config_section *section) +{ + struct wet_compositor *wet = to_wet_compositor(output->compositor); + const struct weston_drm_output_api *api; + enum weston_drm_backend_output_mode mode = + WESTON_DRM_BACKEND_OUTPUT_PREFERRED; + uint32_t transform = WL_OUTPUT_TRANSFORM_NORMAL; + char *s; + char *modeline = NULL; + char *gbm_format = NULL; + char *seat = NULL; + + api = weston_drm_output_get_api(output->compositor); + if (!api) { + weston_log("Cannot use weston_drm_output_api.\n"); + return -1; + } + + weston_config_section_get_string(section, "mode", &s, "preferred"); + + if (strcmp(s, "off") == 0) { + assert(0 && "off was supposed to be pruned"); + return -1; + } else if (wet->drm_use_current_mode || strcmp(s, "current") == 0) { + mode = WESTON_DRM_BACKEND_OUTPUT_CURRENT; + } else if (strcmp(s, "preferred") != 0) { + modeline = s; + s = NULL; + } + free(s); + + if (api->set_mode(output, mode, modeline) < 0) { + weston_log("Cannot configure an output using weston_drm_output_api.\n"); + free(modeline); + return -1; + } + free(modeline); + + if (count_remaining_heads(output, NULL) == 1) { + struct weston_head *head = weston_output_get_first_head(output); + transform = weston_head_get_transform(head); + } + + wet_output_set_scale(output, section, 1, 0); + if (wet_output_set_transform(output, section, transform, + UINT32_MAX) < 0) { + return -1; + } + + weston_config_section_get_string(section, + "gbm-format", &gbm_format, NULL); + + api->set_gbm_format(output, gbm_format); + free(gbm_format); + + weston_config_section_get_string(section, "seat", &seat, ""); + + api->set_seat(output, seat); + free(seat); + + allow_content_protection(output, section); + + return 0; +} + +/* Find the output section to use for configuring the output with the + * named head. If an output section with the given name contains + * a "same-as" key, ignore all other settings in the output section and + * instead find an output section named by the "same-as". Do this + * recursively. + */ +static struct weston_config_section * +drm_config_find_controlling_output_section(struct weston_config *config, + const char *head_name) +{ + struct weston_config_section *section; + char *same_as; + int depth = 0; + + same_as = strdup(head_name); + do { + section = weston_config_get_section(config, "output", + "name", same_as); + if (!section && depth > 0) + weston_log("Configuration error: " + "output section referred to with " + "'same-as=%s' not found.\n", same_as); + + free(same_as); + + if (!section) + return NULL; + + if (++depth > 10) { + weston_log("Configuration error: " + "'same-as' nested too deep for output '%s'.\n", + head_name); + return NULL; + } + + weston_config_section_get_string(section, "same-as", + &same_as, NULL); + } while (same_as); + + return section; +} + +static struct wet_layoutput * +wet_compositor_create_layoutput(struct wet_compositor *compositor, + const char *name, + struct weston_config_section *section) +{ + struct wet_layoutput *lo; + + lo = zalloc(sizeof *lo); + if (!lo) + return NULL; + + lo->compositor = compositor; + wl_list_insert(compositor->layoutput_list.prev, &lo->compositor_link); + wl_list_init(&lo->output_list); + lo->name = strdup(name); + lo->section = section; + + return lo; +} + +static void +wet_layoutput_destroy(struct wet_layoutput *lo) +{ + wl_list_remove(&lo->compositor_link); + assert(wl_list_empty(&lo->output_list)); + free(lo->name); + free(lo); +} + +static void +wet_output_handle_destroy(struct wl_listener *listener, void *data) +{ + struct wet_output *output; + + output = wl_container_of(listener, output, output_destroy_listener); + assert(output->output == data); + + output->output = NULL; + wl_list_remove(&output->output_destroy_listener.link); +} + +static struct wet_output * +wet_layoutput_create_output(struct wet_layoutput *lo, const char *name) +{ + struct wet_output *output; + + output = zalloc(sizeof *output); + if (!output) + return NULL; + + output->output = + weston_compositor_create_output(lo->compositor->compositor, + name); + if (!output->output) { + free(output); + return NULL; + } + + output->layoutput = lo; + wl_list_insert(lo->output_list.prev, &output->link); + output->output_destroy_listener.notify = wet_output_handle_destroy; + weston_output_add_destroy_listener(output->output, + &output->output_destroy_listener); + + return output; +} + +static struct wet_output * +wet_output_from_weston_output(struct weston_output *base) +{ + struct wl_listener *lis; + + lis = weston_output_get_destroy_listener(base, + wet_output_handle_destroy); + if (!lis) + return NULL; + + return container_of(lis, struct wet_output, output_destroy_listener); +} + +static void +wet_output_destroy(struct wet_output *output) +{ + if (output->output) { + /* output->output destruction may be deferred in some cases (see + * drm_output_destroy()), so we need to forcibly trigger the + * destruction callback now, or otherwise would later access + * data that we are about to free + */ + struct weston_output *save = output->output; + wet_output_handle_destroy(&output->output_destroy_listener, save); + weston_output_destroy(save); + } + + wl_list_remove(&output->link); + free(output); +} + +static struct wet_layoutput * +wet_compositor_find_layoutput(struct wet_compositor *wet, const char *name) +{ + struct wet_layoutput *lo; + + wl_list_for_each(lo, &wet->layoutput_list, compositor_link) + if (strcmp(lo->name, name) == 0) + return lo; + + return NULL; +} + +static void +wet_compositor_layoutput_add_head(struct wet_compositor *wet, + const char *output_name, + struct weston_config_section *section, + struct weston_head *head) +{ + struct wet_layoutput *lo; + + lo = wet_compositor_find_layoutput(wet, output_name); + if (!lo) { + lo = wet_compositor_create_layoutput(wet, output_name, section); + if (!lo) + return; + } + + if (lo->add.n + 1 >= ARRAY_LENGTH(lo->add.heads)) + return; + + lo->add.heads[lo->add.n++] = head; +} + +static void +wet_compositor_destroy_layout(struct wet_compositor *wet) +{ + struct wet_layoutput *lo, *lo_tmp; + struct wet_output *output, *output_tmp; + + wl_list_for_each_safe(lo, lo_tmp, + &wet->layoutput_list, compositor_link) { + wl_list_for_each_safe(output, output_tmp, + &lo->output_list, link) { + wet_output_destroy(output); + } + wet_layoutput_destroy(lo); + } +} + +static void +drm_head_prepare_enable(struct wet_compositor *wet, + struct weston_head *head) +{ + const char *name = weston_head_get_name(head); + struct weston_config_section *section; + char *output_name = NULL; + char *mode = NULL; + + section = drm_config_find_controlling_output_section(wet->config, name); + if (section) { + /* skip outputs that are explicitly off, or non-desktop and not + * explicitly enabled. The backend turns them off automatically. + */ + weston_config_section_get_string(section, "mode", &mode, NULL); + if (mode && strcmp(mode, "off") == 0) { + free(mode); + return; + } + if (!mode && weston_head_is_non_desktop(head)) + return; + free(mode); + + weston_config_section_get_string(section, "name", + &output_name, NULL); + assert(output_name); + + wet_compositor_layoutput_add_head(wet, output_name, + section, head); + free(output_name); + } else { + wet_compositor_layoutput_add_head(wet, name, NULL, head); + } +} + +static bool +drm_head_should_force_enable(struct wet_compositor *wet, + struct weston_head *head) +{ + const char *name = weston_head_get_name(head); + struct weston_config_section *section; + bool force; + + section = drm_config_find_controlling_output_section(wet->config, name); + if (!section) + return false; + + weston_config_section_get_bool(section, "force-on", &force, false); + return force; +} + +static void +drm_try_attach(struct weston_output *output, + struct wet_head_array *add, + struct wet_head_array *failed) +{ + unsigned i; + + /* try to attach all heads, this probably succeeds */ + for (i = 0; i < add->n; i++) { + if (!add->heads[i]) + continue; + + if (weston_output_attach_head(output, add->heads[i]) < 0) { + assert(failed->n < ARRAY_LENGTH(failed->heads)); + + failed->heads[failed->n++] = add->heads[i]; + add->heads[i] = NULL; + } + } +} + +static int +drm_try_enable(struct weston_output *output, + struct wet_head_array *undo, + struct wet_head_array *failed) +{ + /* Try to enable, and detach heads one by one until it succeeds. */ + while (!output->enabled) { + if (weston_output_enable(output) == 0) + return 0; + + /* the next head to drop */ + while (undo->n > 0 && undo->heads[--undo->n] == NULL) + ; + + /* No heads left to undo and failed to enable. */ + if (undo->heads[undo->n] == NULL) + return -1; + + assert(failed->n < ARRAY_LENGTH(failed->heads)); + + /* undo one head */ + weston_head_detach(undo->heads[undo->n]); + failed->heads[failed->n++] = undo->heads[undo->n]; + undo->heads[undo->n] = NULL; + } + + return 0; +} + +static int +drm_try_attach_enable(struct weston_output *output, struct wet_layoutput *lo) +{ + struct wet_head_array failed = {}; + unsigned i; + + assert(!output->enabled); + + drm_try_attach(output, &lo->add, &failed); + if (drm_backend_output_configure(output, lo->section) < 0) + return -1; + + if (drm_try_enable(output, &lo->add, &failed) < 0) + return -1; + + /* For all successfully attached/enabled heads */ + for (i = 0; i < lo->add.n; i++) + if (lo->add.heads[i]) + wet_head_tracker_create(lo->compositor, + lo->add.heads[i]); + + /* Push failed heads to the next round. */ + lo->add = failed; + + return 0; +} + +static int +drm_process_layoutput(struct wet_compositor *wet, struct wet_layoutput *lo) +{ + struct wet_output *output, *tmp; + char *name = NULL; + int ret; + + /* + * For each existing wet_output: + * try attach + * While heads left to enable: + * Create output + * try attach, try enable + */ + + wl_list_for_each_safe(output, tmp, &lo->output_list, link) { + struct wet_head_array failed = {}; + + if (!output->output) { + /* Clean up left-overs from destroyed heads. */ + wet_output_destroy(output); + continue; + } + + assert(output->output->enabled); + + drm_try_attach(output->output, &lo->add, &failed); + lo->add = failed; + if (lo->add.n == 0) + return 0; + } + + if (!weston_compositor_find_output_by_name(wet->compositor, lo->name)) + name = strdup(lo->name); + + while (lo->add.n > 0) { + if (!wl_list_empty(&lo->output_list)) { + weston_log("Error: independent-CRTC clone mode is not implemented.\n"); + return -1; + } + + if (!name) { + ret = asprintf(&name, "%s:%s", lo->name, + weston_head_get_name(lo->add.heads[0])); + if (ret < 0) + return -1; + } + output = wet_layoutput_create_output(lo, name); + free(name); + name = NULL; + + if (!output) + return -1; + + if (drm_try_attach_enable(output->output, lo) < 0) { + wet_output_destroy(output); + return -1; + } + } + + return 0; +} + +static int +drm_process_layoutputs(struct wet_compositor *wet) +{ + struct wet_layoutput *lo; + int ret = 0; + + wl_list_for_each(lo, &wet->layoutput_list, compositor_link) { + if (lo->add.n == 0) + continue; + + if (drm_process_layoutput(wet, lo) < 0) { + lo->add = (struct wet_head_array){}; + ret = -1; + } + } + + return ret; +} + +static void +drm_head_disable(struct weston_head *head) +{ + struct weston_output *output_base; + struct wet_output *output; + struct wet_head_tracker *track; + + track = wet_head_tracker_from_head(head); + if (track) + wet_head_tracker_destroy(track); + + output_base = weston_head_get_output(head); + assert(output_base); + output = wet_output_from_weston_output(output_base); + assert(output && output->output == output_base); + + weston_head_detach(head); + if (count_remaining_heads(output->output, NULL) == 0) + wet_output_destroy(output); +} + +static void +drm_heads_changed(struct wl_listener *listener, void *arg) +{ + struct weston_compositor *compositor = arg; + struct wet_compositor *wet = to_wet_compositor(compositor); + struct weston_head *head = NULL; + bool connected; + bool enabled; + bool changed; + bool forced; + + /* We need to collect all cloned heads into outputs before enabling the + * output. + */ + while ((head = weston_compositor_iterate_heads(compositor, head))) { + connected = weston_head_is_connected(head); + enabled = weston_head_is_enabled(head); + changed = weston_head_is_device_changed(head); + forced = drm_head_should_force_enable(wet, head); + + if ((connected || forced) && !enabled) { + drm_head_prepare_enable(wet, head); + } else if (!(connected || forced) && enabled) { + drm_head_disable(head); + } else if (enabled && changed) { + weston_log("Detected a monitor change on head '%s', " + "not bothering to do anything about it.\n", + weston_head_get_name(head)); + } + weston_head_reset_device_changed(head); + } + + if (drm_process_layoutputs(wet) < 0) + wet->init_failed = true; +} + +static int +drm_backend_remoted_output_configure(struct weston_output *output, + struct weston_config_section *section, + char *modeline, + const struct weston_remoting_api *api) +{ + char *gbm_format = NULL; + char *seat = NULL; + char *host = NULL; + char *pipeline = NULL; + int port, ret; + + ret = api->set_mode(output, modeline); + if (ret < 0) { + weston_log("Cannot configure an output \"%s\" using " + "weston_remoting_api. Invalid mode\n", + output->name); + return -1; + } + + wet_output_set_scale(output, section, 1, 0); + if (wet_output_set_transform(output, section, + WL_OUTPUT_TRANSFORM_NORMAL, + UINT32_MAX) < 0) { + return -1; + }; + + weston_config_section_get_string(section, "gbm-format", &gbm_format, + NULL); + api->set_gbm_format(output, gbm_format); + free(gbm_format); + + weston_config_section_get_string(section, "seat", &seat, ""); + + api->set_seat(output, seat); + free(seat); + + weston_config_section_get_string(section, "gst-pipeline", &pipeline, + NULL); + if (pipeline) { + api->set_gst_pipeline(output, pipeline); + free(pipeline); + return 0; + } + + weston_config_section_get_string(section, "host", &host, NULL); + weston_config_section_get_int(section, "port", &port, 0); + if (!host || port <= 0 || 65533 < port) { + weston_log("Cannot configure an output \"%s\". " + "Need to specify gst-pipeline or " + "host and port (1-65533).\n", output->name); + } + api->set_host(output, host); + free(host); + api->set_port(output, port); + + return 0; +} + +static void +remoted_output_init(struct weston_compositor *c, + struct weston_config_section *section, + const struct weston_remoting_api *api) +{ + struct weston_output *output = NULL; + char *output_name, *modeline = NULL; + int ret; + + weston_config_section_get_string(section, "name", &output_name, + NULL); + if (!output_name) + return; + + weston_config_section_get_string(section, "mode", &modeline, "off"); + if (strcmp(modeline, "off") == 0) + goto err; + + output = api->create_output(c, output_name); + if (!output) { + weston_log("Cannot create remoted output \"%s\".\n", + output_name); + goto err; + } + + ret = drm_backend_remoted_output_configure(output, section, modeline, + api); + if (ret < 0) { + weston_log("Cannot configure remoted output \"%s\".\n", + output_name); + goto err; + } + + if (weston_output_enable(output) < 0) { + weston_log("Enabling remoted output \"%s\" failed.\n", + output_name); + goto err; + } + + free(modeline); + free(output_name); + weston_log("remoted output '%s' enabled\n", output->name); + return; + +err: + free(modeline); + free(output_name); + if (output) + weston_output_destroy(output); +} + +static void +load_remoting(struct weston_compositor *c, struct weston_config *wc) +{ + const struct weston_remoting_api *api = NULL; + int (*module_init)(struct weston_compositor *ec); + struct weston_config_section *section = NULL; + const char *section_name; + + /* read remote-output section in weston.ini */ + while (weston_config_next_section(wc, §ion, §ion_name)) { + if (strcmp(section_name, "remote-output")) + continue; + + if (!api) { + char *module_name; + struct weston_config_section *core_section = + weston_config_get_section(wc, "core", NULL, + NULL); + + weston_config_section_get_string(core_section, + "remoting", + &module_name, + "remoting-plugin.so"); + module_init = weston_load_module(module_name, + "weston_module_init"); + free(module_name); + if (!module_init) { + weston_log("Can't load remoting-plugin\n"); + return; + } + if (module_init(c) < 0) { + weston_log("Remoting-plugin init failed\n"); + return; + } + + api = weston_remoting_get_api(c); + if (!api) + return; + } + + remoted_output_init(c, section, api); + } +} + +static int +drm_backend_pipewire_output_configure(struct weston_output *output, + struct weston_config_section *section, + char *modeline, + const struct weston_pipewire_api *api) +{ + char *seat = NULL; + int ret; + + ret = api->set_mode(output, modeline); + if (ret < 0) { + weston_log("Cannot configure an output \"%s\" using " + "weston_pipewire_api. Invalid mode\n", + output->name); + return -1; + } + + wet_output_set_scale(output, section, 1, 0); + if (wet_output_set_transform(output, section, + WL_OUTPUT_TRANSFORM_NORMAL, + UINT32_MAX) < 0) { + return -1; + } + + weston_config_section_get_string(section, "seat", &seat, ""); + + api->set_seat(output, seat); + free(seat); + + return 0; +} + +static void +pipewire_output_init(struct weston_compositor *c, + struct weston_config_section *section, + const struct weston_pipewire_api *api) +{ + struct weston_output *output = NULL; + char *output_name, *modeline = NULL; + int ret; + + weston_config_section_get_string(section, "name", &output_name, + NULL); + if (!output_name) + return; + + weston_config_section_get_string(section, "mode", &modeline, "off"); + if (strcmp(modeline, "off") == 0) + goto err; + + output = api->create_output(c, output_name); + if (!output) { + weston_log("Cannot create pipewire output \"%s\".\n", + output_name); + goto err; + } + + ret = drm_backend_pipewire_output_configure(output, section, modeline, + api); + if (ret < 0) { + weston_log("Cannot configure pipewire output \"%s\".\n", + output_name); + goto err; + } + + if (weston_output_enable(output) < 0) { + weston_log("Enabling pipewire output \"%s\" failed.\n", + output_name); + goto err; + } + + free(modeline); + free(output_name); + weston_log("pipewire output '%s' enabled\n", output->name); + return; + +err: + free(modeline); + free(output_name); + if (output) + weston_output_destroy(output); +} + +static void +load_pipewire(struct weston_compositor *c, struct weston_config *wc) +{ + const struct weston_pipewire_api *api = NULL; + int (*module_init)(struct weston_compositor *ec); + struct weston_config_section *section = NULL; + const char *section_name; + + /* read pipewire-output section in weston.ini */ + while (weston_config_next_section(wc, §ion, §ion_name)) { + if (strcmp(section_name, "pipewire-output")) + continue; + + if (!api) { + char *module_name; + struct weston_config_section *core_section = + weston_config_get_section(wc, "core", NULL, + NULL); + + weston_config_section_get_string(core_section, + "pipewire", + &module_name, + "pipewire-plugin.so"); + module_init = weston_load_module(module_name, + "weston_module_init"); + free(module_name); + if (!module_init) { + weston_log("Can't load pipewire-plugin\n"); + return; + } + if (module_init(c) < 0) { + weston_log("Pipewire-plugin init failed\n"); + return; + } + + api = weston_pipewire_get_api(c); + if (!api) + return; + } + + pipewire_output_init(c, section, api); + } +} + +static int +load_drm_backend(struct weston_compositor *c, + int *argc, char **argv, struct weston_config *wc) +{ + struct weston_drm_backend_config config = {{ 0, }}; + struct weston_config_section *section; + struct wet_compositor *wet = to_wet_compositor(c); + int ret = 0; + + wet->drm_use_current_mode = false; + + section = weston_config_get_section(wc, "core", NULL, NULL); + weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, + false); + + const struct weston_option options[] = { + { WESTON_OPTION_STRING, "seat", 0, &config.seat_id }, + { WESTON_OPTION_INTEGER, "tty", 0, &config.tty }, + { WESTON_OPTION_STRING, "drm-device", 0, &config.specific_device }, + { WESTON_OPTION_BOOLEAN, "current-mode", 0, &wet->drm_use_current_mode }, + { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, + { WESTON_OPTION_BOOLEAN, "continue-without-input", 0, &config.continue_without_input }, + }; + + parse_options(options, ARRAY_LENGTH(options), argc, argv); + + section = weston_config_get_section(wc, "core", NULL, NULL); + weston_config_section_get_string(section, + "gbm-format", &config.gbm_format, + NULL); + weston_config_section_get_uint(section, "pageflip-timeout", + &config.pageflip_timeout, 0); + weston_config_section_get_bool(section, "pixman-shadow", + &config.use_pixman_shadow, true); + + config.base.struct_version = WESTON_DRM_BACKEND_CONFIG_VERSION; + config.base.struct_size = sizeof(struct weston_drm_backend_config); + config.configure_device = configure_input_device; + + wet->heads_changed_listener.notify = drm_heads_changed; + weston_compositor_add_heads_changed_listener(c, + &wet->heads_changed_listener); + + ret = weston_compositor_load_backend(c, WESTON_BACKEND_DRM, + &config.base); + + /* remoting */ + load_remoting(c, wc); + + /* pipewire */ + load_pipewire(c, wc); + + free(config.gbm_format); + free(config.seat_id); + + return ret; +} + +static int +headless_backend_output_configure(struct weston_output *output) +{ + struct wet_output_config defaults = { + .width = 1024, + .height = 640, + .scale = 1, + .transform = WL_OUTPUT_TRANSFORM_NORMAL + }; + + return wet_configure_windowed_output_from_config(output, &defaults); +} + +static int +load_headless_backend(struct weston_compositor *c, + int *argc, char **argv, struct weston_config *wc) +{ + const struct weston_windowed_output_api *api; + struct weston_headless_backend_config config = {{ 0, }}; + struct weston_config_section *section; + bool no_outputs = false; + int ret = 0; + char *transform = NULL; + + struct wet_output_config *parsed_options = wet_init_parsed_options(c); + if (!parsed_options) + return -1; + + section = weston_config_get_section(wc, "core", NULL, NULL); + weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, + false); + weston_config_section_get_bool(section, "use-gl", &config.use_gl, + false); + + const struct weston_option options[] = { + { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, + { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, + { WESTON_OPTION_INTEGER, "scale", 0, &parsed_options->scale }, + { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, + { WESTON_OPTION_BOOLEAN, "use-gl", 0, &config.use_gl }, + { WESTON_OPTION_STRING, "transform", 0, &transform }, + { WESTON_OPTION_BOOLEAN, "no-outputs", 0, &no_outputs }, + }; + + parse_options(options, ARRAY_LENGTH(options), argc, argv); + + if (transform) { + if (weston_parse_transform(transform, &parsed_options->transform) < 0) { + weston_log("Invalid transform \"%s\"\n", transform); + return -1; + } + free(transform); + } + + config.base.struct_version = WESTON_HEADLESS_BACKEND_CONFIG_VERSION; + config.base.struct_size = sizeof(struct weston_headless_backend_config); + + wet_set_simple_head_configurator(c, headless_backend_output_configure); + + /* load the actual wayland backend and configure it */ + ret = weston_compositor_load_backend(c, WESTON_BACKEND_HEADLESS, + &config.base); + + if (ret < 0) + return ret; + + if (!no_outputs) { + api = weston_windowed_output_get_api(c); + + if (!api) { + weston_log("Cannot use weston_windowed_output_api.\n"); + return -1; + } + + if (api->create_head(c, "headless") < 0) + return -1; + } + + return 0; +} + +static int +rdp_backend_output_configure(struct weston_output *output) +{ + struct wet_compositor *compositor = to_wet_compositor(output->compositor); + struct wet_output_config *parsed_options = compositor->parsed_options; + const struct weston_rdp_output_api *api = weston_rdp_output_get_api(output->compositor); + int width = 640; + int height = 480; + + assert(parsed_options); + + if (!api) { + weston_log("Cannot use weston_rdp_output_api.\n"); + return -1; + } + + if (parsed_options->width) + width = parsed_options->width; + + if (parsed_options->height) + height = parsed_options->height; + + weston_output_set_scale(output, 1); + weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); + + if (api->output_set_size(output, width, height) < 0) { + weston_log("Cannot configure output \"%s\" using weston_rdp_output_api.\n", + output->name); + return -1; + } + + return 0; +} + +static void +weston_rdp_backend_config_init(struct weston_rdp_backend_config *config) +{ + config->base.struct_version = WESTON_RDP_BACKEND_CONFIG_VERSION; + config->base.struct_size = sizeof(struct weston_rdp_backend_config); + + config->bind_address = NULL; + config->port = 3389; + config->rdp_key = NULL; + config->server_cert = NULL; + config->server_key = NULL; + config->env_socket = 0; + config->no_clients_resize = 0; + config->force_no_compression = 0; +} + +static int +load_rdp_backend(struct weston_compositor *c, + int *argc, char *argv[], struct weston_config *wc) +{ + struct weston_rdp_backend_config config = {{ 0, }}; + int ret = 0; + + struct wet_output_config *parsed_options = wet_init_parsed_options(c); + if (!parsed_options) + return -1; + + weston_rdp_backend_config_init(&config); + + const struct weston_option rdp_options[] = { + { WESTON_OPTION_BOOLEAN, "env-socket", 0, &config.env_socket }, + { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, + { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, + { WESTON_OPTION_STRING, "address", 0, &config.bind_address }, + { WESTON_OPTION_INTEGER, "port", 0, &config.port }, + { WESTON_OPTION_BOOLEAN, "no-clients-resize", 0, &config.no_clients_resize }, + { 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 }, + { WESTON_OPTION_BOOLEAN, "force-no-compression", 0, &config.force_no_compression }, + }; + + parse_options(rdp_options, ARRAY_LENGTH(rdp_options), argc, argv); + + wet_set_simple_head_configurator(c, rdp_backend_output_configure); + + ret = weston_compositor_load_backend(c, WESTON_BACKEND_RDP, + &config.base); + + free(config.bind_address); + free(config.rdp_key); + free(config.server_cert); + free(config.server_key); + + return ret; +} + +static int +fbdev_backend_output_configure(struct weston_output *output) +{ + struct weston_config *wc = wet_get_config(output->compositor); + struct weston_config_section *section; + + section = weston_config_get_section(wc, "output", "name", "fbdev"); + + if (wet_output_set_transform(output, section, + WL_OUTPUT_TRANSFORM_NORMAL, + UINT32_MAX) < 0) { + return -1; + } + + weston_output_set_scale(output, 1); + + return 0; +} + +static int +load_fbdev_backend(struct weston_compositor *c, + int *argc, char **argv, struct weston_config *wc) +{ + struct weston_fbdev_backend_config config = {{ 0, }}; + int ret = 0; + + const struct weston_option fbdev_options[] = { + { WESTON_OPTION_INTEGER, "tty", 0, &config.tty }, + { WESTON_OPTION_STRING, "device", 0, &config.device }, + { WESTON_OPTION_STRING, "seat", 0, &config.seat_id }, + }; + + parse_options(fbdev_options, ARRAY_LENGTH(fbdev_options), argc, argv); + + config.base.struct_version = WESTON_FBDEV_BACKEND_CONFIG_VERSION; + config.base.struct_size = sizeof(struct weston_fbdev_backend_config); + config.configure_device = configure_input_device; + + wet_set_simple_head_configurator(c, fbdev_backend_output_configure); + + /* load the actual wayland backend and configure it */ + ret = weston_compositor_load_backend(c, WESTON_BACKEND_FBDEV, + &config.base); + + free(config.device); + return ret; +} + +static int +x11_backend_output_configure(struct weston_output *output) +{ + struct wet_output_config defaults = { + .width = 1024, + .height = 600, + .scale = 1, + .transform = WL_OUTPUT_TRANSFORM_NORMAL + }; + + return wet_configure_windowed_output_from_config(output, &defaults); +} + +static int +load_x11_backend(struct weston_compositor *c, + int *argc, char **argv, struct weston_config *wc) +{ + char *default_output; + const struct weston_windowed_output_api *api; + struct weston_x11_backend_config config = {{ 0, }}; + struct weston_config_section *section; + int ret = 0; + int option_count = 1; + int output_count = 0; + char const *section_name; + int i; + + struct wet_output_config *parsed_options = wet_init_parsed_options(c); + if (!parsed_options) + return -1; + + section = weston_config_get_section(wc, "core", NULL, NULL); + weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, + false); + + const struct weston_option options[] = { + { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, + { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, + { WESTON_OPTION_INTEGER, "scale", 0, &parsed_options->scale }, + { WESTON_OPTION_BOOLEAN, "fullscreen", 'f', &config.fullscreen }, + { WESTON_OPTION_INTEGER, "output-count", 0, &option_count }, + { WESTON_OPTION_BOOLEAN, "no-input", 0, &config.no_input }, + { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, + }; + + parse_options(options, ARRAY_LENGTH(options), argc, argv); + + config.base.struct_version = WESTON_X11_BACKEND_CONFIG_VERSION; + config.base.struct_size = sizeof(struct weston_x11_backend_config); + + wet_set_simple_head_configurator(c, x11_backend_output_configure); + + /* load the actual backend and configure it */ + ret = weston_compositor_load_backend(c, WESTON_BACKEND_X11, + &config.base); + + if (ret < 0) + return ret; + + api = weston_windowed_output_get_api(c); + + if (!api) { + weston_log("Cannot use weston_windowed_output_api.\n"); + return -1; + } + + section = NULL; + while (weston_config_next_section(wc, §ion, §ion_name)) { + char *output_name; + + if (output_count >= option_count) + break; + + if (strcmp(section_name, "output") != 0) { + continue; + } + + weston_config_section_get_string(section, "name", &output_name, NULL); + if (output_name == NULL || output_name[0] != 'X') { + free(output_name); + continue; + } + + if (api->create_head(c, output_name) < 0) { + free(output_name); + return -1; + } + free(output_name); + + output_count++; + } + + default_output = NULL; + + for (i = output_count; i < option_count; i++) { + if (asprintf(&default_output, "screen%d", i) < 0) { + return -1; + } + + if (api->create_head(c, default_output) < 0) { + free(default_output); + return -1; + } + free(default_output); + } + + return 0; +} + +static int +wayland_backend_output_configure(struct weston_output *output) +{ + struct wet_output_config defaults = { + .width = 1024, + .height = 640, + .scale = 1, + .transform = WL_OUTPUT_TRANSFORM_NORMAL + }; + + return wet_configure_windowed_output_from_config(output, &defaults); +} + +static int +load_wayland_backend(struct weston_compositor *c, + int *argc, char **argv, struct weston_config *wc) +{ + struct weston_wayland_backend_config config = {{ 0, }}; + struct weston_config_section *section; + const struct weston_windowed_output_api *api; + const char *section_name; + char *output_name = NULL; + int count = 1; + int ret = 0; + int i; + + struct wet_output_config *parsed_options = wet_init_parsed_options(c); + if (!parsed_options) + return -1; + + config.cursor_size = 32; + config.cursor_theme = NULL; + config.display_name = NULL; + + section = weston_config_get_section(wc, "core", NULL, NULL); + weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, + false); + + const struct weston_option wayland_options[] = { + { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, + { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, + { WESTON_OPTION_INTEGER, "scale", 0, &parsed_options->scale }, + { WESTON_OPTION_STRING, "display", 0, &config.display_name }, + { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, + { WESTON_OPTION_BOOLEAN, "use-tde", 0, &config.use_tde }, // OHOS tde + { WESTON_OPTION_INTEGER, "output-count", 0, &count }, + { WESTON_OPTION_BOOLEAN, "fullscreen", 0, &config.fullscreen }, + { WESTON_OPTION_BOOLEAN, "sprawl", 0, &config.sprawl }, + }; + + parse_options(wayland_options, ARRAY_LENGTH(wayland_options), argc, argv); + + section = weston_config_get_section(wc, "shell", NULL, NULL); + weston_config_section_get_string(section, "cursor-theme", + &config.cursor_theme, NULL); + weston_config_section_get_int(section, "cursor-size", + &config.cursor_size, 32); + + config.base.struct_size = sizeof(struct weston_wayland_backend_config); + config.base.struct_version = WESTON_WAYLAND_BACKEND_CONFIG_VERSION; + + /* load the actual wayland backend and configure it */ + ret = weston_compositor_load_backend(c, WESTON_BACKEND_WAYLAND, + &config.base); + + free(config.cursor_theme); + free(config.display_name); + + if (ret < 0) + return ret; + + api = weston_windowed_output_get_api(c); + + if (api == NULL) { + /* We will just assume if load_backend() finished cleanly and + * windowed_output_api is not present that wayland backend is + * started with --sprawl or runs on fullscreen-shell. + * In this case, all values are hardcoded, so nothing can be + * configured; simply create and enable an output. */ + wet_set_simple_head_configurator(c, NULL); + + return 0; + } + + wet_set_simple_head_configurator(c, wayland_backend_output_configure); + + section = NULL; + while (weston_config_next_section(wc, §ion, §ion_name)) { + if (count == 0) + break; + + if (strcmp(section_name, "output") != 0) { + continue; + } + + weston_config_section_get_string(section, "name", &output_name, NULL); + + if (output_name == NULL) + continue; + + if (output_name[0] != 'W' || output_name[1] != 'L') { + free(output_name); + continue; + } + + if (api->create_head(c, output_name) < 0) { + free(output_name); + return -1; + } + free(output_name); + + --count; + } + + for (i = 0; i < count; i++) { + if (asprintf(&output_name, "wayland%d", i) < 0) + return -1; + + if (api->create_head(c, output_name) < 0) { + free(output_name); + return -1; + } + free(output_name); + } + + return 0; +} + + +static int +load_backend(struct weston_compositor *compositor, const char *backend, + int *argc, char **argv, struct weston_config *config) +{ + if (strstr(backend, "headless-backend.so")) + return load_headless_backend(compositor, argc, argv, config); + else if (strstr(backend, "rdp-backend.so")) + return load_rdp_backend(compositor, argc, argv, config); + else if (strstr(backend, "fbdev-backend.so")) + return load_fbdev_backend(compositor, argc, argv, config); + else if (strstr(backend, "drm-backend.so")) + return load_drm_backend(compositor, argc, argv, config); + else if (strstr(backend, "x11-backend.so")) + return load_x11_backend(compositor, argc, argv, config); + else if (strstr(backend, "wayland-backend.so")) + return load_wayland_backend(compositor, argc, argv, config); + + weston_log("Error: unknown backend \"%s\"\n", backend); + return -1; +} + +static char * +copy_command_line(int argc, char * const argv[]) +{ + FILE *fp; + char *str = NULL; + size_t size = 0; + int i; + + fp = open_memstream(&str, &size); + if (!fp) + return NULL; + + fprintf(fp, "%s", argv[0]); + for (i = 1; i < argc; i++) + fprintf(fp, " %s", argv[i]); + fclose(fp); + + return str; +} + +#if !defined(BUILD_XWAYLAND) +int +wet_load_xwayland(struct weston_compositor *comp) +{ + return -1; +} +#endif + +// OHOS remove logger +//static void +//weston_log_setup_scopes(struct weston_log_context *log_ctx, +// struct weston_log_subscriber *subscriber, +// const char *names) +//{ +// assert(log_ctx); +// assert(subscriber); +// +// char *tokenize = strdup(names); +// char *token = strtok(tokenize, ","); +// while (token) { +// weston_log_subscribe(log_ctx, subscriber, token); +// token = strtok(NULL, ","); +// } +// free(tokenize); +//} +// +//static void +//flight_rec_key_binding_handler(struct weston_keyboard *keyboard, +// const struct timespec *time, uint32_t key, +// void *data) +//{ +// struct weston_log_subscriber *flight_rec = data; +// weston_log_subscriber_display_flight_rec(flight_rec); +//} +// +//static void +//weston_log_subscribe_to_scopes(struct weston_log_context *log_ctx, +// struct weston_log_subscriber *logger, +// struct weston_log_subscriber *flight_rec, +// const char *log_scopes, +// const char *flight_rec_scopes) +//{ +// if (log_scopes) +// weston_log_setup_scopes(log_ctx, logger, log_scopes); +// else +// weston_log_subscribe(log_ctx, logger, "log"); +// +// if (flight_rec_scopes) { +// weston_log_setup_scopes(log_ctx, flight_rec, flight_rec_scopes); +// } else { +// /* by default subscribe to 'log', and 'drm-backend' */ +// weston_log_subscribe(log_ctx, flight_rec, "log"); +// weston_log_subscribe(log_ctx, flight_rec, "drm-backend"); +// } +//} + +WL_EXPORT int +wet_main(int argc, char *argv[]) +{ + int ret = EXIT_FAILURE; + char *cmdline; + struct wl_display *display; + struct wl_event_source *signals[4]; + struct wl_event_loop *loop; + int i, fd; + char *backend = NULL; + char *shell = NULL; + bool xwayland = false; + char *modules = NULL; + char *option_modules = NULL; + char *log = NULL; + char *log_scopes = NULL; + char *flight_rec_scopes = NULL; + char *server_socket = NULL; + int32_t idle_time = -1; + int32_t help = 0; + char *socket_name = NULL; + int32_t version = 0; + int32_t noconfig = 0; + int32_t debug_protocol = 0; + bool numlock_on; + char *config_file = NULL; + struct weston_config *config = NULL; + struct weston_config_section *section; + struct wl_client *primary_client; + struct wl_listener primary_client_destroyed; + struct weston_seat *seat; + struct wet_compositor wet = { 0 }; +// OHOS remove logger +// struct weston_log_context *log_ctx = NULL; +// struct weston_log_subscriber *logger = NULL; +// struct weston_log_subscriber *flight_rec = NULL; + sigset_t mask; + + bool wait_for_debugger = false; + struct wl_protocol_logger *protologger = NULL; + + 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 }, +#if defined(BUILD_XWAYLAND) + { WESTON_OPTION_BOOLEAN, "xwayland", 0, &xwayland }, +#endif + { 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 }, + { WESTON_OPTION_BOOLEAN, "no-config", 0, &noconfig }, + { WESTON_OPTION_STRING, "config", 'c', &config_file }, + { WESTON_OPTION_BOOLEAN, "wait-for-debugger", 0, &wait_for_debugger }, + { WESTON_OPTION_BOOLEAN, "debug", 0, &debug_protocol }, + { WESTON_OPTION_STRING, "logger-scopes", 'l', &log_scopes }, + { WESTON_OPTION_STRING, "flight-rec-scopes", 'f', &flight_rec_scopes }, + }; + + wl_list_init(&wet.layoutput_list); + + os_fd_set_cloexec(fileno(stdin)); + + cmdline = copy_command_line(argc, argv); + parse_options(core_options, ARRAY_LENGTH(core_options), &argc, argv); + + if (help) { + free(cmdline); + usage(EXIT_SUCCESS); + } + + if (version) { + printf(PACKAGE_STRING "\n"); + free(cmdline); + + return EXIT_SUCCESS; + } + +// OHOS remove logger +// log_ctx = weston_log_ctx_create(); +// if (!log_ctx) { +// fprintf(stderr, "Failed to initialize weston debug framework.\n"); +// return EXIT_FAILURE; +// } +// +// log_scope = weston_log_ctx_add_log_scope(log_ctx, "log", +// "Weston and Wayland log\n", NULL, NULL, NULL); +// +// if (!weston_log_file_open(log)) +// return EXIT_FAILURE; +// +// weston_log_set_handler(vlog, vlog_continue); +// +// logger = weston_log_subscriber_create_log(weston_logfile); +// flight_rec = weston_log_subscriber_create_flight_rec(DEFAULT_FLIGHT_REC_SIZE); +// +// weston_log_subscribe_to_scopes(log_ctx, logger, flight_rec, +// log_scopes, flight_rec_scopes); + + 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); + weston_log("Command line: %s\n", cmdline); + free(cmdline); + log_uname(); + + verify_xdg_runtime_dir(); + + display = wl_display_create(); + if (display == NULL) { + weston_log("fatal: failed to create display\n"); + goto out_display; + } + + 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 (!signals[0] || !signals[1] || !signals[2] || !signals[3]) + goto out_signals; + + /* Xwayland uses SIGUSR1 for communicating with weston. Since some + weston plugins may create additional threads, set up any necessary + signal blocking early so that these threads can inherit the settings + when created. */ + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + pthread_sigmask(SIG_BLOCK, &mask, NULL); + + if (load_configuration(&config, noconfig, config_file) < 0) + goto out_signals; + wet.config = config; + wet.parsed_options = NULL; + + section = weston_config_get_section(config, "core", NULL, NULL); + +// OHOS remove debugger +// if (!wait_for_debugger) { +// weston_config_section_get_bool(section, "wait-for-debugger", +// &wait_for_debugger, false); +// } +// if (wait_for_debugger) { +// weston_log("Weston PID is %ld - " +// "waiting for debugger, send SIGCONT to continue...\n", +// (long)getpid()); +// raise(SIGSTOP); +// } + + if (!backend) { + weston_config_section_get_string(section, "backend", &backend, + NULL); + if (!backend) + backend = weston_choose_default_backend(); + } + +// OHOS remove logger +// wet.compositor = weston_compositor_create(display, log_ctx, &wet); + wet.compositor = weston_compositor_create(display, &wet); + if (wet.compositor == NULL) { + weston_log("fatal: failed to create compositor\n"); + goto out; + } + segv_compositor = wet.compositor; + +// OHOS remove log, debugger +// protocol_scope = +// weston_log_ctx_add_log_scope(log_ctx, "proto", +// "Wayland protocol dump for all clients.\n", +// NULL, NULL, NULL); +// +// protologger = wl_display_add_protocol_logger(display, +// protocol_log_fn, +// NULL); +// if (debug_protocol) +// weston_compositor_enable_debug_protocol(wet.compositor); +// +// weston_compositor_add_debug_binding(wet.compositor, KEY_D, +// flight_rec_key_binding_handler, +// flight_rec); + + if (weston_compositor_init_config(wet.compositor, config) < 0) + goto out; + + weston_config_section_get_bool(section, "require-input", + &wet.compositor->require_input, true); + + if (load_backend(wet.compositor, backend, &argc, argv, config) < 0) { + weston_log("fatal: failed to create compositor backend\n"); + goto out; + } + + weston_compositor_flush_heads_changed(wet.compositor); + // OHOS + // if (wet.init_failed) + // goto out; + if (wet.init_failed && wl_list_empty(&wet.compositor->output_list)) + goto out; + + if (idle_time < 0) + weston_config_section_get_int(section, "idle-time", &idle_time, -1); + if (idle_time < 0) + idle_time = 300; /* default idle timeout, in seconds */ + + wet.compositor->idle_time = idle_time; + wet.compositor->default_pointer_grab = NULL; + wet.compositor->exit = handle_exit; + + weston_compositor_log_capabilities(wet.compositor); + + server_socket = getenv("WAYLAND_SERVER_SOCKET"); + if (server_socket) { + weston_log("Running with single client\n"); + if (!safe_strtoint(server_socket, &fd)) + fd = -1; + } else { + fd = -1; + } + + if (fd != -1) { + primary_client = wl_client_create(display, fd); + if (!primary_client) { + weston_log("fatal: failed to add client: %s\n", + strerror(errno)); + goto out; + } + primary_client_destroyed.notify = + handle_primary_client_destroyed; + wl_client_add_destroy_listener(primary_client, + &primary_client_destroyed); + } else if (weston_create_listening_socket(display, socket_name)) { + goto out; + } + + if (!shell) + weston_config_section_get_string(section, "shell", &shell, + "desktop-shell.so"); + + if (wet_load_shell(wet.compositor, shell, &argc, argv) < 0) + goto out; + + weston_config_section_get_string(section, "modules", &modules, ""); + if (load_modules(wet.compositor, modules, &argc, argv, &xwayland) < 0) + goto out; + + if (load_modules(wet.compositor, option_modules, &argc, argv, &xwayland) < 0) + goto out; + + if (!xwayland) { + weston_config_section_get_bool(section, "xwayland", &xwayland, + false); + } + if (xwayland) { + if (wet_load_xwayland(wet.compositor) < 0) + goto out; + } + + section = weston_config_get_section(config, "keyboard", NULL, NULL); + weston_config_section_get_bool(section, "numlock-on", &numlock_on, false); + if (numlock_on) { + wl_list_for_each(seat, &wet.compositor->seat_list, link) { + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(seat); + + if (keyboard) + weston_keyboard_set_locks(keyboard, + WESTON_NUM_LOCK, + WESTON_NUM_LOCK); + } + } + + for (i = 1; i < argc; i++) + weston_log("fatal: unhandled option: %s\n", argv[i]); + if (argc > 1) + goto out; + + weston_compositor_wake(wet.compositor); + + wl_display_run(display); + + /* Allow for setting return exit code after + * wl_display_run returns normally. This is + * useful for devs/testers and automated tests + * that want to indicate failure status to + * testing infrastructure above + */ + ret = wet.compositor->exit_code; + +out: + wet_compositor_destroy_layout(&wet); + + /* free(NULL) is valid, and it won't be NULL if it's used */ + free(wet.parsed_options); + +// OHOS remove logger +// if (protologger) +// wl_protocol_logger_destroy(protologger); + + weston_compositor_destroy(wet.compositor); +// OHOS remove logger +// weston_log_scope_destroy(protocol_scope); +// protocol_scope = NULL; +// weston_log_scope_destroy(log_scope); +// log_scope = NULL; +// weston_log_subscriber_destroy(logger); +// weston_log_subscriber_destroy(flight_rec); +// weston_log_ctx_destroy(log_ctx); + +out_signals: + for (i = ARRAY_LENGTH(signals) - 1; i >= 0; i--) + if (signals[i]) + wl_event_source_remove(signals[i]); + + wl_display_destroy(display); + +out_display: +// OHOS remove logger +// weston_log_file_close(); + + if (config) + weston_config_destroy(config); + free(config_file); + free(backend); + free(shell); + free(socket_name); + free(option_modules); + free(log); + free(modules); + + return ret; +} diff --git a/compositor/meson.build b/compositor/meson.build new file mode 100644 index 0000000..9dc95f3 --- /dev/null +++ b/compositor/meson.build @@ -0,0 +1,187 @@ +srcs_weston = [ + git_version_h, + 'main.c', + 'testsuite-util.c', + 'text-backend.c', + 'weston-screenshooter.c', + text_input_unstable_v1_server_protocol_h, + text_input_unstable_v1_protocol_c, + input_method_unstable_v1_server_protocol_h, + input_method_unstable_v1_protocol_c, + weston_screenshooter_server_protocol_h, + weston_screenshooter_protocol_c, +] +deps_weston = [ + dep_libshared, + dep_libweston_public, + dep_libinput, + dep_libevdev, + dep_libdl, + dep_threads, +] + +if get_option('xwayland') + config_h.set('BUILD_XWAYLAND', '1') + + srcs_weston += 'xwayland.c' + config_h.set_quoted('XSERVER_PATH', get_option('xwayland-path')) +endif + +libexec_weston = shared_library( + 'exec_weston', + sources: srcs_weston, + include_directories: common_inc, + dependencies: deps_weston, + install_dir: dir_module_weston, + install: true, + version: '0.0.0', + soversion: 0 +) +dep_libexec_weston = declare_dependency( + link_with: libexec_weston, + include_directories: [ include_directories('.'), public_inc ], + dependencies: dep_libweston_public +) +exe_weston = executable( + 'weston', + 'executable.c', + include_directories: common_inc, + dependencies: dep_libexec_weston, + install_rpath: dir_module_weston, + install: true +) +install_headers('weston.h', subdir: 'weston') + +pkgconfig.generate( + filebase: 'weston', + name: 'Weston Plugin API', + version: version_weston, + description: 'Header files for Weston plugin development', + requires_private: [ lib_weston ], + variables: [ + 'libexecdir=' + join_paths('${prefix}', get_option('libexecdir')), + 'pkglibexecdir=${libexecdir}/weston' + ], + subdirs: 'weston' +) + +install_data( + 'weston.desktop', + install_dir: join_paths(dir_data, 'wayland-sessions') +) + +if get_option('screenshare') + srcs_screenshare = [ + 'screen-share.c', + fullscreen_shell_unstable_v1_client_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + ] + deps_screenshare = [ + dep_libexec_weston, + dep_libshared, + dep_libweston_public, + dep_libweston_private_h, # XXX: https://gitlab.freedesktop.org/wayland/weston/issues/292 + dep_wayland_client, + ] + plugin_screenshare = shared_library( + 'screen-share', + srcs_screenshare, + include_directories: common_inc, + dependencies: deps_screenshare, + name_prefix: '', + install: true, + install_dir: dir_module_weston, + install_rpath: '$ORIGIN' + ) + env_modmap += 'screen-share.so=@0@;'.format(plugin_screenshare.full_path()) +endif + +if get_option('color-management-lcms') + config_h.set('HAVE_LCMS', '1') + + srcs_lcms = [ + 'cms-static.c', + 'cms-helper.c', + ] + + dep_lcms2 = dependency('lcms2', required: false) + if not dep_lcms2.found() + error('cms-static requires lcms2 which was not found. Or, you can use \'-Dcolor-management-lcms=false\'.') + endif + + plugin_lcms = shared_library( + 'cms-static', + srcs_lcms, + include_directories: common_inc, + dependencies: [ dep_libexec_weston, dep_libweston_public, dep_lcms2 ], + name_prefix: '', + install: true, + install_dir: dir_module_weston, + install_rpath: '$ORIGIN' + ) + env_modmap += 'cms-static.so=@0@;'.format(plugin_lcms.full_path()) +endif + +if get_option('color-management-colord') + if not get_option('color-management-lcms') + error('LCMS must be enabled to support colord') + endif + + srcs_colord = [ + 'cms-colord.c', + 'cms-helper.c', + ] + + dep_colord = dependency('colord', version: '>= 0.1.27', required: false) + if not dep_colord.found() + error('cms-colord requires colord >= 0.1.27 which was not found. Or, you can use \'-Dcolor-management-colord=false\'.') + endif + + plugin_colord_deps = [ dep_libweston_public, dep_colord, dep_lcms2 ] + + foreach depname : [ 'glib-2.0', 'gobject-2.0' ] + dep = dependency(depname, required: false) + if not dep.found() + error('cms-colord requires \'@0@\' which was not found. If you rather not build this, set \'-Dcolor-management-colord=false\'.'.format(depname)) + endif + plugin_colord_deps += dep + endforeach + + plugin_colord = shared_library( + 'cms-colord', + srcs_colord, + include_directories: common_inc, + dependencies: plugin_colord_deps, + name_prefix: '', + install: true, + install_dir: dir_module_weston + ) + env_modmap += 'cms-colord.so=@0@;'.format(plugin_colord.full_path()) +endif + +if get_option('systemd') + dep_libsystemd = dependency('libsystemd', required: false) + if not dep_libsystemd.found() + error('systemd-notify requires libsystemd which was not found. Or, you can use \'-Dsystemd=false\'.') + endif + + plugin_systemd_notify = shared_library( + 'systemd-notify', + 'systemd-notify.c', + include_directories: common_inc, + dependencies: [ dep_libweston_public, dep_libsystemd ], + name_prefix: '', + install: true, + install_dir: dir_module_weston + ) + env_modmap += 'systemd-notify.so=@0@;'.format(plugin_systemd_notify.full_path()) +endif + +weston_ini_config = configuration_data() +weston_ini_config.set('bindir', dir_bin) +weston_ini_config.set('libexecdir', dir_libexec) +configure_file( + input: '../weston.ini.in', + output: 'weston.ini', + configuration: weston_ini_config +) diff --git a/compositor/screen-share.c b/compositor/screen-share.c new file mode 100644 index 0000000..8c37452 --- /dev/null +++ b/compositor/screen-share.c @@ -0,0 +1,1184 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2014 Jason Ekstrand + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "backend.h" +#include "libweston-internal.h" +#include "weston.h" +#include "shared/helpers.h" +#include "shared/os-compatibility.h" +#include "shared/timespec-util.h" +#include "fullscreen-shell-unstable-v1-client-protocol.h" + +struct shared_output { + struct weston_output *output; + struct wl_listener output_destroyed; + struct wl_list seat_list; + + struct { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_shm *shm; + uint32_t shm_formats; + struct zwp_fullscreen_shell_v1 *fshell; + struct wl_output *output; + struct wl_surface *surface; + struct wl_callback *frame_cb; + struct zwp_fullscreen_shell_mode_feedback_v1 *mode_feedback; + } parent; + + struct wl_event_source *event_source; + struct wl_listener frame_listener; + + struct { + int32_t width, height; + + struct wl_list buffers; + struct wl_list free_buffers; + } shm; + + int cache_dirty; + pixman_image_t *cache_image; + uint32_t *tmp_data; + size_t tmp_data_size; +}; + +struct ss_seat { + struct weston_seat base; + struct shared_output *output; + struct wl_list link; + uint32_t id; + + struct { + struct wl_seat *seat; + struct wl_pointer *pointer; + struct wl_keyboard *keyboard; + } parent; + + enum weston_key_state_update keyboard_state_update; + uint32_t key_serial; +}; + +struct ss_shm_buffer { + struct shared_output *output; + struct wl_list link; + struct wl_list free_link; + + struct wl_buffer *buffer; + void *data; + size_t size; + pixman_region32_t damage; + + pixman_image_t *pm_image; +}; + +struct screen_share { + struct weston_compositor *compositor; + /* XXX: missing compositor destroy listener + * https://gitlab.freedesktop.org/wayland/weston/issues/298 + */ + char *command; +}; + +static void +ss_seat_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 ss_seat *seat = data; + + /* No transformation of input position is required here because we are + * always receiving the input in the same coordinates as the output. */ + + notify_pointer_focus(&seat->base, NULL, 0, 0); +} + +static void +ss_seat_handle_pointer_leave(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface) +{ + struct ss_seat *seat = data; + + notify_pointer_focus(&seat->base, NULL, 0, 0); +} + +static void +ss_seat_handle_motion(void *data, struct wl_pointer *pointer, + uint32_t time, wl_fixed_t x, wl_fixed_t y) +{ + struct ss_seat *seat = data; + struct timespec ts; + + timespec_from_msec(&ts, time); + + /* No transformation of input position is required here because we are + * always receiving the input in the same coordinates as the output. */ + + notify_motion_absolute(&seat->base, &ts, + wl_fixed_to_double(x), wl_fixed_to_double(y)); + notify_pointer_frame(&seat->base); +} + +static void +ss_seat_handle_button(void *data, struct wl_pointer *pointer, + uint32_t serial, uint32_t time, uint32_t button, + uint32_t state) +{ + struct ss_seat *seat = data; + struct timespec ts; + + timespec_from_msec(&ts, time); + + notify_button(&seat->base, &ts, button, state); + notify_pointer_frame(&seat->base); +} + +static void +ss_seat_handle_axis(void *data, struct wl_pointer *pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) +{ + struct ss_seat *seat = data; + struct weston_pointer_axis_event weston_event; + struct timespec ts; + + weston_event.axis = axis; + weston_event.value = wl_fixed_to_double(value); + weston_event.has_discrete = false; + + timespec_from_msec(&ts, time); + + notify_axis(&seat->base, &ts, &weston_event); + notify_pointer_frame(&seat->base); +} + +static const struct wl_pointer_listener ss_seat_pointer_listener = { + ss_seat_handle_pointer_enter, + ss_seat_handle_pointer_leave, + ss_seat_handle_motion, + ss_seat_handle_button, + ss_seat_handle_axis, +}; + +static void +ss_seat_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int fd, uint32_t size) +{ + struct ss_seat *seat = data; + struct xkb_keymap *keymap; + char *map_str; + + if (!data) + goto error_no_seat; + + if (format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (map_str == MAP_FAILED) { + weston_log("mmap failed: %s\n", strerror(errno)); + goto error; + } + + keymap = xkb_keymap_new_from_string(seat->base.compositor->xkb_context, + map_str, + XKB_KEYMAP_FORMAT_TEXT_V1, + 0); + munmap(map_str, size); + + if (!keymap) { + weston_log("failed to compile keymap\n"); + goto error; + } + + seat->keyboard_state_update = STATE_UPDATE_NONE; + } else if (format == WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP) { + weston_log("No keymap provided; falling back to default\n"); + keymap = NULL; + seat->keyboard_state_update = STATE_UPDATE_AUTOMATIC; + } else { + weston_log("Invalid keymap\n"); + goto error; + } + + close(fd); + + if (seat->base.keyboard_device_count) + weston_seat_update_keymap(&seat->base, keymap); + else + weston_seat_init_keyboard(&seat->base, keymap); + + xkb_keymap_unref(keymap); + + return; + +error: + wl_keyboard_release(seat->parent.keyboard); +error_no_seat: + close(fd); +} + +static void +ss_seat_handle_keyboard_enter(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) +{ + struct ss_seat *seat = data; + + /* XXX: If we get a modifier event immediately before the focus, + * we should try to keep the same serial. */ + notify_keyboard_focus_in(&seat->base, keys, + STATE_UPDATE_AUTOMATIC); +} + +static void +ss_seat_handle_keyboard_leave(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface) +{ + struct ss_seat *seat = data; + + notify_keyboard_focus_out(&seat->base); +} + +static void +ss_seat_handle_key(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t time, + uint32_t key, uint32_t state) +{ + struct ss_seat *seat = data; + struct timespec ts; + + timespec_from_msec(&ts, time); + seat->key_serial = serial; + notify_key(&seat->base, &ts, key, + state ? WL_KEYBOARD_KEY_STATE_PRESSED : + WL_KEYBOARD_KEY_STATE_RELEASED, + seat->keyboard_state_update); +} + +static void +ss_seat_handle_modifiers(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial_in, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) +{ + struct ss_seat *seat = data; + struct weston_compositor *c = seat->base.compositor; + struct weston_keyboard *keyboard; + 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 == seat->key_serial) + serial_out = wl_display_get_serial(c->wl_display); + else + serial_out = wl_display_next_serial(c->wl_display); + + keyboard = weston_seat_get_keyboard(&seat->base); + xkb_state_update_mask(keyboard->xkb_state.state, + mods_depressed, mods_latched, + mods_locked, 0, 0, group); + notify_modifiers(&seat->base, serial_out); +} + +static const struct wl_keyboard_listener ss_seat_keyboard_listener = { + ss_seat_handle_keymap, + ss_seat_handle_keyboard_enter, + ss_seat_handle_keyboard_leave, + ss_seat_handle_key, + ss_seat_handle_modifiers, +}; + +static void +ss_seat_handle_capabilities(void *data, struct wl_seat *seat, + enum wl_seat_capability caps) +{ + struct ss_seat *ss_seat = data; + + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !ss_seat->parent.pointer) { + ss_seat->parent.pointer = wl_seat_get_pointer(seat); + wl_pointer_set_user_data(ss_seat->parent.pointer, ss_seat); + wl_pointer_add_listener(ss_seat->parent.pointer, + &ss_seat_pointer_listener, ss_seat); + weston_seat_init_pointer(&ss_seat->base); + } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && ss_seat->parent.pointer) { + wl_pointer_destroy(ss_seat->parent.pointer); + ss_seat->parent.pointer = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !ss_seat->parent.keyboard) { + ss_seat->parent.keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_set_user_data(ss_seat->parent.keyboard, ss_seat); + wl_keyboard_add_listener(ss_seat->parent.keyboard, + &ss_seat_keyboard_listener, ss_seat); + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && ss_seat->parent.keyboard) { + wl_keyboard_destroy(ss_seat->parent.keyboard); + ss_seat->parent.keyboard = NULL; + } +} + +static const struct wl_seat_listener ss_seat_listener = { + ss_seat_handle_capabilities, +}; + +static struct ss_seat * +ss_seat_create(struct shared_output *so, uint32_t id) +{ + struct ss_seat *seat; + + seat = zalloc(sizeof *seat); + if (seat == NULL) + return NULL; + + weston_seat_init(&seat->base, so->output->compositor, "default"); + seat->output = so; + seat->id = id; + seat->parent.seat = wl_registry_bind(so->parent.registry, id, + &wl_seat_interface, 1); + wl_list_insert(so->seat_list.prev, &seat->link); + + wl_seat_add_listener(seat->parent.seat, &ss_seat_listener, seat); + wl_seat_set_user_data(seat->parent.seat, seat); + + return seat; +} + +static void +ss_seat_destroy(struct ss_seat *seat) +{ + if (seat->parent.pointer) + wl_pointer_release(seat->parent.pointer); + if (seat->parent.keyboard) + wl_keyboard_release(seat->parent.keyboard); + wl_seat_destroy(seat->parent.seat); + + wl_list_remove(&seat->link); + + weston_seat_release(&seat->base); + + free(seat); +} + +static void +ss_shm_buffer_destroy(struct ss_shm_buffer *buffer) +{ + pixman_image_unref(buffer->pm_image); + + wl_buffer_destroy(buffer->buffer); + munmap(buffer->data, buffer->size); + + pixman_region32_fini(&buffer->damage); + + wl_list_remove(&buffer->link); + wl_list_remove(&buffer->free_link); + free(buffer); +} + +static void +buffer_release(void *data, struct wl_buffer *buffer) +{ + struct ss_shm_buffer *sb = data; + + if (sb->output) { + wl_list_insert(&sb->output->shm.free_buffers, &sb->free_link); + } else { + ss_shm_buffer_destroy(sb); + } +} + +static const struct wl_buffer_listener buffer_listener = { + buffer_release +}; + +static struct ss_shm_buffer * +shared_output_get_shm_buffer(struct shared_output *so) +{ + struct ss_shm_buffer *sb, *bnext; + struct wl_shm_pool *pool; + int width, height, stride; + int fd; + unsigned char *data; + + width = so->output->width; + height = so->output->height; + stride = width * 4; + + /* If the size of the output changed, we free the old buffers and + * make new ones. */ + if (so->shm.width != width || + so->shm.height != height) { + + /* Destroy free buffers */ + wl_list_for_each_safe(sb, bnext, &so->shm.free_buffers, free_link) + ss_shm_buffer_destroy(sb); + + /* Orphan in-use buffers so they get destroyed */ + wl_list_for_each(sb, &so->shm.buffers, link) + sb->output = NULL; + + so->shm.width = width; + so->shm.height = height; + } + + if (!wl_list_empty(&so->shm.free_buffers)) { + sb = container_of(so->shm.free_buffers.next, + struct ss_shm_buffer, free_link); + wl_list_remove(&sb->free_link); + wl_list_init(&sb->free_link); + + return sb; + } + + fd = os_create_anonymous_file(height * stride); + if (fd < 0) { + weston_log("os_create_anonymous_file: %s\n", strerror(errno)); + return NULL; + } + + data = mmap(NULL, height * stride, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + weston_log("mmap: %s\n", strerror(errno)); + goto out_close; + } + + sb = zalloc(sizeof *sb); + if (!sb) + goto out_unmap; + + sb->output = so; + wl_list_init(&sb->free_link); + wl_list_insert(&so->shm.buffers, &sb->link); + + pixman_region32_init_rect(&sb->damage, 0, 0, width, height); + + sb->data = data; + sb->size = height * stride; + + pool = wl_shm_create_pool(so->parent.shm, fd, sb->size); + + sb->buffer = wl_shm_pool_create_buffer(pool, 0, + width, height, stride, + WL_SHM_FORMAT_ARGB8888); + wl_buffer_add_listener(sb->buffer, &buffer_listener, sb); + wl_shm_pool_destroy(pool); + close(fd); + fd = -1; + + memset(data, 0, sb->size); + + sb->pm_image = + pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, + (uint32_t *)data, stride); + if (!sb->pm_image) + goto out_pixman_error; + + return sb; + +out_pixman_error: + pixman_region32_fini(&sb->damage); +out_unmap: + munmap(data, height * stride); +out_close: + if (fd != -1) + close(fd); + return NULL; +} + +static void +output_compute_transform(struct weston_output *output, + pixman_transform_t *transform) +{ + pixman_fixed_t fw, fh; + + pixman_transform_init_identity(transform); + + fw = pixman_int_to_fixed(output->width); + fh = pixman_int_to_fixed(output->height); + + 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); + } + + 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, fw); + 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, fh, 0); + break; + } + + pixman_transform_scale(transform, NULL, + pixman_fixed_1 * output->current_scale, + pixman_fixed_1 * output->current_scale); +} + +static void +shared_output_destroy(struct shared_output *so); + +static int +shared_output_ensure_tmp_data(struct shared_output *so, + pixman_region32_t *region) +{ + pixman_box32_t *ext; + size_t size; + + if (!pixman_region32_not_empty(region)) + return 0; + + ext = pixman_region32_extents(region); + + /* Damage is in output coordinates. + * + * We are multiplying by 4 because the temporary data needs to be able + * to store an 32 bit-per-pixel buffer. + */ + size = 4 * (ext->x2 - ext->x1) * (ext->y2 - ext->y1) + * so->output->current_scale * so->output->current_scale; + + if (so->tmp_data != NULL && size <= so->tmp_data_size) + return 0; + + free(so->tmp_data); + so->tmp_data = malloc(size); + if (so->tmp_data == NULL) { + so->tmp_data_size = 0; + errno = ENOMEM; + return -1; + } + + so->tmp_data_size = size; + + return 0; +} + +static void +shared_output_update(struct shared_output *so); + +static void +shared_output_frame_callback(void *data, struct wl_callback *cb, uint32_t time) +{ + struct shared_output *so = data; + + if (cb != so->parent.frame_cb) + return; + + wl_callback_destroy(cb); + so->parent.frame_cb = NULL; + + shared_output_update(so); +} + +static const struct wl_callback_listener shared_output_frame_listener = { + shared_output_frame_callback +}; + +static void +shared_output_update(struct shared_output *so) +{ + struct ss_shm_buffer *sb; + pixman_box32_t *r; + int i, nrects; + pixman_transform_t transform; + + /* Only update if we need to */ + if (!so->cache_dirty || so->parent.frame_cb) + return; + + sb = shared_output_get_shm_buffer(so); + if (sb == NULL) { + shared_output_destroy(so); + return; + } + + output_compute_transform(so->output, &transform); + pixman_image_set_transform(so->cache_image, &transform); + + pixman_image_set_clip_region32(sb->pm_image, &sb->damage); + + if (so->output->current_scale == 1) { + pixman_image_set_filter(so->cache_image, + PIXMAN_FILTER_NEAREST, NULL, 0); + } else { + pixman_image_set_filter(so->cache_image, + PIXMAN_FILTER_BILINEAR, NULL, 0); + } + + pixman_image_composite32(PIXMAN_OP_SRC, + so->cache_image, /* src */ + NULL, /* mask */ + sb->pm_image, /* dest */ + 0, 0, /* src_x, src_y */ + 0, 0, /* mask_x, mask_y */ + 0, 0, /* dest_x, dest_y */ + so->output->width, /* width */ + so->output->height /* height */); + + pixman_image_set_transform(sb->pm_image, NULL); + pixman_image_set_clip_region32(sb->pm_image, NULL); + + r = pixman_region32_rectangles(&sb->damage, &nrects); + for (i = 0; i < nrects; ++i) + wl_surface_damage(so->parent.surface, r[i].x1, r[i].y1, + r[i].x2 - r[i].x1, r[i].y2 - r[i].y1); + + wl_surface_attach(so->parent.surface, sb->buffer, 0, 0); + + so->parent.frame_cb = wl_surface_frame(so->parent.surface); + wl_callback_add_listener(so->parent.frame_cb, + &shared_output_frame_listener, so); + + wl_surface_commit(so->parent.surface); + wl_callback_destroy(wl_display_sync(so->parent.display)); + wl_display_flush(so->parent.display); + + /* Clear the buffer damage */ + pixman_region32_fini(&sb->damage); + pixman_region32_init(&sb->damage); +} + +static void +shm_handle_format(void *data, struct wl_shm *wl_shm, uint32_t format) +{ + struct shared_output *so = data; + + so->parent.shm_formats |= (1 << format); +} + +struct wl_shm_listener shm_listener = { + shm_handle_format +}; + +static void +registry_handle_global(void *data, struct wl_registry *registry, + uint32_t id, const char *interface, uint32_t version) +{ + struct shared_output *so = data; + + if (strcmp(interface, "wl_compositor") == 0) { + so->parent.compositor = + wl_registry_bind(registry, + id, &wl_compositor_interface, 1); + } else if (strcmp(interface, "wl_output") == 0 && !so->parent.output) { + so->parent.output = + wl_registry_bind(registry, + id, &wl_output_interface, 1); + } else if (strcmp(interface, "wl_seat") == 0) { + ss_seat_create(so, id); + } else if (strcmp(interface, "wl_shm") == 0) { + so->parent.shm = + wl_registry_bind(registry, + id, &wl_shm_interface, 1); + wl_shm_add_listener(so->parent.shm, &shm_listener, so); + } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { + so->parent.fshell = + wl_registry_bind(registry, + id, + &zwp_fullscreen_shell_v1_interface, + 1); + } +} + +static void +registry_handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ + struct shared_output *so = data; + struct ss_seat *seat, *next; + + wl_list_for_each_safe(seat, next, &so->seat_list, link) + if (seat->id == name) + ss_seat_destroy(seat); +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static int +shared_output_handle_event(int fd, uint32_t mask, void *data) +{ + struct shared_output *so = data; + int count = 0; + + if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { + shared_output_destroy(so); + return 0; + } + + if (mask & WL_EVENT_READABLE) + count = wl_display_dispatch(so->parent.display); + if (mask & WL_EVENT_WRITABLE) + wl_display_flush(so->parent.display); + + if (mask == 0) { + count = wl_display_dispatch_pending(so->parent.display); + wl_display_flush(so->parent.display); + } + + return count; +} + +static void +output_destroyed(struct wl_listener *l, void *data) +{ + struct shared_output *so; + + so = container_of(l, struct shared_output, output_destroyed); + + shared_output_destroy(so); +} + +static void +mode_feedback_ok(void *data, struct zwp_fullscreen_shell_mode_feedback_v1 *fb) +{ + struct shared_output *so = data; + + zwp_fullscreen_shell_mode_feedback_v1_destroy(so->parent.mode_feedback); +} + +static void +mode_feedback_failed(void *data, struct zwp_fullscreen_shell_mode_feedback_v1 *fb) +{ + struct shared_output *so = data; + + zwp_fullscreen_shell_mode_feedback_v1_destroy(so->parent.mode_feedback); + + weston_log("Screen share failed: present_surface_for_mode failed\n"); + shared_output_destroy(so); +} + +struct zwp_fullscreen_shell_mode_feedback_v1_listener mode_feedback_listener = { + mode_feedback_ok, + mode_feedback_failed, + mode_feedback_ok, +}; + +static void +shared_output_repainted(struct wl_listener *listener, void *data) +{ + struct shared_output *so = + container_of(listener, struct shared_output, frame_listener); + pixman_region32_t damage; + pixman_region32_t *current_damage = data; + struct ss_shm_buffer *sb; + int32_t x, y, width, height, stride; + int i, nrects, do_yflip, y_orig; + pixman_box32_t *r; + pixman_image_t *damaged_image; + pixman_transform_t transform; + + width = so->output->current_mode->width; + height = so->output->current_mode->height; + stride = width; + + if (!so->cache_image || + pixman_image_get_width(so->cache_image) != width || + pixman_image_get_height(so->cache_image) != height) { + if (so->cache_image) + pixman_image_unref(so->cache_image); + + so->cache_image = + pixman_image_create_bits(PIXMAN_a8r8g8b8, + width, height, NULL, + stride); + if (!so->cache_image) + goto err_shared_output; + + pixman_region32_init_rect(&damage, 0, 0, width, height); + } else { + /* Damage in output coordinates */ + pixman_region32_init(&damage); + pixman_region32_intersect(&damage, &so->output->region, current_damage); + pixman_region32_translate(&damage, -so->output->x, -so->output->y); + } + + /* Apply damage to all buffers */ + wl_list_for_each(sb, &so->shm.buffers, link) + pixman_region32_union(&sb->damage, &sb->damage, &damage); + + /* Transform to buffer coordinates */ + weston_transformed_region(so->output->width, so->output->height, + so->output->transform, + so->output->current_scale, + &damage, &damage); + + if (shared_output_ensure_tmp_data(so, &damage) < 0) + goto err_pixman_init; + + do_yflip = !!(so->output->compositor->capabilities & WESTON_CAP_CAPTURE_YFLIP); + + r = pixman_region32_rectangles(&damage, &nrects); + for (i = 0; i < nrects; ++i) { + x = r[i].x1; + y = r[i].y1; + width = r[i].x2 - r[i].x1; + height = r[i].y2 - r[i].y1; + + if (do_yflip) + y_orig = so->output->current_mode->height - r[i].y2; + else + y_orig = y; + + so->output->compositor->renderer->read_pixels( + so->output, PIXMAN_a8r8g8b8, so->tmp_data, + x, y_orig, width, height); + + damaged_image = pixman_image_create_bits(PIXMAN_a8r8g8b8, + width, height, + so->tmp_data, + (PIXMAN_FORMAT_BPP(PIXMAN_a8r8g8b8) / 8) * width); + if (!damaged_image) + goto err_pixman_init; + + if (do_yflip) { + pixman_transform_init_scale(&transform, + pixman_fixed_1, + pixman_fixed_minus_1); + + pixman_transform_translate(&transform, NULL, + 0, + pixman_int_to_fixed(height)); + + pixman_image_set_transform(damaged_image, &transform); + } + + pixman_image_composite32(PIXMAN_OP_SRC, + damaged_image, + NULL, + so->cache_image, + 0, 0, + 0, 0, + x, y, + width, height); + pixman_image_unref(damaged_image); + } + + so->cache_dirty = 1; + + pixman_region32_fini(&damage); + shared_output_update(so); + + return; + +err_pixman_init: + pixman_region32_fini(&damage); +err_shared_output: + shared_output_destroy(so); +} + +static struct shared_output * +shared_output_create(struct weston_output *output, int parent_fd) +{ + struct shared_output *so; + struct wl_event_loop *loop; + struct ss_seat *seat, *tmp; + int epoll_fd; + + so = zalloc(sizeof *so); + if (so == NULL) + goto err_close; + + wl_list_init(&so->seat_list); + + so->parent.display = wl_display_connect_to_fd(parent_fd); + if (!so->parent.display) + goto err_alloc; + + so->parent.registry = wl_display_get_registry(so->parent.display); + if (!so->parent.registry) + goto err_display; + wl_registry_add_listener(so->parent.registry, + ®istry_listener, so); + wl_display_roundtrip(so->parent.display); + if (so->parent.shm == NULL) { + weston_log("Screen share failed: No wl_shm found\n"); + goto err_display; + } + if (so->parent.fshell == NULL) { + weston_log("Screen share failed: " + "Parent does not support wl_fullscreen_shell\n"); + goto err_display; + } + if (so->parent.compositor == NULL) { + weston_log("Screen share failed: No wl_compositor found\n"); + goto err_display; + } + + /* Get SHM formats */ + wl_display_roundtrip(so->parent.display); + if (!(so->parent.shm_formats & (1 << WL_SHM_FORMAT_XRGB8888))) { + weston_log("Screen share failed: " + "WL_SHM_FORMAT_XRGB8888 not available\n"); + goto err_display; + } + + so->parent.surface = + wl_compositor_create_surface(so->parent.compositor); + if (!so->parent.surface) { + weston_log("Screen share failed: %s\n", strerror(errno)); + goto err_display; + } + + so->parent.mode_feedback = + zwp_fullscreen_shell_v1_present_surface_for_mode(so->parent.fshell, + so->parent.surface, + so->parent.output, + output->current_mode->refresh); + if (!so->parent.mode_feedback) { + weston_log("Screen share failed: %s\n", strerror(errno)); + goto err_display; + } + zwp_fullscreen_shell_mode_feedback_v1_add_listener(so->parent.mode_feedback, + &mode_feedback_listener, + so); + + loop = wl_display_get_event_loop(output->compositor->wl_display); + + epoll_fd = wl_display_get_fd(so->parent.display); + so->event_source = + wl_event_loop_add_fd(loop, epoll_fd, WL_EVENT_READABLE, + shared_output_handle_event, so); + if (!so->event_source) { + weston_log("Screen share failed: %s\n", strerror(errno)); + goto err_display; + } + + /* Ok, everything's created. We should be good to go */ + wl_list_init(&so->shm.buffers); + wl_list_init(&so->shm.free_buffers); + + so->output = output; + so->output_destroyed.notify = output_destroyed; + wl_signal_add(&so->output->destroy_signal, &so->output_destroyed); + + so->frame_listener.notify = shared_output_repainted; + wl_signal_add(&output->frame_signal, &so->frame_listener); + weston_output_disable_planes_incr(output); + weston_output_damage(output); + + return so; + +err_display: + wl_list_for_each_safe(seat, tmp, &so->seat_list, link) + ss_seat_destroy(seat); + wl_display_disconnect(so->parent.display); +err_alloc: + free(so); +err_close: + close(parent_fd); + return NULL; +} + +static void +shared_output_destroy(struct shared_output *so) +{ + struct ss_shm_buffer *buffer, *bnext; + + weston_output_disable_planes_decr(so->output); + + wl_list_for_each_safe(buffer, bnext, &so->shm.buffers, link) + ss_shm_buffer_destroy(buffer); + wl_list_for_each_safe(buffer, bnext, &so->shm.free_buffers, free_link) + ss_shm_buffer_destroy(buffer); + + wl_display_disconnect(so->parent.display); + wl_event_source_remove(so->event_source); + + wl_list_remove(&so->output_destroyed.link); + wl_list_remove(&so->frame_listener.link); + + pixman_image_unref(so->cache_image); + free(so->tmp_data); + + free(so); +} + +static struct shared_output * +weston_output_share(struct weston_output *output, const char* command) +{ + int sv[2]; + char str[32]; + pid_t pid; + sigset_t allsigs; + char *const argv[] = { + "/bin/sh", + "-c", + (char*)command, + NULL + }; + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) { + weston_log("weston_output_share: socketpair failed: %s\n", + strerror(errno)); + return NULL; + } + + pid = fork(); + + if (pid == -1) { + close(sv[0]); + close(sv[1]); + weston_log("weston_output_share: fork failed: %s\n", + strerror(errno)); + return NULL; + } + + if (pid == 0) { + /* do not give our signal mask to the new process */ + sigfillset(&allsigs); + sigprocmask(SIG_UNBLOCK, &allsigs, NULL); + + /* Launch clients as the user. Do not launch clients with + * wrong euid. */ + if (seteuid(getuid()) == -1) { + weston_log("weston_output_share: setuid failed: %s\n", + strerror(errno)); + abort(); + } + + sv[1] = dup(sv[1]); + if (sv[1] == -1) { + weston_log("weston_output_share: dup failed: %s\n", + strerror(errno)); + abort(); + } + + snprintf(str, sizeof str, "%d", sv[1]); + setenv("WAYLAND_SERVER_SOCKET", str, 1); + + execv(argv[0], argv); + weston_log("weston_output_share: exec failed: %s\n", + strerror(errno)); + abort(); + } else { + close(sv[1]); + return shared_output_create(output, sv[0]); + } + + return NULL; +} + +static struct weston_output * +weston_output_find(struct weston_compositor *c, int32_t x, int32_t y) +{ + struct weston_output *output; + + wl_list_for_each(output, &c->output_list, link) { + if (x >= output->x && y >= output->y && + x < output->x + output->width && + y < output->y + output->height) + return output; + } + + return NULL; +} + +static void +share_output_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) +{ + struct weston_output *output; + struct weston_pointer *pointer; + struct screen_share *ss = data; + + pointer = weston_seat_get_pointer(keyboard->seat); + if (!pointer) { + weston_log("Cannot pick output: Seat does not have pointer\n"); + return; + } + + output = weston_output_find(pointer->seat->compositor, + wl_fixed_to_int(pointer->x), + wl_fixed_to_int(pointer->y)); + if (!output) { + weston_log("Cannot pick output: Pointer not on any output\n"); + return; + } + + weston_output_share(output, ss->command); +} + +WL_EXPORT int +wet_module_init(struct weston_compositor *compositor, + int *argc, char *argv[]) +{ + struct screen_share *ss; + struct weston_config *config; + struct weston_config_section *section; + + ss = zalloc(sizeof *ss); + if (ss == NULL) + return -1; + ss->compositor = compositor; + + config = wet_get_config(compositor); + + section = weston_config_get_section(config, "screen-share", NULL, NULL); + + weston_config_section_get_string(section, "command", &ss->command, ""); + + weston_compositor_add_key_binding(compositor, KEY_S, + MODIFIER_CTRL | MODIFIER_ALT, + share_output_binding, ss); + return 0; +} diff --git a/compositor/systemd-notify.c b/compositor/systemd-notify.c new file mode 100644 index 0000000..61196d8 --- /dev/null +++ b/compositor/systemd-notify.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2015 General Electric Company. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "shared/helpers.h" +#include "shared/string-helpers.h" +#include +#include +#include "weston.h" + +struct systemd_notifier { + int watchdog_time; + struct wl_event_source *watchdog_source; + struct wl_listener compositor_destroy_listener; +}; + +static int +add_systemd_sockets(struct weston_compositor *compositor) +{ + int fd; + int cnt_systemd_sockets; + int current_fd = 0; + + cnt_systemd_sockets = sd_listen_fds(1); + + if (cnt_systemd_sockets < 0) { + weston_log("sd_listen_fds failed with: %d\n", + cnt_systemd_sockets); + return -1; + } + + /* socket-based activation not used, return silently */ + if (cnt_systemd_sockets == 0) + return 0; + + while (current_fd < cnt_systemd_sockets) { + fd = SD_LISTEN_FDS_START + current_fd; + + if (sd_is_socket(fd, AF_UNIX, SOCK_STREAM,1) <= 0) { + weston_log("invalid socket provided from systemd\n"); + return -1; + } + + if (wl_display_add_socket_fd(compositor->wl_display, fd)) { + weston_log("wl_display_add_socket_fd failed" + "for systemd provided socket\n"); + return -1; + } + current_fd++; + } + + weston_log("info: add %d socket(s) provided by systemd\n", + current_fd); + + return current_fd; +} + +static int +watchdog_handler(void *data) +{ + struct systemd_notifier *notifier = data; + + wl_event_source_timer_update(notifier->watchdog_source, + notifier->watchdog_time); + + sd_notify(0, "WATCHDOG=1"); + + return 1; +} + +static void +weston_compositor_destroy_listener(struct wl_listener *listener, void *data) +{ + struct systemd_notifier *notifier; + + sd_notify(0, "STOPPING=1"); + + notifier = container_of(listener, + struct systemd_notifier, + compositor_destroy_listener); + + if (notifier->watchdog_source) + wl_event_source_remove(notifier->watchdog_source); + + wl_list_remove(¬ifier->compositor_destroy_listener.link); + free(notifier); +} + +WL_EXPORT int +wet_module_init(struct weston_compositor *compositor, + int *argc, char *argv[]) +{ + char *watchdog_time_env; + struct wl_event_loop *loop; + int32_t watchdog_time_conv; + struct systemd_notifier *notifier; + + notifier = zalloc(sizeof *notifier); + if (notifier == NULL) + return -1; + + if (!weston_compositor_add_destroy_listener_once(compositor, + ¬ifier->compositor_destroy_listener, + weston_compositor_destroy_listener)) { + free(notifier); + return 0; + } + + if (add_systemd_sockets(compositor) < 0) + return -1; + + sd_notify(0, "READY=1"); + + /* 'WATCHDOG_USEC' is environment variable that is set + * by systemd to transfer 'WatchdogSec' watchdog timeout + * setting from service file.*/ + watchdog_time_env = getenv("WATCHDOG_USEC"); + if (!watchdog_time_env) + return 0; + + if (!safe_strtoint(watchdog_time_env, &watchdog_time_conv)) + return 0; + + /* Convert 'WATCHDOG_USEC' to milliseconds and notify + * systemd every half of that time.*/ + watchdog_time_conv /= 1000 * 2; + if (watchdog_time_conv <= 0) + return 0; + + notifier->watchdog_time = watchdog_time_conv; + + loop = wl_display_get_event_loop(compositor->wl_display); + notifier->watchdog_source = + wl_event_loop_add_timer(loop, watchdog_handler, notifier); + wl_event_source_timer_update(notifier->watchdog_source, + notifier->watchdog_time); + + return 0; +} + diff --git a/compositor/testsuite-util.c b/compositor/testsuite-util.c new file mode 100644 index 0000000..34882d1 --- /dev/null +++ b/compositor/testsuite-util.c @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include "weston.h" + +static struct wet_testsuite_data *wet_testsuite_data_global; + +/** Set global test suite data + * + * \param data Custom test suite data. + * + * The type struct wet_testsuite_data is free to be defined by any test suite + * in any way they want. This function stores a single pointer to that data + * in a global variable. + * + * The data is expected to be fetched from a test suite specific plugin that + * knows how to interpret it. + * + * \sa wet_testsuite_data_get + */ +WL_EXPORT void +wet_testsuite_data_set(struct wet_testsuite_data *data) +{ + wet_testsuite_data_global = data; +} + +/** Get global test suite data + * + * \return Custom test suite data. + * + * Returns the value last set with wet_testsuite_data_set(). + */ +WL_EXPORT struct wet_testsuite_data * +wet_testsuite_data_get(void) +{ + return wet_testsuite_data_global; +} diff --git a/compositor/text-backend.c b/compositor/text-backend.c new file mode 100644 index 0000000..722dcb2 --- /dev/null +++ b/compositor/text-backend.c @@ -0,0 +1,1099 @@ +/* + * Copyright © 2012 Openismus GmbH + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include "weston.h" +#include "text-input-unstable-v1-server-protocol.h" +#include "input-method-unstable-v1-server-protocol.h" +#include "shared/helpers.h" +#include "shared/timespec-util.h" + +struct text_input_manager; +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; + + bool input_panel_visible; + + struct text_input_manager *manager; +}; + +struct text_input_manager { + struct wl_global *text_input_manager_global; + struct wl_listener destroy_listener; + + struct text_input *current_text_input; + + 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 *input; + + struct wl_list link; + + struct wl_listener keyboard_focus_listener; + + bool focus_listener_initialized; + + struct input_method_context *context; + + struct text_backend *text_backend; +}; + +struct input_method_context { + struct wl_resource *resource; + + struct text_input *input; + struct input_method *input_method; + + struct wl_resource *keyboard; +}; + +struct text_backend { + struct weston_compositor *compositor; + + struct { + char *path; + struct wl_client *client; + + unsigned deathcount; + struct timespec deathstamp; + } input_method; + + struct wl_listener client_listener; + struct wl_listener seat_created_listener; +}; + +static void +input_method_context_create(struct text_input *input, + 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_input_method(struct input_method *input_method) +{ + struct text_input *text_input = input_method->input; + struct weston_compositor *ec = text_input->ec; + + if (input_method->context && input_method->input_method_binding) { + input_method_context_end_keyboard_grab(input_method->context); + zwp_input_method_v1_send_deactivate( + input_method->input_method_binding, + input_method->context->resource); + input_method->context->input = NULL; + } + + wl_list_remove(&input_method->link); + input_method->input = NULL; + input_method->context = NULL; + + if (wl_list_empty(&text_input->input_methods) && + text_input->input_panel_visible && + text_input->manager->current_text_input == text_input) { + wl_signal_emit(&ec->hide_input_panel_signal, ec); + text_input->input_panel_visible = false; + } + + if (text_input->manager->current_text_input == text_input) + text_input->manager->current_text_input = NULL; + + zwp_text_input_v1_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_input_method(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; + zwp_input_method_context_v1_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; + struct weston_compositor *ec = text_input->ec; + struct text_input *current; + + if (!weston_seat) + return; + + input_method = weston_seat->input_method; + if (input_method->input == text_input) + return; + + if (input_method->input) + deactivate_input_method(input_method); + + input_method->input = 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); + + current = text_input->manager->current_text_input; + + if (current && current != text_input) { + current->input_panel_visible = false; + wl_signal_emit(&ec->hide_input_panel_signal, ec); + } + + 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); + } + text_input->manager->current_text_input = text_input; + + zwp_text_input_v1_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 weston_seat *weston_seat = wl_resource_get_user_data(seat); + + if (weston_seat && weston_seat->input_method->input) + deactivate_input_method(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; + zwp_input_method_context_v1_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; + zwp_input_method_context_v1_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; + zwp_input_method_context_v1_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; + zwp_input_method_context_v1_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 = true; + + if (!wl_list_empty(&text_input->input_methods) && + text_input == text_input->manager->current_text_input) { + 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 = false; + + if (!wl_list_empty(&text_input->input_methods) && + text_input == text_input->manager->current_text_input) + 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; + zwp_input_method_context_v1_send_preferred_language( + input_method->context->resource, language); + } +} + +static const struct zwp_text_input_v1_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 = zalloc(sizeof *text_input); + if (text_input == NULL) + return; + + text_input->resource = + wl_resource_create(client, &zwp_text_input_v1_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; + text_input->manager = text_input_manager; + + wl_list_init(&text_input->input_methods); +}; + +static const struct zwp_text_input_manager_v1_interface 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, + &zwp_text_input_manager_v1_interface, 1, id); + if (resource) + wl_resource_set_implementation(resource, + &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_list_remove(&text_input_manager->destroy_listener.link); + 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 = zalloc(sizeof *text_input_manager); + if (text_input_manager == NULL) + return; + + text_input_manager->ec = ec; + + text_input_manager->text_input_manager_global = + wl_global_create(ec->wl_display, + &zwp_text_input_manager_v1_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); + + if (context->input) + zwp_text_input_v1_send_commit_string(context->input->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); + + if (context->input) + zwp_text_input_v1_send_preedit_string(context->input->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); + + if (context->input) + zwp_text_input_v1_send_preedit_styling(context->input->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); + + if (context->input) + zwp_text_input_v1_send_preedit_cursor(context->input->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); + + if (context->input) + zwp_text_input_v1_send_delete_surrounding_text( + context->input->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); + + if (context->input) + zwp_text_input_v1_send_cursor_position(context->input->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); + + if (context->input) + zwp_text_input_v1_send_modifiers_map(context->input->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); + + if (context->input) + zwp_text_input_v1_send_keysym(context->input->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, + const struct timespec *time, uint32_t key, + uint32_t state_w) +{ + struct weston_keyboard *keyboard = grab->keyboard; + struct wl_display *display; + uint32_t serial; + uint32_t msecs; + + 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); + msecs = timespec_to_msec(time); + wl_keyboard_send_key(keyboard->input_method_resource, + serial, msecs, 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 = weston_seat_get_keyboard(seat); + + cr = wl_resource_create(client, &wl_keyboard_interface, 1, id); + wl_resource_set_implementation(cr, NULL, context, unbind_keyboard); + + context->keyboard = cr; + + weston_keyboard_send_keymap(keyboard, cr); + + 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 = weston_seat_get_keyboard(seat); + struct weston_keyboard_grab *default_grab = &keyboard->default_grab; + struct timespec ts; + + timespec_from_msec(&ts, time); + + default_grab->interface->key(default_grab, &ts, 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 = weston_seat_get_keyboard(seat); + 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); + + if (context->input) + zwp_text_input_v1_send_language(context->input->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); + + if (context->input) + zwp_text_input_v1_send_text_direction(context->input->resource, + serial, direction); +} + + +static const struct zwp_input_method_context_v1_interface 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); + + if (context->input_method && context->input_method->context == context) + context->input_method->context = NULL; + + free(context); +} + +static void +input_method_context_create(struct text_input *input, + struct input_method *input_method) +{ + struct input_method_context *context; + struct wl_resource *binding; + + if (!input_method->input_method_binding) + return; + + context = zalloc(sizeof *context); + if (context == NULL) + return; + + binding = input_method->input_method_binding; + context->resource = + wl_resource_create(wl_resource_get_client(binding), + &zwp_input_method_context_v1_interface, + 1, 0); + wl_resource_set_implementation(context->resource, + &context_implementation, + context, destroy_input_method_context); + + context->input = input; + context->input_method = input_method; + input_method->context = context; + + + zwp_input_method_v1_send_activate(binding, context->resource); +} + +static void +input_method_context_end_keyboard_grab(struct input_method_context *context) +{ + struct weston_keyboard_grab *grab; + struct weston_keyboard *keyboard; + + keyboard = weston_seat_get_keyboard(context->input_method->seat); + if (!keyboard) + return; + + grab = &keyboard->input_method_grab; + keyboard = grab->keyboard; + if (!keyboard) + return; + + if (keyboard->grab == grab) + weston_keyboard_end_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); + + input_method->input_method_binding = NULL; + input_method->context = 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, + &zwp_input_method_v1_interface, 1, id); + + if (input_method->input_method_binding != NULL) { + wl_resource_post_error(resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "interface object already bound"); + return; + } + + if (text_backend->input_method.client != client) { + wl_resource_post_error(resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "permission to bind " + "input_method denied"); + return; + } + + wl_resource_set_implementation(resource, NULL, input_method, + unbind_input_method); + input_method->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->input) + deactivate_input_method(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->input) + return; + + if (!surface || input_method->input->surface != surface) + deactivate_input_method(input_method); +} + +static void +input_method_init_seat(struct weston_seat *seat) +{ + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + + if (seat->input_method->focus_listener_initialized) + return; + + if (keyboard) { + seat->input_method->keyboard_focus_listener.notify = + handle_keyboard_focus; + wl_signal_add(&keyboard->focus_signal, + &seat->input_method->keyboard_focus_listener); + keyboard->input_method_grab.interface = + &input_method_context_grab; + } + + seat->input_method->focus_listener_initialized = true; +} + +static void launch_input_method(struct text_backend *text_backend); + +static void +respawn_input_method_process(struct text_backend *text_backend) +{ + struct timespec time; + int64_t tdiff; + + /* if input_method dies more than 5 times in 10 seconds, give up */ + weston_compositor_get_time(&time); + tdiff = timespec_sub_to_msec(&time, + &text_backend->input_method.deathstamp); + if (tdiff > 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 disconnected, giving up.\n"); + return; + } + + weston_log("input_method disconnected, respawning...\n"); + launch_input_method(text_backend); +} + +static void +input_method_client_notifier(struct wl_listener *listener, void *data) +{ + struct text_backend *text_backend; + + text_backend = container_of(listener, struct text_backend, + client_listener); + + text_backend->input_method.client = NULL; + respawn_input_method_process(text_backend); +} + +static void +launch_input_method(struct text_backend *text_backend) +{ + if (!text_backend->input_method.path) + return; + + if (strcmp(text_backend->input_method.path, "") == 0) + return; + + text_backend->input_method.client = + weston_client_start(text_backend->compositor, + text_backend->input_method.path); + + if (!text_backend->input_method.client) { + weston_log("not able to start %s\n", + text_backend->input_method.path); + return; + } + + text_backend->client_listener.notify = input_method_client_notifier; + wl_client_add_destroy_listener(text_backend->input_method.client, + &text_backend->client_listener); +} + +static void +text_backend_seat_created(struct text_backend *text_backend, + struct weston_seat *seat) +{ + struct input_method *input_method; + struct weston_compositor *ec = seat->compositor; + + input_method = zalloc(sizeof *input_method); + if (input_method == NULL) + return; + + input_method->seat = seat; + input_method->input = NULL; + input_method->focus_listener_initialized = false; + input_method->context = NULL; + input_method->text_backend = text_backend; + + input_method->input_method_global = + wl_global_create(ec->wl_display, + &zwp_input_method_v1_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; +} + +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); + + text_backend_seat_created(text_backend, seat); +} + +static void +text_backend_configuration(struct text_backend *text_backend) +{ + struct weston_config *config = wet_get_config(text_backend->compositor); + struct weston_config_section *section; + char *client; + + section = weston_config_get_section(config, + "input-method", NULL, NULL); + client = wet_get_libexec_path("weston-keyboard"); + weston_config_section_get_string(section, "path", + &text_backend->input_method.path, + client); + free(client); +} + +WL_EXPORT void +text_backend_destroy(struct text_backend *text_backend) +{ + wl_list_remove(&text_backend->seat_created_listener.link); + + if (text_backend->input_method.client) { + /* disable respawn */ + wl_list_remove(&text_backend->client_listener.link); + wl_client_destroy(text_backend->input_method.client); + } + + free(text_backend->input_method.path); + free(text_backend); +} + +WL_EXPORT struct text_backend * +text_backend_init(struct weston_compositor *ec) +{ + struct text_backend *text_backend; + struct weston_seat *seat; + + text_backend = zalloc(sizeof(*text_backend)); + if (text_backend == NULL) + return NULL; + + text_backend->compositor = ec; + + text_backend_configuration(text_backend); + + wl_list_for_each(seat, &ec->seat_list, link) + text_backend_seat_created(text_backend, seat); + text_backend->seat_created_listener.notify = handle_seat_created; + wl_signal_add(&ec->seat_created_signal, + &text_backend->seat_created_listener); + + text_input_manager_create(ec); + + launch_input_method(text_backend); + + return text_backend; +} diff --git a/compositor/weston-screenshooter.c b/compositor/weston-screenshooter.c new file mode 100755 index 0000000..fd2234c --- /dev/null +++ b/compositor/weston-screenshooter.c @@ -0,0 +1,202 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include +#include "weston.h" +#include "weston-screenshooter-server-protocol.h" +#include "shared/helpers.h" +#include + +struct screenshooter { + struct weston_compositor *ec; + struct wl_global *global; + struct wl_client *client; + struct weston_process process; + struct wl_listener destroy_listener; + struct weston_recorder *recorder; +}; + +static void +screenshooter_done(void *data, enum weston_screenshooter_outcome outcome) +{ + struct wl_resource *resource = data; + + switch (outcome) { + case WESTON_SCREENSHOOTER_SUCCESS: + weston_screenshooter_send_done(resource); + break; + case WESTON_SCREENSHOOTER_NO_MEMORY: + wl_resource_post_no_memory(resource); + break; + default: + break; + } +} + +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 = + weston_head_from_resource(output_resource)->output; + struct weston_buffer *buffer = + weston_buffer_from_resource(buffer_resource); + + if (buffer == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + weston_screenshooter_shoot(output, buffer, screenshooter_done, resource); +} + +struct weston_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; +// OHOS remove debugger +// bool debug_enabled = +// weston_compositor_is_debug_protocol_enabled(shooter->ec); + bool debug_enabled = false; + + resource = wl_resource_create(client, + &weston_screenshooter_interface, 1, id); + + if (!debug_enabled && !shooter->client) { + wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "screenshooter failed: permission denied. "\ + "Debug protocol must be enabled"); + return; + } else if (!debug_enabled && client != shooter->client) { + wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "screenshooter failed: permission denied."); + return; + } + + 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_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) +{ + struct screenshooter *shooter = data; + char *screenshooter_exe; + + + screenshooter_exe = wet_get_bindir_path("weston-screenshooter"); + if (!screenshooter_exe) { + weston_log("Could not construct screenshooter path.\n"); + return; + } + + if (!shooter->client) + shooter->client = weston_client_launch(shooter->ec, + &shooter->process, + screenshooter_exe, screenshooter_sigchld); + free(screenshooter_exe); +} + +static void +recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) +{ + struct weston_compositor *ec = keyboard->seat->compositor; + struct weston_output *output; + struct screenshooter *shooter = data; + struct weston_recorder *recorder = shooter->recorder;; + static const char filename[] = "capture.wcap"; + + if (recorder) { + weston_recorder_stop(recorder); + shooter->recorder = NULL; + } else { + if (keyboard->focus && keyboard->focus->output) + output = keyboard->focus->output; + else + output = container_of(ec->output_list.next, + struct weston_output, link); + + shooter->recorder = weston_recorder_start(output, filename); + } +} + +static void +screenshooter_destroy(struct wl_listener *listener, void *data) +{ + struct screenshooter *shooter = + container_of(listener, struct screenshooter, destroy_listener); + + wl_list_remove(&shooter->destroy_listener.link); + + wl_global_destroy(shooter->global); + free(shooter); +} + +WL_EXPORT void +screenshooter_create(struct weston_compositor *ec) +{ + struct screenshooter *shooter; + + shooter = zalloc(sizeof *shooter); + if (shooter == NULL) + return; + + shooter->ec = ec; + + shooter->global = wl_global_create(ec->wl_display, + &weston_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/compositor/weston.desktop b/compositor/weston.desktop new file mode 100644 index 0000000..009b692 --- /dev/null +++ b/compositor/weston.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Name=Weston +Comment=The reference Wayland server +Exec=weston +Type=Application diff --git a/compositor/weston.h b/compositor/weston.h new file mode 100644 index 0000000..e09397f --- /dev/null +++ b/compositor/weston.h @@ -0,0 +1,117 @@ +/* + * Copyright © 2016 Giulio Camuffo + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_H +#define WESTON_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +void +screenshooter_create(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); + +struct wl_client * +weston_client_start(struct weston_compositor *compositor, const char *path); + +void +weston_watch_process(struct weston_process *process); + +struct weston_config * +wet_get_config(struct weston_compositor *compositor); + +void * +wet_load_module_entrypoint(const char *name, const char *entrypoint); + +int +wet_shell_init(struct weston_compositor *ec, + int *argc, char *argv[]); +int +wet_module_init(struct weston_compositor *ec, + int *argc, char *argv[]); +int +wet_load_module(struct weston_compositor *compositor, + const char *name, int *argc, char *argv[]); + +int +module_init(struct weston_compositor *compositor, + int *argc, char *argv[]); + +char * +wet_get_libexec_path(const char *name); + +char * +wet_get_bindir_path(const char *name); + +int +wet_load_xwayland(struct weston_compositor *comp); + +struct text_backend; + +struct text_backend * +text_backend_init(struct weston_compositor *ec); + +void +text_backend_destroy(struct text_backend *text_backend); + +int +wet_main(int argc, char *argv[]); + + +/* test suite utilities */ + +/** Opaque type for a test suite to define. */ +struct wet_testsuite_data; + +void +wet_testsuite_data_set(struct wet_testsuite_data *data); + +struct wet_testsuite_data * +wet_testsuite_data_get(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/compositor/xwayland.c b/compositor/xwayland.c new file mode 100644 index 0000000..8eadbe2 --- /dev/null +++ b/compositor/xwayland.c @@ -0,0 +1,212 @@ +/* + * Copyright © 2011 Intel Corporation + * Copyright © 2016 Giulio Camuffo + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include "compositor/weston.h" +#include +#include "shared/helpers.h" + +struct wet_xwayland { + struct weston_compositor *compositor; + const struct weston_xwayland_api *api; + struct weston_xwayland *xwayland; + struct wl_event_source *sigusr1_source; + struct wl_client *client; + int wm_fd; + struct weston_process process; +}; + +static int +handle_sigusr1(int signal_number, void *data) +{ + struct wet_xwayland *wxw = data; + + /* We'd be safer if we actually had the struct + * signalfd_siginfo from the signalfd data and could verify + * this came from Xwayland.*/ + wxw->api->xserver_loaded(wxw->xwayland, wxw->client, wxw->wm_fd); + wl_event_source_remove(wxw->sigusr1_source); + + return 1; +} + +static pid_t +spawn_xserver(void *user_data, const char *display, int abstract_fd, int unix_fd) +{ + struct wet_xwayland *wxw = user_data; + pid_t pid; + char s[12], abstract_fd_str[12], unix_fd_str[12], wm_fd_str[12]; + int sv[2], wm[2], fd; + char *xserver = NULL; + struct weston_config *config = wet_get_config(wxw->compositor); + struct weston_config_section *section; + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) { + weston_log("wl connection socketpair failed\n"); + return 1; + } + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wm) < 0) { + weston_log("X wm connection socketpair failed\n"); + return 1; + } + + pid = fork(); + switch (pid) { + case 0: + /* SOCK_CLOEXEC closes both ends, so we need to unset + * the flag on the client fd. */ + fd = dup(sv[1]); + if (fd < 0) + goto fail; + snprintf(s, sizeof s, "%d", fd); + setenv("WAYLAND_SOCKET", s, 1); + + fd = dup(abstract_fd); + if (fd < 0) + goto fail; + snprintf(abstract_fd_str, sizeof abstract_fd_str, "%d", fd); + fd = dup(unix_fd); + if (fd < 0) + goto fail; + snprintf(unix_fd_str, sizeof unix_fd_str, "%d", fd); + fd = dup(wm[1]); + if (fd < 0) + goto fail; + snprintf(wm_fd_str, sizeof wm_fd_str, "%d", fd); + + section = weston_config_get_section(config, + "xwayland", NULL, NULL); + weston_config_section_get_string(section, "path", + &xserver, XSERVER_PATH); + + /* Ignore SIGUSR1 in the child, which will make the X + * server send SIGUSR1 to the parent (weston) when + * it's done with initialization. During + * initialization the X server will round trip and + * block on the wayland compositor, so avoid making + * blocking requests (like xcb_connect_to_fd) until + * it's done with that. */ + signal(SIGUSR1, SIG_IGN); + + if (execl(xserver, + xserver, + display, + "-rootless", + "-listen", abstract_fd_str, + "-listen", unix_fd_str, + "-wm", wm_fd_str, + "-terminate", + NULL) < 0) + weston_log("exec of '%s %s -rootless " + "-listen %s -listen %s -wm %s " + "-terminate' failed: %s\n", + xserver, display, + abstract_fd_str, unix_fd_str, wm_fd_str, + strerror(errno)); + fail: + _exit(EXIT_FAILURE); + + default: + close(sv[1]); + wxw->client = wl_client_create(wxw->compositor->wl_display, sv[0]); + + close(wm[1]); + wxw->wm_fd = wm[0]; + + wxw->process.pid = pid; + weston_watch_process(&wxw->process); + break; + + case -1: + weston_log("Failed to fork to spawn xserver process\n"); + break; + } + + return pid; +} + +static void +xserver_cleanup(struct weston_process *process, int status) +{ + struct wet_xwayland *wxw = + container_of(process, struct wet_xwayland, process); + struct wl_event_loop *loop = + wl_display_get_event_loop(wxw->compositor->wl_display); + + wxw->api->xserver_exited(wxw->xwayland, status); + wxw->sigusr1_source = wl_event_loop_add_signal(loop, SIGUSR1, + handle_sigusr1, wxw); + wxw->client = NULL; +} + +int +wet_load_xwayland(struct weston_compositor *comp) +{ + const struct weston_xwayland_api *api; + struct weston_xwayland *xwayland; + struct wet_xwayland *wxw; + struct wl_event_loop *loop; + + if (weston_compositor_load_xwayland(comp) < 0) + return -1; + + api = weston_xwayland_get_api(comp); + if (!api) { + weston_log("Failed to get the xwayland module API.\n"); + return -1; + } + + xwayland = api->get(comp); + if (!xwayland) { + weston_log("Failed to get the xwayland object.\n"); + return -1; + } + + wxw = zalloc(sizeof *wxw); + if (!wxw) + return -1; + + wxw->compositor = comp; + wxw->api = api; + wxw->xwayland = xwayland; + wxw->process.cleanup = xserver_cleanup; + if (api->listen(xwayland, wxw, spawn_xserver) < 0) + return -1; + + loop = wl_display_get_event_loop(comp->wl_display); + wxw->sigusr1_source = wl_event_loop_add_signal(loop, SIGUSR1, + handle_sigusr1, wxw); + + return 0; +} diff --git a/data/COPYING b/data/COPYING new file mode 100644 index 0000000..0345023 --- /dev/null +++ b/data/COPYING @@ -0,0 +1,55 @@ +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. + + +(C) 2013 DENSO CORPORATION + +Permission to use, copy, modify, distribute, and sell following listed images +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 images +without specific, written prior permission. The copyright holders make +no representations about the suitability of these images for any +purpose. It is provided "as is" without express or implied warranty. + +background.png +tiling.png +fullscreen.png +panel.png +random.png +sidebyside.png +home.png +icon_ivi_clickdot.png +icon_ivi_flower.png +icon_ivi_simple-egl.png +icon_ivi_simple-shm.png +icon_ivi_smoke.png + + +For the SVG icons: + +© 2016 Samsung Electronics Co., Ltd + +This work is dual-licenced under both the MIT "Expat" License and the +Creative Commons Attribution-Share Alike 3.0 United States License, and +may be redistributed under either (or both) licenses as desired. See +Weston's COPYING for details of the MIT license. To view a copy of the +CC-SA-3.0 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. + +icons.svg +icon_terminal.png +icon_editor.png +icon_flower.png diff --git a/data/background.png b/data/background.png new file mode 100644 index 0000000000000000000000000000000000000000..67caee38c48b2f9ea146d1eeb37f3bee0d80052d GIT binary patch literal 135501 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%0D%gKDBS-i7v#{{g5F`DU!T*zY(Xrk0 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

YKLj$Mur0@2vW#2 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 tZi&86Rh82PO5&U=(k%>Nm zAd{qWRbrX@P~rOIr~)-dm0oDPNN%Mlet%^0!eT0ZjT5UMNv0w}Zcl6G2*M+Z;`Z!u z%Isr@2-C~t#W8!$tUh=Y;XfB$Lp@TlQZb=S?jeq3ubE^dbnnynkCO#HpJqa3@;wcB z&?$rlj#C|Ro5dr$(XurYMRUmIQ6-c~rxIqf*{w0=JtFq)wg3>e+wHH_YV~bA9)GVd zHlWveeL&O!^Oo@*hRR% z{{we-zhSvtewa)qKiu>wrCc6-5V5p*&{~)4^?G+=15>I~$RyFD(de_C%;%Sv&%_=> zo)@4Lz-F^~vREvB`a!6w%J&d#4fSj8n5M#leh?xFOek9VyTu zMxCrIOSIAF=jZ(8X%BdyNLpj3(@~b(J{Mp=g6j_=wyn69+Y+H(YDT5HzEYu#gJ_{D zAQfHh-T=5lAqYIQKebF3V{1bu)JxU1RAeqktP@N$oyr{z?%M=H47p9)hDxPV>ZNMt z$YsSM915Avs;Xo(BXHB{;zW9xcHNoKI!J_KNJccwH^s?}7b?){Y`WN*{%uHw`Y`>E z6RP~(Jjh6fGLPcovZ+mBgA~y^~QLOCu$f@S0 zeXuNEmF;^X9_ABK*(~bAr=;m!udBN30a+lQOKy7tA3z-k0gVa&?@M*)b!&iRxs?GE zigmfj(s`aWPIUtP2~trm&AKu>V_mKRi`t&*P}=~#Qn`K3PGqHG>v)Dbxc$$Uhb=FW zovq8ZZDn0ocDGh9!x_qXmlEiik);%!5ke+*taa!(6l1wlsd5@;gGy_&&QlcV6uO;C zyXCi0g5EYK{&0TCqU-RoN7 zzyjHs+GS=c=YE|7JBc}n{ZLbz;BC$L@TJzneW@4{+wRBXk*I!6t1Ot88CwC_DhjmH zPft(pzs9%nyJn}4bNr^c{ncsgT4{Etywj;XS%4JR#*4fWETPij-%yk8m05~6wKi{f zY2xRvo)w)x-O!({^|`Sg?K%;vTkqPtU+&b7-S*>Ve_U>wRLvJ)YrHbv#LLy+0C1KU z6A77sYrM-fB?f?4VrJQ8-^YRNvUrwPxm`a3bR2w!y4_C@i)^>pmh}eMq9ZFc9w0jx zL}sD$-gdw8?%;L<e z%Tjzfn4K~+GX&IREi#ob(T4jb2&L*izJNn;D8U9KOKw#}?-BrNVIoTb^l0KPd#LoC ze?u__+^sFMSd#+=NDrzPYSEa7DTQ0LXkO09!y8k!r$H`_B?}B2LPdVj&AHv<>UE-f z&hzh7wI9a+j>?=5;q8pZ`vu4vzV=x>Tl{Y*zNI_j-A!=2nR7kpN^_k9K)cJ%L8$P~ zIG8s?`j9t5^e}q?^_O0{__-``Joq(2>BGzeXNm$)@d6Xy1kEzc@gUwgHm=Q>mCeJ9 z1GOSdMS^4;-j#D-Ub$xdV8T#4HXNMt3crfXLX(wH{aaV~^~YoW8(0VR;pU;sHg^Dt jLuDp%oT@%^DS!JH*9hwt6#3*K00000NkvXXu0mjfSkwe6 literal 0 HcmV?d00001 diff --git a/data/fullscreen.png b/data/fullscreen.png new file mode 100644 index 0000000000000000000000000000000000000000..dd4944b15db6cc32c9a36ecb8003f95bc80fe699 GIT binary patch literal 3152 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 literal 0 HcmV?d00001 diff --git a/data/icon_editor.png b/data/icon_editor.png new file mode 100644 index 0000000000000000000000000000000000000000..d1324ad97e9969eec8209930b3b0289b83e0c2b7 GIT binary patch literal 737 zcmV<70v`Q|P)C;QK2CS{EZF>{&b520uy=P643PrHnjvXudHIScQ120cc z`1!a)=c<{H*~*oJx(u|GmLeyo1xZPS#KbfpE-p?&Nl6J-uB^k>%`Wh8^}t0%{IK_w z9}>AVkeiFt)D|pQ(2gBD+F)f>2O}dR2^TLGKqj+;T<#CWIqwuvBPXAcugNduEAoLP zz!7S78*bfdLwUJ$X%Z7#@cHK{47|bdv7!NIVzv%}7vJCmqbDT+sv zzsNV_6C&%@4D#}t@Z*nplt01LsU>i9bcCs?Dc-()i-QLbCQva_6n`%+i}*%q2qY&b zgYOGOxY@Vx7l7h!8_3x9uq+_-TgLKJ@{5o-$6^L;5TEfpv( zE=ER122#WC!^i$S?%jI{Wpmn;ueRaL^zh{sgc-GFdQ0|NsExCBT^P7;Jfm{E0gwICMvr{@8V-KV<& z8-4>C(AU?$&$Vq!jf`yL*dut8jk^rI8?bKW^z`(!c6WETB^3kp@!>O@=9NAGTir1c T023aD00000NkvXXu0mjfQ1?nu literal 0 HcmV?d00001 diff --git a/data/icon_flower.png b/data/icon_flower.png new file mode 100644 index 0000000000000000000000000000000000000000..1e80f2c8cde36d8ceeb69bc2d9ba62ab808d5258 GIT binary patch literal 1160 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�ki~KAiaLq$=5q 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 literal 0 HcmV?d00001 diff --git a/data/icon_terminal.png b/data/icon_terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..4c4c3b69468f6083a659092d7522991ec2632eed GIT binary patch literal 525 zcmV+o0`mQdP)w&|)nVC7~PT zKLR+>0O#}BSZgtx&0t?!JOmGDqz$kt>nt*x!G;#@yMsMjT=L7$c)5W4NaoHtj7B4j z$72XIK)c-rD~X~Acnfq)2VpZr@+N`v9dehW`O<{jJGfU4(U%D3lfmh9B6SiL#OnFW zZViMm4DtB&5l_7*H1Y=G&oPe2BgiyOZ(}nBMOigZpx-J89!lV%zzjYh%kc8m3Tje5P# zZ(bwp);~oV%cR<1v)L4E9&SpK1iRf1{MLz7c?C5u6EEvfHv&PXWN ze5=(0PbL_>2fbeJdB5Lxt`!)K%ey45f%5gDc|sT7%WAdy!1v&vzi2-I`FxD7^n85b P00000NkvXXu0mjfqD=Mf literal 0 HcmV?d00001 diff --git a/data/icon_window.png b/data/icon_window.png new file mode 100644 index 0000000000000000000000000000000000000000..2a07cc1619242a84bfbbbf6303fd55872c84a105 GIT binary patch literal 86 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`vYsxEAr*|t3)p`6bIR$lP2ddR jl`u`X!q8=~lz|~=NBGfW3(v^|6*73b`njxgN@xNA^5PXE literal 0 HcmV?d00001 diff --git a/data/icons.svg b/data/icons.svg new file mode 100644 index 0000000..f57a02b --- /dev/null +++ b/data/icons.svg @@ -0,0 +1,1012 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + $ weston + + + + + + + + + + + W + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/meson.build b/data/meson.build new file mode 100644 index 0000000..16f172f --- /dev/null +++ b/data/meson.build @@ -0,0 +1,29 @@ +install_data( + [ + 'background.png', + 'border.png', + 'fullscreen.png', + 'home.png', + 'icon_editor.png', + 'icon_flower.png', + 'icon_ivi_clickdot.png', + 'icon_ivi_flower.png', + 'icon_ivi_simple-egl.png', + 'icon_ivi_simple-shm.png', + 'icon_ivi_smoke.png', + 'icon_terminal.png', + 'icon_window.png', + 'panel.png', + 'pattern.png', + 'random.png', + 'sidebyside.png', + 'sign_close.png', + 'sign_maximize.png', + 'sign_minimize.png', + 'terminal.png', + 'tiling.png', + 'wayland.png', + 'wayland.svg', + ], + install_dir: join_paths(dir_data, 'weston') +) diff --git a/data/panel.png b/data/panel.png new file mode 100644 index 0000000000000000000000000000000000000000..f0f46b2b9db87df497f5ae65d3b6ea685a6e8567 GIT binary patch literal 27761 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 z z9B_(+L!_{lsqjUD;}W_^R7;S)06~nRXqQHmp2gX{an}(h5o%6pRRD1sXjB%N*q{Z; z3Wto`32{!0D$-LQjUDn|bEm#t5=JWrs0Su)6(dcfR6y^eVgM&EQ)GlkMHS9b4Q|iZ z`(wGEy2AM9q;UV-KQCB{DZ2uYp)hdF0xH~ot@4p3XfKYOyIFnLld!2X|pZm{IP+=XgMeSz&CbG=8ft*vB9af-9= zvdlK6of5JF#NqKS60X_Uf`_4FL(3(cB5O_J^>9BoXyO1ZrCf2%yGcNc#NtvhZ=4bi zp#RTXcN~6HPGV4ph@*^LSujp1XDY5KyfHR$I#A65cy0;qa>A*M^V%#1btAgO{e64> zyzpTf&T>voieC1--|xeS_UoA4UqG8|v&E>4e=Y~`zT#}D@r;+?l3gE`WG}T_>i{_7 zFNXr}&lUIizHroTs3lkIc8iqUmU*|%%W@xY#nGET4Ik5JNu;lv{yo>9E(KjvN^2d` z&l}(fzN|5GjVYxwR_rQ2pZDR7(xzSc0yD<4u47kT@cW+5$ZJ1e`n_^DR~C%gufWM0 zw}0@40=M_%s|};~`p1yd20VzEG14<;nq&vnR&aFyM3E>hR6!tH00000NkvXXu0mjf DB&LQ6 literal 0 HcmV?d00001 diff --git a/data/random.png b/data/random.png new file mode 100644 index 0000000000000000000000000000000000000000..2ed2a1c56e96e802bdd82c6a1c87a320669375b4 GIT binary patch literal 4553 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 literal 0 HcmV?d00001 diff --git a/data/sidebyside.png b/data/sidebyside.png new file mode 100644 index 0000000000000000000000000000000000000000..f5b6f5363fe77c5f9d856871b035838d6b2c0583 GIT binary patch literal 3630 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 C`XO2X literal 0 HcmV?d00001 diff --git a/data/sign_maximize.png b/data/sign_maximize.png new file mode 100644 index 0000000000000000000000000000000000000000..ae9e86abea358a359c42eb6a0406f1b74ab17bbb GIT binary patch literal 85 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`GM+AuAr*|t5^)dgSI%H#o4~-f imT@uLgr0VG1_p0+lQa8Vf;EBa7(8A5T-G@yGywqJc@y&h literal 0 HcmV?d00001 diff --git a/data/sign_minimize.png b/data/sign_minimize.png new file mode 100644 index 0000000000000000000000000000000000000000..fd3a64aaffa6afd4117a5b13e5c7416cc6555253 GIT binary patch literal 79 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`VxBIJAr*|t68jQFdYb;X7(8X; b(Pv=z!zTEC!o8cKKotz0u6{1-oD!MR4#oxbw!>MU7Gc)tb*)!)yuz)xq!}D^A0IgJ0nk8uzj6YSjZ9Gjq zm$8TO%!X!~OK3c|?KQM*yD!tXBhPnPR^%7yj5zpD!ZghlGz_EUoF7K-!}Gj6ecvy+ zc3l_y5B?tooHu|{3iC@#*bQ92az!*Fh(Q>J$o1mO$B)QC(=;)jo|#5{T|H8%6dpc$ zh<9(_p{%?N(^J#1Z5y#z49R3tXnUUMk*gcj)z{(QfBuyZPo6qO1E8&~on{J3*ATtc z*xZEiu`$%t)j~H!0!q_J1^er_5(AB7It2rl5ga~x1iws7;Nry#fJRYnDH?wI^cksi z8XEi1)ZBtJ&r@02b{a`$NQf3SOEbu!p|O#vPhpC@b^A8jJKAAdmPGD_zC_>G+Xt)6 z5+R@824R38!y~WLwgY1A5_!IluiwApw?F^jxrgSaCd3m7$?WU5Z_=0Q>S`odZ$`~5 zZ0Sw52e}MN3`$DW(9nRlZ{MP#vJzFS`M}T+ni?8eq7*)V{X)ZL7?ugX&Z7l02v8Q5 zBghKcG+eDjg&r_!g9Xu5L>>KoeWJkLd@uu1(n zy+Vi2Y{!;zgd~TF^t0lm=WV=-YHDj?Mj0?tqv-db>6$b+_kI|NL0&i@J)VDf^M3tR z_kIG~aNW7i`C%XQovm#P$UrC6PPIvoJWhUA vSy78D6Y+ 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 + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c new file mode 100644 index 0000000..7e9a324 --- /dev/null +++ b/desktop-shell/exposay.c @@ -0,0 +1,737 @@ +/* + * Copyright © 2010-2012 Intel Corporation + * Copyright © 2011-2012 Collabora, Ltd. + * Copyright © 2013 Raspberry Pi Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include "shell.h" +#include "shared/helpers.h" + +struct exposay_surface { + struct desktop_shell *shell; + struct exposay_output *eoutput; + struct weston_surface *surface; + struct weston_view *view; + struct wl_listener view_destroy_listener; + struct wl_list link; + + int x; + int y; + int width; + int height; + double scale; + + int row; + int column; + + /* The animations only apply a transformation for their own lifetime, + * and don't have an option to indefinitely maintain the + * transformation in a steady state - so, we apply our own once the + * animation has finished. */ + struct weston_transform transform; +}; + +static void exposay_set_state(struct desktop_shell *shell, + enum exposay_target_state state, + struct weston_seat *seat); +static void exposay_check_state(struct desktop_shell *shell); + +static void +exposay_surface_destroy(struct exposay_surface *esurface) +{ + wl_list_remove(&esurface->link); + wl_list_remove(&esurface->view_destroy_listener.link); + + if (esurface->shell->exposay.focus_current == esurface->view) + esurface->shell->exposay.focus_current = NULL; + if (esurface->shell->exposay.focus_prev == esurface->view) + esurface->shell->exposay.focus_prev = NULL; + + free(esurface); +} + +static void +exposay_in_flight_inc(struct desktop_shell *shell) +{ + shell->exposay.in_flight++; +} + +static void +exposay_in_flight_dec(struct desktop_shell *shell) +{ + if (--shell->exposay.in_flight > 0) + return; + + exposay_check_state(shell); +} + +static void +exposay_animate_in_done(struct weston_view_animation *animation, void *data) +{ + struct exposay_surface *esurface = data; + + wl_list_insert(&esurface->view->geometry.transformation_list, + &esurface->transform.link); + weston_matrix_init(&esurface->transform.matrix); + weston_matrix_scale(&esurface->transform.matrix, + esurface->scale, esurface->scale, 1.0f); + weston_matrix_translate(&esurface->transform.matrix, + esurface->x - esurface->view->geometry.x, + esurface->y - esurface->view->geometry.y, + 0); + + weston_view_geometry_dirty(esurface->view); + weston_compositor_schedule_repaint(esurface->view->surface->compositor); + + exposay_in_flight_dec(esurface->shell); +} + +static void +exposay_animate_in(struct exposay_surface *esurface) +{ + exposay_in_flight_inc(esurface->shell); + + weston_move_scale_run(esurface->view, + esurface->x - esurface->view->geometry.x, + esurface->y - esurface->view->geometry.y, + 1.0, esurface->scale, 0, + exposay_animate_in_done, esurface); +} + +static void +exposay_animate_out_done(struct weston_view_animation *animation, void *data) +{ + struct exposay_surface *esurface = data; + struct desktop_shell *shell = esurface->shell; + + exposay_surface_destroy(esurface); + + exposay_in_flight_dec(shell); +} + +static void +exposay_animate_out(struct exposay_surface *esurface) +{ + exposay_in_flight_inc(esurface->shell); + + /* Remove the static transformation set up by + * exposay_transform_in_done(). */ + wl_list_remove(&esurface->transform.link); + weston_view_geometry_dirty(esurface->view); + + weston_move_scale_run(esurface->view, + esurface->x - esurface->view->geometry.x, + esurface->y - esurface->view->geometry.y, + 1.0, esurface->scale, 1, + exposay_animate_out_done, esurface); +} + +static void +exposay_highlight_surface(struct desktop_shell *shell, + struct exposay_surface *esurface) +{ + struct weston_view *view = esurface->view; + + if (shell->exposay.focus_current == view) + return; + + shell->exposay.row_current = esurface->row; + shell->exposay.column_current = esurface->column; + shell->exposay.cur_output = esurface->eoutput; + + activate(shell, view, shell->exposay.seat, + WESTON_ACTIVATE_FLAG_NONE); + shell->exposay.focus_current = view; +} + +static int +exposay_is_animating(struct desktop_shell *shell) +{ + if (shell->exposay.state_cur == EXPOSAY_LAYOUT_INACTIVE || + shell->exposay.state_cur == EXPOSAY_LAYOUT_OVERVIEW) + return 0; + + return (shell->exposay.in_flight > 0); +} + +static void +exposay_pick(struct desktop_shell *shell, int x, int y) +{ + struct exposay_surface *esurface; + + if (exposay_is_animating(shell)) + return; + + wl_list_for_each(esurface, &shell->exposay.surface_list, link) { + if (x < esurface->x || x > esurface->x + esurface->width) + continue; + if (y < esurface->y || y > esurface->y + esurface->height) + continue; + + exposay_highlight_surface(shell, esurface); + return; + } +} + +static void +handle_view_destroy(struct wl_listener *listener, void *data) +{ + struct exposay_surface *esurface = container_of(listener, + struct exposay_surface, + view_destroy_listener); + + exposay_surface_destroy(esurface); +} + +/* Compute each surface size and then inner pad (10% of surface size). + * After that, it's necessary to recompute surface size (90% of its + * original size). Also, each surface can't be bigger than half the + * exposay area width and height. + */ +static void +exposay_surface_and_inner_pad_size(pixman_rectangle32_t exposay_area, struct exposay_output *eoutput) +{ + if (exposay_area.height < exposay_area.width) + eoutput->surface_size = exposay_area.height / eoutput->grid_size; + else + eoutput->surface_size = exposay_area.width / eoutput->grid_size; + + eoutput->padding_inner = eoutput->surface_size / 10; + eoutput->surface_size -= eoutput->padding_inner; + + if ((uint32_t)eoutput->surface_size > (exposay_area.width / 2)) + eoutput->surface_size = exposay_area.width / 2; + if ((uint32_t)eoutput->surface_size > (exposay_area.height / 2)) + eoutput->surface_size = exposay_area.height / 2; +} + +/* Compute the exposay top/left margin in order to centralize it */ +static void +exposay_margin_size(struct desktop_shell *shell, pixman_rectangle32_t exposay_area, + int row_size, int column_size, int *left_margin, int *top_margin) +{ + (*left_margin) = exposay_area.x + (exposay_area.width - row_size) / 2; + (*top_margin) = exposay_area.y + (exposay_area.height - column_size) / 2; +} + +/* Pretty lame layout for now; just tries to make a square. Should take + * aspect ratio into account really. Also needs to be notified of surface + * addition and removal and adjust layout/animate accordingly. + * + * Lay the grid out as square as possible, losing surfaces from the + * bottom row if required. Start with fixed padding of a 10% margin + * around the outside, and maximise the area made available to surfaces + * after this. Also, add an inner padding between surfaces that varies + * with the surface size (10% of its size). + * + * If we can't make a square grid, add one extra row at the bottom which + * will have a smaller number of columns. + */ +static enum exposay_layout_state +exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) +{ + struct workspace *workspace = shell->exposay.workspace; + struct weston_output *output = shell_output->output; + struct exposay_output *eoutput = &shell_output->eoutput; + struct weston_view *view; + struct exposay_surface *esurface, *highlight = NULL; + pixman_rectangle32_t exposay_area; + int pad, row_size, column_size, left_margin, top_margin; + int last_row_size, last_row_margin_increase; + int populated_rows; + int i; + + eoutput->num_surfaces = 0; + wl_list_for_each(view, &workspace->layer.view_list.link, layer_link.link) { + if (!get_shell_surface(view->surface)) + continue; + if (view->output != output) + continue; + eoutput->num_surfaces++; + } + + if (eoutput->num_surfaces == 0) { + eoutput->grid_size = 0; + eoutput->padding_inner = 0; + eoutput->surface_size = 0; + return EXPOSAY_LAYOUT_OVERVIEW; + } + + /* Get exposay area and position, taking into account + * the shell panel position and size */ + get_output_work_area(shell, output, &exposay_area); + + /* Compute grid size */ + eoutput->grid_size = floor(sqrtf(eoutput->num_surfaces)); + if (pow(eoutput->grid_size, 2) != eoutput->num_surfaces) + eoutput->grid_size++; + + /* Compute each surface size and the inner padding between them */ + exposay_surface_and_inner_pad_size(exposay_area, eoutput); + + /* Compute each row/column size */ + pad = eoutput->surface_size + eoutput->padding_inner; + row_size = (pad * eoutput->grid_size) - eoutput->padding_inner; + /* We may have empty rows that should be desconsidered to compute + * column size */ + populated_rows = ceil(eoutput->num_surfaces / (float) eoutput->grid_size); + column_size = (pad * populated_rows) - eoutput->padding_inner; + + /* The last row size can be different, since it may have less surfaces + * than the grid size. Also, its margin may be increased to centralize + * its surfaces, in the case where we don't have a perfect grid. */ + last_row_size = ((eoutput->num_surfaces % eoutput->grid_size) * pad) - eoutput->padding_inner; + if (eoutput->num_surfaces % eoutput->grid_size) + last_row_margin_increase = (row_size - last_row_size) / 2; + else + last_row_margin_increase = 0; + + /* Compute a top/left margin to centralize the exposay */ + exposay_margin_size(shell, exposay_area, row_size, column_size, &left_margin, &top_margin); + + i = 0; + wl_list_for_each(view, &workspace->layer.view_list.link, layer_link.link) { + + if (!get_shell_surface(view->surface)) + continue; + if (view->output != output) + continue; + + esurface = malloc(sizeof(*esurface)); + if (!esurface) { + exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, + shell->exposay.seat); + break; + } + + wl_list_insert(&shell->exposay.surface_list, &esurface->link); + esurface->shell = shell; + esurface->eoutput = eoutput; + esurface->view = view; + + esurface->row = i / eoutput->grid_size; + esurface->column = i % eoutput->grid_size; + + esurface->x = left_margin + (pad * esurface->column); + esurface->y = top_margin + (pad * esurface->row); + + /* If this is the last row, increase left margin (it sums 0 if + * we have a perfect square) to centralize the surfaces */ + if (eoutput->num_surfaces / eoutput->grid_size == esurface->row) + esurface->x += last_row_margin_increase; + + if (view->surface->width > view->surface->height) + esurface->scale = eoutput->surface_size / (float) view->surface->width; + else + esurface->scale = eoutput->surface_size / (float) view->surface->height; + esurface->width = view->surface->width * esurface->scale; + esurface->height = view->surface->height * esurface->scale; + + /* Surfaces are usually rectangular, but their exposay surfaces + * are square. centralize them in their own square */ + if (esurface->width > esurface->height) + esurface->y += (esurface->width - esurface->height) / 2; + else + esurface->x += (esurface->height - esurface->width) / 2; + + if (shell->exposay.focus_current == esurface->view) + highlight = esurface; + + exposay_animate_in(esurface); + + /* We want our destroy handler to be after the animation + * destroy handler in the list, this way when the view is + * destroyed, the animation can safely call the animation + * completion callback before we free the esurface in our + * destroy handler. + */ + esurface->view_destroy_listener.notify = handle_view_destroy; + wl_signal_add(&view->destroy_signal, &esurface->view_destroy_listener); + + i++; + } + + if (highlight) { + shell->exposay.focus_current = NULL; + exposay_highlight_surface(shell, highlight); + } + + weston_compositor_schedule_repaint(shell->compositor); + + return EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW; +} + +static void +exposay_focus(struct weston_pointer_grab *grab) +{ +} + +static void +exposay_motion(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + struct desktop_shell *shell = + container_of(grab, struct desktop_shell, exposay.grab_ptr); + + weston_pointer_move(grab->pointer, event); + + exposay_pick(shell, + wl_fixed_to_int(grab->pointer->x), + wl_fixed_to_int(grab->pointer->y)); +} + +static void +exposay_button(struct weston_pointer_grab *grab, const struct timespec *time, + uint32_t button, uint32_t state_w) +{ + struct desktop_shell *shell = + container_of(grab, struct desktop_shell, exposay.grab_ptr); + struct weston_seat *seat = grab->pointer->seat; + enum wl_pointer_button_state state = state_w; + + if (button != BTN_LEFT) + return; + + /* Store the surface we clicked on, and don't do anything if we end up + * releasing on a different surface. */ + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + shell->exposay.clicked = shell->exposay.focus_current; + return; + } + + if (shell->exposay.focus_current == shell->exposay.clicked) + exposay_set_state(shell, EXPOSAY_TARGET_SWITCH, seat); + else + shell->exposay.clicked = NULL; +} + +static void +exposay_axis(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_axis_event *event) +{ +} + +static void +exposay_axis_source(struct weston_pointer_grab *grab, uint32_t source) +{ +} + +static void +exposay_frame(struct weston_pointer_grab *grab) +{ +} + +static void +exposay_pointer_grab_cancel(struct weston_pointer_grab *grab) +{ + struct desktop_shell *shell = + container_of(grab, struct desktop_shell, exposay.grab_ptr); + + exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, shell->exposay.seat); +} + +static const struct weston_pointer_grab_interface exposay_ptr_grab = { + exposay_focus, + exposay_motion, + exposay_button, + exposay_axis, + exposay_axis_source, + exposay_frame, + exposay_pointer_grab_cancel, +}; + +static int +exposay_maybe_move(struct desktop_shell *shell, int row, int column) +{ + struct exposay_surface *esurface; + + wl_list_for_each(esurface, &shell->exposay.surface_list, link) { + if (esurface->eoutput != shell->exposay.cur_output || + esurface->row != row || esurface->column != column) + continue; + + exposay_highlight_surface(shell, esurface); + return 1; + } + + return 0; +} + +static void +exposay_key(struct weston_keyboard_grab *grab, const struct timespec *time, + uint32_t key, uint32_t state_w) +{ + struct weston_seat *seat = grab->keyboard->seat; + struct desktop_shell *shell = + container_of(grab, struct desktop_shell, exposay.grab_kbd); + enum wl_keyboard_key_state state = state_w; + + if (state != WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + switch (key) { + case KEY_ESC: + exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, seat); + break; + case KEY_ENTER: + exposay_set_state(shell, EXPOSAY_TARGET_SWITCH, seat); + break; + case KEY_UP: + exposay_maybe_move(shell, shell->exposay.row_current - 1, + shell->exposay.column_current); + break; + case KEY_DOWN: + /* Special case for trying to move to the bottom row when it + * has fewer items than all the others. */ + if (!exposay_maybe_move(shell, shell->exposay.row_current + 1, + shell->exposay.column_current) && + shell->exposay.row_current < (shell->exposay.cur_output->grid_size - 1)) { + exposay_maybe_move(shell, shell->exposay.row_current + 1, + (shell->exposay.cur_output->num_surfaces % + shell->exposay.cur_output->grid_size) - 1); + } + break; + case KEY_LEFT: + exposay_maybe_move(shell, shell->exposay.row_current, + shell->exposay.column_current - 1); + break; + case KEY_RIGHT: + exposay_maybe_move(shell, shell->exposay.row_current, + shell->exposay.column_current + 1); + break; + case KEY_TAB: + /* Try to move right, then down (and to the leftmost column), + * then if all else fails, to the top left. */ + if (!exposay_maybe_move(shell, shell->exposay.row_current, + shell->exposay.column_current + 1) && + !exposay_maybe_move(shell, shell->exposay.row_current + 1, 0)) + exposay_maybe_move(shell, 0, 0); + break; + default: + break; + } +} + +static void +exposay_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 desktop_shell *shell = + container_of(grab, struct desktop_shell, exposay.grab_kbd); + struct weston_seat *seat = (struct weston_seat *) grab->keyboard->seat; + + /* We want to know when mod has been pressed and released. + * FIXME: There is a problem here: if mod is pressed, then a key + * is pressed and released, then mod is released, we will treat that + * as if only mod had been pressed and released. */ + if (seat->modifier_state) { + if (seat->modifier_state == shell->binding_modifier) { + shell->exposay.mod_pressed = true; + } else { + shell->exposay.mod_invalid = true; + } + } else { + if (shell->exposay.mod_pressed && !shell->exposay.mod_invalid) + exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, seat); + + shell->exposay.mod_invalid = false; + shell->exposay.mod_pressed = false; + } + + return; +} + +static void +exposay_cancel(struct weston_keyboard_grab *grab) +{ + struct desktop_shell *shell = + container_of(grab, struct desktop_shell, exposay.grab_kbd); + + exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, shell->exposay.seat); +} + +static const struct weston_keyboard_grab_interface exposay_kbd_grab = { + exposay_key, + exposay_modifier, + exposay_cancel, +}; + +/** + * Called when the transition from overview -> inactive has completed. + */ +static enum exposay_layout_state +exposay_set_inactive(struct desktop_shell *shell) +{ + struct weston_seat *seat = shell->exposay.seat; + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + if (pointer) + weston_pointer_end_grab(pointer); + + if (keyboard) { + weston_keyboard_end_grab(keyboard); + if (keyboard->input_method_resource) + keyboard->grab = &keyboard->input_method_grab; + } + + return EXPOSAY_LAYOUT_INACTIVE; +} + +/** + * Begins the transition from overview to inactive. */ +static enum exposay_layout_state +exposay_transition_inactive(struct desktop_shell *shell, int switch_focus) +{ + struct exposay_surface *esurface; + + /* Call activate() before we start the animations to avoid + * animating back the old state and then immediately transitioning + * to the new. */ + if (switch_focus && shell->exposay.focus_current) + activate(shell, shell->exposay.focus_current, + shell->exposay.seat, + WESTON_ACTIVATE_FLAG_CONFIGURE); + else if (shell->exposay.focus_prev) + activate(shell, shell->exposay.focus_prev, + shell->exposay.seat, + WESTON_ACTIVATE_FLAG_CONFIGURE); + + wl_list_for_each(esurface, &shell->exposay.surface_list, link) + exposay_animate_out(esurface); + weston_compositor_schedule_repaint(shell->compositor); + + return EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE; +} + +static enum exposay_layout_state +exposay_transition_active(struct desktop_shell *shell) +{ + struct weston_seat *seat = shell->exposay.seat; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + struct shell_output *shell_output; + bool animate = false; + + shell->exposay.workspace = get_current_workspace(shell); + shell->exposay.focus_prev = get_default_view(keyboard->focus); + shell->exposay.focus_current = get_default_view(keyboard->focus); + shell->exposay.clicked = NULL; + wl_list_init(&shell->exposay.surface_list); + + lower_fullscreen_layer(shell, NULL); + shell->exposay.grab_kbd.interface = &exposay_kbd_grab; + weston_keyboard_start_grab(keyboard, + &shell->exposay.grab_kbd); + weston_keyboard_set_focus(keyboard, NULL); + + shell->exposay.grab_ptr.interface = &exposay_ptr_grab; + if (pointer) { + weston_pointer_start_grab(pointer, + &shell->exposay.grab_ptr); + weston_pointer_clear_focus(pointer); + } + wl_list_for_each(shell_output, &shell->output_list, link) { + enum exposay_layout_state state; + + state = exposay_layout(shell, shell_output); + + if (state == EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW) + animate = true; + } + + return animate ? EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW + : EXPOSAY_LAYOUT_OVERVIEW; +} + +static void +exposay_check_state(struct desktop_shell *shell) +{ + enum exposay_layout_state state_new = shell->exposay.state_cur; + int do_switch = 0; + + /* Don't do anything whilst animations are running, just store up + * target state changes and only act on them when the animations have + * completed. */ + if (exposay_is_animating(shell)) + return; + + switch (shell->exposay.state_target) { + case EXPOSAY_TARGET_OVERVIEW: + switch (shell->exposay.state_cur) { + case EXPOSAY_LAYOUT_OVERVIEW: + goto out; + case EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW: + state_new = EXPOSAY_LAYOUT_OVERVIEW; + break; + default: + state_new = exposay_transition_active(shell); + break; + } + break; + + case EXPOSAY_TARGET_SWITCH: + do_switch = 1; /* fallthrough */ + case EXPOSAY_TARGET_CANCEL: + switch (shell->exposay.state_cur) { + case EXPOSAY_LAYOUT_INACTIVE: + goto out; + case EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE: + state_new = exposay_set_inactive(shell); + break; + default: + state_new = exposay_transition_inactive(shell, do_switch); + break; + } + + break; + } + +out: + shell->exposay.state_cur = state_new; +} + +static void +exposay_set_state(struct desktop_shell *shell, enum exposay_target_state state, + struct weston_seat *seat) +{ + shell->exposay.state_target = state; + shell->exposay.seat = seat; + exposay_check_state(shell); +} + +void +exposay_binding(struct weston_keyboard *keyboard, enum weston_keyboard_modifier modifier, + void *data) +{ + struct desktop_shell *shell = data; + + exposay_set_state(shell, EXPOSAY_TARGET_OVERVIEW, keyboard->seat); +} diff --git a/desktop-shell/input-panel.c b/desktop-shell/input-panel.c new file mode 100644 index 0000000..8292f20 --- /dev/null +++ b/desktop-shell/input-panel.c @@ -0,0 +1,412 @@ +/* + * Copyright © 2010-2012 Intel Corporation + * Copyright © 2011-2012 Collabora, Ltd. + * Copyright © 2013 Raspberry Pi Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "shell.h" +#include "input-method-unstable-v1-server-protocol.h" +#include "shared/helpers.h" + +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 weston_view *view; + struct wl_listener surface_destroy_listener; + + struct weston_view_animation *anim; + + struct weston_output *output; + uint32_t panel; +}; + +static void +input_panel_slide_done(struct weston_view_animation *animation, void *data) +{ + struct input_panel_surface *ipsurf = data; + + ipsurf->anim = NULL; +} + +static void +show_input_panel_surface(struct input_panel_surface *ipsurf) +{ + struct desktop_shell *shell = ipsurf->shell; + struct weston_seat *seat; + struct weston_surface *focus; + float x, y; + + wl_list_for_each(seat, &shell->compositor->seat_list, link) { + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(seat); + + if (!keyboard || !keyboard->focus) + continue; + focus = weston_surface_get_main_surface(keyboard->focus); + if (!focus) + continue; + ipsurf->output = focus->output; + x = ipsurf->output->x + (ipsurf->output->width - ipsurf->surface->width) / 2; + y = ipsurf->output->y + ipsurf->output->height - ipsurf->surface->height; + weston_view_set_position(ipsurf->view, x, y); + } + + weston_layer_entry_insert(&shell->input_panel_layer.view_list, + &ipsurf->view->layer_link); + weston_view_geometry_dirty(ipsurf->view); + weston_view_update_transform(ipsurf->view); + ipsurf->surface->is_mapped = true; + ipsurf->view->is_mapped = true; + weston_surface_damage(ipsurf->surface); + + if (ipsurf->anim) + weston_view_animation_destroy(ipsurf->anim); + + ipsurf->anim = + weston_slide_run(ipsurf->view, + ipsurf->surface->height * 0.9, 0, + input_panel_slide_done, ipsurf); +} + +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 *ipsurf, *next; + + shell->text_input.surface = (struct weston_surface*)data; + + if (shell->showing_input_panels) + return; + + shell->showing_input_panels = true; + + if (!shell->locked) + weston_layer_set_position(&shell->input_panel_layer, + WESTON_LAYER_POSITION_TOP_UI); + + wl_list_for_each_safe(ipsurf, next, + &shell->input_panel.surfaces, link) { + if (ipsurf->surface->width == 0) + continue; + + show_input_panel_surface(ipsurf); + } +} + +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_view *view, *next; + + if (!shell->showing_input_panels) + return; + + shell->showing_input_panels = false; + + if (!shell->locked) + weston_layer_unset_position(&shell->input_panel_layer); + + wl_list_for_each_safe(view, next, + &shell->input_panel_layer.view_list.link, + layer_link.link) + weston_view_unmap(view); +} + +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 int +input_panel_get_label(struct weston_surface *surface, char *buf, size_t len) +{ + return snprintf(buf, len, "input panel"); +} + +static void +input_panel_committed(struct weston_surface *surface, int32_t sx, int32_t sy) +{ + struct input_panel_surface *ip_surface = surface->committed_private; + struct desktop_shell *shell = ip_surface->shell; + struct weston_view *view; + float x, y; + + if (surface->width == 0) + return; + + if (ip_surface->panel) { + view = get_default_view(shell->text_input.surface); + if (view == NULL) + return; + x = view->geometry.x + shell->text_input.cursor_rectangle.x2; + y = view->geometry.y + shell->text_input.cursor_rectangle.y2; + } else { + x = ip_surface->output->x + (ip_surface->output->width - surface->width) / 2; + y = ip_surface->output->y + ip_surface->output->height - surface->height; + } + + weston_view_set_position(ip_surface->view, x, y); + + if (!weston_surface_is_mapped(surface) && shell->showing_input_panels) + show_input_panel_surface(ip_surface); +} + +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->committed = NULL; + weston_surface_set_label_func(input_panel_surface->surface, NULL); + weston_view_destroy(input_panel_surface->view); + + free(input_panel_surface); +} + +static struct input_panel_surface * +get_input_panel_surface(struct weston_surface *surface) +{ + if (surface->committed == input_panel_committed) { + return surface->committed_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->committed = input_panel_committed; + surface->committed_private = input_panel_surface; + weston_surface_set_label_func(surface, input_panel_get_label); + + input_panel_surface->shell = shell; + + input_panel_surface->surface = surface; + input_panel_surface->view = weston_view_create(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; + struct weston_head *head; + + wl_list_insert(&shell->input_panel.surfaces, + &input_panel_surface->link); + + head = weston_head_from_resource(output_resource); + input_panel_surface->output = head->output; + 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 zwp_input_panel_surface_v1_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->committed already set"); + return; + } + + ipsurf->resource = + wl_resource_create(client, + &zwp_input_panel_surface_v1_interface, + 1, + id); + wl_resource_set_implementation(ipsurf->resource, + &input_panel_surface_implementation, + ipsurf, + destroy_input_panel_surface_resource); +} + +static const struct zwp_input_panel_v1_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, + &zwp_input_panel_v1_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"); +} + +void +input_panel_destroy(struct desktop_shell *shell) +{ + wl_list_remove(&shell->show_input_panel_listener.link); + wl_list_remove(&shell->hide_input_panel_listener.link); +} + +int +input_panel_setup(struct desktop_shell *shell) +{ + struct weston_compositor *ec = shell->compositor; + + 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); + + wl_list_init(&shell->input_panel.surfaces); + + if (wl_global_create(shell->compositor->wl_display, + &zwp_input_panel_v1_interface, 1, + shell, bind_input_panel) == NULL) + return -1; + + return 0; +} diff --git a/desktop-shell/meson.build b/desktop-shell/meson.build new file mode 100644 index 0000000..c67bfd6 --- /dev/null +++ b/desktop-shell/meson.build @@ -0,0 +1,31 @@ +if get_option('shell-desktop') + config_h.set_quoted('WESTON_SHELL_CLIENT', get_option('desktop-shell-client-default')) + + srcs_shell_desktop = [ + 'shell.c', + 'exposay.c', + 'input-panel.c', + weston_desktop_shell_server_protocol_h, + weston_desktop_shell_protocol_c, + input_method_unstable_v1_server_protocol_h, + input_method_unstable_v1_protocol_c, + ] + deps_shell_desktop = [ + dep_libm, + dep_libexec_weston, + dep_libshared, + dep_lib_desktop, + dep_libweston_public, + ] + plugin_shell_desktop = shared_library( + 'desktop-shell', + srcs_shell_desktop, + include_directories: common_inc, + dependencies: deps_shell_desktop, + name_prefix: '', + install: true, + install_dir: dir_module_weston, + install_rpath: '$ORIGIN' + ) + env_modmap += 'desktop-shell.so=@0@;'.format(plugin_shell_desktop.full_path()) +endif diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c new file mode 100755 index 0000000..b35aab2 --- /dev/null +++ b/desktop-shell/shell.c @@ -0,0 +1,5253 @@ +/* + * Copyright © 2010-2012 Intel Corporation + * Copyright © 2011-2012 Collabora, Ltd. + * Copyright © 2013 Raspberry Pi Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shell.h" +#include "compositor/weston.h" +#include "weston-desktop-shell-server-protocol.h" +#include +#include "shared/helpers.h" +#include "shared/timespec-util.h" +#include + +#define DEFAULT_NUM_WORKSPACES 1 +#define DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH 200 + +struct focus_state { + struct desktop_shell *shell; + 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; +}; + +/* + * Surface stacking and ordering. + * + * This is handled using several linked lists of surfaces, organised into + * ‘layers’. The layers are ordered, and each of the surfaces in one layer are + * above all of the surfaces in the layer below. The set of layers is static and + * in the following order (top-most first): + * • Lock layer (only ever displayed on its own) + * • Cursor layer + * • Input panel layer + * • Fullscreen layer + * • Panel layer + * • Workspace layers + * • Background layer + * + * The list of layers may be manipulated to remove whole layers of surfaces from + * display. For example, when locking the screen, all layers except the lock + * layer are removed. + * + * A surface’s layer is modified on configuring the surface, in + * set_surface_type() (which is only called when the surface’s type change is + * _committed_). If a surface’s type changes (e.g. when making a window + * fullscreen) its layer changes too. + * + * In order to allow popup and transient surfaces to be correctly stacked above + * their parent surfaces, each surface tracks both its parent surface, and a + * linked list of its children. When a surface’s layer is updated, so are the + * layers of its children. Note that child surfaces are *not* the same as + * subsurfaces — child/parent surfaces are purely for maintaining stacking + * order. + * + * The children_link list of siblings of a surface (i.e. those surfaces which + * have the same parent) only contains weston_surfaces which have a + * shell_surface. Stacking is not implemented for non-shell_surface + * weston_surfaces. This means that the following implication does *not* hold: + * (shsurf->parent != NULL) ⇒ !wl_list_is_empty(shsurf->children_link) + */ + +struct shell_surface { + struct wl_signal destroy_signal; + + struct weston_desktop_surface *desktop_surface; + struct weston_view *view; + int32_t last_width, last_height; + + struct desktop_shell *shell; + + struct wl_list children_list; + struct wl_list children_link; + + int32_t saved_x, saved_y; + bool saved_position_valid; + bool saved_rotation_valid; + int unresponsive, grabbed; + uint32_t resize_edges; + + struct { + struct weston_transform transform; + struct weston_matrix rotation; + } rotation; + + struct { + struct weston_transform transform; /* matrix from x, y */ + struct weston_view *black_view; + } fullscreen; + + struct weston_transform workspace_transform; + + struct weston_output *fullscreen_output; + struct weston_output *output; + struct wl_listener output_destroy_listener; + + struct surface_state { + bool fullscreen; + bool maximized; + bool lowered; + } state; + + struct { + bool is_set; + int32_t x; + int32_t y; + } xwayland; + + int focus_count; + + bool destroying; +}; + +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; + bool client_initiated; +}; + +struct weston_touch_move_grab { + struct shell_touch_grab base; + int active; + 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 weston_surface *focused_surface; + + struct wl_listener caps_changed_listener; + struct wl_listener pointer_focus_listener; + struct wl_listener keyboard_focus_listener; +}; + + +static struct desktop_shell * +shell_surface_get_shell(struct shell_surface *shsurf); + +static void +set_busy_cursor(struct shell_surface *shsurf, struct weston_pointer *pointer); + +static void +surface_rotate(struct shell_surface *surface, struct weston_pointer *pointer); + +static void +shell_fade_startup(struct desktop_shell *shell); + +static void +shell_fade(struct desktop_shell *shell, enum fade_type type); + +static struct shell_seat * +get_shell_seat(struct weston_seat *seat); + +static void +get_output_panel_size(struct desktop_shell *shell, + struct weston_output *output, + int *width, int *height); + +static void +shell_surface_update_child_surface_layers(struct shell_surface *shsurf); + +static int +shell_surface_get_label(struct weston_surface *surface, char *buf, size_t len) +{ + const char *t, *c; + struct weston_desktop_surface *desktop_surface = + weston_surface_get_desktop_surface(surface); + + t = weston_desktop_surface_get_title(desktop_surface); + c = weston_desktop_surface_get_app_id(desktop_surface); + + return snprintf(buf, len, "%s window%s%s%s%s%s", + "top-level", + t ? " '" : "", t ?: "", t ? "'" : "", + c ? " of " : "", c ?: ""); +} + +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; +} + +struct weston_view * +get_default_view(struct weston_surface *surface) +{ + struct shell_surface *shsurf; + struct weston_view *view; + + if (!surface || wl_list_empty(&surface->views)) + return NULL; + + shsurf = get_shell_surface(surface); + if (shsurf) + return shsurf->view; + + wl_list_for_each(view, &surface->views, surface_link) + if (weston_view_is_mapped(view)) + return view; + + return container_of(surface->views.next, struct weston_view, surface_link); +} + +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 weston_desktop_shell_cursor cursor) +{ + struct desktop_shell *shell = shsurf->shell; + + weston_seat_break_desktop_grabs(pointer->seat); + + 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); + + shsurf->grabbed = 1; + weston_pointer_start_grab(pointer, &grab->grab); + if (shell->child.desktop_shell) { + weston_desktop_shell_send_grab_cursor(shell->child.desktop_shell, + cursor); + weston_pointer_set_focus(pointer, + get_default_view(shell->grab_surface), + wl_fixed_from_int(0), + wl_fixed_from_int(0)); + } +} + +static void +get_panel_size(struct desktop_shell *shell, + struct weston_view *view, + int *width, + int *height) +{ + float x1, y1; + float x2, y2; + weston_view_to_global_float(view, 0, 0, &x1, &y1); + weston_view_to_global_float(view, + view->surface->width, + view->surface->height, + &x2, &y2); + *width = (int)(x2 - x1); + *height = (int)(y2 - y1); +} + +static void +get_output_panel_size(struct desktop_shell *shell, + struct weston_output *output, + int *width, + int *height) +{ + struct weston_view *view; + + *width = 0; + *height = 0; + + if (!output) + return; + + wl_list_for_each(view, &shell->panel_layer.view_list.link, layer_link.link) { + if (view->surface->output == output) { + get_panel_size(shell, view, width, height); + return; + } + } + + /* the correct view wasn't found */ +} + +void +get_output_work_area(struct desktop_shell *shell, + struct weston_output *output, + pixman_rectangle32_t *area) +{ + int32_t panel_width = 0, panel_height = 0; + + if (!output) { + area->x = 0; + area->y = 0; + area->width = 0; + area->height = 0; + + return; + } + + area->x = output->x; + area->y = output->y; + + get_output_panel_size(shell, output, &panel_width, &panel_height); + switch (shell->panel_position) { + case WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP: + default: + area->y += panel_height; + /* fallthrough */ + case WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM: + area->width = output->width; + area->height = output->height - panel_height; + break; + case WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT: + area->x += panel_width; + /* fallthrough */ + case WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT: + area->width = output->width - panel_width; + area->height = output->height; + break; + } +} + +static void +shell_grab_end(struct shell_grab *grab) +{ + if (grab->shsurf) { + wl_list_remove(&grab->shsurf_destroy_listener.link); + grab->shsurf->grabbed = 0; + + if (grab->shsurf->resize_edges) { + grab->shsurf->resize_edges = 0; + } + } + + 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; + + weston_seat_break_desktop_grabs(touch->seat); + + 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; + shsurf->grabbed = 1; + + weston_touch_start_grab(touch, &grab->grab); + if (shell->child.desktop_shell) + weston_touch_set_focus(touch, + get_default_view(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); + grab->shsurf->grabbed = 0; + } + + weston_touch_end_grab(grab->touch); +} + +static void +center_on_output(struct weston_view *view, + 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 if (!strcmp("none", modifier)) + return 0; + else + return MODIFIER_SUPER; +} + +static enum animation_type +get_animation_type(char *animation) +{ + if (!animation) + return ANIMATION_NONE; + + if (!strcmp("zoom", animation)) + return ANIMATION_ZOOM; + else if (!strcmp("fade", animation)) + return ANIMATION_FADE; + else if (!strcmp("dim-layer", animation)) + return ANIMATION_DIM_LAYER; + else + return ANIMATION_NONE; +} + +static void +shell_configuration(struct desktop_shell *shell) +{ + struct weston_config_section *section; + char *s, *client; + bool allow_zap; + + section = weston_config_get_section(wet_get_config(shell->compositor), + "shell", NULL, NULL); + client = wet_get_libexec_path(WESTON_SHELL_CLIENT); + weston_config_section_get_string(section, "client", &s, client); + free(client); + shell->client = s; + + weston_config_section_get_bool(section, + "allow-zap", &allow_zap, true); + shell->allow_zap = allow_zap; + + weston_config_section_get_string(section, + "binding-modifier", &s, "super"); + shell->binding_modifier = get_modifier(s); + free(s); + + weston_config_section_get_string(section, + "exposay-modifier", &s, "none"); + shell->exposay_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, "close-animation", &s, "fade"); + shell->win_close_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_string(section, "focus-animation", &s, "none"); + shell->focus_animation_type = get_animation_type(s); + free(s); + weston_config_section_get_uint(section, "num-workspaces", + &shell->workspaces.num, + DEFAULT_NUM_WORKSPACES); +} + +struct weston_output * +get_default_output(struct weston_compositor *compositor) +{ + if (wl_list_empty(&compositor->output_list)) + return NULL; + + return container_of(compositor->output_list.next, + struct weston_output, link); +} + +static int +focus_surface_get_label(struct weston_surface *surface, char *buf, size_t len) +{ + return snprintf(buf, len, "focus highlight effect for output %s", + (surface->output ? surface->output->name : "NULL")); +} + +/* no-op func for checking focus surface */ +static void +focus_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) +{ +} + +static struct focus_surface * +get_focus_surface(struct weston_surface *surface) +{ + if (surface->committed == focus_surface_committed) + return surface->committed_private; + else + return NULL; +} + +static bool +is_focus_surface (struct weston_surface *es) +{ + return (es->committed == focus_surface_committed); +} + +static bool +is_focus_view (struct weston_view *view) +{ + return is_focus_surface (view->surface); +} + +static struct focus_surface * +create_focus_surface(struct weston_compositor *ec, + struct weston_output *output) +{ + struct focus_surface *fsurf = NULL; + struct weston_surface *surface = NULL; + + fsurf = malloc(sizeof *fsurf); + if (!fsurf) + return NULL; + + fsurf->surface = weston_surface_create(ec); + surface = fsurf->surface; + if (surface == NULL) { + free(fsurf); + return NULL; + } + + surface->committed = focus_surface_committed; + surface->output = output; + surface->is_mapped = true; + surface->committed_private = fsurf; + weston_surface_set_label_func(surface, focus_surface_get_label); + + fsurf->view = weston_view_create(surface); + if (fsurf->view == NULL) { + weston_surface_destroy(surface); + free(fsurf); + return NULL; + } + weston_view_set_output(fsurf->view, output); + fsurf->view->is_mapped = true; + + weston_surface_set_size(surface, output->width, output->height); + weston_view_set_position(fsurf->view, output->x, output->y); + weston_surface_set_color(surface, 0.0, 0.0, 0.0, 1.0); + pixman_region32_fini(&surface->opaque); + pixman_region32_init_rect(&surface->opaque, output->x, output->y, + output->width, output->height); + pixman_region32_fini(&surface->input); + pixman_region32_init(&surface->input); + + wl_list_init(&fsurf->workspace_transform.link); + + return fsurf; +} + +static void +focus_surface_destroy(struct focus_surface *fsurf) +{ + weston_surface_destroy(fsurf->surface); + free(fsurf); +} + +static void +focus_animation_done(struct weston_view_animation *animation, void *data) +{ + struct workspace *ws = data; + + ws->focus_animation = NULL; +} + +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 weston_surface *main_surface; + struct weston_view *next; + struct weston_view *view; + + main_surface = weston_surface_get_main_surface(state->keyboard_focus); + + next = NULL; + wl_list_for_each(view, + &state->ws->layer.view_list.link, layer_link.link) { + if (view->surface == main_surface) + continue; + if (is_focus_view(view)) + continue; + if (!get_shell_surface(view->surface)) + continue; + + next = view; + break; + } + + /* if the focus was a sub-surface, activate its main surface */ + if (main_surface != state->keyboard_focus) + next = get_default_view(main_surface); + + if (next) { + if (state->keyboard_focus) { + wl_list_remove(&state->surface_destroy_listener.link); + wl_list_init(&state->surface_destroy_listener.link); + } + state->keyboard_focus = NULL; + activate(state->shell, next, state->seat, + WESTON_ACTIVATE_FLAG_CONFIGURE); + } else { + if (state->shell->focus_animation_type == ANIMATION_DIM_LAYER) { + if (state->ws->focus_animation) + weston_view_animation_destroy(state->ws->focus_animation); + + state->ws->focus_animation = weston_fade_run( + state->ws->fsurf_front->view, + state->ws->fsurf_front->view->alpha, 0.0, 300, + focus_animation_done, state->ws); + } + + wl_list_remove(&state->link); + focus_state_destroy(state); + } +} + +static struct focus_state * +focus_state_create(struct desktop_shell *shell, struct weston_seat *seat, + struct workspace *ws) +{ + struct focus_state *state; + + state = malloc(sizeof *state); + if (state == NULL) + return NULL; + + state->shell = shell; + state->keyboard_focus = 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(shell, seat, ws); + + return state; +} + +static void +focus_state_set_focus(struct focus_state *state, + struct weston_surface *surface) +{ + if (state->keyboard_focus) { + wl_list_remove(&state->surface_destroy_listener.link); + wl_list_init(&state->surface_destroy_listener.link); + } + + state->keyboard_focus = surface; + if (surface) + wl_signal_add(&surface->destroy_signal, + &state->surface_destroy_listener); +} + +static void +restore_focus_state(struct desktop_shell *shell, struct workspace *ws) +{ + struct focus_state *state, *next; + struct weston_surface *surface; + struct wl_list pending_seat_list; + struct weston_seat *seat, *next_seat; + + /* Temporarily steal the list of seats so that we can keep + * track of the seats we've already processed */ + wl_list_init(&pending_seat_list); + wl_list_insert_list(&pending_seat_list, &shell->compositor->seat_list); + wl_list_init(&shell->compositor->seat_list); + + wl_list_for_each_safe(state, next, &ws->focus_list, link) { + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(state->seat); + + wl_list_remove(&state->seat->link); + wl_list_insert(&shell->compositor->seat_list, + &state->seat->link); + + if (!keyboard) + continue; + + surface = state->keyboard_focus; + + weston_keyboard_set_focus(keyboard, surface); + } + + /* For any remaining seats that we don't have a focus state + * for we'll reset the keyboard focus to NULL */ + wl_list_for_each_safe(seat, next_seat, &pending_seat_list, link) { + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(seat); + + wl_list_insert(&shell->compositor->seat_list, &seat->link); + + if (!keyboard) + continue; + + weston_keyboard_set_focus(keyboard, NULL); + } +} + +static void +replace_focus_state(struct desktop_shell *shell, struct workspace *ws, + struct weston_seat *seat) +{ + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + struct focus_state *state; + + wl_list_for_each(state, &ws->focus_list, link) { + if (state->seat == seat) { + focus_state_set_focus(state, keyboard->focus); + 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) + focus_state_set_focus(state, NULL); +} + +static void +animate_focus_change(struct desktop_shell *shell, struct workspace *ws, + struct weston_view *from, struct weston_view *to) +{ + struct weston_output *output; + bool focus_surface_created = false; + + /* FIXME: Only support dim animation using two layers */ + if (from == to || shell->focus_animation_type != ANIMATION_DIM_LAYER) + return; + + output = get_default_output(shell->compositor); + if (ws->fsurf_front == NULL && (from || to)) { + ws->fsurf_front = create_focus_surface(shell->compositor, output); + if (ws->fsurf_front == NULL) + return; + ws->fsurf_front->view->alpha = 0.0; + + ws->fsurf_back = create_focus_surface(shell->compositor, output); + if (ws->fsurf_back == NULL) { + focus_surface_destroy(ws->fsurf_front); + return; + } + ws->fsurf_back->view->alpha = 0.0; + + focus_surface_created = true; + } else { + weston_layer_entry_remove(&ws->fsurf_front->view->layer_link); + weston_layer_entry_remove(&ws->fsurf_back->view->layer_link); + } + + if (ws->focus_animation) { + weston_view_animation_destroy(ws->focus_animation); + ws->focus_animation = NULL; + } + + if (to) + weston_layer_entry_insert(&to->layer_link, + &ws->fsurf_front->view->layer_link); + else if (from) + weston_layer_entry_insert(&ws->layer.view_list, + &ws->fsurf_front->view->layer_link); + + if (focus_surface_created) { + ws->focus_animation = weston_fade_run( + ws->fsurf_front->view, + ws->fsurf_front->view->alpha, 0.4, 300, + focus_animation_done, ws); + } else if (from) { + weston_layer_entry_insert(&from->layer_link, + &ws->fsurf_back->view->layer_link); + ws->focus_animation = weston_stable_fade_run( + ws->fsurf_front->view, 0.0, + ws->fsurf_back->view, 0.4, + focus_animation_done, ws); + } else if (to) { + weston_layer_entry_insert(&ws->layer.view_list, + &ws->fsurf_back->view->layer_link); + ws->focus_animation = weston_stable_fade_run( + ws->fsurf_front->view, 0.0, + ws->fsurf_back->view, 0.4, + focus_animation_done, ws); + } +} + +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); + + if (ws->fsurf_front) + focus_surface_destroy(ws->fsurf_front); + if (ws->fsurf_back) + focus_surface_destroy(ws->fsurf_back); + + 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(struct desktop_shell *shell) +{ + struct workspace *ws = malloc(sizeof *ws); + if (ws == NULL) + return NULL; + + weston_layer_init(&ws->layer, shell->compositor); + + wl_list_init(&ws->focus_list); + wl_list_init(&ws->seat_destroyed_listener.link); + ws->seat_destroyed_listener.notify = seat_destroyed; + ws->fsurf_front = NULL; + ws->fsurf_back = NULL; + ws->focus_animation = NULL; + + return ws; +} + +static int +workspace_is_empty(struct workspace *ws) +{ + return wl_list_empty(&ws->layer.view_list.link); +} + +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; +} + +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); + weston_layer_set_position(&ws->layer, WESTON_LAYER_POSITION_NORMAL); + + 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 struct weston_transform * +view_get_transform(struct weston_view *view) +{ + struct focus_surface *fsurf = NULL; + struct shell_surface *shsurf = NULL; + + if (is_focus_view(view)) { + fsurf = get_focus_surface(view->surface); + return &fsurf->workspace_transform; + } + + shsurf = get_shell_surface(view->surface); + if (shsurf) + return &shsurf->workspace_transform; + + return NULL; +} + +static void +view_translate(struct workspace *ws, struct weston_view *view, double d) +{ + struct weston_transform *transform = view_get_transform(view); + + if (!transform) + return; + + if (wl_list_empty(&transform->link)) + wl_list_insert(view->geometry.transformation_list.prev, + &transform->link); + + weston_matrix_init(&transform->matrix); + weston_matrix_translate(&transform->matrix, + 0.0, d, 0.0); + weston_view_geometry_dirty(view); +} + +static void +workspace_translate_out(struct workspace *ws, double fraction) +{ + struct weston_view *view; + unsigned int height; + double d; + + wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { + height = get_output_height(view->surface->output); + d = height * fraction; + + view_translate(ws, view, d); + } +} + +static void +workspace_translate_in(struct workspace *ws, double fraction) +{ + struct weston_view *view; + unsigned int height; + double d; + + wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { + height = get_output_height(view->surface->output); + + if (fraction > 0) + d = -(height - height * fraction); + else + d = height + height * fraction; + + view_translate(ws, view, d); + } +} + +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 = (struct timespec) { 0 }; + + weston_layer_set_position(&to->layer, WESTON_LAYER_POSITION_NORMAL); + weston_layer_set_position(&from->layer, WESTON_LAYER_POSITION_NORMAL - 1); + + weston_compositor_schedule_repaint(shell->compositor); +} + +static void +workspace_deactivate_transforms(struct workspace *ws) +{ + struct weston_view *view; + struct weston_transform *transform; + + wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { + transform = view_get_transform(view); + if (!transform) + continue; + + if (!wl_list_empty(&transform->link)) { + wl_list_remove(&transform->link); + wl_list_init(&transform->link); + } + weston_view_geometry_dirty(view); + } +} + +static void +finish_workspace_change_animation(struct desktop_shell *shell, + struct workspace *from, + struct workspace *to) +{ + struct weston_view *view; + + weston_compositor_schedule_repaint(shell->compositor); + + /* Views that extend past the bottom of the output are still + * visible after the workspace animation ends but before its layer + * is hidden. In that case, we need to damage below those views so + * that the screen is properly repainted. */ + wl_list_for_each(view, &from->layer.view_list.link, layer_link.link) + weston_view_damage_below(view); + + wl_list_remove(&shell->workspaces.animation.link); + workspace_deactivate_transforms(from); + workspace_deactivate_transforms(to); + shell->workspaces.anim_to = NULL; + + weston_layer_unset_position(&shell->workspaces.anim_from->layer); +} + +static void +animate_workspace_change_frame(struct weston_animation *animation, + struct weston_output *output, + const struct timespec *time) +{ + 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; + int64_t t; + double x, y; + + if (workspace_is_empty(from) && workspace_is_empty(to)) { + finish_workspace_change_animation(shell, from, to); + return; + } + + if (timespec_is_zero(&shell->workspaces.anim_timestamp)) { + if (shell->workspaces.anim_current == 0.0) + shell->workspaces.anim_timestamp = *time; + else + timespec_add_msec(&shell->workspaces.anim_timestamp, + time, + /* Inverse of movement function 'y' below. */ + -(asin(1.0 - shell->workspaces.anim_current) * + DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH * + M_2_PI)); + } + + t = timespec_sub_to_msec(time, &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 = (struct timespec) { 0 }; + + output = container_of(shell->compositor->output_list.next, + struct weston_output, link); + wl_list_insert(&output->animation_list, + &shell->workspaces.animation.link); + + weston_layer_set_position(&to->layer, WESTON_LAYER_POSITION_NORMAL); + weston_layer_set_position(&from->layer, WESTON_LAYER_POSITION_NORMAL - 1); + + 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; + weston_layer_set_position(&to->layer, WESTON_LAYER_POSITION_NORMAL); + weston_layer_unset_position(&from->layer); +} + +static void +change_workspace(struct desktop_shell *shell, unsigned int index) +{ + struct workspace *from; + struct workspace *to; + struct focus_state *state; + + if (index == shell->workspaces.current) + return; + + /* Don't change workspace when there is any fullscreen surfaces. */ + if (!wl_list_empty(&shell->fullscreen_layer.view_list.link)) + 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); + 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 (shell->focus_animation_type != ANIMATION_NONE) { + wl_list_for_each(state, &from->focus_list, link) + if (state->keyboard_focus) + animate_focus_change(shell, from, + get_default_view(state->keyboard_focus), NULL); + + wl_list_for_each(state, &to->focus_list, link) + if (state->keyboard_focus) + animate_focus_change(shell, to, + NULL, get_default_view(state->keyboard_focus)); + } + + if (workspace_is_empty(to) && workspace_is_empty(from)) + update_workspace(shell, index, from, to); + else + animate_workspace_change(shell, index, from, to); +} + +static bool +workspace_has_only(struct workspace *ws, struct weston_surface *surface) +{ + struct wl_list *list = &ws->layer.view_list.link; + 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_view, layer_link.link)->surface == surface; +} + +static void +surface_keyboard_focus_lost(struct weston_surface *surface) +{ + struct weston_compositor *compositor = surface->compositor; + struct weston_seat *seat; + struct weston_surface *focus; + + wl_list_for_each(seat, &compositor->seat_list, link) { + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(seat); + + if (!keyboard) + continue; + + focus = weston_surface_get_main_surface(keyboard->focus); + if (focus == surface) + weston_keyboard_set_focus(keyboard, NULL); + } +} + +static void +take_surface_to_workspace_by_seat(struct desktop_shell *shell, + struct weston_seat *seat, + unsigned int index) +{ + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + struct weston_surface *surface; + struct weston_view *view; + struct shell_surface *shsurf; + struct workspace *from; + struct workspace *to; + struct focus_state *state; + + surface = weston_surface_get_main_surface(keyboard->focus); + view = get_default_view(surface); + if (view == NULL || + index == shell->workspaces.current || + is_focus_view(view)) + return; + + from = get_current_workspace(shell); + to = get_workspace(shell, index); + + weston_layer_entry_remove(&view->layer_link); + weston_layer_entry_insert(&to->layer.view_list, &view->layer_link); + + shsurf = get_shell_surface(surface); + if (shsurf != NULL) + shell_surface_update_child_surface_layers(shsurf); + + replace_focus_state(shell, to, seat); + drop_focus_state(shell, from, surface); + + if (shell->workspaces.anim_from == to && + shell->workspaces.anim_to == from) { + reverse_workspace_change_animation(shell, index, from, to); + + 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 { + if (shsurf != NULL && + 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); + } + + state = ensure_focus_state(shell, seat); + if (state != NULL) + focus_state_set_focus(state, surface); +} + +static void +touch_move_grab_down(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id, wl_fixed_t x, wl_fixed_t y) +{ +} + +static void +touch_move_grab_up(struct weston_touch_grab *grab, const struct timespec *time, + int touch_id) +{ + struct weston_touch_move_grab *move = + (struct weston_touch_move_grab *) container_of( + grab, struct shell_touch_grab, grab); + + if (touch_id == 0) + move->active = 0; + + if (grab->touch->num_tp == 0) { + shell_touch_grab_end(&move->base); + free(move); + } +} + +static void +touch_move_grab_motion(struct weston_touch_grab *grab, + const struct timespec *time, int touch_id, + wl_fixed_t x, wl_fixed_t y) +{ + 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 || !move->active) + return; + + es = weston_desktop_surface_get_surface(shsurf->desktop_surface); + + weston_view_set_position(shsurf->view, dx, dy); + + weston_compositor_schedule_repaint(es->compositor); +} + +static void +touch_move_grab_frame(struct weston_touch_grab *grab) +{ +} + +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_frame, + touch_move_grab_cancel, +}; + +static int +surface_touch_move(struct shell_surface *shsurf, struct weston_touch *touch) +{ + struct weston_touch_move_grab *move; + + if (!shsurf) + return -1; + + if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return 0; + + move = malloc(sizeof *move); + if (!move) + return -1; + + move->active = 1; + move->dx = wl_fixed_from_double(shsurf->view->geometry.x) - + touch->grab_x; + move->dy = wl_fixed_from_double(shsurf->view->geometry.y) - + touch->grab_y; + + shell_touch_grab_start(&move->base, &touch_move_grab_interface, shsurf, + touch); + + return 0; +} + +static void +noop_grab_focus(struct weston_pointer_grab *grab) +{ +} + +static void +noop_grab_axis(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_axis_event *event) +{ +} + +static void +noop_grab_axis_source(struct weston_pointer_grab *grab, + uint32_t source) +{ +} + +static void +noop_grab_frame(struct weston_pointer_grab *grab) +{ +} + +static void +constrain_position(struct weston_move_grab *move, int *cx, int *cy) +{ + struct shell_surface *shsurf = move->base.shsurf; + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_pointer *pointer = move->base.grab.pointer; + int x, y, bottom; + const int safety = 50; + pixman_rectangle32_t area; + struct weston_geometry geometry; + + x = wl_fixed_to_int(pointer->x + move->dx); + y = wl_fixed_to_int(pointer->y + move->dy); + + if (shsurf->shell->panel_position == + WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP) { + get_output_work_area(shsurf->shell, surface->output, &area); + geometry = + weston_desktop_surface_get_geometry(shsurf->desktop_surface); + + bottom = y + geometry.height + geometry.y; + if (bottom - safety < area.y) + y = area.y + safety - geometry.height + - geometry.y; + + if (move->client_initiated && + y + geometry.y < area.y) + y = area.y - geometry.y; + } + + *cx = x; + *cy = y; +} + +static void +move_grab_motion(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + 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 *surface; + int cx, cy; + + weston_pointer_move(pointer, event); + if (!shsurf) + return; + + surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); + + constrain_position(move, &cx, &cy); + + weston_view_set_position(shsurf->view, cx, cy); + + weston_compositor_schedule_repaint(surface->compositor); +} + +static void +move_grab_button(struct weston_pointer_grab *grab, + const struct timespec *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, + noop_grab_axis, + noop_grab_axis_source, + noop_grab_frame, + move_grab_cancel, +}; + +static int +surface_move(struct shell_surface *shsurf, struct weston_pointer *pointer, + bool client_initiated) +{ + struct weston_move_grab *move; + + if (!shsurf) + return -1; + + if (shsurf->grabbed || + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return 0; + + move = malloc(sizeof *move); + if (!move) + return -1; + + move->dx = wl_fixed_from_double(shsurf->view->geometry.x) - + pointer->grab_x; + move->dy = wl_fixed_from_double(shsurf->view->geometry.y) - + pointer->grab_y; + move->client_initiated = client_initiated; + + shell_grab_start(&move->base, &move_grab_interface, shsurf, + pointer, WESTON_DESKTOP_SHELL_CURSOR_MOVE); + + return 0; +} + +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, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + 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; + struct weston_size min_size, max_size; + wl_fixed_t from_x, from_y; + wl_fixed_t to_x, to_y; + + weston_pointer_move(pointer, event); + + if (!shsurf) + return; + + weston_view_from_global_fixed(shsurf->view, + pointer->grab_x, pointer->grab_y, + &from_x, &from_y); + weston_view_from_global_fixed(shsurf->view, + 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); + } + + max_size = weston_desktop_surface_get_max_size(shsurf->desktop_surface); + min_size = weston_desktop_surface_get_min_size(shsurf->desktop_surface); + + min_size.width = MAX(1, min_size.width); + min_size.height = MAX(1, min_size.height); + + if (width < min_size.width) + width = min_size.width; + else if (max_size.width > 0 && width > max_size.width) + width = max_size.width; + if (height < min_size.height) + height = min_size.height; + else if (max_size.width > 0 && width > max_size.width) + width = max_size.width; + weston_desktop_surface_set_size(shsurf->desktop_surface, width, height); +} + +static void +resize_grab_button(struct weston_pointer_grab *grab, + const struct timespec *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) { + if (resize->base.shsurf != NULL) { + struct weston_desktop_surface *desktop_surface = + resize->base.shsurf->desktop_surface; + weston_desktop_surface_set_resizing(desktop_surface, + false); + } + + 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; + + if (resize->base.shsurf != NULL) { + struct weston_desktop_surface *desktop_surface = + resize->base.shsurf->desktop_surface; + weston_desktop_surface_set_resizing(desktop_surface, false); + } + + 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, + noop_grab_axis, + noop_grab_axis_source, + noop_grab_frame, + resize_grab_cancel, +}; + +/* + * Returns the bounding box of a surface and all its sub-surfaces, + * in surface-local coordinates. */ +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->width, + surface->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->width, + subsurface->surface->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_pointer *pointer, uint32_t edges) +{ + struct weston_resize_grab *resize; + const unsigned resize_topbottom = + WL_SHELL_SURFACE_RESIZE_TOP | WL_SHELL_SURFACE_RESIZE_BOTTOM; + const unsigned resize_leftright = + WL_SHELL_SURFACE_RESIZE_LEFT | WL_SHELL_SURFACE_RESIZE_RIGHT; + const unsigned resize_any = resize_topbottom | resize_leftright; + struct weston_geometry geometry; + + if (shsurf->grabbed || + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return 0; + + /* Check for invalid edge combinations. */ + if (edges == WL_SHELL_SURFACE_RESIZE_NONE || edges > resize_any || + (edges & resize_topbottom) == resize_topbottom || + (edges & resize_leftright) == resize_leftright) + return 0; + + resize = malloc(sizeof *resize); + if (!resize) + return -1; + + resize->edges = edges; + + geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); + resize->width = geometry.width; + resize->height = geometry.height; + + shsurf->resize_edges = edges; + weston_desktop_surface_set_resizing(shsurf->desktop_surface, true); + shell_grab_start(&resize->base, &resize_grab_interface, shsurf, + pointer, edges); + + return 0; +} + +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_desktop_surface *desktop_surface; + struct weston_view *view; + wl_fixed_t sx, sy; + + view = weston_compositor_pick_view(pointer->seat->compositor, + pointer->x, pointer->y, + &sx, &sy); + desktop_surface = weston_surface_get_desktop_surface(view->surface); + + if (!grab->shsurf || grab->shsurf->desktop_surface != desktop_surface) { + shell_grab_end(grab); + free(grab); + } +} + +static void +busy_cursor_grab_motion(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + weston_pointer_move(grab->pointer, event); +} + +static void +busy_cursor_grab_button(struct weston_pointer_grab *base, + const struct timespec *time, + uint32_t button, uint32_t state) +{ + struct shell_grab *grab = (struct shell_grab *) base; + struct shell_surface *shsurf = grab->shsurf; + struct weston_pointer *pointer = grab->grab.pointer; + struct weston_seat *seat = pointer->seat; + + if (shsurf && button == BTN_LEFT && state) { + activate(shsurf->shell, shsurf->view, seat, + WESTON_ACTIVATE_FLAG_CONFIGURE); + surface_move(shsurf, pointer, false); + } else if (shsurf && button == BTN_RIGHT && state) { + activate(shsurf->shell, shsurf->view, seat, + WESTON_ACTIVATE_FLAG_CONFIGURE); + surface_rotate(shsurf, pointer); + } +} + +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, + noop_grab_axis, + noop_grab_axis_source, + noop_grab_frame, + busy_cursor_grab_cancel, +}; + +static void +handle_pointer_focus(struct wl_listener *listener, void *data) +{ + struct weston_pointer *pointer = data; + struct weston_view *view = pointer->focus; + struct shell_surface *shsurf; + struct weston_desktop_client *client; + + if (!view) + return; + + shsurf = get_shell_surface(view->surface); + if (!shsurf) + return; + + client = weston_desktop_surface_get_client(shsurf->desktop_surface); + + if (shsurf->unresponsive) + set_busy_cursor(shsurf, pointer); + else + weston_desktop_client_ping(client); +} + +static void +shell_surface_lose_keyboard_focus(struct shell_surface *shsurf) +{ + if (--shsurf->focus_count == 0) + weston_desktop_surface_set_activated(shsurf->desktop_surface, false); +} + +static void +shell_surface_gain_keyboard_focus(struct shell_surface *shsurf) +{ + if (shsurf->focus_count++ == 0) + weston_desktop_surface_set_activated(shsurf->desktop_surface, true); +} + +static void +handle_keyboard_focus(struct wl_listener *listener, void *data) +{ + struct weston_keyboard *keyboard = data; + struct shell_seat *seat = get_shell_seat(keyboard->seat); + + if (seat->focused_surface) { + struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); + if (shsurf) + shell_surface_lose_keyboard_focus(shsurf); + } + + seat->focused_surface = weston_surface_get_main_surface(keyboard->focus); + + if (seat->focused_surface) { + struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); + if (shsurf) + shell_surface_gain_keyboard_focus(shsurf); + } +} + +/* The surface will be inserted into the list immediately after the link + * returned by this function (i.e. will be stacked immediately above the + * returned link). */ +static struct weston_layer_entry * +shell_surface_calculate_layer_link (struct shell_surface *shsurf) +{ + struct workspace *ws; + + if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) && + !shsurf->state.lowered) { + return &shsurf->shell->fullscreen_layer.view_list; + } + + /* Move the surface to a normal workspace layer so that surfaces + * which were previously fullscreen or transient are no longer + * rendered on top. */ + ws = get_current_workspace(shsurf->shell); + return &ws->layer.view_list; +} + +static void +shell_surface_update_child_surface_layers (struct shell_surface *shsurf) +{ + weston_desktop_surface_propagate_layer(shsurf->desktop_surface); +} + +/* Update the surface’s layer. Mark both the old and new views as having dirty + * geometry to ensure the changes are redrawn. + * + * If any child surfaces exist and are mapped, ensure they’re in the same layer + * as this surface. */ +static void +shell_surface_update_layer(struct shell_surface *shsurf) +{ + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_layer_entry *new_layer_link; + + new_layer_link = shell_surface_calculate_layer_link(shsurf); + + if (new_layer_link == NULL) + return; + if (new_layer_link == &shsurf->view->layer_link) + return; + + weston_view_geometry_dirty(shsurf->view); + weston_layer_entry_remove(&shsurf->view->layer_link); + weston_layer_entry_insert(new_layer_link, &shsurf->view->layer_link); + weston_view_geometry_dirty(shsurf->view); + weston_surface_damage(surface); + + shell_surface_update_child_surface_layers(shsurf); +} + +static void +notify_output_destroy(struct wl_listener *listener, void *data) +{ + struct shell_surface *shsurf = + container_of(listener, + struct shell_surface, output_destroy_listener); + + shsurf->output = NULL; + shsurf->output_destroy_listener.notify = NULL; +} + +static void +shell_surface_set_output(struct shell_surface *shsurf, + struct weston_output *output) +{ + struct weston_surface *es = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + + /* get the default output, if the client set it as NULL + check whether the output is available */ + if (output) + shsurf->output = output; + else if (es->output) + shsurf->output = es->output; + else + shsurf->output = get_default_output(es->compositor); + + if (shsurf->output_destroy_listener.notify) { + wl_list_remove(&shsurf->output_destroy_listener.link); + shsurf->output_destroy_listener.notify = NULL; + } + + if (!shsurf->output) + return; + + shsurf->output_destroy_listener.notify = notify_output_destroy; + wl_signal_add(&shsurf->output->destroy_signal, + &shsurf->output_destroy_listener); +} + +static void +weston_view_set_initial_position(struct weston_view *view, + struct desktop_shell *shell); + +static void +unset_fullscreen(struct shell_surface *shsurf) +{ + /* Unset the fullscreen output, driver configuration and transforms. */ + wl_list_remove(&shsurf->fullscreen.transform.link); + wl_list_init(&shsurf->fullscreen.transform.link); + + if (shsurf->fullscreen.black_view) + weston_surface_destroy(shsurf->fullscreen.black_view->surface); + shsurf->fullscreen.black_view = NULL; + + if (shsurf->saved_position_valid) + weston_view_set_position(shsurf->view, + shsurf->saved_x, shsurf->saved_y); + else + weston_view_set_initial_position(shsurf->view, shsurf->shell); + shsurf->saved_position_valid = false; + + if (shsurf->saved_rotation_valid) { + wl_list_insert(&shsurf->view->geometry.transformation_list, + &shsurf->rotation.transform.link); + shsurf->saved_rotation_valid = false; + } +} + +static void +unset_maximized(struct shell_surface *shsurf) +{ + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + + /* undo all maximized things here */ + shell_surface_set_output(shsurf, get_default_output(surface->compositor)); + + if (shsurf->saved_position_valid) + weston_view_set_position(shsurf->view, + shsurf->saved_x, shsurf->saved_y); + else + weston_view_set_initial_position(shsurf->view, shsurf->shell); + shsurf->saved_position_valid = false; + + if (shsurf->saved_rotation_valid) { + wl_list_insert(&shsurf->view->geometry.transformation_list, + &shsurf->rotation.transform.link); + shsurf->saved_rotation_valid = false; + } +} + +static void +set_minimized(struct weston_surface *surface) +{ + struct shell_surface *shsurf; + struct workspace *current_ws; + struct weston_view *view; + + view = get_default_view(surface); + if (!view) + return; + + assert(weston_surface_get_main_surface(view->surface) == view->surface); + + shsurf = get_shell_surface(surface); + current_ws = get_current_workspace(shsurf->shell); + + weston_layer_entry_remove(&view->layer_link); + weston_layer_entry_insert(&shsurf->shell->minimized_layer.view_list, &view->layer_link); + + drop_focus_state(shsurf->shell, current_ws, view->surface); + surface_keyboard_focus_lost(surface); + + shell_surface_update_child_surface_layers(shsurf); + weston_view_damage_below(view); +} + + +static struct desktop_shell * +shell_surface_get_shell(struct shell_surface *shsurf) +{ + return shsurf->shell; +} + +static int +black_surface_get_label(struct weston_surface *surface, char *buf, size_t len) +{ + struct weston_view *fs_view = surface->committed_private; + struct weston_surface *fs_surface = fs_view->surface; + int n; + int rem; + int ret; + + n = snprintf(buf, len, "black background surface for "); + if (n < 0) + return n; + + rem = (int)len - n; + if (rem < 0) + rem = 0; + + if (fs_surface->get_label) + ret = fs_surface->get_label(fs_surface, buf + n, rem); + else + ret = snprintf(buf + n, rem, ""); + + if (ret < 0) + return n; + + return n + ret; +} + +static void +black_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy); + +static struct weston_view * +create_black_surface(struct weston_compositor *ec, + struct weston_view *fs_view, + float x, float y, int w, int h) +{ + struct weston_surface *surface = NULL; + struct weston_view *view; + + surface = weston_surface_create(ec); + if (surface == NULL) { + weston_log("no memory\n"); + return NULL; + } + view = weston_view_create(surface); + if (surface == NULL) { + weston_log("no memory\n"); + weston_surface_destroy(surface); + return NULL; + } + + surface->committed = black_surface_committed; + surface->committed_private = fs_view; + weston_surface_set_label_func(surface, black_surface_get_label); + 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); + + weston_surface_set_size(surface, w, h); + weston_view_set_position(view, x, y); + + return view; +} + +static void +shell_ensure_fullscreen_black_view(struct shell_surface *shsurf) +{ + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_output *output = shsurf->fullscreen_output; + + assert(weston_desktop_surface_get_fullscreen(shsurf->desktop_surface)); + + if (!shsurf->fullscreen.black_view) + shsurf->fullscreen.black_view = + create_black_surface(surface->compositor, + shsurf->view, + output->x, output->y, + output->width, + output->height); + + weston_view_geometry_dirty(shsurf->fullscreen.black_view); + weston_layer_entry_remove(&shsurf->fullscreen.black_view->layer_link); + weston_layer_entry_insert(&shsurf->view->layer_link, + &shsurf->fullscreen.black_view->layer_link); + weston_view_geometry_dirty(shsurf->fullscreen.black_view); + weston_surface_damage(surface); + + shsurf->fullscreen.black_view->is_mapped = true; + shsurf->state.lowered = false; +} + +/* 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_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + int32_t surf_x, surf_y, surf_width, surf_height; + + /* Reverse the effect of lower_fullscreen_layer() */ + weston_layer_entry_remove(&shsurf->view->layer_link); + weston_layer_entry_insert(&shsurf->shell->fullscreen_layer.view_list, + &shsurf->view->layer_link); + + if (!shsurf->fullscreen_output) { + /* If there is no output, there's not much we can do. + * Position the window somewhere, whatever. */ + weston_view_set_position(shsurf->view, 0, 0); + return; + } + + shell_ensure_fullscreen_black_view(shsurf); + + surface_subsurfaces_boundingbox(surface, &surf_x, &surf_y, + &surf_width, &surf_height); + + if (surface->buffer_ref.buffer) + center_on_output(shsurf->view, shsurf->fullscreen_output); +} + +static void +shell_map_fullscreen(struct shell_surface *shsurf) +{ + shell_configure_fullscreen(shsurf); +} + +static struct weston_output * +get_focused_output(struct weston_compositor *compositor) +{ + struct weston_seat *seat; + struct weston_output *output = NULL; + + wl_list_for_each(seat, &compositor->seat_list, link) { + struct weston_touch *touch = weston_seat_get_touch(seat); + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(seat); + + /* Priority has touch focus, then pointer and + * then keyboard focus. We should probably have + * three for loops and check first for touch, + * then for pointer, etc. but unless somebody has some + * objections, I think this is sufficient. */ + if (touch && touch->focus) + output = touch->focus->output; + else if (pointer && pointer->focus) + output = pointer->focus->output; + else if (keyboard && keyboard->focus) + output = keyboard->focus->output; + + if (output) + break; + } + + return output; +} + +static void +destroy_shell_seat(struct wl_listener *listener, void *data) +{ + struct shell_seat *shseat = + container_of(listener, + struct shell_seat, seat_destroy_listener); + + wl_list_remove(&shseat->seat_destroy_listener.link); + free(shseat); +} + +static void +shell_seat_caps_changed(struct wl_listener *listener, void *data) +{ + struct weston_keyboard *keyboard; + struct weston_pointer *pointer; + struct shell_seat *seat; + + seat = container_of(listener, struct shell_seat, caps_changed_listener); + keyboard = weston_seat_get_keyboard(seat->seat); + pointer = weston_seat_get_pointer(seat->seat); + + if (keyboard && + wl_list_empty(&seat->keyboard_focus_listener.link)) { + wl_signal_add(&keyboard->focus_signal, + &seat->keyboard_focus_listener); + } else if (!keyboard) { + wl_list_remove(&seat->keyboard_focus_listener.link); + wl_list_init(&seat->keyboard_focus_listener.link); + } + + if (pointer && + wl_list_empty(&seat->pointer_focus_listener.link)) { + wl_signal_add(&pointer->focus_signal, + &seat->pointer_focus_listener); + } else if (!pointer) { + wl_list_remove(&seat->pointer_focus_listener.link); + wl_list_init(&seat->pointer_focus_listener.link); + } +} + +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; + + shseat->seat_destroy_listener.notify = destroy_shell_seat; + wl_signal_add(&seat->destroy_signal, + &shseat->seat_destroy_listener); + + shseat->keyboard_focus_listener.notify = handle_keyboard_focus; + wl_list_init(&shseat->keyboard_focus_listener.link); + + shseat->pointer_focus_listener.notify = handle_pointer_focus; + wl_list_init(&shseat->pointer_focus_listener.link); + + shseat->caps_changed_listener.notify = shell_seat_caps_changed; + wl_signal_add(&seat->updated_caps_signal, + &shseat->caps_changed_listener); + shell_seat_caps_changed(&shseat->caps_changed_listener, NULL); + + 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); + assert(listener != NULL); + + return container_of(listener, + struct shell_seat, seat_destroy_listener); +} + +static void +fade_out_done_idle_cb(void *data) +{ + struct shell_surface *shsurf = data; + + weston_surface_destroy(shsurf->view->surface); + + if (shsurf->output_destroy_listener.notify) { + wl_list_remove(&shsurf->output_destroy_listener.link); + shsurf->output_destroy_listener.notify = NULL; + } + + free(shsurf); +} + +static void +fade_out_done(struct weston_view_animation *animation, void *data) +{ + struct shell_surface *shsurf = data; + struct wl_event_loop *loop; + + loop = wl_display_get_event_loop(shsurf->shell->compositor->wl_display); + + if (weston_view_is_mapped(shsurf->view)) { + weston_view_unmap(shsurf->view); + wl_event_loop_add_idle(loop, fade_out_done_idle_cb, shsurf); + } +} + +struct shell_surface * +get_shell_surface(struct weston_surface *surface) +{ + if (weston_surface_is_desktop_surface(surface)) { + struct weston_desktop_surface *desktop_surface = + weston_surface_get_desktop_surface(surface); + return weston_desktop_surface_get_user_data(desktop_surface); + } + return NULL; +} + +/* + * libweston-desktop + */ + +static void +desktop_surface_added(struct weston_desktop_surface *desktop_surface, + void *shell) +{ + struct weston_desktop_client *client = + weston_desktop_surface_get_client(desktop_surface); + struct wl_client *wl_client = + weston_desktop_client_get_client(client); + struct weston_view *view; + struct shell_surface *shsurf; + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + + view = weston_desktop_surface_create_view(desktop_surface); + if (!view) + return; + + shsurf = calloc(1, sizeof *shsurf); + if (!shsurf) { + if (wl_client) + wl_client_post_no_memory(wl_client); + else + weston_log("no memory to allocate shell surface\n"); + return; + } + + weston_surface_set_label_func(surface, shell_surface_get_label); + + shsurf->shell = (struct desktop_shell *) shell; + shsurf->unresponsive = 0; + shsurf->saved_position_valid = false; + shsurf->saved_rotation_valid = false; + shsurf->desktop_surface = desktop_surface; + shsurf->view = view; + shsurf->fullscreen.black_view = NULL; + wl_list_init(&shsurf->fullscreen.transform.link); + + shell_surface_set_output( + shsurf, get_default_output(shsurf->shell->compositor)); + + wl_signal_init(&shsurf->destroy_signal); + + /* 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); + + /* + * initialize list as well as link. The latter allows to use + * wl_list_remove() even when this surface is not in another list. + */ + wl_list_init(&shsurf->children_list); + wl_list_init(&shsurf->children_link); + + weston_desktop_surface_set_user_data(desktop_surface, shsurf); + weston_desktop_surface_set_activated(desktop_surface, + shsurf->focus_count > 0); +} + +static void +desktop_surface_removed(struct weston_desktop_surface *desktop_surface, + void *shell) +{ + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct shell_surface *shsurf_child, *tmp; + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + + if (!shsurf) + return; + + wl_list_for_each_safe(shsurf_child, tmp, &shsurf->children_list, children_link) { + wl_list_remove(&shsurf_child->children_link); + wl_list_init(&shsurf_child->children_link); + } + wl_list_remove(&shsurf->children_link); + + wl_signal_emit(&shsurf->destroy_signal, shsurf); + + if (shsurf->fullscreen.black_view) + weston_surface_destroy(shsurf->fullscreen.black_view->surface); + + weston_surface_set_label_func(surface, NULL); + weston_desktop_surface_set_user_data(shsurf->desktop_surface, NULL); + shsurf->desktop_surface = NULL; + + weston_desktop_surface_unlink_view(shsurf->view); + if (weston_surface_is_mapped(surface) && + shsurf->shell->win_close_animation_type == ANIMATION_FADE) { + pixman_region32_fini(&surface->pending.input); + pixman_region32_init(&surface->pending.input); + pixman_region32_fini(&surface->input); + pixman_region32_init(&surface->input); + weston_fade_run(shsurf->view, 1.0, 0.0, 300.0, + fade_out_done, shsurf); + } else { + weston_view_destroy(shsurf->view); + + if (shsurf->output_destroy_listener.notify) { + wl_list_remove(&shsurf->output_destroy_listener.link); + shsurf->output_destroy_listener.notify = NULL; + } + + free(shsurf); + } +} + +static void +set_maximized_position(struct desktop_shell *shell, + struct shell_surface *shsurf) +{ + pixman_rectangle32_t area; + struct weston_geometry geometry; + + get_output_work_area(shell, shsurf->output, &area); + geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); + + weston_view_set_position(shsurf->view, + area.x - geometry.x, + area.y - geometry.y); +} + +static void +set_position_from_xwayland(struct shell_surface *shsurf) +{ + struct weston_geometry geometry; + float x; + float y; + + assert(shsurf->xwayland.is_set); + + geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); + x = shsurf->xwayland.x - geometry.x; + y = shsurf->xwayland.y - geometry.y; + + weston_view_set_position(shsurf->view, x, y); + +#ifdef WM_DEBUG + weston_log("%s: XWM %d, %d; geometry %d, %d; view %f, %f\n", + __func__, shsurf->xwayland.x, shsurf->xwayland.y, + geometry.x, geometry.y, x, y); +#endif +} + +static void +map(struct desktop_shell *shell, struct shell_surface *shsurf, + int32_t sx, int32_t sy) +{ + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_compositor *compositor = shell->compositor; + struct weston_seat *seat; + + /* initial positioning, see also configure() */ + if (shsurf->state.fullscreen) { + center_on_output(shsurf->view, shsurf->fullscreen_output); + shell_map_fullscreen(shsurf); + } else if (shsurf->state.maximized) { + set_maximized_position(shell, shsurf); + } else if (shsurf->xwayland.is_set) { + set_position_from_xwayland(shsurf); + } else { + weston_view_set_initial_position(shsurf->view, shell); + } + + /* Surface stacking order, see also activate(). */ + shell_surface_update_layer(shsurf); + + weston_view_update_transform(shsurf->view); + shsurf->view->is_mapped = true; + if (shsurf->state.maximized) { + surface->output = shsurf->output; + weston_view_set_output(shsurf->view, shsurf->output); + } + + if (!shell->locked) { + wl_list_for_each(seat, &compositor->seat_list, link) + activate(shell, shsurf->view, seat, + WESTON_ACTIVATE_FLAG_CONFIGURE); + } + + if (!shsurf->state.fullscreen && !shsurf->state.maximized) { + switch (shell->win_animation_type) { + case ANIMATION_FADE: + weston_fade_run(shsurf->view, 0.0, 1.0, 300.0, NULL, NULL); + break; + case ANIMATION_ZOOM: + weston_zoom_run(shsurf->view, 0.5, 1.0, NULL, NULL); + break; + case ANIMATION_NONE: + default: + break; + } + } +} + +static void +desktop_surface_committed(struct weston_desktop_surface *desktop_surface, + int32_t sx, int32_t sy, void *data) +{ + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + struct weston_view *view = shsurf->view; + struct desktop_shell *shell = data; + bool was_fullscreen; + bool was_maximized; + + if (surface->width == 0) + return; + + was_fullscreen = shsurf->state.fullscreen; + was_maximized = shsurf->state.maximized; + + shsurf->state.fullscreen = + weston_desktop_surface_get_fullscreen(desktop_surface); + shsurf->state.maximized = + weston_desktop_surface_get_maximized(desktop_surface); + + if (!weston_surface_is_mapped(surface)) { + map(shell, shsurf, sx, sy); + surface->is_mapped = true; + if (shsurf->shell->win_close_animation_type == ANIMATION_FADE) + ++surface->ref_count; + return; + } + + if (sx == 0 && sy == 0 && + shsurf->last_width == surface->width && + shsurf->last_height == surface->height && + was_fullscreen == shsurf->state.fullscreen && + was_maximized == shsurf->state.maximized) + return; + + if (was_fullscreen) + unset_fullscreen(shsurf); + if (was_maximized) + unset_maximized(shsurf); + + if ((shsurf->state.fullscreen || shsurf->state.maximized) && + !shsurf->saved_position_valid) { + shsurf->saved_x = shsurf->view->geometry.x; + shsurf->saved_y = shsurf->view->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_view_geometry_dirty(shsurf->view); + shsurf->saved_rotation_valid = true; + } + } + + if (shsurf->state.fullscreen) { + shell_configure_fullscreen(shsurf); + } else if (shsurf->state.maximized) { + set_maximized_position(shell, shsurf); + surface->output = shsurf->output; + } else { + float from_x, from_y; + float to_x, to_y; + float x, y; + + if (shsurf->resize_edges) { + sx = 0; + sy = 0; + } + + if (shsurf->resize_edges & WL_SHELL_SURFACE_RESIZE_LEFT) + sx = shsurf->last_width - surface->width; + if (shsurf->resize_edges & WL_SHELL_SURFACE_RESIZE_TOP) + sy = shsurf->last_height - surface->height; + + weston_view_to_global_float(shsurf->view, 0, 0, &from_x, &from_y); + weston_view_to_global_float(shsurf->view, sx, sy, &to_x, &to_y); + x = shsurf->view->geometry.x + to_x - from_x; + y = shsurf->view->geometry.y + to_y - from_y; + + weston_view_set_position(shsurf->view, x, y); + } + + shsurf->last_width = surface->width; + shsurf->last_height = surface->height; + + /* XXX: would a fullscreen surface need the same handling? */ + if (surface->output) { + wl_list_for_each(view, &surface->views, surface_link) + weston_view_update_transform(view); + } +} + +static void +get_maximized_size(struct shell_surface *shsurf, int32_t *width, int32_t *height) +{ + struct desktop_shell *shell; + pixman_rectangle32_t area; + + shell = shell_surface_get_shell(shsurf); + get_output_work_area(shell, shsurf->output, &area); + + *width = area.width; + *height = area.height; +} + +static void +set_fullscreen(struct shell_surface *shsurf, bool fullscreen, + struct weston_output *output) +{ + struct weston_desktop_surface *desktop_surface = shsurf->desktop_surface; + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + int32_t width = 0, height = 0; + + if (fullscreen) { + /* handle clients launching in fullscreen */ + if (output == NULL && !weston_surface_is_mapped(surface)) { + /* Set the output to the one that has focus currently. */ + output = get_focused_output(surface->compositor); + } + + shell_surface_set_output(shsurf, output); + shsurf->fullscreen_output = shsurf->output; + + if (shsurf->output) { + width = shsurf->output->width; + height = shsurf->output->height; + } + } else if (weston_desktop_surface_get_maximized(desktop_surface)) { + get_maximized_size(shsurf, &width, &height); + } + weston_desktop_surface_set_fullscreen(desktop_surface, fullscreen); + weston_desktop_surface_set_size(desktop_surface, width, height); +} + +static void +desktop_surface_move(struct weston_desktop_surface *desktop_surface, + struct weston_seat *seat, uint32_t serial, void *shell) +{ + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_touch *touch = weston_seat_get_touch(seat); + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct wl_resource *resource = surface->resource; + struct weston_surface *focus; + + if (pointer && + pointer->focus && + pointer->button_count > 0 && + pointer->grab_serial == serial) { + focus = weston_surface_get_main_surface(pointer->focus->surface); + if ((focus == surface) && + (surface_move(shsurf, pointer, true) < 0)) + wl_resource_post_no_memory(resource); + } else if (touch && + touch->focus && + touch->grab_serial == serial) { + focus = weston_surface_get_main_surface(touch->focus->surface); + if ((focus == surface) && + (surface_touch_move(shsurf, touch) < 0)) + wl_resource_post_no_memory(resource); + } +} + +static void +desktop_surface_resize(struct weston_desktop_surface *desktop_surface, + struct weston_seat *seat, uint32_t serial, + enum weston_desktop_surface_edge edges, void *shell) +{ + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct wl_resource *resource = surface->resource; + struct weston_surface *focus; + + if (!pointer || + pointer->button_count == 0 || + pointer->grab_serial != serial || + pointer->focus == NULL) + return; + + focus = weston_surface_get_main_surface(pointer->focus->surface); + if (focus != surface) + return; + + if (surface_resize(shsurf, pointer, edges) < 0) + wl_resource_post_no_memory(resource); +} + +static void +desktop_surface_set_parent(struct weston_desktop_surface *desktop_surface, + struct weston_desktop_surface *parent, + void *shell) +{ + struct shell_surface *shsurf_parent; + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + /* unlink any potential child */ + wl_list_remove(&shsurf->children_link); + + if (parent) { + shsurf_parent = weston_desktop_surface_get_user_data(parent); + wl_list_insert(shsurf_parent->children_list.prev, + &shsurf->children_link); + } else { + wl_list_init(&shsurf->children_link); + } +} + +static void +desktop_surface_fullscreen_requested(struct weston_desktop_surface *desktop_surface, + bool fullscreen, + struct weston_output *output, void *shell) +{ + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + set_fullscreen(shsurf, fullscreen, output); +} + +static void +set_maximized(struct shell_surface *shsurf, bool maximized) +{ + struct weston_desktop_surface *desktop_surface = shsurf->desktop_surface; + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + int32_t width = 0, height = 0; + + if (maximized) { + struct weston_output *output; + + if (!weston_surface_is_mapped(surface)) + output = get_focused_output(surface->compositor); + else + output = surface->output; + + shell_surface_set_output(shsurf, output); + + get_maximized_size(shsurf, &width, &height); + } + weston_desktop_surface_set_maximized(desktop_surface, maximized); + weston_desktop_surface_set_size(desktop_surface, width, height); +} + +static void +desktop_surface_maximized_requested(struct weston_desktop_surface *desktop_surface, + bool maximized, void *shell) +{ + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + set_maximized(shsurf, maximized); +} + +static void +desktop_surface_minimized_requested(struct weston_desktop_surface *desktop_surface, + void *shell) +{ + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + + /* apply compositor's own minimization logic (hide) */ + set_minimized(surface); +} + +static void +set_busy_cursor(struct shell_surface *shsurf, struct weston_pointer *pointer) +{ + struct shell_grab *grab; + + if (pointer->grab->interface == &busy_cursor_grab_interface) + return; + + grab = malloc(sizeof *grab); + if (!grab) + return; + + shell_grab_start(grab, &busy_cursor_grab_interface, shsurf, pointer, + WESTON_DESKTOP_SHELL_CURSOR_BUSY); + /* Mark the shsurf as ungrabbed so that button binding is able + * to move it. */ + shsurf->grabbed = 0; +} + +static void +end_busy_cursor(struct weston_compositor *compositor, + struct weston_desktop_client *desktop_client) +{ + struct shell_surface *shsurf; + struct shell_grab *grab; + struct weston_seat *seat; + + wl_list_for_each(seat, &compositor->seat_list, link) { + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_desktop_client *grab_client; + + if (!pointer) + continue; + + if (pointer->grab->interface != &busy_cursor_grab_interface) + continue; + + grab = (struct shell_grab *) pointer->grab; + shsurf = grab->shsurf; + if (!shsurf) + continue; + + grab_client = + weston_desktop_surface_get_client(shsurf->desktop_surface); + if (grab_client == desktop_client) { + shell_grab_end(grab); + free(grab); + } + } +} + +static void +desktop_surface_set_unresponsive(struct weston_desktop_surface *desktop_surface, + void *user_data) +{ + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + bool *unresponsive = user_data; + + shsurf->unresponsive = *unresponsive; +} + +static void +desktop_surface_ping_timeout(struct weston_desktop_client *desktop_client, + void *shell_) +{ + struct desktop_shell *shell = shell_; + struct shell_surface *shsurf; + struct weston_seat *seat; + bool unresponsive = true; + + weston_desktop_client_for_each_surface(desktop_client, + desktop_surface_set_unresponsive, + &unresponsive); + + + wl_list_for_each(seat, &shell->compositor->seat_list, link) { + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_desktop_client *grab_client; + + if (!pointer || !pointer->focus) + continue; + + shsurf = get_shell_surface(pointer->focus->surface); + if (!shsurf) + continue; + + grab_client = + weston_desktop_surface_get_client(shsurf->desktop_surface); + if (grab_client == desktop_client) + set_busy_cursor(shsurf, pointer); + } +} + +static void +desktop_surface_pong(struct weston_desktop_client *desktop_client, + void *shell_) +{ + struct desktop_shell *shell = shell_; + bool unresponsive = false; + + weston_desktop_client_for_each_surface(desktop_client, + desktop_surface_set_unresponsive, + &unresponsive); + end_busy_cursor(shell->compositor, desktop_client); +} + +static void +desktop_surface_set_xwayland_position(struct weston_desktop_surface *surface, + int32_t x, int32_t y, void *shell_) +{ + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(surface); + + shsurf->xwayland.x = x; + shsurf->xwayland.y = y; + shsurf->xwayland.is_set = true; +} + +static const struct weston_desktop_api shell_desktop_api = { + .struct_size = sizeof(struct weston_desktop_api), + .surface_added = desktop_surface_added, + .surface_removed = desktop_surface_removed, + .committed = desktop_surface_committed, + .move = desktop_surface_move, + .resize = desktop_surface_resize, + .set_parent = desktop_surface_set_parent, + .fullscreen_requested = desktop_surface_fullscreen_requested, + .maximized_requested = desktop_surface_maximized_requested, + .minimized_requested = desktop_surface_minimized_requested, + .ping_timeout = desktop_surface_ping_timeout, + .pong = desktop_surface_pong, + .set_xwayland_position = desktop_surface_set_xwayland_position, +}; + +/* ************************ * + * end of libweston-desktop * + * ************************ */ +static void +configure_static_view(struct weston_view *ev, struct weston_layer *layer, int x, int y) +{ + struct weston_view *v, *next; + + if (!ev->output) + return; + + wl_list_for_each_safe(v, next, &layer->view_list.link, layer_link.link) { + if (v->output == ev->output && v != ev) { + weston_view_unmap(v); + v->surface->committed = NULL; + weston_surface_set_label_func(v->surface, NULL); + } + } + + weston_view_set_position(ev, ev->output->x + x, ev->output->y + y); + ev->surface->is_mapped = true; + ev->is_mapped = true; + + if (wl_list_empty(&ev->layer_link.link)) { + weston_layer_entry_insert(&layer->view_list, &ev->layer_link); + weston_compositor_schedule_repaint(ev->surface->compositor); + } +} + + +static struct shell_output * +find_shell_output_from_weston_output(struct desktop_shell *shell, + struct weston_output *output) +{ + struct shell_output *shell_output; + + wl_list_for_each(shell_output, &shell->output_list, link) { + if (shell_output->output == output) + return shell_output; + } + + return NULL; +} + +static int +background_get_label(struct weston_surface *surface, char *buf, size_t len) +{ + return snprintf(buf, len, "background for output %s", + (surface->output ? surface->output->name : "NULL")); +} + +static void +background_committed(struct weston_surface *es, int32_t sx, int32_t sy) +{ + struct desktop_shell *shell = es->committed_private; + struct weston_view *view; + + view = container_of(es->views.next, struct weston_view, surface_link); + + configure_static_view(view, &shell->background_layer, 0, 0); +} + +static void +handle_background_surface_destroy(struct wl_listener *listener, void *data) +{ + struct shell_output *output = + container_of(listener, struct shell_output, background_surface_listener); + + weston_log("background surface gone\n"); + wl_list_remove(&output->background_surface_listener.link); + output->background_surface = NULL; +} + +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); + struct shell_output *sh_output; + struct weston_view *view, *next; + + if (surface->committed) { + wl_resource_post_error(surface_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "surface role already assigned"); + return; + } + + wl_list_for_each_safe(view, next, &surface->views, surface_link) + weston_view_destroy(view); + view = weston_view_create(surface); + + surface->committed = background_committed; + surface->committed_private = shell; + weston_surface_set_label_func(surface, background_get_label); + surface->output = weston_head_from_resource(output_resource)->output; + weston_view_set_output(view, surface->output); + + sh_output = find_shell_output_from_weston_output(shell, surface->output); + if (sh_output->background_surface) { + /* The output already has a background, tell our helper + * there is no need for another one. */ + weston_desktop_shell_send_configure(resource, 0, + surface_resource, + 0, 0); + } else { + weston_desktop_shell_send_configure(resource, 0, + surface_resource, + surface->output->width, + surface->output->height); + + sh_output->background_surface = surface; + + sh_output->background_surface_listener.notify = + handle_background_surface_destroy; + wl_signal_add(&surface->destroy_signal, + &sh_output->background_surface_listener); + } +} + +static int +panel_get_label(struct weston_surface *surface, char *buf, size_t len) +{ + return snprintf(buf, len, "panel for output %s", + (surface->output ? surface->output->name : "NULL")); +} + +static void +panel_committed(struct weston_surface *es, int32_t sx, int32_t sy) +{ + struct desktop_shell *shell = es->committed_private; + struct weston_view *view; + int width, height; + int x = 0, y = 0; + + view = container_of(es->views.next, struct weston_view, surface_link); + + get_panel_size(shell, view, &width, &height); + switch (shell->panel_position) { + case WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP: + break; + case WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM: + y = view->output->height - height; + break; + case WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT: + break; + case WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT: + x = view->output->width - width; + break; + } + + configure_static_view(view, &shell->panel_layer, x, y); +} + +static void +handle_panel_surface_destroy(struct wl_listener *listener, void *data) +{ + struct shell_output *output = + container_of(listener, struct shell_output, panel_surface_listener); + + weston_log("panel surface gone\n"); + wl_list_remove(&output->panel_surface_listener.link); + output->panel_surface = NULL; +} + + +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); + struct weston_view *view, *next; + struct shell_output *sh_output; + + if (surface->committed) { + wl_resource_post_error(surface_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "surface role already assigned"); + return; + } + + wl_list_for_each_safe(view, next, &surface->views, surface_link) + weston_view_destroy(view); + view = weston_view_create(surface); + + surface->committed = panel_committed; + surface->committed_private = shell; + weston_surface_set_label_func(surface, panel_get_label); + surface->output = weston_head_from_resource(output_resource)->output; + weston_view_set_output(view, surface->output); + + sh_output = find_shell_output_from_weston_output(shell, surface->output); + if (sh_output->panel_surface) { + /* The output already has a panel, tell our helper + * there is no need for another one. */ + weston_desktop_shell_send_configure(resource, 0, + surface_resource, + 0, 0); + } else { + weston_desktop_shell_send_configure(resource, 0, + surface_resource, + surface->output->width, + surface->output->height); + + sh_output->panel_surface = surface; + + sh_output->panel_surface_listener.notify = handle_panel_surface_destroy; + wl_signal_add(&surface->destroy_signal, &sh_output->panel_surface_listener); + } +} + +static int +lock_surface_get_label(struct weston_surface *surface, char *buf, size_t len) +{ + return snprintf(buf, len, "lock window"); +} + +static void +lock_surface_committed(struct weston_surface *surface, int32_t sx, int32_t sy) +{ + struct desktop_shell *shell = surface->committed_private; + struct weston_view *view; + + view = container_of(surface->views.next, struct weston_view, surface_link); + + if (surface->width == 0) + return; + + center_on_output(view, get_default_output(shell->compositor)); + + if (!weston_surface_is_mapped(surface)) { + weston_layer_entry_insert(&shell->lock_layer.view_list, + &view->layer_link); + weston_view_update_transform(view); + surface->is_mapped = true; + view->is_mapped = true; + 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); + + weston_view_create(surface); + surface->committed = lock_surface_committed; + surface->committed_private = shell; + weston_surface_set_label_func(surface, lock_surface_get_label); +} + +static void +resume_desktop(struct desktop_shell *shell) +{ + struct workspace *ws = get_current_workspace(shell); + + weston_layer_unset_position(&shell->lock_layer); + + if (shell->showing_input_panels) + weston_layer_set_position(&shell->input_panel_layer, + WESTON_LAYER_POSITION_TOP_UI); + weston_layer_set_position(&shell->fullscreen_layer, + WESTON_LAYER_POSITION_FULLSCREEN); + weston_layer_set_position(&shell->panel_layer, + WESTON_LAYER_POSITION_UI); + weston_layer_set_position(&ws->layer, WESTON_LAYER_POSITION_NORMAL); + + 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); + weston_view_create(shell->grab_surface); +} + +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 void +desktop_shell_set_panel_position(struct wl_client *client, + struct wl_resource *resource, + uint32_t position) +{ + struct desktop_shell *shell = wl_resource_get_user_data(resource); + + if (position != WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP && + position != WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM && + position != WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT && + position != WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT) { + wl_resource_post_error(resource, + WESTON_DESKTOP_SHELL_ERROR_INVALID_ARGUMENT, + "bad position argument"); + return; + } + + shell->panel_position = position; +} + +static const struct weston_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, + desktop_shell_set_panel_position +}; + +static void +move_binding(struct weston_pointer *pointer, const struct timespec *time, + uint32_t button, void *data) +{ + struct weston_surface *focus; + struct weston_surface *surface; + struct shell_surface *shsurf; + + if (pointer->focus == NULL) + return; + + focus = pointer->focus->surface; + + surface = weston_surface_get_main_surface(focus); + if (surface == NULL) + return; + + shsurf = get_shell_surface(surface); + if (shsurf == NULL || + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return; + + surface_move(shsurf, pointer, false); +} + +static void +maximize_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t button, void *data) +{ + struct weston_surface *focus = keyboard->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) + return; + + set_maximized(shsurf, !weston_desktop_surface_get_maximized(shsurf->desktop_surface)); +} + +static void +fullscreen_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t button, void *data) +{ + struct weston_surface *focus = keyboard->focus; + struct weston_surface *surface; + struct shell_surface *shsurf; + bool fullscreen; + + surface = weston_surface_get_main_surface(focus); + if (surface == NULL) + return; + + shsurf = get_shell_surface(surface); + if (shsurf == NULL) + return; + + fullscreen = + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface); + + set_fullscreen(shsurf, !fullscreen, NULL); +} + +static void +touch_move_binding(struct weston_touch *touch, const struct timespec *time, void *data) +{ + struct weston_surface *focus; + struct weston_surface *surface; + struct shell_surface *shsurf; + + if (touch->focus == NULL) + return; + + focus = touch->focus->surface; + surface = weston_surface_get_main_surface(focus); + if (surface == NULL) + return; + + shsurf = get_shell_surface(surface); + if (shsurf == NULL || + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return; + + surface_touch_move(shsurf, touch); +} + +static void +resize_binding(struct weston_pointer *pointer, const struct timespec *time, + uint32_t button, void *data) +{ + struct weston_surface *focus; + struct weston_surface *surface; + uint32_t edges = 0; + int32_t x, y; + struct shell_surface *shsurf; + + if (pointer->focus == NULL) + return; + + focus = pointer->focus->surface; + + surface = weston_surface_get_main_surface(focus); + if (surface == NULL) + return; + + shsurf = get_shell_surface(surface); + if (shsurf == NULL || + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return; + + weston_view_from_global(shsurf->view, + wl_fixed_to_int(pointer->grab_x), + wl_fixed_to_int(pointer->grab_y), + &x, &y); + + if (x < surface->width / 3) + edges |= WL_SHELL_SURFACE_RESIZE_LEFT; + else if (x < 2 * surface->width / 3) + edges |= 0; + else + edges |= WL_SHELL_SURFACE_RESIZE_RIGHT; + + if (y < surface->height / 3) + edges |= WL_SHELL_SURFACE_RESIZE_TOP; + else if (y < 2 * surface->height / 3) + edges |= 0; + else + edges |= WL_SHELL_SURFACE_RESIZE_BOTTOM; + + surface_resize(shsurf, pointer, edges); +} + +static void +surface_opacity_binding(struct weston_pointer *pointer, + const struct timespec *time, + struct weston_pointer_axis_event *event, + void *data) +{ + float step = 0.005; + struct shell_surface *shsurf; + struct weston_surface *focus = pointer->focus->surface; + 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; + + shsurf->view->alpha -= event->value * step; + + if (shsurf->view->alpha > 1.0) + shsurf->view->alpha = 1.0; + if (shsurf->view->alpha < step) + shsurf->view->alpha = step; + + weston_view_geometry_dirty(shsurf->view); + weston_surface_damage(surface); +} + +static void +do_zoom(struct weston_seat *seat, const struct timespec *time, uint32_t key, + uint32_t axis, double value) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_output *output; + float increment; + + if (!pointer) { + weston_log("Zoom hotkey pressed but seat '%s' contains no pointer.\n", seat->seat_name); + return; + } + + wl_list_for_each(output, &compositor->output_list, link) { + if (pixman_region32_contains_point(&output->region, + wl_fixed_to_double(pointer->x), + wl_fixed_to_double(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 * + -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; + + if (!output->zoom.active) { + if (output->zoom.level <= 0.0) + continue; + weston_output_activate_zoom(output, seat); + } + + output->zoom.spring_z.target = output->zoom.level; + + weston_output_update_zoom(output); + } + } +} + +static void +zoom_axis_binding(struct weston_pointer *pointer, const struct timespec *time, + struct weston_pointer_axis_event *event, + void *data) +{ + do_zoom(pointer->seat, time, 0, event->axis, event->value); +} + +static void +zoom_key_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) +{ + do_zoom(keyboard->seat, time, key, 0, 0); +} + +static void +terminate_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) +{ + struct weston_compositor *compositor = data; + + weston_compositor_exit(compositor); +} + +static void +rotate_grab_motion(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + 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; + + weston_pointer_move(pointer, event); + + if (!shsurf) + return; + + surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); + + cx = 0.5f * surface->width; + cy = 0.5f * surface->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_view_geometry_dirty(shsurf->view); + + 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->view->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 = shsurf->view->geometry.x + cx; + cposy = shsurf->view->geometry.y + cy; + dposx = rotate->center.x - cposx; + dposy = rotate->center.y - cposy; + if (dposx != 0.0f || dposy != 0.0f) { + weston_view_set_position(shsurf->view, + shsurf->view->geometry.x + dposx, + shsurf->view->geometry.y + dposy); + } + + /* Repaint implies weston_view_update_transform(), which + * lazily applies the damage due to rotation update. + */ + weston_compositor_schedule_repaint(surface->compositor); +} + +static void +rotate_grab_button(struct weston_pointer_grab *grab, + const struct timespec *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, + noop_grab_axis, + noop_grab_axis_source, + noop_grab_frame, + rotate_grab_cancel, +}; + +static void +surface_rotate(struct shell_surface *shsurf, struct weston_pointer *pointer) +{ + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct rotate_grab *rotate; + float dx, dy; + float r; + + rotate = malloc(sizeof *rotate); + if (!rotate) + return; + + weston_view_to_global_float(shsurf->view, + surface->width * 0.5f, + surface->height * 0.5f, + &rotate->center.x, &rotate->center.y); + + 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); + if (r > 20.0f) { + struct weston_matrix inverse; + + weston_matrix_init(&inverse); + weston_matrix_rotate_xy(&inverse, dx / r, -dy / r); + weston_matrix_multiply(&shsurf->rotation.rotation, &inverse); + + weston_matrix_init(&rotate->rotation); + weston_matrix_rotate_xy(&rotate->rotation, dx / r, dy / r); + } else { + weston_matrix_init(&shsurf->rotation.rotation); + weston_matrix_init(&rotate->rotation); + } + + shell_grab_start(&rotate->base, &rotate_grab_interface, shsurf, + pointer, WESTON_DESKTOP_SHELL_CURSOR_ARROW); +} + +static void +rotate_binding(struct weston_pointer *pointer, const struct timespec *time, + uint32_t button, void *data) +{ + struct weston_surface *focus; + struct weston_surface *base_surface; + struct shell_surface *surface; + + if (pointer->focus == NULL) + return; + + focus = pointer->focus->surface; + + base_surface = weston_surface_get_main_surface(focus); + if (base_surface == NULL) + return; + + surface = get_shell_surface(base_surface); + if (surface == NULL || + weston_desktop_surface_get_fullscreen(surface->desktop_surface) || + weston_desktop_surface_get_maximized(surface->desktop_surface)) + return; + + surface_rotate(surface, pointer); +} + +/* Move all fullscreen layers down to the current workspace and hide their + * black views. The surfaces' state is set to both fullscreen and lowered, + * and this is reversed when such a surface is re-configured, see + * shell_configure_fullscreen() and shell_ensure_fullscreen_black_view(). + * + * lowering_output = NULL - Lower on all outputs, else only lower on the + * specified output. + * + * This should be used when implementing shell-wide overlays, such as + * the alt-tab switcher, which need to de-promote fullscreen layers. */ +void +lower_fullscreen_layer(struct desktop_shell *shell, + struct weston_output *lowering_output) +{ + struct workspace *ws; + struct weston_view *view, *prev; + + ws = get_current_workspace(shell); + wl_list_for_each_reverse_safe(view, prev, + &shell->fullscreen_layer.view_list.link, + layer_link.link) { + struct shell_surface *shsurf = get_shell_surface(view->surface); + + if (!shsurf) + continue; + + /* Only lower surfaces which have lowering_output as their fullscreen + * output, unless a NULL output asks for lowering on all outputs. + */ + if (lowering_output && (shsurf->fullscreen_output != lowering_output)) + continue; + + /* We can have a non-fullscreen popup for a fullscreen surface + * in the fullscreen layer. */ + if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface)) { + /* Hide the black view */ + weston_layer_entry_remove(&shsurf->fullscreen.black_view->layer_link); + wl_list_init(&shsurf->fullscreen.black_view->layer_link.link); + weston_view_damage_below(shsurf->fullscreen.black_view); + + } + + /* Lower the view to the workspace layer */ + weston_layer_entry_remove(&view->layer_link); + weston_layer_entry_insert(&ws->layer.view_list, &view->layer_link); + weston_view_damage_below(view); + weston_surface_damage(view->surface); + + shsurf->state.lowered = true; + } +} + +static struct shell_surface *get_last_child(struct shell_surface *shsurf) +{ + struct shell_surface *shsurf_child; + + wl_list_for_each_reverse(shsurf_child, &shsurf->children_list, children_link) { + if (weston_view_is_mapped(shsurf_child->view)) + return shsurf_child; + } + + return NULL; +} + +void +activate(struct desktop_shell *shell, struct weston_view *view, + struct weston_seat *seat, uint32_t flags) +{ + struct weston_surface *es = view->surface; + struct weston_surface *main_surface; + struct focus_state *state; + struct workspace *ws; + struct weston_surface *old_es; + struct shell_surface *shsurf, *shsurf_child; + + main_surface = weston_surface_get_main_surface(es); + shsurf = get_shell_surface(main_surface); + assert(shsurf); + + shsurf_child = get_last_child(shsurf); + if (shsurf_child) { + /* Activate last xdg child instead of parent. */ + activate(shell, shsurf_child->view, seat, flags); + return; + } + + /* Only demote fullscreen surfaces on the output of activated shsurf. + * Leave fullscreen surfaces on unrelated outputs alone. */ + if (shsurf->output) + lower_fullscreen_layer(shell, shsurf->output); + + weston_view_activate(view, seat, flags); + + state = ensure_focus_state(shell, seat); + if (state == NULL) + return; + + old_es = state->keyboard_focus; + focus_state_set_focus(state, es); + + if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) && + flags & WESTON_ACTIVATE_FLAG_CONFIGURE) + shell_configure_fullscreen(shsurf); + + /* Update the surface’s layer. This brings it to the top of the stacking + * order as appropriate. */ + shell_surface_update_layer(shsurf); + + if (shell->focus_animation_type != ANIMATION_NONE) { + ws = get_current_workspace(shell); + animate_focus_change(shell, ws, get_default_view(old_es), get_default_view(es)); + } +} + +/* no-op func for checking black surface */ +static void +black_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) +{ +} + +static bool +is_black_surface_view(struct weston_view *view, struct weston_view **fs_view) +{ + struct weston_surface *surface = view->surface; + + if (surface->committed == black_surface_committed) { + if (fs_view) + *fs_view = surface->committed_private; + return true; + } + return false; +} + +static void +activate_binding(struct weston_seat *seat, + struct desktop_shell *shell, + struct weston_view *focus_view, + uint32_t flags) +{ + struct weston_view *main_view; + struct weston_surface *main_surface; + + if (!focus_view) + return; + + if (is_black_surface_view(focus_view, &main_view)) + focus_view = main_view; + + main_surface = weston_surface_get_main_surface(focus_view->surface); + if (!get_shell_surface(main_surface)) + return; + + activate(shell, focus_view, seat, flags); +} + +static void +click_to_activate_binding(struct weston_pointer *pointer, + const struct timespec *time, + uint32_t button, void *data) +{ + if (pointer->grab != &pointer->default_grab) + return; + if (pointer->focus == NULL) + return; + + activate_binding(pointer->seat, data, pointer->focus, + WESTON_ACTIVATE_FLAG_CLICKED | + WESTON_ACTIVATE_FLAG_CONFIGURE); +} + +static void +touch_to_activate_binding(struct weston_touch *touch, + const struct timespec *time, + void *data) +{ + if (touch->grab != &touch->default_grab) + return; + if (touch->focus == NULL) + return; + + activate_binding(touch->seat, data, touch->focus, + WESTON_ACTIVATE_FLAG_CONFIGURE); +} + +static void +unfocus_all_seats(struct desktop_shell *shell) +{ + struct weston_seat *seat, *next; + + wl_list_for_each_safe(seat, next, &shell->compositor->seat_list, link) { + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(seat); + + if (!keyboard) + continue; + + weston_keyboard_set_focus(keyboard, NULL); + } +} + +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. */ + + weston_layer_unset_position(&shell->panel_layer); + weston_layer_unset_position(&shell->fullscreen_layer); + if (shell->showing_input_panels) + weston_layer_unset_position(&shell->input_panel_layer); + weston_layer_unset_position(&ws->layer); + + weston_layer_set_position(&shell->lock_layer, + WESTON_LAYER_POSITION_LOCK); + + weston_compositor_sleep(shell->compositor); + + /* Remove the keyboard focus on all seats. This will be + * restored to the workspace's saved state via + * restore_focus_state when the compositor is unlocked */ + unfocus_all_seats(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) +{ + struct wl_resource *shell_resource; + + 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; + + shell_resource = shell->child.desktop_shell; + weston_desktop_shell_send_prepare_lock_surface(shell_resource); + shell->prepare_event_sent = true; +} + +static void +shell_fade_done_for_output(struct weston_view_animation *animation, void *data) +{ + struct shell_output *shell_output = data; + struct desktop_shell *shell = shell_output->shell; + + shell_output->fade.animation = NULL; + switch (shell_output->fade.type) { + case FADE_IN: + weston_surface_destroy(shell_output->fade.view->surface); + shell_output->fade.view = NULL; + break; + case FADE_OUT: + lock(shell); + break; + default: + break; + } +} + +static struct weston_view * +shell_fade_create_surface_for_output(struct desktop_shell *shell, struct shell_output *shell_output) +{ + struct weston_compositor *compositor = shell->compositor; + struct weston_surface *surface; + struct weston_view *view; + + surface = weston_surface_create(compositor); + if (!surface) + return NULL; + + view = weston_view_create(surface); + if (!view) { + weston_surface_destroy(surface); + return NULL; + } + + weston_surface_set_size(surface, shell_output->output->width, shell_output->output->height); + weston_view_set_position(view, shell_output->output->x, shell_output->output->y); + weston_surface_set_color(surface, 0.0, 0.0, 0.0, 1.0); + weston_layer_entry_insert(&compositor->fade_layer.view_list, + &view->layer_link); + pixman_region32_init(&surface->input); + surface->is_mapped = true; + view->is_mapped = true; + + return view; +} + +static void +shell_fade(struct desktop_shell *shell, enum fade_type type) +{ + float tint; + struct shell_output *shell_output; + + 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; + } + + /* Create a separate fade surface for each output */ + wl_list_for_each(shell_output, &shell->output_list, link) { + shell_output->fade.type = type; + + if (shell_output->fade.view == NULL) { + shell_output->fade.view = shell_fade_create_surface_for_output(shell, shell_output); + if (!shell_output->fade.view) + continue; + + shell_output->fade.view->alpha = 1.0 - tint; + weston_view_update_transform(shell_output->fade.view); + } + + if (shell_output->fade.view->output == NULL) { + /* If the black view gets a NULL output, we lost the + * last output and we'll just cancel the fade. This + * happens when you close the last window under the + * X11 or Wayland backends. */ + shell->locked = false; + weston_surface_destroy(shell_output->fade.view->surface); + shell_output->fade.view = NULL; + } else if (shell_output->fade.animation) { + weston_fade_update(shell_output->fade.animation, tint); + } else { + shell_output->fade.animation = + weston_fade_run(shell_output->fade.view, + 1.0 - tint, tint, 300.0, + shell_fade_done_for_output, shell_output); + } + } +} + +static void +do_shell_fade_startup(void *data) +{ + struct desktop_shell *shell = data; + struct shell_output *shell_output; + + if (shell->startup_animation_type == ANIMATION_FADE) { + shell_fade(shell, FADE_IN); + } else { + weston_log("desktop shell: " + "unexpected fade-in animation type %d\n", + shell->startup_animation_type); + wl_list_for_each(shell_output, &shell->output_list, link) { + weston_surface_destroy(shell_output->fade.view->surface); + shell_output->fade.view = NULL; + } + } +} + +static void +shell_fade_startup(struct desktop_shell *shell) +{ + struct wl_event_loop *loop; + struct shell_output *shell_output; + bool has_fade = false; + + wl_list_for_each(shell_output, &shell->output_list, link) { + if (!shell_output->fade.startup_timer) + continue; + + wl_event_source_remove(shell_output->fade.startup_timer); + shell_output->fade.startup_timer = NULL; + has_fade = true; + } + + if (has_fade) { + 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; + struct shell_output *shell_output; + + if (shell->startup_animation_type == ANIMATION_NONE) + return; + + wl_list_for_each(shell_output, &shell->output_list, link) { + if (shell_output->fade.view != NULL) { + weston_log("%s: warning: fade surface already exists\n", + __func__); + continue; + } + + shell_output->fade.view = shell_fade_create_surface_for_output(shell, shell_output); + if (!shell_output->fade.view) + continue; + + weston_view_update_transform(shell_output->fade.view); + weston_surface_damage(shell_output->fade.view->surface); + + loop = wl_display_get_event_loop(shell->compositor->wl_display); + shell_output->fade.startup_timer = + wl_event_loop_add_timer(loop, fade_startup_timeout, shell); + wl_event_source_timer_update(shell_output->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); + + struct weston_seat *seat; + + wl_list_for_each(seat, &shell->compositor->seat_list, link) + weston_seat_break_desktop_grabs(seat); + + shell_fade(shell, FADE_OUT); + /* lock() is called from shell_fade_done_for_output() */ +} + +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 +transform_handler(struct wl_listener *listener, void *data) +{ + struct weston_surface *surface = data; + struct shell_surface *shsurf = get_shell_surface(surface); + const struct weston_xwayland_surface_api *api; + int x, y; + + if (!shsurf) + return; + + api = shsurf->shell->xwayland_surface_api; + if (!api) { + api = weston_xwayland_surface_get_api(shsurf->shell->compositor); + shsurf->shell->xwayland_surface_api = api; + } + + if (!api || !api->is_xwayland_surface(surface)) + return; + + if (!weston_view_is_mapped(shsurf->view)) + return; + + x = shsurf->view->geometry.x; + y = shsurf->view->geometry.y; + + api->send_position(surface, x, y); +} + +static void +center_on_output(struct weston_view *view, struct weston_output *output) +{ + int32_t surf_x, surf_y, width, height; + float x, y; + + if (!output) { + weston_view_set_position(view, 0, 0); + return; + } + + surface_subsurfaces_boundingbox(view->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_view_set_position(view, x, y); +} + +static void +weston_view_set_initial_position(struct weston_view *view, + struct desktop_shell *shell) +{ + struct weston_compositor *compositor = shell->compositor; + int ix = 0, iy = 0; + int32_t range_x, range_y; + int32_t x, y; + struct weston_output *output, *target_output = NULL; + struct weston_seat *seat; + pixman_rectangle32_t area; + + /* 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) { + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + if (pointer) { + ix = wl_fixed_to_int(pointer->x); + iy = wl_fixed_to_int(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_view_set_position(view, 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. + */ + get_output_work_area(shell, target_output, &area); + + x = area.x; + y = area.y; + range_x = area.width - view->surface->width; + range_y = area.height - view->surface->height; + + if (range_x > 0) + x += random() % range_x; + + if (range_y > 0) + y += random() % range_y; + + weston_view_set_position(view, x, y); +} + +static bool +check_desktop_shell_crash_too_early(struct desktop_shell *shell) +{ + struct timespec now; + + if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) + return false; + + /* + * If the shell helper client dies before the session has been + * up for roughly 30 seconds, better just make Weston shut down, + * because the user likely has no way to interact with the desktop + * anyway. + */ + if (now.tv_sec - shell->startup_time.tv_sec < 30) { + weston_log("Error: %s apparently cannot run at all.\n", + shell->client); + weston_log_continue(STAMP_SPACE "Quitting..."); + weston_compositor_exit_with_code(shell->compositor, + EXIT_FAILURE); + + return true; + } + + return false; +} + +static void launch_desktop_shell_process(void *data); + +static void +respawn_desktop_shell_process(struct desktop_shell *shell) +{ + struct timespec time; + + /* if desktop-shell dies more than 5 times in 30 seconds, give up */ + weston_compositor_get_time(&time); + if (timespec_sub_to_msec(&time, &shell->child.deathstamp) > 30000) { + shell->child.deathstamp = time; + shell->child.deathcount = 0; + } + + shell->child.deathcount++; + if (shell->child.deathcount > 5) { + weston_log("%s disconnected, giving up.\n", shell->client); + return; + } + + weston_log("%s disconnected, respawning...\n", shell->client); + launch_desktop_shell_process(shell); +} + +static void +desktop_shell_client_destroy(struct wl_listener *listener, void *data) +{ + struct desktop_shell *shell; + + shell = container_of(listener, struct desktop_shell, + child.client_destroy_listener); + + wl_list_remove(&shell->child.client_destroy_listener.link); + shell->child.client = NULL; + /* + * unbind_desktop_shell() will reset shell->child.desktop_shell + * before the respawned process has a chance to create a new + * desktop_shell object, because we are being called from the + * wl_client destructor which destroys all wl_resources before + * returning. + */ + + if (!check_desktop_shell_crash_too_early(shell)) + respawn_desktop_shell_process(shell); + + shell_fade_startup(shell); +} + +static void +launch_desktop_shell_process(void *data) +{ + struct desktop_shell *shell = data; + + shell->child.client = weston_client_start(shell->compositor, + shell->client); + + if (!shell->child.client) { + weston_log("not able to start %s\n", shell->client); + return; + } + + shell->child.client_destroy_listener.notify = + desktop_shell_client_destroy; + wl_client_add_destroy_listener(shell->child.client, + &shell->child.client_destroy_listener); +} + +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, &weston_desktop_shell_interface, + 1, id); + + if (client == shell->child.client) { + wl_resource_set_implementation(resource, + &desktop_shell_implementation, + shell, unbind_desktop_shell); + shell->child.desktop_shell = resource; + return; + } + + wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "permission to bind desktop_shell denied"); +} + +struct switcher { + struct desktop_shell *shell; + struct weston_view *current; + struct wl_listener listener; + struct weston_keyboard_grab grab; + struct wl_array minimized_array; +}; + +static void +switcher_next(struct switcher *switcher) +{ + struct weston_view *view; + struct weston_view *first = NULL, *prev = NULL, *next = NULL; + struct shell_surface *shsurf; + struct workspace *ws = get_current_workspace(switcher->shell); + + /* temporary re-display minimized surfaces */ + struct weston_view *tmp; + struct weston_view **minimized; + wl_list_for_each_safe(view, tmp, &switcher->shell->minimized_layer.view_list.link, layer_link.link) { + weston_layer_entry_remove(&view->layer_link); + weston_layer_entry_insert(&ws->layer.view_list, &view->layer_link); + minimized = wl_array_add(&switcher->minimized_array, sizeof *minimized); + *minimized = view; + } + + wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { + shsurf = get_shell_surface(view->surface); + if (shsurf) { + if (first == NULL) + first = view; + if (prev == switcher->current) + next = view; + prev = view; + view->alpha = 0.25; + weston_view_geometry_dirty(view); + weston_surface_damage(view->surface); + } + + if (is_black_surface_view(view, NULL)) { + view->alpha = 0.25; + weston_view_geometry_dirty(view); + weston_surface_damage(view->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; + wl_list_for_each(view, &next->surface->views, surface_link) + view->alpha = 1.0; + + shsurf = get_shell_surface(switcher->current->surface); + if (shsurf && weston_desktop_surface_get_fullscreen(shsurf->desktop_surface)) + shsurf->fullscreen.black_view->alpha = 1.0; +} + +static void +switcher_handle_view_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_view *view; + struct weston_keyboard *keyboard = switcher->grab.keyboard; + struct workspace *ws = get_current_workspace(switcher->shell); + + wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { + if (is_focus_view(view)) + continue; + + view->alpha = 1.0; + weston_surface_damage(view->surface); + } + + if (switcher->current) { + activate(switcher->shell, switcher->current, + keyboard->seat, + WESTON_ACTIVATE_FLAG_CONFIGURE); + } + + wl_list_remove(&switcher->listener.link); + weston_keyboard_end_grab(keyboard); + if (keyboard->input_method_resource) + keyboard->grab = &keyboard->input_method_grab; + + /* re-hide surfaces that were temporary shown during the switch */ + struct weston_view **minimized; + wl_array_for_each(minimized, &switcher->minimized_array) { + /* with the exception of the current selected */ + if ((*minimized)->surface != switcher->current->surface) { + weston_layer_entry_remove(&(*minimized)->layer_link); + weston_layer_entry_insert(&switcher->shell->minimized_layer.view_list, &(*minimized)->layer_link); + weston_view_damage_below(*minimized); + } + } + wl_array_release(&switcher->minimized_array); + + free(switcher); +} + +static void +switcher_key(struct weston_keyboard_grab *grab, + const struct timespec *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 = 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_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) +{ + struct desktop_shell *shell = data; + struct switcher *switcher; + + switcher = malloc(sizeof *switcher); + if (!switcher) + return; + + switcher->shell = shell; + switcher->current = NULL; + switcher->listener.notify = switcher_handle_view_destroy; + wl_list_init(&switcher->listener.link); + wl_array_init(&switcher->minimized_array); + + lower_fullscreen_layer(switcher->shell, NULL); + switcher->grab.interface = &switcher_grab; + weston_keyboard_start_grab(keyboard, &switcher->grab); + weston_keyboard_set_focus(keyboard, NULL); + switcher_next(switcher); +} + +static void +backlight_binding(struct weston_keyboard *keyboard, const struct timespec *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); +} + +static void +force_kill_binding(struct weston_keyboard *keyboard, + const struct timespec *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 = 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_keyboard *keyboard, + const struct timespec *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_keyboard *keyboard, + const struct timespec *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_keyboard *keyboard, + const struct timespec *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_keyboard *keyboard, + const struct timespec *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, keyboard->seat, new_index); +} + +static void +workspace_move_surface_down_binding(struct weston_keyboard *keyboard, + const struct timespec *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, keyboard->seat, new_index); +} + +static void +shell_reposition_view_on_output_change(struct weston_view *view) +{ + struct weston_output *output, *first_output; + struct weston_compositor *ec = view->surface->compositor; + struct shell_surface *shsurf; + float x, y; + int visible; + + if (wl_list_empty(&ec->output_list)) + return; + + x = view->geometry.x; + y = view->geometry.y; + + /* At this point the destroyed output is not in the list anymore. + * If the view is still visible somewhere, we leave where it is, + * otherwise, move it to the first output. */ + visible = 0; + wl_list_for_each(output, &ec->output_list, link) { + if (pixman_region32_contains_point(&output->region, + x, y, NULL)) { + visible = 1; + break; + } + } + + if (!visible) { + first_output = container_of(ec->output_list.next, + struct weston_output, link); + + x = first_output->x + first_output->width / 4; + y = first_output->y + first_output->height / 4; + + weston_view_set_position(view, x, y); + } else { + weston_view_geometry_dirty(view); + } + + + shsurf = get_shell_surface(view->surface); + if (!shsurf) + return; + + shsurf->saved_position_valid = false; + set_maximized(shsurf, false); + set_fullscreen(shsurf, false, NULL); +} + +void +shell_for_each_layer(struct desktop_shell *shell, + shell_for_each_layer_func_t func, void *data) +{ + struct workspace **ws; + + func(shell, &shell->fullscreen_layer, data); + func(shell, &shell->panel_layer, data); + func(shell, &shell->background_layer, data); + func(shell, &shell->lock_layer, data); + func(shell, &shell->input_panel_layer, data); + + wl_array_for_each(ws, &shell->workspaces.array) + func(shell, &(*ws)->layer, data); +} + +static void +shell_output_changed_move_layer(struct desktop_shell *shell, + struct weston_layer *layer, + void *data) +{ + struct weston_view *view; + + wl_list_for_each(view, &layer->view_list.link, layer_link.link) + shell_reposition_view_on_output_change(view); + +} + +static void +handle_output_destroy(struct wl_listener *listener, void *data) +{ + struct shell_output *output_listener = + container_of(listener, struct shell_output, destroy_listener); + struct desktop_shell *shell = output_listener->shell; + + shell_for_each_layer(shell, shell_output_changed_move_layer, NULL); + + if (output_listener->panel_surface) + wl_list_remove(&output_listener->panel_surface_listener.link); + if (output_listener->background_surface) + wl_list_remove(&output_listener->background_surface_listener.link); + wl_list_remove(&output_listener->destroy_listener.link); + wl_list_remove(&output_listener->link); + free(output_listener); +} + +static void +shell_resize_surface_to_output(struct desktop_shell *shell, + struct weston_surface *surface, + const struct weston_output *output) +{ + if (!surface) + return; + + weston_desktop_shell_send_configure(shell->child.desktop_shell, 0, + surface->resource, + output->width, + output->height); +} + + +static void +handle_output_resized(struct wl_listener *listener, void *data) +{ + struct desktop_shell *shell = + container_of(listener, struct desktop_shell, resized_listener); + struct weston_output *output = (struct weston_output *)data; + struct shell_output *sh_output = find_shell_output_from_weston_output(shell, output); + + shell_resize_surface_to_output(shell, sh_output->background_surface, output); + shell_resize_surface_to_output(shell, sh_output->panel_surface, output); +} + +static void +create_shell_output(struct desktop_shell *shell, + struct weston_output *output) +{ + struct shell_output *shell_output; + + shell_output = zalloc(sizeof *shell_output); + if (shell_output == NULL) + return; + + shell_output->output = output; + shell_output->shell = shell; + shell_output->destroy_listener.notify = handle_output_destroy; + wl_signal_add(&output->destroy_signal, + &shell_output->destroy_listener); + wl_list_insert(shell->output_list.prev, &shell_output->link); + + if (wl_list_length(&shell->output_list) == 1) + shell_for_each_layer(shell, + shell_output_changed_move_layer, NULL); +} + +static void +handle_output_create(struct wl_listener *listener, void *data) +{ + struct desktop_shell *shell = + container_of(listener, struct desktop_shell, output_create_listener); + struct weston_output *output = (struct weston_output *)data; + + create_shell_output(shell, output); +} + +static void +handle_output_move_layer(struct desktop_shell *shell, + struct weston_layer *layer, void *data) +{ + struct weston_output *output = data; + struct weston_view *view; + float x, y; + + wl_list_for_each(view, &layer->view_list.link, layer_link.link) { + if (view->output != output) + continue; + + x = view->geometry.x + output->move_x; + y = view->geometry.y + output->move_y; + weston_view_set_position(view, x, y); + } +} + +static void +handle_output_move(struct wl_listener *listener, void *data) +{ + struct desktop_shell *shell; + + shell = container_of(listener, struct desktop_shell, + output_move_listener); + + shell_for_each_layer(shell, handle_output_move_layer, data); +} + +static void +setup_output_destroy_handler(struct weston_compositor *ec, + struct desktop_shell *shell) +{ + struct weston_output *output; + + wl_list_init(&shell->output_list); + wl_list_for_each(output, &ec->output_list, link) + create_shell_output(shell, output); + + shell->output_create_listener.notify = handle_output_create; + wl_signal_add(&ec->output_created_signal, + &shell->output_create_listener); + + shell->output_move_listener.notify = handle_output_move; + wl_signal_add(&ec->output_moved_signal, &shell->output_move_listener); +} + +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; + struct shell_output *shell_output, *tmp; + + /* Force state to unlocked so we don't try to fade */ + shell->locked = false; + + if (shell->child.client) { + /* disable respawn */ + wl_list_remove(&shell->child.client_destroy_listener.link); + wl_client_destroy(shell->child.client); + } + + wl_list_remove(&shell->destroy_listener.link); + wl_list_remove(&shell->idle_listener.link); + wl_list_remove(&shell->wake_listener.link); + wl_list_remove(&shell->transform_listener.link); + +// OHOS remove text_backend +// text_backend_destroy(shell->text_backend); + input_panel_destroy(shell); + + wl_list_for_each_safe(shell_output, tmp, &shell->output_list, link) { + wl_list_remove(&shell_output->destroy_listener.link); + wl_list_remove(&shell_output->link); + free(shell_output); + } + + wl_list_remove(&shell->output_create_listener.link); + wl_list_remove(&shell->output_move_listener.link); + wl_list_remove(&shell->resized_listener.link); + + wl_array_for_each(ws, &shell->workspaces.array) + workspace_destroy(*ws); + wl_array_release(&shell->workspaces.array); + + free(shell->client); + free(shell); +} + +static void +shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) +{ + uint32_t mod; + int i, num_workspace_bindings; + + if (shell->allow_zap) + weston_compositor_add_key_binding(ec, KEY_BACKSPACE, + MODIFIER_CTRL | MODIFIER_ALT, + terminate_binding, ec); + + /* fixed bindings */ + weston_compositor_add_button_binding(ec, BTN_LEFT, 0, + click_to_activate_binding, + shell); + weston_compositor_add_button_binding(ec, BTN_RIGHT, 0, + click_to_activate_binding, + shell); + weston_compositor_add_touch_binding(ec, 0, + touch_to_activate_binding, + shell); + weston_compositor_add_key_binding(ec, KEY_BRIGHTNESSDOWN, 0, + backlight_binding, ec); + weston_compositor_add_key_binding(ec, KEY_BRIGHTNESSUP, 0, + backlight_binding, ec); + + /* configurable bindings */ + if (shell->exposay_modifier) + weston_compositor_add_modifier_binding(ec, shell->exposay_modifier, + exposay_binding, shell); + + mod = shell->binding_modifier; + if (!mod) + return; + + /* This binding is not configurable, but is only enabled if there is a + * valid binding modifier. */ + 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, + mod, zoom_axis_binding, + NULL); + + 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_key_binding(ec, KEY_M, mod | MODIFIER_SHIFT, + maximize_binding, NULL); + weston_compositor_add_key_binding(ec, KEY_F, mod | MODIFIER_SHIFT, + fullscreen_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_RIGHT, mod, + resize_binding, shell); + weston_compositor_add_button_binding(ec, BTN_LEFT, + mod | MODIFIER_SHIFT, + resize_binding, shell); + + if (ec->capabilities & WESTON_CAP_ROTATION_ANY) + weston_compositor_add_button_binding(ec, BTN_MIDDLE, 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_F10, mod, 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); + } + + weston_install_debug_key_binding(ec, mod); +} + +static void +handle_seat_created(struct wl_listener *listener, void *data) +{ + struct weston_seat *seat = data; + + create_shell_seat(seat); +} + +WL_EXPORT int +wet_shell_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; + + if (!weston_compositor_add_destroy_listener_once(ec, + &shell->destroy_listener, + shell_destroy)) { + free(shell); + return 0; + } + + 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->transform_listener.notify = transform_handler; + wl_signal_add(&ec->transform_signal, &shell->transform_listener); + + weston_layer_init(&shell->fullscreen_layer, ec); + weston_layer_init(&shell->panel_layer, ec); + weston_layer_init(&shell->background_layer, ec); + weston_layer_init(&shell->lock_layer, ec); + weston_layer_init(&shell->input_panel_layer, ec); + + weston_layer_set_position(&shell->fullscreen_layer, + WESTON_LAYER_POSITION_FULLSCREEN); + weston_layer_set_position(&shell->panel_layer, + WESTON_LAYER_POSITION_UI); + weston_layer_set_position(&shell->background_layer, + WESTON_LAYER_POSITION_BACKGROUND); + + wl_array_init(&shell->workspaces.array); + wl_list_init(&shell->workspaces.client_list); + + if (input_panel_setup(shell) < 0) + return -1; + +// OHOS remove text_backend +// shell->text_backend = text_backend_init(ec); +// if (!shell->text_backend) +// return -1; + + shell_configuration(shell); + + shell->exposay.state_cur = EXPOSAY_LAYOUT_INACTIVE; + shell->exposay.state_target = EXPOSAY_TARGET_CANCEL; + + 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(shell); + if (*pws == NULL) + return -1; + } + activate_workspace(shell, 0); + + weston_layer_init(&shell->minimized_layer, ec); + + wl_list_init(&shell->workspaces.anim_sticky_list); + wl_list_init(&shell->workspaces.animation.link); + shell->workspaces.animation.frame = animate_workspace_change_frame; + + shell->desktop = weston_desktop_create(ec, &shell_desktop_api, shell); + if (!shell->desktop) + return -1; + + if (wl_global_create(ec->wl_display, + &weston_desktop_shell_interface, 1, + shell, bind_desktop_shell) == NULL) + return -1; + + weston_compositor_get_time(&shell->child.deathstamp); + + shell->panel_position = WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP; + + setup_output_destroy_handler(ec, shell); + + loop = wl_display_get_event_loop(ec->wl_display); +// OHOS +// wl_event_loop_add_idle(loop, launch_desktop_shell_process, shell); + + wl_list_for_each(seat, &ec->seat_list, link) + handle_seat_created(NULL, seat); + shell->seat_create_listener.notify = handle_seat_created; + wl_signal_add(&ec->seat_created_signal, &shell->seat_create_listener); + + shell->resized_listener.notify = handle_output_resized; + wl_signal_add(&ec->output_resized_signal, &shell->resized_listener); + + screenshooter_create(ec); + + shell_add_bindings(ec, shell); + + shell_fade_init(shell); + + clock_gettime(CLOCK_MONOTONIC, &shell->startup_time); + + return 0; +} diff --git a/desktop-shell/shell.h b/desktop-shell/shell.h new file mode 100644 index 0000000..9f0b6ab --- /dev/null +++ b/desktop-shell/shell.h @@ -0,0 +1,263 @@ +/* + * Copyright © 2010-2012 Intel Corporation + * Copyright © 2011-2012 Collabora, Ltd. + * Copyright © 2013 Raspberry Pi Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include + +#include +#include + +#include "weston-desktop-shell-server-protocol.h" + +enum animation_type { + ANIMATION_NONE, + + ANIMATION_ZOOM, + ANIMATION_FADE, + ANIMATION_DIM_LAYER, +}; + +enum fade_type { + FADE_IN, + FADE_OUT +}; + +enum exposay_target_state { + EXPOSAY_TARGET_OVERVIEW, /* show all windows */ + EXPOSAY_TARGET_CANCEL, /* return to normal, same focus */ + EXPOSAY_TARGET_SWITCH, /* return to normal, switch focus */ +}; + +enum exposay_layout_state { + EXPOSAY_LAYOUT_INACTIVE = 0, /* normal desktop */ + EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE, /* in transition to normal */ + EXPOSAY_LAYOUT_OVERVIEW, /* show all windows */ + EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW, /* in transition to all windows */ +}; + +struct exposay_output { + int num_surfaces; + int grid_size; + int surface_size; + int padding_inner; +}; + +struct exposay { + /* XXX: Make these exposay_surfaces. */ + struct weston_view *focus_prev; + struct weston_view *focus_current; + struct weston_view *clicked; + struct workspace *workspace; + struct weston_seat *seat; + + struct wl_list surface_list; + + struct weston_keyboard_grab grab_kbd; + struct weston_pointer_grab grab_ptr; + + enum exposay_target_state state_target; + enum exposay_layout_state state_cur; + int in_flight; /* number of animations still running */ + + int row_current; + int column_current; + struct exposay_output *cur_output; + + bool mod_pressed; + bool mod_invalid; +}; + +struct focus_surface { + struct weston_surface *surface; + struct weston_view *view; + struct weston_transform workspace_transform; +}; + +struct workspace { + struct weston_layer layer; + + struct wl_list focus_list; + struct wl_listener seat_destroyed_listener; + + struct focus_surface *fsurf_front; + struct focus_surface *fsurf_back; + struct weston_view_animation *focus_animation; +}; + +struct shell_output { + struct desktop_shell *shell; + struct weston_output *output; + struct exposay_output eoutput; + struct wl_listener destroy_listener; + struct wl_list link; + + struct weston_surface *panel_surface; + struct wl_listener panel_surface_listener; + + struct weston_surface *background_surface; + struct wl_listener background_surface_listener; + + struct { + struct weston_view *view; + struct weston_view_animation *animation; + enum fade_type type; + struct wl_event_source *startup_timer; + } fade; +}; + +struct weston_desktop; +struct desktop_shell { + struct weston_compositor *compositor; + struct weston_desktop *desktop; + const struct weston_xwayland_surface_api *xwayland_surface_api; + + struct wl_listener idle_listener; + struct wl_listener wake_listener; + struct wl_listener transform_listener; + struct wl_listener resized_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 wl_client *client; + struct wl_resource *desktop_shell; + struct wl_listener client_destroy_listener; + + unsigned deathcount; + struct timespec deathstamp; + } child; + + bool locked; + bool showing_input_panels; + bool prepare_event_sent; + + struct text_backend *text_backend; + + 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; + struct timespec anim_timestamp; + double anim_current; + struct workspace *anim_from; + struct workspace *anim_to; + } workspaces; + + struct { + struct wl_resource *binding; + struct wl_list surfaces; + } input_panel; + + struct exposay exposay; + + bool allow_zap; + uint32_t binding_modifier; + uint32_t exposay_modifier; + enum animation_type win_animation_type; + enum animation_type win_close_animation_type; + enum animation_type startup_animation_type; + enum animation_type focus_animation_type; + + struct weston_layer minimized_layer; + + struct wl_listener seat_create_listener; + struct wl_listener output_create_listener; + struct wl_listener output_move_listener; + struct wl_list output_list; + + enum weston_desktop_shell_panel_position panel_position; + + char *client; + + struct timespec startup_time; +}; + +struct weston_output * +get_default_output(struct weston_compositor *compositor); + +struct weston_view * +get_default_view(struct weston_surface *surface); + +struct shell_surface * +get_shell_surface(struct weston_surface *surface); + +struct workspace * +get_current_workspace(struct desktop_shell *shell); + +void +get_output_work_area(struct desktop_shell *shell, + struct weston_output *output, + pixman_rectangle32_t *area); + +void +lower_fullscreen_layer(struct desktop_shell *shell, + struct weston_output *lowering_output); + +void +activate(struct desktop_shell *shell, struct weston_view *view, + struct weston_seat *seat, uint32_t flags); + +void +exposay_binding(struct weston_keyboard *keyboard, + enum weston_keyboard_modifier modifier, + void *data); +int +input_panel_setup(struct desktop_shell *shell); +void +input_panel_destroy(struct desktop_shell *shell); + +typedef void (*shell_for_each_layer_func_t)(struct desktop_shell *, + struct weston_layer *, void *); + +void +shell_for_each_layer(struct desktop_shell *shell, + shell_for_each_layer_func_t func, + void *data); diff --git a/doc/doxygen/devtools.dox b/doc/doxygen/devtools.dox new file mode 100644 index 0000000..2d6672f --- /dev/null +++ b/doc/doxygen/devtools.dox @@ -0,0 +1,51 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** +@mainpage + +- @ref zunitc - Simple test framework + +@section tools_overview Overview + +The tools area currently consists of one sub-project (@ref zunitc) that is +refined from the prior single weston/tests source folder. + +@subsection tools_overview_old Old Code Organization + +The original 'tests' folder contained basic weston testing with an +integrated test runner framework. Over time things progressed to the +stage where splitting apart into discrete layers was warranted. + +@dotfile tools_arch_old.gv "Original test code organization" + +@subsection tools_overview_new New Code Organization + +The test code that is not weston-specific gets split out to a separate +folder and/or folders. + +@dotfile tools_arch_new.gv "Refactored test code organization" + +*/ diff --git a/doc/doxygen/tooldev.doxygen.in b/doc/doxygen/tooldev.doxygen.in new file mode 100644 index 0000000..b3d86f5 --- /dev/null +++ b/doc/doxygen/tooldev.doxygen.in @@ -0,0 +1,12 @@ +PROJECT_NAME = "Tool Internals" +OUTPUT_DIRECTORY = @top_builddir@/docs/developer +JAVADOC_AUTOBRIEF = YES +OPTIMIZE_OUTPUT_FOR_C = YES +EXTRACT_ALL = YES +INPUT = \ + @top_srcdir@/doc/doxygen/devtools.dox \ + @top_srcdir@/tools/zunitc +RECURSIVE = YES +GENERATE_LATEX = NO +DOTFILE_DIRS = @top_srcdir@/doc/doxygen +STRIP_FROM_PATH = @top_srcdir@ diff --git a/doc/doxygen/tools.dox b/doc/doxygen/tools.dox new file mode 100644 index 0000000..9bbc11d --- /dev/null +++ b/doc/doxygen/tools.dox @@ -0,0 +1,31 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** +@mainpage + +- @ref zunitc - Simple test framework + +*/ diff --git a/doc/doxygen/tools.doxygen.in b/doc/doxygen/tools.doxygen.in new file mode 100644 index 0000000..613edd4 --- /dev/null +++ b/doc/doxygen/tools.doxygen.in @@ -0,0 +1,11 @@ +PROJECT_NAME = "Tools" +OUTPUT_DIRECTORY = @top_builddir@/docs/tools +JAVADOC_AUTOBRIEF = YES +OPTIMIZE_OUTPUT_FOR_C = YES +INPUT = \ + @top_srcdir@/doc/doxygen/tools.dox \ + @top_srcdir@/tools/zunitc/doc/zunitc.dox \ + @top_srcdir@/tools/zunitc/inc/zunitc/zunitc.h +GENERATE_LATEX = NO +DOTFILE_DIRS = @top_srcdir@/doc/doxygen +STRIP_FROM_PATH = @top_srcdir@ diff --git a/doc/doxygen/tools_arch_new.gv b/doc/doxygen/tools_arch_new.gv new file mode 100644 index 0000000..e4626f3 --- /dev/null +++ b/doc/doxygen/tools_arch_new.gv @@ -0,0 +1,85 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +digraph toolarch_new { + rankdir = "TB"; + + node[shape=record] + + subgraph cluster_0 { + label = "./tests"; + + keyboard_test_c [label = "{keyboard-test.c|tests\l}"] + text_test_c [label = "{text-test.c|tests\l}"] + vertex_clip_test_c [label = "{vertex-clip-test.c|tests\l}"] + + spacer [shape = point, style = invis] + + weston_test_client_helper [label = "{weston-test-client-helper.h/.c|Weston test protocol\l}"] + + weston_test_c [label = "{weston-test.c|Extension protocol\nimplementation}"] + } + + subgraph cluster_1 { + label = "./tools/waycheck"; + + waycheck [label = "{waycheck.c| \n \n }"] + } + + subgraph cluster_2 { + label = "./tools/wayland_fixtures"; + + wtst_fixtures [label = "{wtst_fixtures.h/c|Wayland tracking structs\lWayland callbacks\l}"] + } + + subgraph cluster_3 { + label = "./tools/zunitc"; + + zunitc [label = "{zunitc|Test definition macros\lTest running functions\lTest reporting functions\lTest run lifecycle\l}"] + } + + keyboard_test_c -> weston_test_client_helper + keyboard_test_c -> wtst_fixtures + keyboard_test_c -> zunitc + vertex_clip_test_c -> zunitc + text_test_c -> weston_test_client_helper + text_test_c -> wtst_fixtures + text_test_c -> zunitc + + waycheck -> wtst_fixtures + waycheck -> zunitc + + wtst_fixtures -> zunitc + + edge [style = dashed, arrowhead = open] + weston_test_client_helper -> weston_test_c + + edge [style = invis] + weston_test_client_helper -> zunitc + + text_test_c -> spacer + keyboard_test_c -> spacer + spacer -> weston_test_client_helper +} diff --git a/doc/doxygen/tools_arch_old.gv b/doc/doxygen/tools_arch_old.gv new file mode 100644 index 0000000..1b38123 --- /dev/null +++ b/doc/doxygen/tools_arch_old.gv @@ -0,0 +1,53 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +digraph toolarch_old { + rankdir = "TB"; + + node[shape = record] + + subgraph cluster_0 { + label = "./tests"; + + keyboard_test_c [label = "{keyboard-test.c|tests\l}"] + text_test_c [label = "{text-test.c|tests\l}"] + vertex_clip_test_c [label = "{vertex-clip-test.c|tests\l}"] + + weston_test_client_helper [label = "{weston-test-client-helper.h/.c|Wayland tracking structs\lWeston test protocol\lWayland callbacks\lTest run lifecycle\l}"] + + weston_test_c [label = "{weston-test.c|Extension protocol\nimplementation}"] + weston_test_runner [label = "{weston-test-runner.h/.c|Test definition macros\lTest running functions\lTest reporting functions\lTest run lifecycle\l}"] + } + + weston_test_client_helper -> weston_test_runner + keyboard_test_c -> weston_test_client_helper + keyboard_test_c -> weston_test_runner + vertex_clip_test_c -> weston_test_runner + text_test_c -> weston_test_client_helper + text_test_c -> weston_test_runner + + edge [style = dashed, arrowhead = open] + weston_test_client_helper -> weston_test_c +} diff --git a/doc/scripts/calibration-helper.bash b/doc/scripts/calibration-helper.bash new file mode 100755 index 0000000..72effe3 --- /dev/null +++ b/doc/scripts/calibration-helper.bash @@ -0,0 +1,66 @@ +#!/bin/bash + +# Copyright 2018 Collabora, Ltd. +# Copyright 2018 General Electric Company +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice (including the +# next paragraph) shall be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# This is an example script working as Weston's calibration helper. +# Its purpose is to permanently store the calibration matrix for the given +# touchscreen input device into a udev property. Since this script naturally +# runs as the user that runs Weston, it presumably cannot write directly into +# /etc. It is left for the administrator to set up appropriate files and +# permissions. + +# To use this script, one needs to edit weston.ini, in section [libinput], add: +# calibration_helper=/path/to/bin/calibration-helper.bash + +# exit immediately if any command fails +set -e + +# The arguments Weston gives us: +SYSPATH="$1" +MATRIX="$2 $3 $4 $5 $6 $7" + +# Pick something to recognize the right touch device with. +# Usually one would use something like a serial. +SERIAL=$(udevadm info "$SYSPATH" --query=property | \ + awk -- 'BEGIN { FS="=" } { if ($1 == "ID_SERIAL") { print $2; exit } }') + +# If cannot find a serial, tell the server to not use the new calibration. +[ -z "$SERIAL" ] && exit 1 + +# You'd have this write a file instead. +echo "ACTION==\"add|change\",SUBSYSTEM==\"input\",ENV{ID_SERIAL}==\"$SERIAL\",ENV{LIBINPUT_CALIBRATION_MATRIX}=\"$MATRIX\"" + +# Then you'd tell udev to reload the rules: +#udevadm control --reload +# This lets Weston get the new calibration if you unplug and replug the input +# device. Instead of writing a udev rule directly, you could have a udev rule +# with IMPORT{file}="/path/to/calibration", write +# "LIBINPUT_CALIBRATION_MATRIX=\"$MATRIX\"" into /path/to/calibration instead, +# and skip this reload step. + +# Make udev process the new rule by triggering a "change" event: +#udevadm trigger "$SYSPATH" +# If you were to restart Weston without rebooting, this lets it pick up the new +# calibration. diff --git a/doc/scripts/gdb/flight_rec.py b/doc/scripts/gdb/flight_rec.py new file mode 100755 index 0000000..9872f17 --- /dev/null +++ b/doc/scripts/gdb/flight_rec.py @@ -0,0 +1,103 @@ +# +# Copyright © 2019 Collabora Ltd. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice (including the +# next paragraph) shall be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Usage: source this script then 'display_flight_rec' +# + +import gdb + +class DisplayFlightRecorder(gdb.Command): + def __init__(self): + + self.rb = '' + symbol_found = False + + ring_buff = gdb.lookup_global_symbol("weston_primary_flight_recorder_ring_buffer") + if ring_buff == None: + print("'weston_ring_buffer' symbol not found!") + print("Either weston is too old or weston hasn't been loaded in memory") + else: + self.rb = ring_buff + self.display_rb_data(self.rb) + symbol_found = True + + if symbol_found: + super(DisplayFlightRecorder, self).__init__("display_flight_rec", + gdb.COMMAND_DATA) + + def display_rb_data(self, rb): + print("Flight recorder data found. Use 'display_flight_rec' " + "to display its contents") + # display this data (only) if symbol is not empty (happens if the program is not ran at all) + if rb.value(): + print("Data at byte {append}, Size: {size}B, " + "Overlaped: {overlap}".format(append=rb.value()['append_pos'], + size=rb.value()['size'], + overlap=rb.value()['overlap'])) + + # poor's man fwrite() + def gen_contents(self, start, stop): + _str = '' + for j in range(start, stop): + _str += chr(self.rb.value()['buf'][j]) + return _str + + # mirrors C version, as to make sure we're not reading other parts... + def display_flight_rec_contents(self): + + # symbol is there but not loaded, we're not far enough + if self.rb.value() == 0x0: + print("Flight recorder found, but not loaded yet!") + return + else: + print("Displaying flight recorder contents:") + + append_pos = self.rb.value()['append_pos'] + size = self.rb.value()['size'] + overlap = self.rb.value()['overlap'] + + # if we haven't overflown and we're still at 0 means + # we still aren't far enough to be populated + if append_pos == 0 and not overlap: + print("Flight recorder doesn't have anything to display right now") + return + + # now we can print stuff + rb_data = '' + if not overlap: + if append_pos: + rb_data = self.gen_contents(0, append_pos) + else: + rb_data = self.gen_contents(0, size) + else: + rb_data = self.gen_contents(append_pos, size) + rb_data += self.gen_contents(0, append_pos) + + print("{data}".format(data=rb_data)) + + # called when invoking 'display_flight_rec' + def invoke(self, arg, from_tty): + self.display_flight_rec_contents() + +DisplayFlightRecorder() diff --git a/doc/scripts/remoting-client-receive.bash b/doc/scripts/remoting-client-receive.bash new file mode 100755 index 0000000..b518689 --- /dev/null +++ b/doc/scripts/remoting-client-receive.bash @@ -0,0 +1,38 @@ +#!/bin/bash + +# Copyright © 2018 Renesas Electronics Corp. +# +# 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: IGEL Co., Ltd. + +# By using this script, client can receive remoted output via gstreamer. +# Usage: +# remoting-client-receive.bash + +gst-launch-1.0 rtpbin name=rtpbin \ + udpsrc caps="application/x-rtp,media=(string)video,clock-rate=(int)90000,encoding-name=JPEG,payload=26" port=$1 ! \ + rtpbin.recv_rtp_sink_0 \ + rtpbin. ! rtpjpegdepay ! jpegdec ! autovideosink \ + udpsrc port=$(($1 + 1)) ! rtpbin.recv_rtcp_sink_0 \ + rtpbin.send_rtcp_src_0 ! \ + udpsink port=$(($1 + 2)) sync=false async=false diff --git a/doc/sphinx/conf.py.in b/doc/sphinx/conf.py.in new file mode 100644 index 0000000..ab8f9bf --- /dev/null +++ b/doc/sphinx/conf.py.in @@ -0,0 +1,204 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +import sphinx + +sys.path.append(os.path.abspath('sphinxext')) + +# -- Project information ----------------------------------------------------- + +project = u'weston' +copyright = u'2019, Weston community' +author = u'Weston community ' + + +# The short X.Y version +version = u'' +# The full version, including alpha/beta/rc tags +release = u'@VERSION@' + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +needs_sphinx = '2.1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.autosectionlabel', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', + 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', + 'breathe', +] + +breathe_projects = { "weston": "@BUILD_ROOT@/xml/" } +breathe_default_members = ('members', 'undoc-members') +breathe_default_project = "weston" + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +source_suffix = ['.rst' ] + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = None + +# default domain +primary_domain = 'cpp' + +# To automatically number figures, tables, etc. and be able to reference them. +numfig = True + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +# html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'weston' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'weston.tex', u'Weston Documentation', + u'Weston community', 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'weston', u'Weston Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'weston', u'Wweston Documentation', + author, 'Weston community', 'Weston Documentation' + 'Miscellaneous'), +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + + +# -- Extension configuration ------------------------------------------------- + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/3': None} + +# -- Options for todo extension ---------------------------------------------- + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True diff --git a/doc/sphinx/doxygen.ini.in b/doc/sphinx/doxygen.ini.in new file mode 100644 index 0000000..cfeba09 --- /dev/null +++ b/doc/sphinx/doxygen.ini.in @@ -0,0 +1,2480 @@ +# Doxyfile 1.8.13 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "libweston" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @OUTPUT_DIR@ + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = "rst=\verbatim embed:rst" +ALIASES += "endrst=\endverbatim" +ALIASES += "rststar=\verbatim embed:rst:leading-asterisk" +ALIASES += "endrststar=\endverbatim" + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = YES + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @SRC_ROOT@/libweston \ + @SRC_ROOT@/include/libweston \ + @SRC_ROOT@/tests + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = NO + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /E{TXW$$ohJ-O8AO0NrnXK@BTO9Ky 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 w [label = "weston_head_is_connected()"]; + c << w [label = "bool"]; + c => w [label = "weston_head_is_enabled()"]; + c << w [label = "bool"]; + c => w [label = "weston_head_is_device_changed()"]; + c << w [label = "bool"]; + c abox c [label = "If the head needs enabling, create an output."]; + c abox c [label = "If the head needs disabling, disable the output or destroy the output."]; + c >> w; +} diff --git a/doc/sphinx/toc/libweston/images/react-to-heads-changed.png b/doc/sphinx/toc/libweston/images/react-to-heads-changed.png new file mode 100644 index 0000000000000000000000000000000000000000..ba1b8132b82bc609283cbe8a8c35bdbfc090fe03 GIT binary patch literal 27241 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

B{ID)0=_E(+a7O8Pc?P@(CePraNrRS8~36&9qs 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

!+x^QFxqC=C?Ap9 zRF<)e)Zv&7PM=X9%u0wbG_nHL1Ke01kc%=y^}Occ(sFM|6C~5gfyz=xJi;tMxw@cd z-KU5zzQ=90y!|6%?cNsog^P}r)I&PWrLyZhP8{a9@b;8!H}j{0NHxYvKx$-odKF%v zRl2HLvE0?l=2S@S$p#*P6IjUu`gMX7`HVpH!dyb_D)dnX$w&WY)*wvI(|c*inMC9x zlg8YjwAR$a9VMC5w1o&~#iYNY%*;E|Hz~Y=&GwDkla!AtC)+lWm?L@lN=?3f2g_v{ zzT9{R&Dqs!Cf-kP=TG{?xWS#6IK7QjVu-Els0UHE<((2diQh!*S@B`b<-a1uU!-NAR7X<^=6rIBtM|T2~^CZ zH=emBN2R2fSgmpT*D189rlB8_w9hU*I)SZn~vBwp~VG)ei1`gmQi|D|K1CP4&9Mju@qSfVGdv; ziMLmO*>@7gJK2Y>p&R!rTQzr){UD+OLMhoW-HMZ@<=7e#xell-2yWblSYH~vj$P>e z&kItiI7Lu$QPKJ-%=npIbj2-9U~L2&Gn(Opc_3x8c?#Nt5AEFwG*wU$Q60oAFeK7z*oeaTjo*%c;&c5w)L&OB5$?G*w`Ar7LfY$*tMB zb2nNwjl{LR9izSsO4*`uwxHJU#!e{=9#~&JnlP|z;x*Wvwn9sqFg_{Yl}Tg) z8#N{RQ4|!Hv!GU6@zD$sN?{j99p1wXr5Pd-D9Rn<%2$c=JcJP&ilT&2VMUit!_`|K>d^o7t|1|vTVZ)(LIQGaOdE)HbV~>~Z0dHRX?ck5SLC0h$ zz|hh>iu<5Ye_CbB7QCcCVc{%3AYQWMeBfXE#%EYx0y? zdiIA(l?vehaA{Vd%cO(2OJ^-e;>OJ{!?8trPbzBTgnHTj2((>yUzPwt+liy*^hkl2qyE0|TbC+Et@7@gVWuYYqh0wd2R&9o?Ah;14JJNU8 zWrn$AX^wiAuuX>Q&jp7?yC2{Ko~X!ijf>2*ZGUGWX@0rPhY zZUDnXp2Zh=MC1m3(H1aB6rFF>%hYJpk}kF_$vOXd&@q$b+L0cc66K_Elm2{0uF{3| z41Y*yp*3~M6;^!}N)!E;Z?s33^g{;97$-#tKgOT)g_v7QHTil^c(<+dV8cy}sa=Hl zfyfQE*l`m7{kB)}_MBAxV!lp_8=vDrnX?Kl;<9<9xf<3tSmI0TB_lmevf?Lthjo2) zee?uSnquo`3tRux7ACgXJ^L%N-N*qON-k|R$tshbV?1*TM-&9`1zwjU7AWl3(oEKj zd-tta`Aq!Pv%BBFeSY`v9A0?#*oZ0?i&{eFWyWvf=?9Y1`k618RSoecLZH~wqjTiX znMpF}kphlXrM=!cOspPD(oI-KJu zd?U2Y?{TQm|wP5Pthy2)P4X{-p~}%N55wovxmDPj zv*p*X>E9;FZs!@FHl9bdHH@ zz!EI0=hPmM$ea)>lS-bEbi_107Y!~~ zu%0f&I>z^Zr_s52>7?*FJ~B=?v$(fn(sJdCOZ5ts`G;pzloxSjO|$PVB-LHm%~u2< zfIzG=@o}c|K2TXa{q6xDltNjc%Ueg$Ux07SI~fh+Z{;vI($h20ndMno9Z-rCptM{j zEnFvNwzmENtl4w5j=HKUuexSFso{4GjLm*q?T&=bsOxbs-Z=85F7NK>CrCeYriQKP zc8Ix@mGT(B-vSD~G+B0{Ey*t9;^LZ&Dv9S~_W;wHF*aoz0PaDD^n-~aY+KZg2?$;J zoBM`bJ3V4RCXn$N+OM)zlvG}32g1W9dJ#{ZHr(O?97K{L#Rxd9jm|AP0U$P^ei!;u zi2oDafC}AjfN1};I#y*>QLKt-rhE&Gj1*VZM90vzGZ_c%K~Tb_1;7=RPToQqMqjA; zW9lhgFzuHF1Ka5)SJacvX7vi=bvqdP`dVbHWP>uRhcBtw+p*+Bci`HWu8f5_&c;2C zK+mCX>33TL1o|GsT1zaynUI9-lX^n!(jv=nHI-Cb5JEp*Xi1{Fm9pmp7*N|frB~#6 zX&R7%9L;MS=%-s(+pC)5@!XF4wgJ`lB_zl!ZRtAh} z(vW+{DV}OMMXtIVG9L32*`4qk+0!^UaD<<1o;m&0fkgSAid8bHwAU%^K>>IT3Gt_| zh+4X|{kK(YS$Ewt-qNxuC}b%VzolYRs58`nU-oy_^z#wavqC$25!`ubw>o z%V1j4wYs^W7tRxg?s2N$7qwwFH)7kh!VQ%rk`o*9!qNVZOPF5{*l}1Ol?$# z=F&;tPb<^f{6)aPIaHZ?L7Ocrb(HLVR$b(r+}=toP+tF7F8c4u1!s&*}yQI#(JC_?HxTnqn67@p~q1pzf zdKL|DH^*IVnvJhL=xq_>?~X#z)=)X#rcjI%CT@8n&pNL%w^T#Ep{r%<2g1JLGVG$$ zA8xDtOY#TF?3n**&hJWz0&v|EOh$eatUweq)H1}Suur}kMETfW zui^m9RzJPE&=9E}S*gA5osmo(_SJoiK|`f5tV`a#pvWbMN1}jKn-pS(%I zs8k>H`h^M)2JIdg@#HgxNE&P{L1{#YW=i!U-rAzoEiNJn^zOKC)@^_-#&s1T90$)1 zYX1c5fppK1$v0EnRPL+w$;#Eh^H7-Es344zJA=*wUhFw&3!G%UL=!xH)6d5LbRk53 z|A-g+kx>~DnO|aG@9}-&=`P}(=EZk6Zhv{7zg1ptxpV8^LvQ~A^kI2pN1P>e-x7S<>fypIGa~msnhnm3M#c$mz6FG zP&B@K|Bf3Cu0opCqo2&D#)kFRd0a0~y?`M%xX=@0l$2~6^W-%k+;)~Ns`V`a3((~# z8e2%mx#P5C_n5J*JP6pjf{M)CMe$%I>U)x`1Mqq01V2#!6!Tw7Z0~*Gk)Wc8O$t>Q zB))x~_y|EXxL=(SI3N{sSqG};F(+FbnCmt1Y^3w<71r*g7;vLg^F*&uMhQ?3&-ejm z$x^zNe*=)b9_GI9@~RC`F6;6{ZT3~X#{CqdHcFqM`21)AAg)oN6UqlM45t%qagMlE z;pA7(-`mq?!P{GwF~Fbe4lU>o6J8*-ig>=B9Yhz%EOF^Ir00$@L77`yW@iQ?Qz@{7 zU=Z`(?Z^b|r*Ge$Zp*vQ9ML2Yq+wm1m3ipQg*7$FdBFU!=k}MbBMH$g*psiHfI|69 zGpe8`(>~XoYLtPkGjz+YuDrZBhInb}JO1~L?euc`xf8H*t&v-a&$?W+0NjQ95Xg+A(i|=aj-R(wMn&6mVba%(Mv-S@O`<*O8kkI8t2?aWs z>ENXib#ZI2>ddJadZ>P+;ILe{eTRdGU8{sPHz; zzGbP&lDl(mx#RyI|18sBF+rc%NxM$-c;FU(^B95mt$A!K0LwCf-2RVE4mK?wnSvOX)_(Wv*#l#`*|~k& zSA-;EM>TzqB`5lJWN32F{`(*0UA)&NTU-54t@)2E%Y(n)|NAUMZsAWR(if!-5Nk5LCH%9Xe*>sSWWt@6 zYBziuGjbEQ9C;;;hsO^estO&JHvi)r;n+4o zQT;g`xVbX&d;+pD9-(PM9c3cY+9ReL0Jdh=Ra%mWZ2O*&UA0iCS8kzC?C=$?fAc^t z@l8`(FclvOB3iqP54NWKPQ?S2y=R)8CKSJigm2kyW;J}+&7Xb#x?!d%u%E?C?x991 z$$D9f_X$xf(WHVHo0$k)&WYdU$n#M{bMJ+B&0oH^-w3z+pfx*#{0-o-x7h4TzQK7! zkugB#1gzRomXe$rkCWBgJU>m8AhPeQmBD)v*uOtP zX2l$@&O_|#b*8S~i@7spg$&<7!XVvCpTC6M1yT=2&~-`|A+@*)Dr^CzJzPO>`hdFg z-vGZ};m-%${C4)ND}UdteItj3`O=#Iub%kcKU42n`nmSm$QfNyuaT{Cto#|>GnvPZ zlWq@VzvhIrw&9t^^AWzgdjUX8Hd;@@t;#~tiO2X>II=-WAo^%P0vbt$HD$A>9_4uE=6p|rQ(K~a!hW8zy0AX-)& z%YkF2YAB5xAMH>gNr`_}@~Hf#`s)r5BpBZ78Q6XxGh7+*es($Wye7c zy{eb8JrrO1W=GdI2*RA15*W+g0-#Yj*nfVtc=Kwc(PWHMSt)i`HF4nBAy{TudZXexi`K+;ChH=+PqAd?gJQg!&C@5=? zHy`Py(lDJ}_b$2E1kJ+UbcFYhAkaGHd`S$#5~%C08H<;zVB97)G6thM^1e{s0CeU7 z2c=C|y9vhKE&Jmme_jJ+Fv7u9qii+JY8|ND}vh*sarNe#{Urcd>iOxDj zd&<2LR+Gmj_gPfJdyE{Uz>)S0IC2SwH z;^t+&2h^6CEY-jgzo4HHB|aNv-jG`>_B2xz)Z=KY5jz&Q0br*15b6p^)FAESVMh_jUF!8P+yIMA!ZvSphJ;F z;3wFte8W_{TsIwFOm&gAw0;r701MKJur3zq(k~;L8@NgV2jB1h%igoJTVFHlmE*6k zr_CPafST3eS1Fco0lZn0n8VZ$$W^z=JIy|eY)TxOBxh&8TV)$I*B#(mtf4)wuS~25 zQ@Xml3>qq-!?C^oXTCvRA{606*fIz}Vs*KV3>JL&1{JLj?I zAHC4GsXD-jMqSxGl&9-N_Zw8*Iy`;PL0A^!wR86}&X*A(wQ)LI&ZoFQ4T2dYa!iQM zdLcf_hewVS3j>y7J2YAPQ50v#iTz0U)XEu3!}oe?=# z4P#3}YmvV2x&-Wf=w|O2foV$w4d34M`6yHixNL}YM;0mczjq!crhK{PLx@pGeaA?S z5>a2Tw!@D|xyW)3t~sQmf7`}j^vAFNG?IAtiL{ll!Zm%u>)u~o7>GM~OzCSApus=J zG4Q-Q{>zG>VI@tDY8x=!;EZ?(Mffrh>BY5C#zHEWps%<0M8N)+l^ zlUz}&V3^e*UdtZ+yq)*g?R8)j+z_ua@y1m2Pt999JM&BP;*+u#c0L_`CBHZQOjqkO z{D%ozeTp{?yN$GBM5OYo#x#3~0^6XgSE?>nTE=ffX{A74a_goat)TVx1-?&|-@@0P zocEf7xnKS``Ty>B-=_Q)u>}74eY1)ZA)mq)O=I8Pz`X-p#>OdNHcrly|J3g2?;3yF zF8fRwzji=vDv{hHdN&cWx*vTsbw_i2-2KBkPfCw-Z94rJ_9=&l81oencjnjI`qpsP zBWsAm@Pk|~UWqyEv8|K8d=t=t?s6mb=_G5*ym7e8p;qg%p7wV5)spEZ86wOi5kqwM zqBnC_U%_q735&xUr9n+-R{XRX{RgHDZ~dZuy9ZLO_+oR#VveWCmdVna*_fH8i4uu1 zp5=sA>-&Nm;f1M4Wn|`B{T0qwT_Tq*MjW{M<0IV;fpB#Z;vDt?f_v}Kq-uqy(;evh zBG=zLtL0gbXUCbOJr%NPsUT$l9c^_sfFQuPZRKG=Y^o@X@b6I3sJ>1uE|$dWlMVx?Hp7tj3ld zN65&=r5Ld%z##Q>m*owragRW~bOJTnNItP`8H4OPLSf`gtm05Gy8_Uo4%s%SR@3KX zpk0?gJrEVa+gYn0rNPNDO6V6>mW3i0W68TY#v~fr1m@+mTc!Cs&f@89$x3 zFTj=}^?;vyj7iH)1&%++79qB!cImLjoG$amrOXrMFGa6$M3cwMy-DBl&d_zzQ|#|Z zkQ9o4q}%xQ`kw>xZzC-AK##6VO8YqgI47}ysYGX*FyXnBHm|2^)py6F0iO{c1d zsFe-%9zd$T+O)}_@Fr)+<)JEoy)x_5Kg8swx)B>Kn?+Cp&viELuwHw99?=FC?I51F zhZ7(g>1rKJrC`lGA_QTu1$YL!j%6Oru7ltjQLp5cWp4^{;U(m*-r{XKbBnBNK+YXA z3Z@eqE{m^^xgZ9(C-a0azSYJ8*o%tyd{PUuh%?8^g@@8ZW8#fEYEGVm$?6YRU=lu6 z`5DHorX6tb16^Kc7#C=mPlBLlJB(=nUCcozmZ4cZaMo+lgHWhkxNHdOQW@3=H9m>^ zssL#B6Kk@ttrjN4B-T|4JrL9wlp7E+KDIQPFhbXf-*a#(hdc?75Wik2LP0(x zHp?MYl+4ttbF(3nT_s|YV*WKz-h_)70&l$}xO?>@DFC5y7-44PHV}%jrmAy_m+C7K3~T7==^uT@RN#RVk&}hk=d(b#MFW{>`QTd z^qME$2mS?RnC>kg1TC%xu?_5=7qJY+=^kM_?V!-KOk35vvJbb50!6VYqU0Aj*Gy8* ziM{mOSprFPgCnEtu7UGTyc{RjNz)|%9-hfga_n^OQL z+?z>$L-Zbl3So(_Vb`{Gw6;lMObjC9 z?XBUgTB5wQ7_iJbl#n{MqX1U-?KPpWip{uY(Ws|gP871Senp(ti3&XZ)<%W}OJ5?a z5Kj>r3dJ5dh-#w#ntE zPsuH5_wILrYz(iemk}^oP#@H$mrUc=YS5+8XqTj{)?@c1tqkuoc%SK1UvTkWedZUx z`og(Vll#xH21e%6&!1>eP9&x0SeqOB@m=Lw7tdDE?PB$t@-XT4W#XkkshD8Db5=kY z6iEFC9qQ-|5I&+|>CD8(hi5&43vyXU2Zx7D*;h6rKa|4+qfXo*AmkdVzuzH z?!j7;M`6zhPnx%`3yT5FJ{>VNf3e|ZqRnHKn0n@`?;jl~F)G=xT5TjWGaMs%3@h;l z-jp1aU>J{NQ);ynO?w=RZE^-wQH}CD3*V^yYQ(i!>N0-Oa_PQU-Sve^7_ZWAE?7m5iid9d6lc zaM~#9MG-Vc^8Uwug-V_2ac^6ydTlk{0r`hy4EvE7+N|yiVIq?59O-cX+qVl;4;v^S zhi8{c%eL5OuECrl>t1Gc=6L@G(EP)=yt(1`EC$4p5Yj6gY4A|?X~5=p+n*}-J72lC zRTYIExDovXb7pIY&Sb4N9rv!AR!VGefklR}!So>*Lf2#=(EBpr=_gCB7Zs+C0b{!G z;H<(GNlzl-UHfH7T+3o00R4u#;C<(I4(5hdP>jUo?Y3Ql5lz;ab)Z)-L(w7;WwBQS z&Ot;2cDWFUqj8R3&QyVQ=|l#NDjKBfvRU^Z2hH-Nc~Zocn}qx9>zQ;#=uE{2JyFb{ zXz>6}OBO_F6LiCXLA-7kNlmjXBea~`b#vDzDpXyT$*&pMmyy<^ssdPpyNxoRQmYxa zNWjFiNtZlCN7Cc?Bp<`D(YSoFyD}H7Sq^C7C`3JNj+$aFi3_=fHX(y6n1r`TDe8c- z*O)^+pH!Oc6=V1ss}Pn4Yb3fE1;x2UsRlkk>>=X5zJ?aj*&~``9y3u&fM$jLo!B;? z1_FHLz6f^{K~@aO{JVeymcWdLM+W&CaDQ_D;Ul@&McRoYSuCW=Nrdtt>y!uG+zuFL zk03UJ)hiy+kLAh75c`K_#fg;BFSC&?yE~Gv$?1!`(asXU+pYj zpnjojVIm_RCKOm^azN(# zyBg7>T^hjwz|PnQ^vxw?+*x$?y9k*&BZWArm8sGEDV^3P@}PptL?gSSEunB>Xj-Av z8Iw_$wx@%Hab2U?J;0qqs^&`hm{FnP`=Z1w@jdZztpk*=^#B$Lw5_S3O2D`m$aiJ49(-6r90p4D?d*D7)t+GrmtJ*SWR#8z?$yG)~VCRnoKq-i9lBrlQ zg{w&0^QkI9{efLkX6a+BED96y@(&y%t(A9mlN;L6Sa$P9$o!9V$n(FmW)gf)Tv$lu zIK|~+Ov8-;IoG?M63}pF%A+nxIvkP8?ohrq+VKLG#w+x?SQ@P`E$DCHmr?!Q2u-o^ z%@wIm`xfu0j^E3>BBkk5w(octv6M`tJhRuELB@Cs0C_GujY(R%&d zOBH&I1?W-vKgCvc-#sFx0%P5SKjg{$C;#R@zfG|e#V*;|A-()3mu9%{e29acroD4) zOY;UkfA%TF<|l?*{hF7Th(+pTiFhHV6x8c)(GnI^_7BS6bR`3M>(~yMX)EP;Jj>GU zxic_@G6d2?LDXrl?V!DAs}IvJPpn;HV#`t*T4`QVKlZ>JIB^JMXo_wv14gPSRbA!) zFF9}UqXjaMJKuf-v@SaZX&B!0`<4`V8=0A#?q^UV5A?;y-$)%lxgu!0T)7mm9$srX zx(cl7V)ShRz8=td>aKD@ZI296r3p{JII8+c4Uyc zc7Q9Bp4=8s1YhfLi48Fl1+bJCMX@V9 zAQ7XCEWSvlC_Kybm9TI@;#~9)%kd(J%RTmXG@GmFwJR&`)X(#rbSHG8I5)uYHtxh- z)*IYD-Q_kcDAJb@;j*ErE}z6%Z}$rIE*}9X5@i$Cy^iR?q?0ybCPw~jVKrT5mPIQ) zx(U)MId4#4J<4P_(vY}~x(GvQ?$*;%L#QX%y5XhNsH+cE9zrG2qITUc*gTUn^!IY( zZT@uNjcMhM!a8O*RjuC1X8FKaaxAV2wXdZCv^eWD$7xC5OLf)ryihJ;xMlN3IlTMQ zBR8X_x{R>=*ZgSCuB(dq=AyTR+PAprXZ2p_m7I{Fp-Kk(P0yR2Nayj)bSBUhSNqj) zP#1{OVhQvp`WF&weeWX{B6?Vx-}(Y?9v^HKW4;3G;@BTI;Y?5jy^iR+))V`o+41p5UNNo^ScDU zT7SCfT(G{%OTk~B;b~%c;?KH|(PB z?ip)3J(934t6V>Qm?}f=9_Vs${yzjZI&mL+zh8eaBF^z3ZEVQlb)%2*6TX#9 z54FEvc=4hA?<(?FtaEkx>-w_xXWh<3eA*XmtP4PR6JueTU^-!l!dv_T+6&7|M`VO}6jtOvHT`nnXr=)2-#E zEp!|}{|HIP_kD_~**Kfu^DU*WUWpaJQ*ZLn%2%GMF=gvXe|@O-xU)uk&OA$hMK-rT z0evIqEn?cbP*5|1`amt~p?tX(j4fc%RjgR!_ygPMr|f1UV-OM5Of#+SE0lk~_xFm# z2-VZ{`Tbdw@$@`yDILG!J=N)ZxGQMQ^khJh0;3{%UgPg56Dv+3#&zf0jjX#rW81%? zANa_9`?95(G+ks?Kq;}QOIS6t_eb(|6E1fWjZTsN<-OpQ^kOFeg6B@;S|I8GunnC| ztUH5}u%M)V+kge7_#}c#U(Hp;oW9k{d})CP8g4=-`NZ=JcSBhbctt4KnuU`km7$z6 z<7O838=wTWWG4kQ)?mxaf<$zc#znj#$13~L6+tgslI3bN&r(HS83wZ)0U8Pu#HfcY zZ2=rV9Zhve*33_NIT*0%&~io^kxvB@ooty3icvWdh!bdqwAGZW%R(k;<2*8qs49$I zC6TW)s&#>R3R84TCcI{PCw_3IJyv^u9!8Vuu%l$FFS zRPR#7#h07mt|XOlf;(Z2bg>=Hy*0$)QcKWkXtJvaT2|Fc5Mg7qcrJ^8(#p+w zXO%n}3Q4nzoyj$VeyWXFGyqnyx#WSV6Lbn(w7ne6h>g9S?fvGp>mM`mEZOg+V#DHR zr7Lih>3ckJQiCfDsI@kBg`&yp@+P2iQ|>kvK*d`DJKDMu?kq}O7HuypmPN70iXN5S zqG(t0jixH{To zR&kM+xNBs?--t9X`aN+iPRAsv#_tTBHyj+1RoB+ zn*ZI*{tT1;FksGiybi?-4O}5{uBmNbJ77zFPr97A6s2N1pR-#!Ah|2TY&m(PH7Y5G zMC5~xqB*c@xQ#tS{T>!7!XV*?x4JRcNW&39S`%L0t0d3&me=A*x^;eV^Rj@~T4rSY%jT_thF+Sqr$RXy&mjcdA+p%Wa^+d{3Z4UIF564KkU4`^Q+;ukD;mB~J8b&4;$KXr{r zBNdW|yG5pIr5!X9Q!(q(;I(}7ZUrHVAT!a53o(lmveBO`qM45rFtZaatTEm3$Ay>= zOE%@--HuF|i;Xs6aQ+f|VoN3i&pW81n01{Wh^pT zCmHD1(q_bx?2CtU0NWPe*F8TXud%$5k26Zshpo=RFT`s%l?{pUIVWtFS@wOnFw?jf z`0iguuYV}{mQ=YLGwiqRmh;xIgV@V~+{-Mz!3ny-7ZaShs%r?ZP)^WuQOy;eV+8nS zZCmatE;B_XMl}*nLrlYjf`RpE&Jy9QcEF_CaG&MF@@*tP8mZ_V6iJ&ddRUSZPu$bc z=*-&>?F6~88xQkdog;ek56^aqxHkOGBe8rn$LJ-6w@ZDqrTjI=~pR0ol8x+PrJ z{}!m47RTD@H-~q25IZv@aV3w_V!6DBP%5hY@x&oZdv$dOp}Zc;Ua$*Y7jszuqkf<=8HPrc>=b(4CQl2#1m z279jC6UN`=VAIFIJ-XJky?OEe2OjH7T5zw=sB3<;`KRLR!b8vwZ+Wf-BaJvk@=D=a>Xd+kur-p7L?7v@Sgh}XU+wBhbF z&7tj&wh$yObCUdgxtTQEjC~k287)*f7hj#253r8UN+12gu*_}S1jE^D4pgVbr%N_T z&ZmBQ{EfQ8CPGca71n&YBe}o%fmU;H;9J?Mw zmGfxjK#Tf^l*x}SqXUT|E$K~}A3ZdD*UF`Dh@Jx&=ph zL?E+r$K(6!Lzu@a{fUti#6oZXM4%Pk#73Jp7e|HX^{kHUf E0q%_0$^ZZW literal 0 HcmV?d00001 diff --git a/fullscreen-shell/fullscreen-shell.c b/fullscreen-shell/fullscreen-shell.c new file mode 100644 index 0000000..17b25a3 --- /dev/null +++ b/fullscreen-shell/fullscreen-shell.c @@ -0,0 +1,941 @@ +/* + * Copyright © 2013 Jason Ekstrand + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "compositor/weston.h" +#include "fullscreen-shell-unstable-v1-server-protocol.h" +#include "shared/helpers.h" + +struct fullscreen_shell { + struct wl_client *client; + struct wl_listener client_destroyed; + struct weston_compositor *compositor; + /* XXX: missing compositor destroy listener + * https://gitlab.freedesktop.org/wayland/weston/issues/299 + */ + + struct weston_layer layer; + struct wl_list output_list; + struct wl_listener output_created_listener; + + struct wl_listener seat_created_listener; + + /* List of one surface per client, presented for the NULL output + * + * This is implemented as a list in case someone fixes the shell + * implementation to support more than one client. + */ + struct wl_list default_surface_list; /* struct fs_client_surface::link */ +}; + +struct fs_output { + struct fullscreen_shell *shell; + struct wl_list link; + + struct weston_output *output; + struct wl_listener output_destroyed; + + struct { + struct weston_surface *surface; + struct wl_listener surface_destroyed; + struct wl_resource *mode_feedback; + + int presented_for_mode; + enum zwp_fullscreen_shell_v1_present_method method; + int32_t framerate; + } pending; + + struct weston_surface *surface; + struct wl_listener surface_destroyed; + struct weston_view *view; + struct weston_view *black_view; + struct weston_transform transform; /* matrix from x, y */ + + int presented_for_mode; + enum zwp_fullscreen_shell_v1_present_method method; + uint32_t framerate; +}; + +struct pointer_focus_listener { + struct fullscreen_shell *shell; + struct wl_listener pointer_focus; + struct wl_listener seat_caps; + struct wl_listener seat_destroyed; +}; + +struct fs_client_surface { + struct weston_surface *surface; + enum zwp_fullscreen_shell_v1_present_method method; + struct wl_list link; /* struct fullscreen_shell::default_surface_list */ + struct wl_listener surface_destroyed; +}; + +static void +remove_default_surface(struct fs_client_surface *surf) +{ + wl_list_remove(&surf->surface_destroyed.link); + wl_list_remove(&surf->link); + free(surf); +} + +static void +default_surface_destroy_listener(struct wl_listener *listener, void *data) +{ + struct fs_client_surface *surf; + + surf = container_of(listener, struct fs_client_surface, surface_destroyed); + + remove_default_surface(surf); +} + +static void +replace_default_surface(struct fullscreen_shell *shell, struct weston_surface *surface, + enum zwp_fullscreen_shell_v1_present_method method) +{ + struct fs_client_surface *surf, *prev = NULL; + + if (!wl_list_empty(&shell->default_surface_list)) + prev = container_of(shell->default_surface_list.prev, + struct fs_client_surface, link); + + surf = zalloc(sizeof *surf); + if (!surf) + return; + + surf->surface = surface; + surf->method = method; + + if (prev) + remove_default_surface(prev); + + wl_list_insert(shell->default_surface_list.prev, &surf->link); + + surf->surface_destroyed.notify = default_surface_destroy_listener; + wl_signal_add(&surface->destroy_signal, &surf->surface_destroyed); +} + +static void +pointer_focus_changed(struct wl_listener *listener, void *data) +{ + struct weston_pointer *pointer = data; + + if (pointer->focus && pointer->focus->surface->resource) + weston_seat_set_keyboard_focus(pointer->seat, pointer->focus->surface); +} + +static void +seat_caps_changed(struct wl_listener *l, void *data) +{ + struct weston_seat *seat = data; + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct pointer_focus_listener *listener; + struct fs_output *fsout; + + listener = container_of(l, struct pointer_focus_listener, seat_caps); + + /* no pointer */ + if (pointer) { + if (!listener->pointer_focus.link.prev) { + wl_signal_add(&pointer->focus_signal, + &listener->pointer_focus); + } + } else { + if (listener->pointer_focus.link.prev) { + wl_list_remove(&listener->pointer_focus.link); + } + } + + if (keyboard && keyboard->focus != NULL) { + wl_list_for_each(fsout, &listener->shell->output_list, link) { + if (fsout->surface) { + weston_seat_set_keyboard_focus(seat, fsout->surface); + return; + } + } + } +} + +static void +seat_destroyed(struct wl_listener *l, void *data) +{ + struct pointer_focus_listener *listener; + + listener = container_of(l, struct pointer_focus_listener, + seat_destroyed); + + free(listener); +} + +static void +seat_created(struct wl_listener *l, void *data) +{ + struct weston_seat *seat = data; + struct pointer_focus_listener *listener; + + listener = zalloc(sizeof *listener); + if (!listener) + return; + + listener->shell = container_of(l, struct fullscreen_shell, + seat_created_listener); + listener->pointer_focus.notify = pointer_focus_changed; + listener->seat_caps.notify = seat_caps_changed; + listener->seat_destroyed.notify = seat_destroyed; + + wl_signal_add(&seat->destroy_signal, &listener->seat_destroyed); + wl_signal_add(&seat->updated_caps_signal, &listener->seat_caps); + + seat_caps_changed(&listener->seat_caps, seat); +} + +static void +black_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) +{ +} + +static struct weston_view * +create_black_surface(struct weston_compositor *ec, struct fs_output *fsout, + float x, float y, int w, int h) +{ + struct weston_surface *surface = NULL; + struct weston_view *view; + + surface = weston_surface_create(ec); + if (surface == NULL) { + weston_log("no memory\n"); + return NULL; + } + view = weston_view_create(surface); + if (!view) { + weston_surface_destroy(surface); + weston_log("no memory\n"); + return NULL; + } + + surface->committed = black_surface_committed; + surface->committed_private = fsout; + weston_surface_set_color(surface, 0.0f, 0.0f, 0.0f, 1.0f); + 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); + + weston_surface_set_size(surface, w, h); + weston_view_set_position(view, x, y); + + return view; +} + +static void +fs_output_set_surface(struct fs_output *fsout, struct weston_surface *surface, + enum zwp_fullscreen_shell_v1_present_method method, + int32_t framerate, int presented_for_mode); +static void +fs_output_apply_pending(struct fs_output *fsout); +static void +fs_output_clear_pending(struct fs_output *fsout); + +static void +fs_output_destroy(struct fs_output *fsout) +{ + fs_output_set_surface(fsout, NULL, 0, 0, 0); + fs_output_clear_pending(fsout); + + wl_list_remove(&fsout->link); + + if (fsout->output) + wl_list_remove(&fsout->output_destroyed.link); +} + +static void +output_destroyed(struct wl_listener *listener, void *data) +{ + struct fs_output *output = container_of(listener, + struct fs_output, + output_destroyed); + fs_output_destroy(output); +} + +static void +surface_destroyed(struct wl_listener *listener, void *data) +{ + struct fs_output *fsout = container_of(listener, + struct fs_output, + surface_destroyed); + fsout->surface = NULL; + fsout->view = NULL; + wl_list_remove(&fsout->transform.link); + wl_list_init(&fsout->transform.link); +} + +static void +pending_surface_destroyed(struct wl_listener *listener, void *data) +{ + struct fs_output *fsout = container_of(listener, + struct fs_output, + pending.surface_destroyed); + fsout->pending.surface = NULL; +} + +static void +configure_presented_surface(struct weston_surface *surface, int32_t sx, + int32_t sy); + +static struct fs_output * +fs_output_create(struct fullscreen_shell *shell, struct weston_output *output) +{ + struct fs_output *fsout; + struct fs_client_surface *surf; + + fsout = zalloc(sizeof *fsout); + if (!fsout) + return NULL; + + fsout->shell = shell; + wl_list_insert(&shell->output_list, &fsout->link); + + fsout->output = output; + fsout->output_destroyed.notify = output_destroyed; + wl_signal_add(&output->destroy_signal, &fsout->output_destroyed); + + fsout->surface_destroyed.notify = surface_destroyed; + fsout->pending.surface_destroyed.notify = pending_surface_destroyed; + fsout->black_view = create_black_surface(shell->compositor, fsout, + output->x, output->y, + output->width, output->height); + fsout->black_view->surface->is_mapped = true; + fsout->black_view->is_mapped = true; + weston_layer_entry_insert(&shell->layer.view_list, + &fsout->black_view->layer_link); + wl_list_init(&fsout->transform.link); + + if (!wl_list_empty(&shell->default_surface_list)) { + surf = container_of(shell->default_surface_list.prev, + struct fs_client_surface, link); + + fs_output_set_surface(fsout, surf->surface, surf->method, 0, 0); + configure_presented_surface(surf->surface, 0, 0); + } + + return fsout; +} + +static struct fs_output * +fs_output_for_output(struct weston_output *output) +{ + struct wl_listener *listener; + + if (!output) + return NULL; + + listener = wl_signal_get(&output->destroy_signal, output_destroyed); + + return container_of(listener, struct fs_output, output_destroyed); +} + +static void +restore_output_mode(struct weston_output *output) +{ + if (output && output->original_mode) + weston_output_mode_switch_to_native(output); +} + +/* + * Returns the bounding box of a surface and all its sub-surfaces, + * in surface-local coordinates. */ +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->width, + surface->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->width, + subsurface->surface->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 void +fs_output_center_view(struct fs_output *fsout) +{ + int32_t surf_x, surf_y, surf_width, surf_height; + float x, y; + struct weston_output *output = fsout->output; + + surface_subsurfaces_boundingbox(fsout->view->surface, &surf_x, &surf_y, + &surf_width, &surf_height); + + x = output->x + (output->width - surf_width) / 2 - surf_x / 2; + y = output->y + (output->height - surf_height) / 2 - surf_y / 2; + + weston_view_set_position(fsout->view, x, y); +} + +static void +fs_output_scale_view(struct fs_output *fsout, float width, float height) +{ + float x, y; + int32_t surf_x, surf_y, surf_width, surf_height; + struct weston_matrix *matrix; + struct weston_view *view = fsout->view; + struct weston_output *output = fsout->output; + + surface_subsurfaces_boundingbox(view->surface, &surf_x, &surf_y, + &surf_width, &surf_height); + + if (output->width == surf_width && output->height == surf_height) { + weston_view_set_position(view, + fsout->output->x - surf_x, + fsout->output->y - surf_y); + } else { + matrix = &fsout->transform.matrix; + weston_matrix_init(matrix); + + weston_matrix_scale(matrix, width / surf_width, + height / surf_height, 1); + wl_list_remove(&fsout->transform.link); + wl_list_insert(&fsout->view->geometry.transformation_list, + &fsout->transform.link); + + x = output->x + (output->width - width) / 2 - surf_x; + y = output->y + (output->height - height) / 2 - surf_y; + + weston_view_set_position(view, x, y); + } +} + +static void +fs_output_configure(struct fs_output *fsout, struct weston_surface *surface); + +static void +fs_output_configure_simple(struct fs_output *fsout, + struct weston_surface *configured_surface) +{ + struct weston_output *output = fsout->output; + float output_aspect, surface_aspect; + int32_t surf_x, surf_y, surf_width, surf_height; + + if (fsout->pending.surface == configured_surface) + fs_output_apply_pending(fsout); + + assert(fsout->view); + + restore_output_mode(fsout->output); + + wl_list_remove(&fsout->transform.link); + wl_list_init(&fsout->transform.link); + + surface_subsurfaces_boundingbox(fsout->view->surface, + &surf_x, &surf_y, + &surf_width, &surf_height); + + output_aspect = (float) output->width / (float) output->height; + surface_aspect = (float) surf_width / (float) surf_height; + + switch (fsout->method) { + case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT: + case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_CENTER: + fs_output_center_view(fsout); + break; + + case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_ZOOM: + if (output_aspect < surface_aspect) + fs_output_scale_view(fsout, + output->width, + output->width / surface_aspect); + else + fs_output_scale_view(fsout, + output->height * surface_aspect, + output->height); + break; + + case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_ZOOM_CROP: + if (output_aspect < surface_aspect) + fs_output_scale_view(fsout, + output->height * surface_aspect, + output->height); + else + fs_output_scale_view(fsout, + output->width, + output->width / surface_aspect); + break; + + case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_STRETCH: + fs_output_scale_view(fsout, output->width, output->height); + break; + default: + break; + } + + weston_view_set_position(fsout->black_view, + fsout->output->x - surf_x, + fsout->output->y - surf_y); + weston_surface_set_size(fsout->black_view->surface, + fsout->output->width, + fsout->output->height); +} + +static void +fs_output_configure_for_mode(struct fs_output *fsout, + struct weston_surface *configured_surface) +{ + int32_t surf_x, surf_y, surf_width, surf_height; + struct weston_mode mode; + int ret; + + if (fsout->pending.surface != configured_surface) { + /* Nothing to really reconfigure. We'll just recenter the + * view in case they played with subsurfaces */ + fs_output_center_view(fsout); + return; + } + + /* We have a pending surface */ + surface_subsurfaces_boundingbox(fsout->pending.surface, + &surf_x, &surf_y, + &surf_width, &surf_height); + + /* The actual output mode is in physical units. We need to + * transform the surface size to physical unit size by flipping and + * possibly scaling it. + */ + switch (fsout->output->transform) { + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + mode.width = surf_height * fsout->output->native_scale; + mode.height = surf_width * fsout->output->native_scale; + break; + + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + default: + mode.width = surf_width * fsout->output->native_scale; + mode.height = surf_height * fsout->output->native_scale; + } + mode.flags = 0; + mode.refresh = fsout->pending.framerate; + + ret = weston_output_mode_switch_to_temporary(fsout->output, &mode, + fsout->output->native_scale); + + if (ret != 0) { + /* The mode switch failed. Clear the pending and + * reconfigure as per normal */ + if (fsout->pending.mode_feedback) { + zwp_fullscreen_shell_mode_feedback_v1_send_mode_failed( + fsout->pending.mode_feedback); + wl_resource_destroy(fsout->pending.mode_feedback); + fsout->pending.mode_feedback = NULL; + } + + fs_output_clear_pending(fsout); + return; + } + + if (fsout->pending.mode_feedback) { + zwp_fullscreen_shell_mode_feedback_v1_send_mode_successful( + fsout->pending.mode_feedback); + wl_resource_destroy(fsout->pending.mode_feedback); + fsout->pending.mode_feedback = NULL; + } + + fs_output_apply_pending(fsout); + + weston_view_set_position(fsout->view, + fsout->output->x - surf_x, + fsout->output->y - surf_y); +} + +static void +fs_output_configure(struct fs_output *fsout, + struct weston_surface *surface) +{ + if (fsout->pending.surface == surface) { + if (fsout->pending.presented_for_mode) + fs_output_configure_for_mode(fsout, surface); + else + fs_output_configure_simple(fsout, surface); + } else { + if (fsout->presented_for_mode) + fs_output_configure_for_mode(fsout, surface); + else + fs_output_configure_simple(fsout, surface); + } + + weston_output_schedule_repaint(fsout->output); +} + +static void +configure_presented_surface(struct weston_surface *surface, int32_t sx, + int32_t sy) +{ + struct fullscreen_shell *shell = surface->committed_private; + struct fs_output *fsout; + + if (surface->committed != configure_presented_surface) + return; + + wl_list_for_each(fsout, &shell->output_list, link) + if (fsout->surface == surface || + fsout->pending.surface == surface) + fs_output_configure(fsout, surface); +} + +static void +fs_output_apply_pending(struct fs_output *fsout) +{ + assert(fsout->pending.surface); + + if (fsout->surface && fsout->surface != fsout->pending.surface) { + wl_list_remove(&fsout->surface_destroyed.link); + + weston_view_destroy(fsout->view); + fsout->view = NULL; + + if (wl_list_empty(&fsout->surface->views)) { + fsout->surface->committed = NULL; + fsout->surface->committed_private = NULL; + } + + fsout->surface = NULL; + } + + fsout->method = fsout->pending.method; + fsout->framerate = fsout->pending.framerate; + fsout->presented_for_mode = fsout->pending.presented_for_mode; + + if (fsout->surface != fsout->pending.surface) { + fsout->surface = fsout->pending.surface; + + fsout->view = weston_view_create(fsout->surface); + if (!fsout->view) { + weston_log("no memory\n"); + return; + } + fsout->view->is_mapped = true; + + wl_signal_add(&fsout->surface->destroy_signal, + &fsout->surface_destroyed); + weston_layer_entry_insert(&fsout->shell->layer.view_list, + &fsout->view->layer_link); + } + + fs_output_clear_pending(fsout); +} + +static void +fs_output_clear_pending(struct fs_output *fsout) +{ + if (!fsout->pending.surface) + return; + + if (fsout->pending.mode_feedback) { + zwp_fullscreen_shell_mode_feedback_v1_send_present_cancelled( + fsout->pending.mode_feedback); + wl_resource_destroy(fsout->pending.mode_feedback); + fsout->pending.mode_feedback = NULL; + } + + wl_list_remove(&fsout->pending.surface_destroyed.link); + fsout->pending.surface = NULL; +} + +static void +fs_output_set_surface(struct fs_output *fsout, struct weston_surface *surface, + enum zwp_fullscreen_shell_v1_present_method method, + int32_t framerate, int presented_for_mode) +{ + fs_output_clear_pending(fsout); + + if (surface) { + if (!surface->committed) { + surface->committed = configure_presented_surface; + surface->committed_private = fsout->shell; + } + + fsout->pending.surface = surface; + wl_signal_add(&fsout->pending.surface->destroy_signal, + &fsout->pending.surface_destroyed); + + fsout->pending.method = method; + fsout->pending.framerate = framerate; + fsout->pending.presented_for_mode = presented_for_mode; + } else if (fsout->surface) { + /* we clear immediately */ + wl_list_remove(&fsout->surface_destroyed.link); + + weston_view_destroy(fsout->view); + fsout->view = NULL; + + if (wl_list_empty(&fsout->surface->views)) { + fsout->surface->committed = NULL; + fsout->surface->committed_private = NULL; + } + + fsout->surface = NULL; + + weston_output_schedule_repaint(fsout->output); + } +} + +static void +fullscreen_shell_release(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +fullscreen_shell_present_surface(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_res, + uint32_t method, + struct wl_resource *output_res) +{ + struct fullscreen_shell *shell = + wl_resource_get_user_data(resource); + struct weston_output *output; + struct weston_surface *surface; + struct weston_seat *seat; + struct fs_output *fsout; + + surface = surface_res ? wl_resource_get_user_data(surface_res) : NULL; + + switch(method) { + case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT: + case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_CENTER: + case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_ZOOM: + case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_ZOOM_CROP: + case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_STRETCH: + break; + default: + wl_resource_post_error(resource, + ZWP_FULLSCREEN_SHELL_V1_ERROR_INVALID_METHOD, + "Invalid presentation method"); + } + + if (output_res) { + output = weston_head_from_resource(output_res)->output; + fsout = fs_output_for_output(output); + fs_output_set_surface(fsout, surface, method, 0, 0); + } else { + replace_default_surface(shell, surface, method); + + wl_list_for_each(fsout, &shell->output_list, link) + fs_output_set_surface(fsout, surface, method, 0, 0); + } + + if (surface) { + wl_list_for_each(seat, &shell->compositor->seat_list, link) { + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(seat); + + if (keyboard && !keyboard->focus) + weston_seat_set_keyboard_focus(seat, surface); + } + } +} + +static void +mode_feedback_destroyed(struct wl_resource *resource) +{ + struct fs_output *fsout = wl_resource_get_user_data(resource); + + fsout->pending.mode_feedback = NULL; +} + +static void +fullscreen_shell_present_surface_for_mode(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_res, + struct wl_resource *output_res, + int32_t framerate, + uint32_t feedback_id) +{ + struct fullscreen_shell *shell = + wl_resource_get_user_data(resource); + struct weston_output *output; + struct weston_surface *surface; + struct weston_seat *seat; + struct fs_output *fsout; + + output = weston_head_from_resource(output_res)->output; + fsout = fs_output_for_output(output); + + if (surface_res == NULL) { + fs_output_set_surface(fsout, NULL, 0, 0, 0); + return; + } + + surface = wl_resource_get_user_data(surface_res); + fs_output_set_surface(fsout, surface, 0, framerate, 1); + + fsout->pending.mode_feedback = + wl_resource_create(client, + &zwp_fullscreen_shell_mode_feedback_v1_interface, + 1, feedback_id); + wl_resource_set_implementation(fsout->pending.mode_feedback, NULL, + fsout, mode_feedback_destroyed); + + wl_list_for_each(seat, &shell->compositor->seat_list, link) { + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(seat); + + if (keyboard && !keyboard->focus) + weston_seat_set_keyboard_focus(seat, surface); + } +} + +struct zwp_fullscreen_shell_v1_interface fullscreen_shell_implementation = { + fullscreen_shell_release, + fullscreen_shell_present_surface, + fullscreen_shell_present_surface_for_mode, +}; + +static void +output_created(struct wl_listener *listener, void *data) +{ + struct fullscreen_shell *shell; + + shell = container_of(listener, struct fullscreen_shell, + output_created_listener); + + fs_output_create(shell, data); +} + +static void +client_destroyed(struct wl_listener *listener, void *data) +{ + struct fullscreen_shell *shell = container_of(listener, + struct fullscreen_shell, + client_destroyed); + shell->client = NULL; +} + +static void +bind_fullscreen_shell(struct wl_client *client, void *data, uint32_t version, + uint32_t id) +{ + struct fullscreen_shell *shell = data; + struct wl_resource *resource; + + if (shell->client != NULL && shell->client != client) + return; + else if (shell->client == NULL) { + shell->client = client; + wl_client_add_destroy_listener(client, &shell->client_destroyed); + } + + resource = wl_resource_create(client, + &zwp_fullscreen_shell_v1_interface, + 1, id); + wl_resource_set_implementation(resource, + &fullscreen_shell_implementation, + shell, NULL); + + if (shell->compositor->capabilities & WESTON_CAP_CURSOR_PLANE) + zwp_fullscreen_shell_v1_send_capability(resource, + ZWP_FULLSCREEN_SHELL_V1_CAPABILITY_CURSOR_PLANE); + + if (shell->compositor->capabilities & WESTON_CAP_ARBITRARY_MODES) + zwp_fullscreen_shell_v1_send_capability(resource, + ZWP_FULLSCREEN_SHELL_V1_CAPABILITY_ARBITRARY_MODES); +} + +WL_EXPORT int +wet_shell_init(struct weston_compositor *compositor, + int *argc, char *argv[]) +{ + struct fullscreen_shell *shell; + struct weston_seat *seat; + struct weston_output *output; + + shell = zalloc(sizeof *shell); + if (shell == NULL) + return -1; + + shell->compositor = compositor; + wl_list_init(&shell->default_surface_list); + + shell->client_destroyed.notify = client_destroyed; + + weston_layer_init(&shell->layer, compositor); + weston_layer_set_position(&shell->layer, + WESTON_LAYER_POSITION_FULLSCREEN); + + wl_list_init(&shell->output_list); + shell->output_created_listener.notify = output_created; + wl_signal_add(&compositor->output_created_signal, + &shell->output_created_listener); + wl_list_for_each(output, &compositor->output_list, link) + fs_output_create(shell, output); + + shell->seat_created_listener.notify = seat_created; + wl_signal_add(&compositor->seat_created_signal, + &shell->seat_created_listener); + wl_list_for_each(seat, &compositor->seat_list, link) + seat_created(&shell->seat_created_listener, seat); + + wl_global_create(compositor->wl_display, + &zwp_fullscreen_shell_v1_interface, 1, shell, + bind_fullscreen_shell); + + return 0; +} diff --git a/fullscreen-shell/meson.build b/fullscreen-shell/meson.build new file mode 100644 index 0000000..bde06db --- /dev/null +++ b/fullscreen-shell/meson.build @@ -0,0 +1,16 @@ +if get_option('shell-fullscreen') + srcs_shell_fullscreen = [ + 'fullscreen-shell.c', + fullscreen_shell_unstable_v1_server_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + ] + shared_library( + 'fullscreen-shell', + srcs_shell_fullscreen, + include_directories: common_inc, + dependencies: dep_libweston_public, + name_prefix: '', + install: true, + install_dir: dir_module_weston + ) +endif diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..b270e04 --- /dev/null +++ b/include/config.h @@ -0,0 +1,260 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Build the Wayland clients */ +/* #undef BUILD_CLIENTS */ + +/* Build the DRM compositor */ +/* #undef BUILD_DRM_COMPOSITOR */ + +/* Build the fbdev compositor */ +#define BUILD_FBDEV_COMPOSITOR 1 + +/* Build the headless compositor */ +/* #undef BUILD_HEADLESS_COMPOSITOR */ + +/* Build the RDP compositor */ +/* #undef BUILD_RDP_COMPOSITOR */ + +/* Build the vaapi recorder */ +/* #undef BUILD_VAAPI_RECORDER */ + +/* Build the Wayland (nested) compositor */ +/* #undef BUILD_WAYLAND_COMPOSITOR */ + +/* Build the wcap tools */ +/* #undef BUILD_WCAP_TOOLS */ + +/* Build the X11 compositor */ +/* #undef BUILD_X11_COMPOSITOR */ + +/* Build the X server launcher */ +/* #undef BUILD_XWAYLAND */ + +/* Build Weston with EGL support */ +/* #define ENABLE_EGL */ + +/* Build Weston with JUnit output support */ +/* #undef ENABLE_JUNIT_XML */ + +/* Have cairoegl */ +/* #undef HAVE_CAIRO_EGL */ + +/* Build with dbus support */ +/* #undef HAVE_DBUS */ + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* libdrm supports modifiers */ +/* #undef HAVE_DRM_ADDFB2_MODIFIERS */ + +/* libdrm supports atomic API */ +/* #undef HAVE_DRM_ATOMIC */ + + +/* libdrm supports modifier advertisement */ +/* #undef HAVE_DRM_FORMATS_BLOB */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_EXECINFO_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_FREERDP_VERSION_H */ + +/* gbm supports import with modifiers */ +/* #undef HAVE_GBM_FD_IMPORT */ + +/* GBM supports modifiers */ +/* #undef HAVE_GBM_MODIFIERS */ + +/* Define to 1 if you have the `initgroups' function. */ +#define HAVE_INITGROUPS 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Have jpeglib */ +/* #undef HAVE_JPEG */ + +/* Have lcms support */ +/* #undef HAVE_LCMS */ + +/* Build etnaviv dmabuf client */ +/* #undef HAVE_LIBDRM_ETNAVIV */ + +/* Build freedreno dmabuf client */ +/* #undef HAVE_LIBDRM_FREEDRENO */ + +/* Build intel dmabuf client */ +/* #undef HAVE_LIBDRM_INTEL */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LINUX_SYNC_FILE_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `mkostemp' function. */ +#define HAVE_MKOSTEMP 1 + +/* Have pango */ +/* #undef HAVE_PANGO */ + +/* Define to 1 if you have the `posix_fallocate' function. */ +/* #undef HAVE_POSIX_FALLOCATE */ + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the `strchrnul' function. */ +#define HAVE_STRCHRNUL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* SURFACE_BITS_CMD has bmp field */ +/* #undef HAVE_SURFACE_BITS_BMP */ + +/* Have systemdlogin */ +/* #undef HAVE_SYSTEMD_LOGIN */ + +/* Have systemdlogin >= 209 */ +/* #undef HAVE_SYSTEMD_LOGIN_209 */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Have webp */ +/* #undef HAVE_WEBP */ + +/* libxcb supports XKB protocol */ +/* #undef HAVE_XCB_XKB */ + +/* Define if xkbcommon is 0.5.0 or newer */ +/* #undef HAVE_XKBCOMMON_COMPOSE */ + +/* Define to the subdirectory where libtool stores uninstalled libraries. */ +#define LT_OBJDIR ".libs/" + +/* Define to 1 if `major', `minor', and `makedev' are declared in . + */ +/* #undef MAJOR_IN_MKDEV */ + +/* Define to 1 if `major', `minor', and `makedev' are declared in + . */ +/* #undef MAJOR_IN_SYSMACROS */ + +/* Name of package */ +#define PACKAGE "weston" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "https://gitlab.freedesktop.org/wayland/weston/issues/" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "weston" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "weston 5.0.0" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "weston" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "https://wayland.freedesktop.org" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "5.0.0" + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Build the systemd sd_notify support */ +/* #undef SYSTEMD_NOTIFY_SUPPORT */ + +/* Use the GLESv2 GL cairo backend */ +/* #undef USE_CAIRO_GLESV2 */ + +/* Use resize memory pool as a performance optimization */ +#define USE_RESIZE_POOL 1 + +#define MAJOR_IN_SYSMACROS 1 + +/* Enable extensions on AIX 3, Interix. */ +#ifndef _ALL_SOURCE +# define _ALL_SOURCE 1 +#endif +/* Enable GNU extensions on systems that have them. */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE 1 +#endif +/* Enable threading extensions on Solaris. */ +#ifndef _POSIX_PTHREAD_SEMANTICS +# define _POSIX_PTHREAD_SEMANTICS 1 +#endif +/* Enable extensions on HP NonStop. */ +#ifndef _TANDEM_SOURCE +# define _TANDEM_SOURCE 1 +#endif +/* Enable general extensions on Solaris. */ +#ifndef __EXTENSIONS__ +# define __EXTENSIONS__ 1 +#endif + + +/* Version number of package */ +#define VERSION "9.0.0" + +/* The default backend to load, if not wayland nor x11. */ +#define WESTON_NATIVE_BACKEND "drm-backend.so" + +/* The default desktop shell client to load. */ +#define WESTON_SHELL_CLIENT "weston-desktop-shell" + +#define BINDIR "/system/bin" + +#define LIBEXECDIR "/system/bin" + +#define DATADIR "data" +#ifdef __aarch64__ +#define LIBWESTON_MODULEDIR "/system/lib64" +#define MODULEDIR "/system/lib64" +#else +#define LIBWESTON_MODULEDIR "/system/lib" +#define MODULEDIR "/system/lib" +#endif + +/* Enable large inode numbers on Mac OS X 10.5. */ +#ifndef _DARWIN_USE_64_BIT_INODE +# define _DARWIN_USE_64_BIT_INODE 1 +#endif + +/* Number of bits in a file offset, on hosts where this is settable. */ +/* #undef _FILE_OFFSET_BITS */ + +/* Define for large files, on AIXstyle hosts. */ +/* #undef _LARGE_FILES */ + +/* Define to 1 if on MINIX. */ +/* #undef _MINIX */ + +/* Define to 2 if the system does not provide POSIX.1 features except with + this defined. */ +/* #undef _POSIX_1_SOURCE */ + +/* Define to 1 if you need to in order for `stat' and other things to work. */ +/* #undef _POSIX_SOURCE */ + +// #define BUILD_DRM_GBM 1 diff --git a/include/libweston-desktop/libweston-desktop.h b/include/libweston-desktop/libweston-desktop.h new file mode 100644 index 0000000..3e7ac73 --- /dev/null +++ b/include/libweston-desktop/libweston-desktop.h @@ -0,0 +1,202 @@ +/* + * Copyright © 2016 Quentin "Sardem FF7" Glidic + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef WESTON_DESKTOP_H +#define WESTON_DESKTOP_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum weston_desktop_surface_edge { + WESTON_DESKTOP_SURFACE_EDGE_NONE = 0, + WESTON_DESKTOP_SURFACE_EDGE_TOP = 1, + WESTON_DESKTOP_SURFACE_EDGE_BOTTOM = 2, + WESTON_DESKTOP_SURFACE_EDGE_LEFT = 4, + WESTON_DESKTOP_SURFACE_EDGE_TOP_LEFT = 5, + WESTON_DESKTOP_SURFACE_EDGE_BOTTOM_LEFT = 6, + WESTON_DESKTOP_SURFACE_EDGE_RIGHT = 8, + WESTON_DESKTOP_SURFACE_EDGE_TOP_RIGHT = 9, + WESTON_DESKTOP_SURFACE_EDGE_BOTTOM_RIGHT = 10, +}; + +struct weston_desktop; +struct weston_desktop_client; +struct weston_desktop_surface; + +struct weston_desktop_api { + size_t struct_size; + void (*ping_timeout)(struct weston_desktop_client *client, + void *user_data); + void (*pong)(struct weston_desktop_client *client, + void *user_data); + + void (*surface_added)(struct weston_desktop_surface *surface, + void *user_data); + void (*surface_removed)(struct weston_desktop_surface *surface, + void *user_data); + void (*committed)(struct weston_desktop_surface *surface, + int32_t sx, int32_t sy, void *user_data); + void (*show_window_menu)(struct weston_desktop_surface *surface, + struct weston_seat *seat, int32_t x, int32_t y, + void *user_data); + void (*set_parent)(struct weston_desktop_surface *surface, + struct weston_desktop_surface *parent, + void *user_data); + void (*move)(struct weston_desktop_surface *surface, + struct weston_seat *seat, uint32_t serial, void *user_data); + void (*resize)(struct weston_desktop_surface *surface, + struct weston_seat *seat, uint32_t serial, + enum weston_desktop_surface_edge edges, void *user_data); + void (*fullscreen_requested)(struct weston_desktop_surface *surface, + bool fullscreen, + struct weston_output *output, + void *user_data); + void (*maximized_requested)(struct weston_desktop_surface *surface, + bool maximized, void *user_data); + void (*minimized_requested)(struct weston_desktop_surface *surface, + void *user_data); + + /** Position suggestion for an Xwayland window + * + * X11 applications assume they can position their windows as necessary, + * which is not possible in Wayland where positioning is driven by the + * shell alone. This function is used to relay absolute position wishes + * from Xwayland clients to the shell. + * + * This is particularly used for mapping windows at specified locations, + * e.g. via the commonly used '-geometry' command line option. In such + * case, a call to surface_added() is immediately followed by + * xwayland_position() if the X11 application specified a position. + * The committed() call that will map the window occurs later, so it + * is recommended to usually store and honour the given position for + * windows that are not yet mapped. + * + * Calls to this function may happen also at other times. + * + * The given coordinates are in the X11 window system coordinate frame + * relative to the X11 root window. Care should be taken to ensure the + * window gets mapped to coordinates that correspond to the proposed + * position from the X11 client perspective. + * + * \param surface The surface in question. + * \param x The absolute X11 coordinate for x. + * \param y The absolute X11 coordinate for y. + * \param user_data The user_data argument passed in to + * weston_desktop_create(). + * + * This callback can be NULL. + */ + void (*set_xwayland_position)(struct weston_desktop_surface *surface, + int32_t x, int32_t y, void *user_data); +}; + +void +weston_seat_break_desktop_grabs(struct weston_seat *seat); + +struct weston_desktop * +weston_desktop_create(struct weston_compositor *compositor, + const struct weston_desktop_api *api, void *user_data); +void +weston_desktop_destroy(struct weston_desktop *desktop); + +struct wl_client * +weston_desktop_client_get_client(struct weston_desktop_client *client); +void +weston_desktop_client_for_each_surface(struct weston_desktop_client *client, + void (*callback)(struct weston_desktop_surface *surface, void *user_data), + void *user_data); +int +weston_desktop_client_ping(struct weston_desktop_client *client); + +bool +weston_surface_is_desktop_surface(struct weston_surface *surface); +struct weston_desktop_surface * +weston_surface_get_desktop_surface(struct weston_surface *surface); + +void +weston_desktop_surface_set_user_data(struct weston_desktop_surface *self, + void *user_data); +struct weston_view * +weston_desktop_surface_create_view(struct weston_desktop_surface *surface); +void +weston_desktop_surface_unlink_view(struct weston_view *view); +void +weston_desktop_surface_propagate_layer(struct weston_desktop_surface *surface); +void +weston_desktop_surface_set_activated(struct weston_desktop_surface *surface, + bool activated); +void +weston_desktop_surface_set_fullscreen(struct weston_desktop_surface *surface, + bool fullscreen); +void +weston_desktop_surface_set_maximized(struct weston_desktop_surface *surface, + bool maximized); +void +weston_desktop_surface_set_resizing(struct weston_desktop_surface *surface, + bool resized); +void +weston_desktop_surface_set_size(struct weston_desktop_surface *surface, + int32_t width, int32_t height); +void +weston_desktop_surface_close(struct weston_desktop_surface *surface); +void +weston_desktop_surface_add_metadata_listener(struct weston_desktop_surface *surface, + struct wl_listener *listener); + +void * +weston_desktop_surface_get_user_data(struct weston_desktop_surface *surface); +struct weston_desktop_client * +weston_desktop_surface_get_client(struct weston_desktop_surface *surface); +struct weston_surface * +weston_desktop_surface_get_surface(struct weston_desktop_surface *surface); +const char * +weston_desktop_surface_get_title(struct weston_desktop_surface *surface); +const char * +weston_desktop_surface_get_app_id(struct weston_desktop_surface *surface); +pid_t +weston_desktop_surface_get_pid(struct weston_desktop_surface *surface); +bool +weston_desktop_surface_get_activated(struct weston_desktop_surface *surface); +bool +weston_desktop_surface_get_maximized(struct weston_desktop_surface *surface); +bool +weston_desktop_surface_get_fullscreen(struct weston_desktop_surface *surface); +bool +weston_desktop_surface_get_resizing(struct weston_desktop_surface *surface); +struct weston_geometry +weston_desktop_surface_get_geometry(struct weston_desktop_surface *surface); +struct weston_size +weston_desktop_surface_get_max_size(struct weston_desktop_surface *surface); +struct weston_size +weston_desktop_surface_get_min_size(struct weston_desktop_surface *surface); + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_DESKTOP_H */ diff --git a/include/libweston/backend-drm.h b/include/libweston/backend-drm.h new file mode 100644 index 0000000..350eeb0 --- /dev/null +++ b/include/libweston/backend-drm.h @@ -0,0 +1,235 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2015 Giulio Camuffo + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_COMPOSITOR_DRM_H +#define WESTON_COMPOSITOR_DRM_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define WESTON_DRM_BACKEND_CONFIG_VERSION 3 + +struct libinput_device; + +enum weston_drm_backend_output_mode { + /** The output is disabled */ + WESTON_DRM_BACKEND_OUTPUT_OFF, + /** The output will use the current active mode */ + WESTON_DRM_BACKEND_OUTPUT_CURRENT, + /** The output will use the preferred mode. A modeline can be provided + * by setting weston_backend_output_config::modeline in the form of + * "WIDTHxHEIGHT" or in the form of an explicit modeline calculated + * using e.g. the cvt tool. If a valid modeline is supplied it will be + * used, if invalid or NULL the preferred available mode will be used. */ + WESTON_DRM_BACKEND_OUTPUT_PREFERRED, +}; + +#define WESTON_DRM_OUTPUT_API_NAME "weston_drm_output_api_v1" + +struct weston_drm_output_api { + /** The mode to be used by the output. Refer to the documentation + * of WESTON_DRM_BACKEND_OUTPUT_PREFERRED for details. + * + * Returns 0 on success, -1 on failure. + */ + int (*set_mode)(struct weston_output *output, + enum weston_drm_backend_output_mode mode, + const char *modeline); + + /** The pixel format to be used by the output. Valid values are: + * - NULL - The format set at backend creation time will be used; + * - "xrgb8888"; + * - "rgb565" + * - "xrgb2101010" + */ + void (*set_gbm_format)(struct weston_output *output, + const char *gbm_format); + + /** The seat to be used by the output. Set to NULL to use the + * default seat. + */ + void (*set_seat)(struct weston_output *output, + const char *seat); +}; + +static inline const struct weston_drm_output_api * +weston_drm_output_get_api(struct weston_compositor *compositor) +{ + const void *api; + api = weston_plugin_api_get(compositor, WESTON_DRM_OUTPUT_API_NAME, + sizeof(struct weston_drm_output_api)); + + return (const struct weston_drm_output_api *)api; +} + +#define WESTON_DRM_VIRTUAL_OUTPUT_API_NAME "weston_drm_virtual_output_api_v1" + +struct drm_fb; +typedef int (*submit_frame_cb)(struct weston_output *output, int fd, + int stride, struct drm_fb *buffer); + +struct weston_drm_virtual_output_api { + /** Create virtual output. + * This is a low-level function, where the caller is expected to wrap + * the weston_output function pointers as necessary to make the virtual + * output useful. The caller must set up output make, model, serial, + * physical size, the mode list and current mode. + * + * Returns output on success, NULL on failure. + */ + struct weston_output* (*create_output)(struct weston_compositor *c, + char *name); + + /** Set pixel format same as drm_output set_gbm_format(). + * + * Returns the set format. + */ + uint32_t (*set_gbm_format)(struct weston_output *output, + const char *gbm_format); + + /** Set a callback to be called when the DRM-backend has drawn a new + * frame and submits it for display. + * The callback will deliver a buffer to the virtual output's the + * owner and assumes the buffer is now reserved for the owner. The + * callback is called in virtual output repaint function. + * The caller must call buffer_released() and finish_frame(). + * + * The callback parameters are output, FD and stride (bytes) of dmabuf, + * and buffer (drm_fb) pointer. + * The callback returns 0 on success, -1 on failure. + * + * The submit_frame_cb callback hook is responsible for closing the fd + * if it returns success. One needs to call the buffer release and + * finish frame functions if and only if this hook returns success. + */ + void (*set_submit_frame_cb)(struct weston_output *output, + submit_frame_cb cb); + + /** Get fd for renderer fence. + * The returned fence signals when the renderer job has completed and + * the buffer is fully drawn. + * + * Returns fd on success, -1 on failure. + */ + int (*get_fence_sync_fd)(struct weston_output *output); + + /** Notify that the caller has finished using buffer */ + void (*buffer_released)(struct drm_fb *fb); + + /** Notify finish frame + * This function allows the output repainting mechanism to advance to + * the next frame. + */ + void (*finish_frame)(struct weston_output *output, + struct timespec *stamp, + uint32_t presented_flags); +}; + +static inline const struct weston_drm_virtual_output_api * +weston_drm_virtual_output_get_api(struct weston_compositor *compositor) +{ + const void *api; + api = weston_plugin_api_get(compositor, + WESTON_DRM_VIRTUAL_OUTPUT_API_NAME, + sizeof(struct weston_drm_virtual_output_api)); + return (const struct weston_drm_virtual_output_api *)api; +} + +/** The backend configuration struct. + * + * weston_drm_backend_config contains the configuration used by a DRM + * backend. + */ +struct weston_drm_backend_config { + struct weston_backend_config base; + + /** The tty to be used. Set to 0 to use the current tty. */ + int tty; + + /** Whether to use the pixman renderer instead of the OpenGL ES renderer. */ + bool use_pixman; + + /** The seat to be used for input and output. + * + * If seat_id is NULL, the seat is taken from XDG_SEAT environment + * variable. If neither is set, "seat0" is used. The backend will + * take ownership of the seat_id pointer and will free it on + * backend destruction. + */ + char *seat_id; + + /** The pixel format of the framebuffer to be used. + * + * Valid values are: + * - NULL - The default format ("xrgb8888") will be used; + * - "xrgb8888"; + * - "rgb565" + * - "xrgb2101010" + * The backend will take ownership of the format pointer and will free + * it on backend destruction. + */ + char *gbm_format; + + /** Callback used to configure input devices. + * + * This function will be called by the backend when a new input device + * needs to be configured. + * If NULL the device will use the default configuration. + */ + void (*configure_device)(struct weston_compositor *compositor, + struct libinput_device *device); + + /** Maximum duration for a pageflip event to arrive, after which the + * compositor will consider the DRM driver crashed and will try to exit + * cleanly. + * + * It is exprimed in milliseconds, 0 means disabled. */ + uint32_t pageflip_timeout; + + /** Specific DRM device to open + * + * A DRM device name, like "card0", to open. If NULL, use heuristics + * based on seat names and boot_vga to find the right device. + */ + char *specific_device; + + /** Use shadow buffer if using Pixman-renderer. */ + bool use_pixman_shadow; + + /** Allow compositor to start without input devices. */ + bool continue_without_input; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_COMPOSITOR_DRM_H */ diff --git a/include/libweston/backend-fbdev.h b/include/libweston/backend-fbdev.h new file mode 100644 index 0000000..4dbdce7 --- /dev/null +++ b/include/libweston/backend-fbdev.h @@ -0,0 +1,70 @@ +/* + * Copyright © 2016 Benoit Gschwind + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_COMPOSITOR_FBDEV_H +#define WESTON_COMPOSITOR_FBDEV_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include + +#define WESTON_FBDEV_BACKEND_CONFIG_VERSION 2 + +struct libinput_device; + +struct weston_fbdev_backend_config { + struct weston_backend_config base; + + int tty; + char *device; + + /** Callback used to configure input devices. + * + * This function will be called by the backend when a new input device + * needs to be configured. + * If NULL the device will use the default configuration. + */ + void (*configure_device)(struct weston_compositor *compositor, + struct libinput_device *device); + + /** The seat to be used for input and output. + * + * If seat_id is NULL, the seat is taken from XDG_SEAT environment + * variable. If neither is set, "seat0" is used. The backend will + * take ownership of the seat_id pointer and will free it on + * backend destruction. + */ + char *seat_id; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_COMPOSITOR_FBDEV_H */ diff --git a/include/libweston/backend-headless.h b/include/libweston/backend-headless.h new file mode 100644 index 0000000..1f53835 --- /dev/null +++ b/include/libweston/backend-headless.h @@ -0,0 +1,53 @@ +/* + * Copyright © 2016 Benoit Gschwind + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_COMPOSITOR_HEADLESS_H +#define WESTON_COMPOSITOR_HEADLESS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include + +#define WESTON_HEADLESS_BACKEND_CONFIG_VERSION 2 + +struct weston_headless_backend_config { + struct weston_backend_config base; + + /** Whether to use the pixman renderer, default is no-op */ + bool use_pixman; + + /** Whether to use the GL renderer, conflicts with use_pixman */ + bool use_gl; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_COMPOSITOR_HEADLESS_H */ diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h new file mode 100644 index 0000000..b354250 --- /dev/null +++ b/include/libweston/backend-rdp.h @@ -0,0 +1,75 @@ +/* + * Copyright © 2016 Benoit Gschwind + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_COMPOSITOR_RDP_H +#define WESTON_COMPOSITOR_RDP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define WESTON_RDP_OUTPUT_API_NAME "weston_rdp_output_api_v1" + +struct weston_rdp_output_api { + /** Initialize a RDP output with specified width and height. + * + * Returns 0 on success, -1 on failure. + */ + int (*output_set_size)(struct weston_output *output, + int width, int height); +}; + +static inline const struct weston_rdp_output_api * +weston_rdp_output_get_api(struct weston_compositor *compositor) +{ + const void *api; + api = weston_plugin_api_get(compositor, WESTON_RDP_OUTPUT_API_NAME, + sizeof(struct weston_rdp_output_api)); + + return (const struct weston_rdp_output_api *)api; +} + +#define WESTON_RDP_BACKEND_CONFIG_VERSION 2 + +struct weston_rdp_backend_config { + struct weston_backend_config base; + char *bind_address; + int port; + char *rdp_key; + char *server_cert; + char *server_key; + int env_socket; + int no_clients_resize; + int force_no_compression; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_COMPOSITOR_RDP_H */ diff --git a/include/libweston/backend-wayland.h b/include/libweston/backend-wayland.h new file mode 100644 index 0000000..6d65610 --- /dev/null +++ b/include/libweston/backend-wayland.h @@ -0,0 +1,54 @@ +/* + * Copyright © 2016 Benoit Gschwind + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_COMPOSITOR_WAYLAND_H +#define WESTON_COMPOSITOR_WAYLAND_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define WESTON_WAYLAND_BACKEND_CONFIG_VERSION 2 + +struct weston_wayland_backend_config { + struct weston_backend_config base; + bool use_pixman; + bool use_tde; // OHOS tde + bool sprawl; + char *display_name; + bool fullscreen; + char *cursor_theme; + int cursor_size; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_COMPOSITOR_WAYLAND_H */ diff --git a/include/libweston/backend-x11.h b/include/libweston/backend-x11.h new file mode 100644 index 0000000..1556e8e --- /dev/null +++ b/include/libweston/backend-x11.h @@ -0,0 +1,53 @@ +/* + * Copyright © 2016 Benoit Gschwind + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_COMPOSITOR_X11_H +#define WESTON_COMPOSITOR_X11_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include + +#define WESTON_X11_BACKEND_CONFIG_VERSION 2 + +struct weston_x11_backend_config { + struct weston_backend_config base; + + bool fullscreen; + bool no_input; + + /** Whether to use the pixman renderer instead of the OpenGL ES renderer. */ + bool use_pixman; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_COMPOSITOR_X11_H_ */ diff --git a/include/libweston/config-parser.h b/include/libweston/config-parser.h new file mode 100644 index 0000000..d82197b --- /dev/null +++ b/include/libweston/config-parser.h @@ -0,0 +1,130 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CONFIGPARSER_H +#define CONFIGPARSER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define WESTON_CONFIG_FILE_ENV_VAR "WESTON_CONFIG_FILE" + +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; + char 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_color(struct weston_config_section *section, + const char *key, + uint32_t *color, uint32_t default_color); +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, + bool *value, bool default_value); + +const char * +weston_config_get_name_from_env(void); + +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/include/libweston/libweston.h b/include/libweston/libweston.h new file mode 100755 index 0000000..03a3d0c --- /dev/null +++ b/include/libweston/libweston.h @@ -0,0 +1,2083 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2012, 2017, 2018 Collabora, Ltd. + * Copyright © 2017, 2018 General Electric Company + * + * 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. + */ +#define LOG_TAG "WAYLAND_SERVER" // OHOS logcat + +#ifndef _WAYLAND_SYSTEM_COMPOSITOR_H_ +#define _WAYLAND_SYSTEM_COMPOSITOR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include // OHOS hilog +#include + +#define WL_HIDE_DEPRECATED +#include + +#include +#include + +struct weston_geometry { + int32_t x, y; + int32_t width, height; +}; + +struct weston_position { + int32_t x, y; +}; + +struct weston_size { + int32_t width, height; +}; + +struct weston_transform { + struct weston_matrix matrix; + struct wl_list link; +}; + +/** 2D device coordinates normalized to [0, 1] range */ +struct weston_point2d_device_normalized { + double x; + double y; +}; + +struct weston_surface; +struct weston_buffer; +struct shell_surface; +struct weston_seat; +struct weston_output; +struct input_method; +struct weston_pointer; +struct linux_dmabuf_buffer; +struct weston_recorder; +struct weston_pointer_constraint; +struct ro_anonymous_file; + +enum weston_keyboard_modifier { + MODIFIER_CTRL = (1 << 0), + MODIFIER_ALT = (1 << 1), + MODIFIER_SUPER = (1 << 2), + MODIFIER_SHIFT = (1 << 3), +}; + +enum weston_keyboard_locks { + WESTON_NUM_LOCK = (1 << 0), + WESTON_CAPS_LOCK = (1 << 1), +}; + +enum weston_led { + LED_NUM_LOCK = (1 << 0), + LED_CAPS_LOCK = (1 << 1), + LED_SCROLL_LOCK = (1 << 2), +}; + +enum weston_mode_aspect_ratio { + /** The picture aspect ratio values, for the aspect_ratio field of + * weston_mode. The values here, are taken from + * DRM_MODE_PICTURE_ASPECT_* from drm_mode.h. + */ + WESTON_MODE_PIC_AR_NONE = 0, /* DRM_MODE_PICTURE_ASPECT_NONE */ + WESTON_MODE_PIC_AR_4_3 = 1, /* DRM_MODE_PICTURE_ASPECT_4_3 */ + WESTON_MODE_PIC_AR_16_9 = 2, /* DRM_MODE_PICTURE_ASPECT_16_9 */ + WESTON_MODE_PIC_AR_64_27 = 3, /* DRM_MODE_PICTURE_ASPECT_64_27 */ + WESTON_MODE_PIC_AR_256_135 = 4, /* DRM_MODE_PICTURE_ASPECT_256_135*/ +}; + +enum weston_surface_protection_mode { + WESTON_SURFACE_PROTECTION_MODE_RELAXED, + WESTON_SURFACE_PROTECTION_MODE_ENFORCED +}; + +struct weston_mode { + uint32_t flags; + enum weston_mode_aspect_ratio aspect_ratio; + int32_t width, height; + uint32_t refresh; + struct wl_list link; +}; + +struct weston_animation { + void (*frame)(struct weston_animation *animation, + struct weston_output *output, + const struct timespec *time); + 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; + struct timespec timestamp; + uint32_t clip; +}; + +struct weston_output_zoom { + bool active; + float increment; + float level; + float max_level; + float trans_x, trans_y; + struct { + double x, y; + } current; + struct weston_seat *seat; + struct weston_animation animation_z; + struct weston_spring spring_z; + struct wl_listener motion_listener; +}; + +/* bit compatible with drm definitions. */ +enum dpms_enum { + WESTON_DPMS_ON, + WESTON_DPMS_STANDBY, + WESTON_DPMS_SUSPEND, + WESTON_DPMS_OFF +}; + +/* enum for content protection requests/status + * + * This enum represents the content protection requests and statuses in + * libweston and its enum values correspond to those of 'type' enum defined in + * weston-content-protection protocol. The values should exactly match to the + * values of the 'type' enum of the protocol. + */ + +enum weston_hdcp_protection { + WESTON_HDCP_DISABLE = 0, + WESTON_HDCP_ENABLE_TYPE_0, + WESTON_HDCP_ENABLE_TYPE_1 +}; + +/** Represents a head, usually a display connector + * + * \rst + See :ref:`libweston-head`. \endrst + * + * \ingroup head + */ +struct weston_head { + struct weston_compositor *compositor; /**< owning compositor */ + struct wl_list compositor_link; /**< in weston_compositor::head_list */ + struct wl_signal destroy_signal; /**< destroy callbacks */ + + struct weston_output *output; /**< the output driving this head */ + struct wl_list output_link; /**< in weston_output::head_list */ + + struct wl_list resource_list; /**< wl_output protocol objects */ + struct wl_global *global; /**< wl_output global */ + + struct wl_list xdg_output_resource_list; /**< xdg_output protocol objects */ + + int32_t mm_width; /**< physical image width in mm */ + int32_t mm_height; /**< physical image height in mm */ + + /** WL_OUTPUT_TRANSFORM enum to apply to match native orientation */ + uint32_t transform; + + char *make; /**< monitor manufacturer (PNP ID) */ + char *model; /**< monitor model */ + char *serial_number; /**< monitor serial */ + uint32_t subpixel; /**< enum wl_output_subpixel */ + bool connection_internal; /**< embedded monitor (e.g. laptop) */ + bool device_changed; /**< monitor information has changed */ + + char *name; /**< head name, e.g. connector name */ + bool connected; /**< is physically connected */ + bool non_desktop; /**< non-desktop display, e.g. HMD */ + + /** Current content protection status */ + enum weston_hdcp_protection current_protection; +}; + +/** Content producer for heads + * + * \rst + See :ref:`libweston-output`. \endrst + * + * \ingroup output + */ +struct weston_output { + uint32_t id; + char *name; + + /** Matches the lifetime from the user perspective */ + struct wl_signal user_destroy_signal; + + void *renderer_state; + + struct wl_list link; + struct weston_compositor *compositor; + + /** From global to output buffer coordinates. */ + struct weston_matrix matrix; + /** From output buffer to global coordinates. */ + struct weston_matrix inverse_matrix; + + struct wl_list animation_list; + int32_t x, y, width, height; + + /** Output area in global coordinates, simple rect */ + pixman_region32_t region; + + /** True if damage has occurred since the last repaint for this output; + * if set, a repaint will eventually occur. */ + bool repaint_needed; + + /** Used only between repaint_begin and repaint_cancel. */ + bool repainted; + + /** State of the repaint loop */ + enum { + REPAINT_NOT_SCHEDULED = 0, /**< idle; no repaint will occur */ + REPAINT_BEGIN_FROM_IDLE, /**< start_repaint_loop scheduled */ + REPAINT_SCHEDULED, /**< repaint is scheduled to occur */ + REPAINT_AWAITING_COMPLETION, /**< last repaint not yet finished */ + } repaint_status; + + /** If repaint_status is REPAINT_SCHEDULED, contains the time the + * next repaint should be run */ + struct timespec next_repaint; + + /** For cancelling the idle_repaint callback on output destruction. */ + struct wl_event_source *idle_repaint_source; + + struct weston_output_zoom zoom; + int dirty; + struct wl_signal frame_signal; + struct wl_signal destroy_signal; /**< sent when disabled */ + int move_x, move_y; + struct timespec frame_time; /* presentation timestamp */ + uint64_t msc; /* media stream counter */ + int disable_planes; + int destroying; + struct wl_list feedback_list; + + 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; + + struct wl_list head_list; /**< List of driven weston_heads */ + + enum weston_hdcp_protection desired_protection; + enum weston_hdcp_protection current_protection; + bool allow_protection; + + int (*start_repaint_loop)(struct weston_output *output); + int (*repaint)(struct weston_output *output, + pixman_region32_t *damage, + void *repaint_data); + void (*destroy)(struct weston_output *output); + void (*assign_planes)(struct weston_output *output, void *repaint_data); + 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); + + uint16_t gamma_size; + void (*set_gamma)(struct weston_output *output, + uint16_t size, + uint16_t *r, + uint16_t *g, + uint16_t *b); + + bool enabled; /**< is in the output_list, not pending list */ + int scale; + + int (*enable)(struct weston_output *output); + int (*disable)(struct weston_output *output); + + /** Attach a head in the backend + * + * @param output The output to attach to. + * @param head The head to attach. + * @return 0 on success, -1 on failure. + * + * Do anything necessary to account for a new head being attached to + * the output, and check any conditions possible. On failure, both + * the head and the output must be left as before the call. + * + * Libweston core will add the head to the head_list after a successful + * call. + */ + int (*attach_head)(struct weston_output *output, + struct weston_head *head); + + /** Detach a head in the backend + * + * @param output The output to detach from. + * @param head The head to detach. + * + * Do any clean-up necessary to detach this head from the output. + * The head has already been removed from the output's head_list. + */ + void (*detach_head)(struct weston_output *output, + struct weston_head *head); +}; + +enum weston_pointer_motion_mask { + WESTON_POINTER_MOTION_ABS = 1 << 0, + WESTON_POINTER_MOTION_REL = 1 << 1, + WESTON_POINTER_MOTION_REL_UNACCEL = 1 << 2, +}; + +struct weston_pointer_motion_event { + uint32_t mask; + struct timespec time; + double x; + double y; + double dx; + double dy; + double dx_unaccel; + double dy_unaccel; +}; + +struct weston_pointer_axis_event { + uint32_t axis; + double value; + bool has_discrete; + int32_t discrete; +}; + +struct weston_pointer_grab; +struct weston_pointer_grab_interface { + void (*focus)(struct weston_pointer_grab *grab); + void (*motion)(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_motion_event *event); + void (*button)(struct weston_pointer_grab *grab, + const struct timespec *time, + uint32_t button, uint32_t state); + void (*axis)(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_axis_event *event); + void (*axis_source)(struct weston_pointer_grab *grab, uint32_t source); + void (*frame)(struct weston_pointer_grab *grab); + 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, + const struct timespec *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, + const struct timespec *time, + int touch_id, + wl_fixed_t sx, + wl_fixed_t sy); + void (*up)(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id); + void (*motion)(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id, + wl_fixed_t sx, + wl_fixed_t sy); + void (*frame)(struct weston_touch_grab *grab); + 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; + uint32_t dnd_actions; + enum wl_data_device_manager_dnd_action preferred_dnd_action; + bool in_ask; +}; + +struct weston_data_source { + struct wl_resource *resource; + struct wl_signal destroy_signal; + struct wl_array mime_types; + struct weston_data_offer *offer; + struct weston_seat *seat; + bool accepted; + bool actions_set; + bool set_selection; + uint32_t dnd_actions; + enum wl_data_device_manager_dnd_action current_dnd_action; + enum wl_data_device_manager_dnd_action compositor_action; + + 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_client { + struct wl_list link; + struct wl_client *client; + struct wl_list pointer_resources; + struct wl_list relative_pointer_resources; +}; + +struct weston_pointer { + struct weston_seat *seat; + + struct wl_list pointer_clients; + + struct weston_view *focus; + struct weston_pointer_client *focus_client; + uint32_t focus_serial; + struct wl_listener focus_view_listener; + struct wl_listener focus_resource_listener; + struct wl_signal focus_signal; + struct wl_signal motion_signal; + struct wl_signal destroy_signal; + + struct weston_view *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; + struct timespec grab_time; + + wl_fixed_t x, y; + wl_fixed_t sx, sy; + uint32_t button_count; + + struct wl_listener output_destroy_listener; + + struct wl_list timestamps_list; +}; + +/** libinput style calibration matrix + * + * See https://wayland.freedesktop.org/libinput/doc/latest/absolute_axes.html + * and libinput_device_config_calibration_set_matrix(). + */ +struct weston_touch_device_matrix { + float m[6]; +}; + +struct weston_touch_device; + +/** Operations for a calibratable touchscreen */ +struct weston_touch_device_ops { + /** Get the associated output if existing. */ + struct weston_output *(*get_output)(struct weston_touch_device *device); + + /** Get the name of the associated head if existing. */ + const char * + (*get_calibration_head_name)(struct weston_touch_device *device); + + /** Retrieve the current calibration matrix. */ + void (*get_calibration)(struct weston_touch_device *device, + struct weston_touch_device_matrix *cal); + + /** Set a new calibration matrix. */ + void (*set_calibration)(struct weston_touch_device *device, + const struct weston_touch_device_matrix *cal); +}; + +enum weston_touch_mode { + /** Normal touch event handling */ + WESTON_TOUCH_MODE_NORMAL, + + /** Prepare moving to WESTON_TOUCH_MODE_CALIB. + * + * Move to WESTON_TOUCH_MODE_CALIB as soon as no touches are down on + * any seat. Until then, all touch events are routed normally. + */ + WESTON_TOUCH_MODE_PREP_CALIB, + + /** Calibration mode + * + * Only a single weston_touch_device forwards events to the calibrator + * all other touch device cause a calibrator "wrong device" event to + * be sent. + */ + WESTON_TOUCH_MODE_CALIB, + + /** Prepare moving to WESTON_TOUCH_MODE_NORMAL. + * + * Move to WESTON_TOUCH_MODE_NORMAL as soon as no touches are down on + * any seat. Until then, touch events are routed as in + * WESTON_TOUCH_MODE_CALIB except "wrong device" events are not sent. + */ + WESTON_TOUCH_MODE_PREP_NORMAL +}; + +/** Represents a physical touchscreen input device */ +struct weston_touch_device { + char *syspath; /**< unique name */ + + struct weston_touch *aggregate; /**< weston_touch this is part of */ + struct wl_list link; /**< in weston_touch::device_list */ + struct wl_signal destroy_signal; /**< destroy notifier */ + + void *backend_data; /**< backend-specific private */ + + const struct weston_touch_device_ops *ops; + struct weston_touch_device_matrix saved_calibration; +}; + +/** Represents a set of touchscreen devices aggregated under a seat */ +struct weston_touch { + struct weston_seat *seat; + + struct wl_list device_list; /* struct weston_touch_device::link */ + + struct wl_list resource_list; + struct wl_list focus_resource_list; + struct weston_view *focus; + struct wl_listener focus_view_listener; + struct wl_listener focus_resource_listener; + uint32_t focus_serial; + struct wl_signal focus_signal; + + uint32_t num_tp; + + 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; + struct timespec grab_time; + + struct wl_list timestamps_list; +}; + +void +weston_pointer_motion_to_abs(struct weston_pointer *pointer, + struct weston_pointer_motion_event *event, + wl_fixed_t *x, wl_fixed_t *y); + +void +weston_pointer_send_motion(struct weston_pointer *pointer, + const struct timespec *time, + struct weston_pointer_motion_event *event); +bool +weston_pointer_has_focus_resource(struct weston_pointer *pointer); +void +weston_pointer_send_button(struct weston_pointer *pointer, + const struct timespec *time, + uint32_t button, uint32_t state_w); +void +weston_pointer_send_axis(struct weston_pointer *pointer, + const struct timespec *time, + struct weston_pointer_axis_event *event); +void +weston_pointer_send_axis_source(struct weston_pointer *pointer, + uint32_t source); +void +weston_pointer_send_frame(struct weston_pointer *pointer); + +void +weston_pointer_set_focus(struct weston_pointer *pointer, + struct weston_view *view, + wl_fixed_t sx, wl_fixed_t sy); +void +weston_pointer_clear_focus(struct weston_pointer *pointer); +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_move(struct weston_pointer *pointer, + struct weston_pointer_motion_event *event); +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); +int +/* + * 'mask' and 'value' should be a bitwise mask of one or more + * valued of the weston_keyboard_locks enum. + */ +weston_keyboard_set_locks(struct weston_keyboard *keyboard, + uint32_t mask, uint32_t value); + +void +weston_keyboard_send_key(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, + enum wl_keyboard_key_state state); +void +weston_keyboard_send_modifiers(struct weston_keyboard *keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, uint32_t group); + +void +weston_touch_set_focus(struct weston_touch *touch, + struct weston_view *view); +void +weston_touch_start_grab(struct weston_touch *touch, + struct weston_touch_grab *grab); +void +weston_touch_end_grab(struct weston_touch *touch); + +void +weston_touch_send_down(struct weston_touch *touch, const struct timespec *time, + int touch_id, wl_fixed_t x, wl_fixed_t y); +void +weston_touch_send_up(struct weston_touch *touch, const struct timespec *time, + int touch_id); +void +weston_touch_send_motion(struct weston_touch *touch, + const struct timespec *time, int touch_id, + wl_fixed_t x, wl_fixed_t y); +void +weston_touch_send_frame(struct weston_touch *touch); + + +void +weston_seat_set_selection(struct weston_seat *seat, + struct weston_data_source *source, uint32_t serial); + +int +weston_pointer_start_drag(struct weston_pointer *pointer, + struct weston_data_source *source, + struct weston_surface *icon, + struct wl_client *client); +struct weston_xkb_info { + struct xkb_keymap *keymap; + struct ro_anonymous_file *keymap_rofile; + 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; + struct wl_listener focus_resource_listener; + 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; + struct timespec 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_xkb_info *xkb_info; + struct { + struct xkb_state *state; + enum weston_led leds; + } xkb_state; + struct xkb_keymap *pending_keymap; + + struct wl_list timestamps_list; +}; + +struct weston_seat { + struct wl_list base_resource_list; + + struct wl_global *global; + struct weston_pointer *pointer_state; + struct weston_keyboard *keyboard_state; + struct weston_touch *touch_state; + int pointer_device_count; + int keyboard_device_count; + int touch_device_count; + + struct weston_output *output; /* constraint */ + + struct wl_signal destroy_signal; + struct wl_signal updated_caps_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; + + void (*led_update)(struct weston_seat *ws, enum weston_led leds); + + struct input_method *input_method; + char *seat_name; +}; + +enum { + WESTON_COMPOSITOR_ACTIVE, /* normal rendering and events */ + 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 dpms + * to off */ +}; + +struct weston_layer_entry { + struct wl_list link; + struct weston_layer *layer; +}; + +/** + * Higher value means higher in the stack. + * + * These values are based on well-known concepts in a classic desktop + * environment. Third-party modules based on libweston are encouraged to use + * them to integrate better with other projects. + * + * A fully integrated environment can use any value, based on these or not, + * at their discretion. + */ +enum weston_layer_position { + /* + * Special value to make the layer invisible and still rendered. + * This is used by compositors wanting e.g. minimized surfaces to still + * receive frame callbacks. + */ + WESTON_LAYER_POSITION_HIDDEN = 0x00000000, + + /* + * There should always be a background layer with a surface covering + * the visible area. + * + * If the compositor handles the background itself, it should use + * BACKGROUND. + * + * If the compositor supports runtime-loadable modules to set the + * background, it should put a solid color surface at (BACKGROUND - 1) + * and modules must use BACKGROUND. + */ + WESTON_LAYER_POSITION_BACKGROUND = 0x00000002, + + /* For "desktop widgets" and applications like conky. */ + WESTON_LAYER_POSITION_BOTTOM_UI = 0x30000000, + + /* For regular applications, only one layer should have this value + * to ensure proper stacking control. */ + WESTON_LAYER_POSITION_NORMAL = 0x50000000, + + /* For desktop UI, like panels. */ + WESTON_LAYER_POSITION_UI = 0x80000000, + + /* For fullscreen applications that should cover UI. */ + WESTON_LAYER_POSITION_FULLSCREEN = 0xb0000000, + + /* For special UI like on-screen keyboard that fullscreen applications + * will need. */ + WESTON_LAYER_POSITION_TOP_UI = 0xe0000000, + + /* For the lock surface. */ + WESTON_LAYER_POSITION_LOCK = 0xffff0000, + + /* Values reserved for libweston internal usage */ + WESTON_LAYER_POSITION_CURSOR = 0xfffffffe, + WESTON_LAYER_POSITION_FADE = 0xffffffff, +}; + +struct weston_layer { + struct weston_compositor *compositor; + struct wl_list link; /* weston_compositor::layer_list */ + enum weston_layer_position position; + pixman_box32_t mask; + struct weston_layer_entry view_list; +}; + +struct weston_plane { + struct weston_compositor *compositor; + pixman_region32_t damage; /**< in global coords */ + 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); + void (*surface_set_color)(struct weston_surface *surface, + float red, float green, + float blue, float alpha); + void (*destroy)(struct weston_compositor *ec); + + + /** See weston_surface_get_content_size() */ + void (*surface_get_content_size)(struct weston_surface *surface, + int *width, int *height); + + /** See weston_surface_copy_content() */ + int (*surface_copy_content)(struct weston_surface *surface, + void *target, size_t size, + int src_x, int src_y, + int width, int height); + + /** See weston_compositor_import_dmabuf() */ + bool (*import_dmabuf)(struct weston_compositor *ec, + struct linux_dmabuf_buffer *buffer); + + /** On error sets num_formats to zero */ + void (*query_dmabuf_formats)(struct weston_compositor *ec, + int **formats, int *num_formats); + + /** On error sets num_modifiers to zero */ + void (*query_dmabuf_modifiers)(struct weston_compositor *ec, + int format, uint64_t **modifiers, + int *num_modifiers); +}; + +enum weston_capability { + /* backend/renderer supports arbitrary rotation */ + WESTON_CAP_ROTATION_ANY = 0x0001, + + /* screencaptures need to be y-flipped */ + WESTON_CAP_CAPTURE_YFLIP = 0x0002, + + /* backend/renderer has a separate cursor plane */ + WESTON_CAP_CURSOR_PLANE = 0x0004, + + /* backend supports setting arbitrary resolutions */ + WESTON_CAP_ARBITRARY_MODES = 0x0008, + + /* renderer supports weston_view_set_mask() clipping */ + WESTON_CAP_VIEW_CLIP_MASK = 0x0010, + + /* renderer supports explicit synchronization */ + WESTON_CAP_EXPLICIT_SYNC = 0x0020, +}; + +/* Configuration struct for a backend. + * + * This struct carries the configuration for a backend, and it's + * passed to the backend's init entry point. The backend will + * likely want to subclass this in order to handle backend specific + * data. + * + * \rststar + * .. note: + * + * Alternate designs were proposed (Feb 2016) for using opaque structures[1] + * and for section+key/value getter/setters[2]. The rationale for selecting + * the transparent structure design is based on several assumptions[3] which + * may require re-evaluating the design choice if they fail to hold. + * + * 1. https://lists.freedesktop.org/archives/wayland-devel/2016-February/026989.html + * 2. https://lists.freedesktop.org/archives/wayland-devel/2016-February/026929.html + * 3. https://lists.freedesktop.org/archives/wayland-devel/2016-February/027228.html + * + * \endrststar + */ +struct weston_backend_config { + /** Major version for the backend-specific config struct + * + * This version must match exactly what the backend expects, otherwise + * the struct is incompatible. + */ + uint32_t struct_version; + + /** Minor version of the backend-specific config struct + * + * This must be set to sizeof(struct backend-specific config). + * If the value here is smaller than what the backend expects, the + * extra config members will assume their default values. + * + * A value greater than what the backend expects is incompatible. + */ + size_t struct_size; +}; + +struct weston_backend; + +/** Callback for saving calibration + * + * \param compositor The compositor. + * \param device The physical touch device to save for. + * \param calibration The new calibration from a client. + * \return -1 on failure, 0 on success. + * + * Failure will prevent taking the new calibration into use. + */ +typedef int (*weston_touch_calibration_save_func)( + struct weston_compositor *compositor, + struct weston_touch_device *device, + const struct weston_touch_device_matrix *calibration); +struct weston_touch_calibrator; + +struct weston_desktop_xwayland; +struct weston_desktop_xwayland_interface; +struct weston_debug_compositor; + +/** Main object, container-like structure which aggregates all other objects. + * + * \ingroup compositor + */ +struct weston_compositor { + struct wl_signal destroy_signal; + + struct wl_display *wl_display; + struct weston_desktop_xwayland *xwayland; + const struct weston_desktop_xwayland_interface *xwayland_interface; + + /* surface signals */ + struct wl_signal create_surface_signal; + 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_signal output_destroyed_signal; + struct wl_signal output_moved_signal; + struct wl_signal output_resized_signal; /* callback argument: resized output */ + + /* Signal for output changes triggered by configuration from frontend + * or head state changes from backend. + */ + struct wl_signal output_heads_changed_signal; /* arg: weston_output */ + + struct wl_signal session_signal; + bool session_active; + + struct weston_layer fade_layer; + struct weston_layer cursor_layer; + + struct wl_list pending_output_list; + struct wl_list output_list; + struct wl_list head_list; /* struct weston_head::compositor_link */ + struct wl_list seat_list; + struct wl_list layer_list; /* struct weston_layer::link */ + struct wl_list view_list; /* struct weston_view::link */ + struct wl_list plane_list; + struct wl_list key_binding_list; + struct wl_list modifier_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 */ + struct wl_event_source *repaint_timer; + + const struct weston_pointer_grab_interface *default_pointer_grab; + + /* Repaint state. */ + struct weston_plane primary_plane; + uint32_t capabilities; /* combination of enum weston_capability */ + + struct weston_renderer *renderer; + + pixman_format_code_t read_format; + + struct weston_backend *backend; + + struct weston_launcher *launcher; + + struct wl_list plugin_api_list; /* struct weston_plugin_api::link */ + + uint32_t output_id_pool; + + struct xkb_rule_names xkb_names; + struct xkb_context *xkb_context; + struct weston_xkb_info *xkb_info; + + int32_t kb_repeat_rate; + int32_t kb_repeat_delay; + + bool vt_switching; + + clockid_t presentation_clock; + int32_t repaint_msec; + + unsigned int activate_serial; + + struct wl_global *pointer_constraints; + + int exit_code; + + void *user_data; + void (*exit)(struct weston_compositor *c); + + /* Whether to let the compositor run without any input device. */ + bool require_input; + + /* Signal for a backend to inform a frontend about possible changes + * in head status. + */ + struct wl_signal heads_changed_signal; + struct wl_event_source *heads_changed_source; + + /* Touchscreen calibrator support: */ + enum weston_touch_mode touch_mode; + struct wl_global *touch_calibration; + weston_touch_calibration_save_func touch_calibration_save; + struct weston_layer calibrator_layer; + struct weston_touch_calibrator *touch_calibrator; + +// OHOS remove logger +// struct weston_log_context *weston_log_ctx; +// struct weston_log_scope *debug_scene; + struct weston_log_scope *timeline; + + struct content_protection *content_protection; +}; + +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_buffer_viewport { + struct { + /* wl_surface.set_buffer_transform */ + uint32_t transform; + + /* wl_surface.set_scaling_factor */ + int32_t scale; + + /* + * If src_width != wl_fixed_from_int(-1), + * then and only then src_* are used. + */ + wl_fixed_t src_x, src_y; + wl_fixed_t src_width, src_height; + } buffer; + + struct { + /* + * If width == -1, the size is inferred from the buffer. + */ + int32_t width, height; + } surface; + + int changed; +}; + +struct weston_buffer_release { + /* The associated zwp_linux_buffer_release_v1 resource. */ + struct wl_resource *resource; + /* How many weston_buffer_release_reference objects point to this + * object. */ + uint32_t ref_count; + /* The fence fd, if any, associated with this release. If the fence fd + * is -1 then this is considered an immediate release. */ + int fence_fd; +}; + +struct weston_buffer_release_reference { + struct weston_buffer_release *buffer_release; + /* Listener for the destruction of the wl_resource associated with the + * referenced buffer_release object. */ + struct wl_listener destroy_listener; +}; + +struct weston_region { + struct wl_resource *resource; + pixman_region32_t region; +}; + +/* Using weston_view transformations + * + * To add a transformation to a view, create a struct weston_transform, and + * add it to the list view->geometry.transformation_list. Whenever you + * change the list, anything under view->geometry, or anything in the + * weston_transforms linked into the list, you must call + * weston_view_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 view-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 view->transform.matrix, and the inverse of it in + * view->transform.inverse. + * + * The list always contains view->transform.position transformation, which + * is the translation by view->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 view->geometry.parent is set, the total transformation of this + * view will be the parent's total transformation and this transformation + * combined: + * Mparent * Mn * ... * M2 * M1 + */ + +struct weston_view { + struct weston_surface *surface; + struct wl_list surface_link; + struct wl_signal destroy_signal; + + struct wl_list link; /* weston_compositor::view_list */ + struct weston_layer_entry layer_link; /* part of geometry */ + struct weston_plane *plane; + + /* For weston_layer inheritance from another view */ + struct weston_view *parent_view; + + unsigned int click_to_activate_serial; + + pixman_region32_t clip; /* See weston_view_damage_below() */ + float alpha; /* part of geometry, see below */ + + 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 */ + + /* struct weston_transform */ + struct wl_list transformation_list; + + /* managed by weston_surface_set_transform_parent() */ + struct weston_view *parent; + struct wl_listener parent_destroy_listener; + struct wl_list child_list; /* geometry.parent_link */ + struct wl_list parent_link; + + /* managed by weston_view_set_mask() */ + bool scissor_enabled; + pixman_region32_t scissor; /* always a simple rect */ + } geometry; + + /* State derived from geometry state, read-only. + * This is updated by weston_view_update_transform(). + */ + struct { + int dirty; + + /* Approximations in global coordinates: + * - boundingbox is guaranteed to include the whole view in + * the smallest possible single rectangle. + * - opaque is guaranteed to be fully opaque, though not + * necessarily include all opaque areas. + */ + 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; + + /* + * The primary output for this view. + * Used for picking the output for driving internal animations on the + * view, inheriting the primary output for related views in shells, etc. + */ + struct weston_output *output; + struct wl_listener output_destroy_listener; + + /* + * A more complete representation of all outputs this surface is + * displayed on. + */ + uint32_t output_mask; + + /* Per-surface Presentation feedback flags, controlled by backend. */ + uint32_t psf_flags; + + bool is_mapped; +}; + +struct weston_surface_state { + /* 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_surface; + /* wl_surface.damage_buffer */ + pixman_region32_t damage_buffer; + + /* 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; + + /* presentation.feedback */ + struct wl_list feedback_list; + + /* wl_surface.set_buffer_transform */ + /* wl_surface.set_scaling_factor */ + /* wp_viewport.set_source */ + /* wp_viewport.set_destination */ + struct weston_buffer_viewport buffer_viewport; + + /* zwp_surface_synchronization_v1.set_acquire_fence */ + int acquire_fence_fd; + + /* zwp_surface_synchronization_v1.get_release */ + struct weston_buffer_release_reference buffer_release_ref; + + /* weston_protected_surface.set_type */ + enum weston_hdcp_protection desired_protection; + + /* weston_protected_surface.enforced/relaxed */ + enum weston_surface_protection_mode protection_mode; +}; + +struct weston_surface_activation_data { + struct weston_surface *surface; + struct weston_seat *seat; +}; + +struct weston_pointer_constraint { + struct wl_list link; + + struct weston_surface *surface; + struct weston_view *view; + struct wl_resource *resource; + struct weston_pointer_grab grab; + struct weston_pointer *pointer; + uint32_t lifetime; + + pixman_region32_t region; + pixman_region32_t region_pending; + bool region_is_pending; + + wl_fixed_t hint_x; + wl_fixed_t hint_y; + wl_fixed_t hint_x_pending; + wl_fixed_t hint_y_pending; + bool hint_is_pending; + + struct wl_listener pointer_destroy_listener; + struct wl_listener surface_destroy_listener; + struct wl_listener surface_commit_listener; + struct wl_listener surface_activate_listener; +}; + +struct weston_surface { + struct wl_resource *resource; + struct wl_signal destroy_signal; /* callback argument: this surface */ + struct weston_compositor *compositor; + struct wl_signal commit_signal; + + /** Damage in local coordinates from the client, for tex upload. */ + pixman_region32_t damage; + + pixman_region32_t opaque; /* part of geometry, see below */ + pixman_region32_t input; + int32_t width, height; + int32_t ref_count; + + /* Not for long-term storage. This exists for book-keeping while + * iterating over surfaces and views + */ + bool touched; + + void *renderer_state; + + struct wl_list views; + + /* + * Which output to vsync this surface to. + * Used to determine whether to send or queue frame events, and for + * other client-visible syncing/throttling tied to the output + * repaint cycle. + */ + 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 wl_list feedback_list; + + struct weston_buffer_reference buffer_ref; + struct weston_buffer_viewport buffer_viewport; + int32_t width_from_buffer; /* before applying viewport */ + int32_t height_from_buffer; + bool keep_buffer; /* for backends to prevent early release */ + + /* wp_viewport resource for this surface */ + struct wl_resource *viewport_resource; + + /* All the pending state, that wl_surface.commit will apply. */ + struct weston_surface_state pending; + + /* Matrices representating of the full transformation between + * buffer and surface coordinates. These matrices are updated + * using the weston_surface_build_buffer_matrix function. */ + struct weston_matrix buffer_to_surface_matrix; + struct weston_matrix surface_to_buffer_matrix; + + /* + * If non-NULL, this function will be called on + * wl_surface::commit after a new buffer has been set up for + * this surface. The integer params are the sx and sy + * parameters supplied to wl_surface::attach. + */ + void (*committed)(struct weston_surface *es, int32_t sx, int32_t sy); + void *committed_private; + int (*get_label)(struct weston_surface *surface, char *buf, size_t len); + + /* 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 */ + + /* + * For tracking protocol role assignments. Different roles may + * have the same configure hook, e.g. in shell.c. Configure hook + * may get reset, this will not. + * XXX: map configure functions 1:1 to roles, and never reset it, + * and replace role_name with configure. + */ + const char *role_name; + + bool is_mapped; + bool is_opaque; + + /* An list of per seat pointer constraints. */ + struct wl_list pointer_constraints; + + /* zwp_surface_synchronization_v1 resource for this surface */ + struct wl_resource *synchronization_resource; + int acquire_fence_fd; + struct weston_buffer_release_reference buffer_release_ref; + + enum weston_hdcp_protection desired_protection; + enum weston_hdcp_protection current_protection; + enum weston_surface_protection_mode protection_mode; + enum wl_surface_type type; // OHOS surface type +}; + +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; + + int has_cached_data; + struct weston_surface_state cached; + struct weston_buffer_reference cached_buffer_ref; + + /* Sub-surface has been reordered; need to apply damage. */ + bool reordered; + + int synchronized; + + /* Used for constructing the view tree */ + struct wl_list unused_views; +}; + +struct protected_surface { + struct weston_surface *surface; + struct wl_listener surface_destroy_listener; + struct wl_list link; + struct wl_resource *protection_resource; + struct content_protection *cp_backptr; +}; + +struct content_protection { + struct weston_compositor *compositor; + struct wl_listener destroy_listener; +// OHOS remove logger +// struct weston_log_scope *debug; + struct wl_list protected_list; + struct wl_event_source *surface_protection_update; +}; + + +enum weston_key_state_update { + STATE_UPDATE_AUTOMATIC, + STATE_UPDATE_NONE, +}; + +enum weston_activate_flag { + WESTON_ACTIVATE_FLAG_NONE = 0, + WESTON_ACTIVATE_FLAG_CONFIGURE = 1 << 0, + WESTON_ACTIVATE_FLAG_CLICKED = 1 << 1, +}; + +void +weston_version(int *major, int *minor, int *micro); + +void +weston_view_set_output(struct weston_view *view, struct weston_output *output); + +void +weston_view_update_transform(struct weston_view *view); + +void +weston_view_geometry_dirty(struct weston_view *view); + +void +weston_view_to_global_float(struct weston_view *view, + float sx, float sy, float *x, float *y); + +void +weston_view_from_global(struct weston_view *view, + int32_t x, int32_t y, int32_t *vx, int32_t *vy); +void +weston_view_from_global_fixed(struct weston_view *view, + wl_fixed_t x, wl_fixed_t y, + wl_fixed_t *vx, wl_fixed_t *vy); + +void +weston_view_activate(struct weston_view *view, + struct weston_seat *seat, + uint32_t flags); + +void +notify_modifiers(struct weston_seat *seat, uint32_t serial); + +void +weston_layer_entry_insert(struct weston_layer_entry *list, + struct weston_layer_entry *entry); +void +weston_layer_entry_remove(struct weston_layer_entry *entry); +void +weston_layer_init(struct weston_layer *layer, + struct weston_compositor *compositor); +void +weston_layer_set_position(struct weston_layer *layer, + enum weston_layer_position position); +void +weston_layer_unset_position(struct weston_layer *layer); + +void +weston_layer_set_mask(struct weston_layer *layer, int x, int y, int width, int height); + +void +weston_layer_set_mask_infinite(struct weston_layer *layer); + +bool +weston_layer_mask_is_infinite(struct weston_layer *layer); + +/* An invalid flag in presented_flags to catch logic errors. */ +#define WP_PRESENTATION_FEEDBACK_INVALID (1U << 31) + +void +weston_output_schedule_repaint(struct weston_output *output); +void +weston_compositor_schedule_repaint(struct weston_compositor *compositor); +void +weston_compositor_damage_all(struct weston_compositor *compositor); +void +weston_compositor_wake(struct weston_compositor *compositor); +void +weston_compositor_sleep(struct weston_compositor *compositor); +struct weston_view * +weston_compositor_pick_view(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_keyboard *keyboard, + const struct timespec *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); +// OHOS remove debugger +//struct weston_binding * +//weston_compositor_add_debug_binding(struct weston_compositor *compositor, +// uint32_t key, +// weston_key_binding_handler_t binding, +// void *data); + +typedef void (*weston_modifier_binding_handler_t)(struct weston_keyboard *keyboard, + enum weston_keyboard_modifier modifier, + void *data); +struct weston_binding * +weston_compositor_add_modifier_binding(struct weston_compositor *compositor, + enum weston_keyboard_modifier modifier, + weston_modifier_binding_handler_t binding, + void *data); + +typedef void (*weston_button_binding_handler_t)(struct weston_pointer *pointer, + const struct timespec *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_touch *touch, + const struct timespec *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_pointer *pointer, + const struct timespec *time, + struct weston_pointer_axis_event *event, + 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); + +void +weston_binding_destroy(struct weston_binding *binding); + +void +weston_install_debug_key_binding(struct weston_compositor *compositor, + uint32_t mod); + +void +weston_compositor_set_default_pointer_grab(struct weston_compositor *compositor, + const struct weston_pointer_grab_interface *interface); + +struct weston_surface * +weston_surface_create(struct weston_compositor *compositor); + +struct weston_view * +weston_view_create(struct weston_surface *surface); + +void +weston_view_destroy(struct weston_view *view); + +void +weston_view_set_position(struct weston_view *view, + float x, float y); + +void +weston_view_set_transform_parent(struct weston_view *view, + struct weston_view *parent); + +void +weston_view_set_mask(struct weston_view *view, + int x, int y, int width, int height); + +void +weston_view_set_mask_infinite(struct weston_view *view); + +bool +weston_view_is_mapped(struct weston_view *view); + +void +weston_view_schedule_repaint(struct weston_view *view); + +bool +weston_surface_is_mapped(struct weston_surface *surface); + +void +weston_surface_set_size(struct weston_surface *surface, + int32_t width, int32_t height); + +void +weston_surface_damage(struct weston_surface *surface); + +void +weston_view_damage_below(struct weston_view *view); + +void +weston_view_unmap(struct weston_view *view); + +void +weston_surface_unmap(struct weston_surface *surface); + +struct weston_surface * +weston_surface_get_main_surface(struct weston_surface *surface); + +int +weston_surface_set_role(struct weston_surface *surface, + const char *role_name, + struct wl_resource *error_resource, + uint32_t error_code); +const char * +weston_surface_get_role(struct weston_surface *surface); + +void +weston_surface_set_label_func(struct weston_surface *surface, + int (*desc)(struct weston_surface *, + char *, size_t)); + +void +weston_surface_get_content_size(struct weston_surface *surface, + int *width, int *height); + +struct weston_geometry +weston_surface_get_bounding_box(struct weston_surface *surface); + +int +weston_surface_copy_content(struct weston_surface *surface, + void *target, size_t size, + int src_x, int src_y, + int width, int height); + +struct weston_buffer * +weston_buffer_from_resource(struct wl_resource *resource); + +void +weston_compositor_get_time(struct timespec *time); + +void +weston_compositor_destroy(struct weston_compositor *ec); + +// OHOS remove logger +//struct weston_compositor * +//weston_compositor_create(struct wl_display *display, +// struct weston_log_context *log_ctx, void *user_data); +struct weston_compositor * +weston_compositor_create(struct wl_display *display, void *user_data); + +bool +weston_compositor_add_destroy_listener_once(struct weston_compositor *compositor, + struct wl_listener *listener, + wl_notify_func_t destroy_handler); + +enum weston_compositor_backend { + WESTON_BACKEND_DRM, + WESTON_BACKEND_FBDEV, + WESTON_BACKEND_HEADLESS, + WESTON_BACKEND_RDP, + WESTON_BACKEND_WAYLAND, + WESTON_BACKEND_X11, +}; + +int +weston_compositor_load_backend(struct weston_compositor *compositor, + enum weston_compositor_backend backend, + struct weston_backend_config *config_base); +void +weston_compositor_exit(struct weston_compositor *ec); +void * +weston_compositor_get_user_data(struct weston_compositor *compositor); +void +weston_compositor_exit_with_code(struct weston_compositor *compositor, + int exit_code); +void +weston_output_update_zoom(struct weston_output *output); +void +weston_output_activate_zoom(struct weston_output *output, + struct weston_seat *seat); +void +weston_output_add_destroy_listener(struct weston_output *output, + struct wl_listener *listener); +struct wl_listener * +weston_output_get_destroy_listener(struct weston_output *output, + wl_notify_func_t notify); +int +weston_compositor_set_xkb_rule_names(struct weston_compositor *ec, + struct xkb_rule_names *names); + +/* String literal of spaces, the same width as the timestamp. */ +#define STAMP_SPACE " " + +/** + * \ingroup wlog + */ +// OHOS remove logger +//typedef int (*log_func_t)(const char *fmt, va_list ap); +//void +//weston_log_set_handler(log_func_t log, log_func_t cont); +//int +//weston_log(const char *fmt, ...) +// __attribute__ ((format (printf, 1, 2))); +//int +//weston_log_continue(const char *fmt, ...) +// __attribute__ ((format (printf, 1, 2))); +// OHOS logcat +#define weston_log(fmt, ...) (HILOG_INFO(LOG_CORE, fmt, ##__VA_ARGS__)) +#define weston_vlog vprintf +#define weston_log_continue(fmt, ...) (HILOG_DEBUG(LOG_CORE, fmt, ##__VA_ARGS__)) + + +enum weston_screenshooter_outcome { + WESTON_SCREENSHOOTER_SUCCESS, + WESTON_SCREENSHOOTER_NO_MEMORY, + WESTON_SCREENSHOOTER_BAD_BUFFER +}; + +typedef void (*weston_screenshooter_done_func_t)(void *data, + enum weston_screenshooter_outcome outcome); +int +weston_screenshooter_shoot(struct weston_output *output, struct weston_buffer *buffer, + weston_screenshooter_done_func_t done, void *data); +struct weston_recorder * +weston_recorder_start(struct weston_output *output, const char *filename); +void +weston_recorder_stop(struct weston_recorder *recorder); + +struct weston_view_animation; +typedef void (*weston_view_animation_done_func_t)(struct weston_view_animation *animation, void *data); + +void +weston_view_animation_destroy(struct weston_view_animation *animation); + +struct weston_view_animation * +weston_zoom_run(struct weston_view *view, float start, float stop, + weston_view_animation_done_func_t done, void *data); + +struct weston_view_animation * +weston_fade_run(struct weston_view *view, + float start, float end, float k, + weston_view_animation_done_func_t done, void *data); + +struct weston_view_animation * +weston_move_scale_run(struct weston_view *view, int dx, int dy, + float start, float end, bool reverse, + weston_view_animation_done_func_t done, void *data); + +struct weston_view_animation * +weston_move_run(struct weston_view *view, int dx, int dy, + float start, float end, bool reverse, + weston_view_animation_done_func_t done, void *data); + +void +weston_fade_update(struct weston_view_animation *fade, float target); + +struct weston_view_animation * +weston_stable_fade_run(struct weston_view *front_view, float start, + struct weston_view *back_view, float end, + weston_view_animation_done_func_t done, void *data); + +struct weston_view_animation * +weston_slide_run(struct weston_view *view, float start, float stop, + weston_view_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_mode_switch_to_temporary(struct weston_output *output, + struct weston_mode *mode, + int32_t scale); +int +weston_output_mode_switch_to_native(struct weston_output *output); + +int +weston_backend_init(struct weston_compositor *c, + struct weston_backend_config *config_base); +int +weston_module_init(struct weston_compositor *compositor); + +void * +weston_load_module(const char *name, const char *entrypoint); + +size_t +weston_module_path_from_env(const char *name, char *path, size_t path_len); + +int +weston_parse_transform(const char *transform, uint32_t *out); + +const char * +weston_transform_to_string(uint32_t output_transform); + +struct weston_keyboard * +weston_seat_get_keyboard(struct weston_seat *seat); + +struct weston_pointer * +weston_seat_get_pointer(struct weston_seat *seat); + +struct weston_touch * +weston_seat_get_touch(struct weston_seat *seat); + +void +weston_seat_set_keyboard_focus(struct weston_seat *seat, + struct weston_surface *surface); + +void +weston_keyboard_send_keymap(struct weston_keyboard *kbd, + struct wl_resource *resource); + +int +weston_compositor_load_xwayland(struct weston_compositor *compositor); + +bool +weston_head_is_connected(struct weston_head *head); + +bool +weston_head_is_enabled(struct weston_head *head); + +bool +weston_head_is_device_changed(struct weston_head *head); + +bool +weston_head_is_non_desktop(struct weston_head *head); + +void +weston_head_reset_device_changed(struct weston_head *head); + +const char * +weston_head_get_name(struct weston_head *head); + +struct weston_output * +weston_head_get_output(struct weston_head *head); + +uint32_t +weston_head_get_transform(struct weston_head *head); + +void +weston_head_detach(struct weston_head *head); + +void +weston_head_add_destroy_listener(struct weston_head *head, + struct wl_listener *listener); + +struct wl_listener * +weston_head_get_destroy_listener(struct weston_head *head, + wl_notify_func_t notify); + +void +weston_head_set_content_protection_status(struct weston_head *head, + enum weston_hdcp_protection status); + +struct weston_head * +weston_compositor_iterate_heads(struct weston_compositor *compositor, + struct weston_head *iter); + +void +weston_compositor_add_heads_changed_listener(struct weston_compositor *compositor, + struct wl_listener *listener); + +struct weston_output * +weston_compositor_find_output_by_name(struct weston_compositor *compositor, + const char *name); + +struct weston_output * +weston_compositor_create_output(struct weston_compositor *compositor, + const char *name); + +struct weston_output * +weston_compositor_create_output_with_head(struct weston_compositor *compositor, + struct weston_head *head); + +void +weston_output_destroy(struct weston_output *output); + +int +weston_output_attach_head(struct weston_output *output, + struct weston_head *head); + +struct weston_head * +weston_output_iterate_heads(struct weston_output *output, + struct weston_head *iter); + +void +weston_output_set_scale(struct weston_output *output, + int32_t scale); + +void +weston_output_set_transform(struct weston_output *output, + uint32_t transform); + +void +weston_output_init(struct weston_output *output, + struct weston_compositor *compositor, + const char *name); + +void +weston_output_move(struct weston_output *output, int x, int y); + +int +weston_output_enable(struct weston_output *output); + +void +weston_output_disable(struct weston_output *output); + +void +weston_compositor_flush_heads_changed(struct weston_compositor *compositor); + +struct weston_head * +weston_head_from_resource(struct wl_resource *resource); + +struct weston_head * +weston_output_get_first_head(struct weston_output *output); + +void +weston_output_allow_protection(struct weston_output *output, + bool allow_protection); + +int +weston_compositor_enable_touch_calibrator(struct weston_compositor *compositor, + weston_touch_calibration_save_func save); + +// OHOS remove logger +//struct weston_log_context * +//weston_log_ctx_create(void); +// +//void +//weston_log_ctx_destroy(struct weston_log_context *log_ctx); + +int +weston_compositor_enable_content_protection(struct weston_compositor *compositor); + +// OHOS remove timeline +//void +//weston_timeline_refresh_subscription_objects(struct weston_compositor *wc, +// void *object); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libweston/matrix.h b/include/libweston/matrix.h new file mode 100644 index 0000000..be4d4eb --- /dev/null +++ b/include/libweston/matrix.h @@ -0,0 +1,85 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2012 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef 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/include/libweston/meson.build b/include/libweston/meson.build new file mode 100644 index 0000000..2c2f772 --- /dev/null +++ b/include/libweston/meson.build @@ -0,0 +1,31 @@ +install_headers( + 'config-parser.h', + 'libweston.h', + 'matrix.h', + 'plugin-registry.h', + 'windowed-output-api.h', + 'weston-log.h', + 'zalloc.h', + subdir: dir_include_libweston_install +) + +backend_drm_h = files('backend-drm.h') +backend_fbdev_h = files('backend-fbdev.h') +backend_headless_h = files('backend-headless.h') +backend_rdp_h = files('backend-rdp.h') +backend_wayland_h = files('backend-wayland.h') +backend_x11_h = files('backend-x11.h') + +xwayland_api_h = files('xwayland-api.h') + +libweston_version_h = configuration_data() +libweston_version_h.set('WESTON_VERSION_MAJOR', version_weston_arr[0]) +libweston_version_h.set('WESTON_VERSION_MINOR', version_weston_arr[1]) +libweston_version_h.set('WESTON_VERSION_MICRO', version_weston_arr[2]) +libweston_version_h.set('WESTON_VERSION', version_weston) +version_h = configure_file( + input: 'version.h.in', + output: 'version.h', + configuration: libweston_version_h +) +install_headers(version_h, subdir: dir_include_libweston_install) diff --git a/include/libweston/plugin-registry.h b/include/libweston/plugin-registry.h new file mode 100644 index 0000000..3f5618d --- /dev/null +++ b/include/libweston/plugin-registry.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 DENSO CORPORATION + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_PLUGIN_REGISTRY_H +#define WESTON_PLUGIN_REGISTRY_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct weston_compositor; + +int +weston_plugin_api_register(struct weston_compositor *compositor, + const char *api_name, + const void *vtable, + size_t vtable_size); + +const void * +weston_plugin_api_get(struct weston_compositor *compositor, + const char *api_name, + size_t vtable_size); + +void +weston_plugin_api_destroy_list(struct weston_compositor *compositor); + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_PLUGIN_REGISTRY_H */ diff --git a/include/libweston/version.h.in b/include/libweston/version.h.in new file mode 100644 index 0000000..b2379d0 --- /dev/null +++ b/include/libweston/version.h.in @@ -0,0 +1,50 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#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@" + +/* This macro may not do what you expect. Weston doesn't guarantee + * a stable API between 1.X and 1.Y, and thus this macro will return + * FALSE on any WESTON_VERSION_AT_LEAST(1,X,0) if the actual version + * is 1.Y.0 and X != Y). In particular, it fails if X < Y, that is, + * 1.3.0 is considered to not be "at least" 1.4.0. + * + * If you want to test for the version number being 1.3.0 or above or + * maybe in a range (eg 1.2.0 to 1.4.0), just use the WESTON_VERSION_* + * defines above directly. + */ + +#define WESTON_VERSION_AT_LEAST(major, minor, micro) \ + (WESTON_VERSION_MAJOR == (major) && \ + WESTON_VERSION_MINOR == (minor) && \ + WESTON_VERSION_MICRO >= (micro)) + +#endif diff --git a/include/libweston/weston-log.h b/include/libweston/weston-log.h new file mode 100755 index 0000000..c17dc37 --- /dev/null +++ b/include/libweston/weston-log.h @@ -0,0 +1,142 @@ +/* + * Copyright © 2017 Pekka Paalanen + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_LOG_H +#define WESTON_LOG_H + +// OHOS remove logger +//#include +//#include +//#include +//#include +//#include +// +//#ifdef __cplusplus +//extern "C" { +//#endif +// +//struct weston_compositor; +//struct weston_log_context; +//struct wl_display; +//struct weston_log_subscriber; +//struct weston_log_subscription; +// +//void +//weston_compositor_enable_debug_protocol(struct weston_compositor *); +// +//bool +//weston_compositor_is_debug_protocol_enabled(struct weston_compositor *); +// +//struct weston_log_scope; +//struct weston_debug_stream; +// +///** weston_log_scope callback +// * +// * @param sub The subscription. +// * @param user_data The \c user_data argument given to +// * weston_log_ctx_add_log_scope() or weston_compositor_add_log_scope(). +// * +// * @memberof weston_log_scope +// */ +//typedef void (*weston_log_scope_cb)(struct weston_log_subscription *sub, +// void *user_data); +// +//struct weston_log_scope * +//weston_log_ctx_add_log_scope(struct weston_log_context *log_ctx, +// const char *name, +// const char *description, +// weston_log_scope_cb new_subscription, +// weston_log_scope_cb destroy_subscription, +// void *user_data); +// +//struct weston_log_scope * +//weston_compositor_add_log_scope(struct weston_compositor *compositor, +// const char *name, +// const char *description, +// weston_log_scope_cb new_subscription, +// weston_log_scope_cb destroy_subscription, +// void *user_data); +// +//void +//weston_log_scope_destroy(struct weston_log_scope *scope); +// +//bool +//weston_log_scope_is_enabled(struct weston_log_scope *scope); +// +//void +//weston_log_scope_write(struct weston_log_scope *scope, +// const char *data, size_t len); +// +//int +//weston_log_scope_vprintf(struct weston_log_scope *scope, +// const char *fmt, va_list ap); +// +//int +//weston_log_scope_printf(struct weston_log_scope *scope, +// const char *fmt, ...) +// __attribute__ ((format (printf, 2, 3))); +//void +//weston_log_subscription_printf(struct weston_log_subscription *sub, +// const char *fmt, ...) +// __attribute__ ((format (printf, 2, 3))); +//void +//weston_log_scope_complete(struct weston_log_scope *scope); +// +//void +//weston_log_subscription_complete(struct weston_log_subscription *sub); +// +//char * +//weston_log_scope_timestamp(struct weston_log_scope *scope, +// char *buf, size_t len); +// +//void +//weston_log_subscriber_destroy(struct weston_log_subscriber *subscriber); +// +//void +//weston_log_subscribe(struct weston_log_context *log_ctx, +// struct weston_log_subscriber *subscriber, +// const char *scope_name); +// +//struct weston_log_subscriber * +//weston_log_subscriber_create_log(FILE *dump_to); +// +//struct weston_log_subscriber * +//weston_log_subscriber_create_flight_rec(size_t size); +// +//void +//weston_log_subscriber_display_flight_rec(struct weston_log_subscriber *sub); +// +//struct weston_log_subscription * +//weston_log_subscription_iterate(struct weston_log_scope *scope, +// struct weston_log_subscription *sub_iter); +// +//void +//weston_log_flight_recorder_display_buffer(FILE *file); + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_LOG_H */ diff --git a/include/libweston/windowed-output-api.h b/include/libweston/windowed-output-api.h new file mode 100644 index 0000000..be4dec6 --- /dev/null +++ b/include/libweston/windowed-output-api.h @@ -0,0 +1,95 @@ +/* + * Copyright © 2016 Armin Krezović + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_WINDOWED_OUTPUT_API_H +#define WESTON_WINDOWED_OUTPUT_API_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct weston_compositor; +struct weston_output; + +#define WESTON_WINDOWED_OUTPUT_API_NAME "weston_windowed_output_api_v1" + +struct weston_windowed_output_api { + /** Assign a given width and height to an output. + * + * \param output An output to be configured. + * \param width Desired width of the output. + * \param height Desired height of the output. + * + * Returns 0 on success, -1 on failure. + * + * This assigns a desired width and height to a windowed + * output. The backend decides what should be done and applies + * the desired configuration. After using this function and + * generic weston_output_set_*, a windowed + * output should be in a state where weston_output_enable() + * can be run. + */ + int (*output_set_size)(struct weston_output *output, + int width, int height); + + /** Create a new windowed head. + * + * \param compositor The compositor instance. + * \param name Desired name for a new head, not NULL. + * + * Returns 0 on success, -1 on failure. + * + * This creates a new head in the backend. The new head will + * be advertised in the compositor's head list and triggers a + * head_changed callback. + * + * A new output can be created for the head. The output must be + * configured with output_set_size() and + * weston_output_set_{scale,transform}() before enabling it. + * + * \sa weston_compositor_set_heads_changed_cb(), + * weston_compositor_create_output_with_head() + */ + int (*create_head)(struct weston_compositor *compositor, + const char *name); +}; + +static inline const struct weston_windowed_output_api * +weston_windowed_output_get_api(struct weston_compositor *compositor) +{ + const void *api; + api = weston_plugin_api_get(compositor, WESTON_WINDOWED_OUTPUT_API_NAME, + sizeof(struct weston_windowed_output_api)); + + return (const struct weston_windowed_output_api *)api; +} + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_WINDOWED_OUTPUT_API_H */ diff --git a/include/libweston/xwayland-api.h b/include/libweston/xwayland-api.h new file mode 100644 index 0000000..ff71fdd --- /dev/null +++ b/include/libweston/xwayland-api.h @@ -0,0 +1,176 @@ +/* + * Copyright © 2016 Giulio Camuffo + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef XWAYLAND_API_H +#define XWAYLAND_API_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include + +struct weston_compositor; +struct weston_xwayland; + +#define WESTON_XWAYLAND_API_NAME "weston_xwayland_v1" +#define WESTON_XWAYLAND_SURFACE_API_NAME "weston_xwayland_surface_v1" + +typedef pid_t +(*weston_xwayland_spawn_xserver_func_t)( + void *user_data, const char *display, int abstract_fd, int unix_fd); + +/** The libweston Xwayland API + * + * This API allows control of the Xwayland libweston module. + * The module must be loaded at runtime with \a weston_compositor_load_xwayland, + * after which the API can be retrieved by using \a weston_xwayland_get_api. + */ +struct weston_xwayland_api { + /** Retrieve the Xwayland context object. + * + * Note that this function does not create a new object, but always + * returns the same object per compositor instance. + * This function cannot fail while this API object is valid. + * + * \param compositor The compositor instance. + */ + struct weston_xwayland * + (*get)(struct weston_compositor *compositor); + + /** Listen for X connections. + * + * This function tells the Xwayland module to begin creating an X socket + * and start listening for client connections. When one such connection is + * detected the given \a spawn_func callback will be called to start + * the Xwayland process. + * + * \param xwayland The Xwayland context object. + * \param user_data The user data pointer to be passed to \a spawn_func. + * \param spawn_func The callback function called to start the Xwayland + * server process. + * + * \return 0 on success, a negative number otherwise. + */ + int + (*listen)(struct weston_xwayland *xwayland, void *user_data, + weston_xwayland_spawn_xserver_func_t spawn_func); + + /** Notify the Xwayland module that the Xwayland server is loaded. + * + * After the Xwayland server process has been spawned it will notify + * the parent that is has finished the initialization by sending a + * SIGUSR1 signal. + * The caller should listen for that signal and call this function + * when it is received. + * + * \param xwayland The Xwayland context object. + * \param client The wl_client object representing the connection of + * the Xwayland server process. + * \param wm_fd The file descriptor for the wm. + */ + void + (*xserver_loaded)(struct weston_xwayland *xwayland, + struct wl_client *client, int wm_fd); + + /** Notify the Xwayland module that the Xwayland server has exited. + * + * Whenever the Xwayland server process quits this function should be + * called. + * The Xwayland module will keep listening for X connections on the + * socket, and may call the spawn function again. + * + * \param xwayland The Xwayland context object. + * \param exit_status The exit status of the Xwayland server process. + */ + void + (*xserver_exited)(struct weston_xwayland *xwayland, int exit_status); +}; + +/** Retrieve the API object for the libweston Xwayland module. + * + * The module must have been previously loaded by calling + * \a weston_compositor_load_xwayland. + * + * \param compositor The compositor instance. + */ +static inline const struct weston_xwayland_api * +weston_xwayland_get_api(struct weston_compositor *compositor) +{ + const void *api; + api = weston_plugin_api_get(compositor, WESTON_XWAYLAND_API_NAME, + sizeof(struct weston_xwayland_api)); + /* The cast is necessary to use this function in C++ code */ + return (const struct weston_xwayland_api *)api; +} + +/** The libweston Xwayland surface API + * + * This API allows control of the Xwayland libweston module surfaces. + * The module must be loaded at runtime with \a weston_compositor_load_xwayland, + * after which the API can be retrieved by using + * \a weston_xwayland_surface_get_api. + */ +struct weston_xwayland_surface_api { + /** Check if the surface is an Xwayland surface + * + * \param surface The surface. + */ + bool + (*is_xwayland_surface)(struct weston_surface *surface); + /** Notify the Xwayland surface that its position changed. + * + * \param surface The Xwayland surface. + * \param x The x-axis position. + * \param y The y-axis position. + */ + void + (*send_position)(struct weston_surface *surface, int32_t x, int32_t y); +}; + +/** Retrieve the API object for the libweston Xwayland surface. + * + * The module must have been previously loaded by calling + * \a weston_compositor_load_xwayland. + * + * \param compositor The compositor instance. + */ +static inline const struct weston_xwayland_surface_api * +weston_xwayland_surface_get_api(struct weston_compositor *compositor) +{ + const void *api; + api = weston_plugin_api_get(compositor, WESTON_XWAYLAND_SURFACE_API_NAME, + sizeof(struct weston_xwayland_surface_api)); + /* The cast is necessary to use this function in C++ code */ + return (const struct weston_xwayland_surface_api *)api; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libweston/zalloc.h b/include/libweston/zalloc.h new file mode 100644 index 0000000..8830df6 --- /dev/null +++ b/include/libweston/zalloc.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2013 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#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/include/meson.build b/include/meson.build new file mode 100644 index 0000000..1ea6dd3 --- /dev/null +++ b/include/meson.build @@ -0,0 +1,6 @@ +subdir('libweston') + +install_headers( + 'libweston-desktop/libweston-desktop.h', + subdir: join_paths(dir_include_libweston, 'libweston-desktop') +) diff --git a/ivi-shell/README b/ivi-shell/README new file mode 100644 index 0000000..1d2212e --- /dev/null +++ b/ivi-shell/README @@ -0,0 +1,78 @@ + In-vehicle infotainment (information and entertainment) + graphical environment support modules for Weston + + +IVI-shell is an alternative shell for Weston, a Wayland display server. +Window management and application interaction with the display server +are very different to that of a normal desktop, which is why this is +a separate shell and not an extension to the desktop-shell suite with +xdg_shell. As such, applications need to be specifically written to use +IVI-shell. + +IVI-shell contains two main features: +- Common layout library for surface, which allow ivi-shell developer + to develop own shell, linking Common layout library. + For the time being, the library refers Genivi ilm interface. + + https://at.projects.genivi.org/wiki/display/WIE/Wayland+IVI+Extension+Home + +- Extension protocol; ivi-application to tie wl_surface and a given ID. + With this ID, shell can identify which wl_surface is drawn by which + application. In in-vehicle infortainment system, a shell has to update + a property of a wl_surface. E.g. there may be a use case when vehicle + starts to move, the wl_surface drawn by Car navigation is expected to + move top of surfaces. + +The actual software components delivered with Weston are: + +- ivi-application.xml: + Wayland protocol extension for IVI-applications; the public + shell protocol (the same concept as xdg_shell). + Implemented by ivi-shell.so. + +- ivi-shell.so: + A Weston shell module that implements ivi-application.xml interfaces. + Loads ivi-layout.so. + +- ivi-layout.so: + Implements the IVI window management concepts: Screen, Layer, + Surface, groups of Layers, groups of Surfaces, see: + https://at.projects.genivi.org/wiki/display/WIE/Summary+of+Layer+manager+APIs + Offers a stable API for writing IVI-controller modules like + hmi-controller.so against the IVI concepts. In other words, + it offers an API to write IVI window manager modules. + +- hmi-controller.so: + A sample implementation of an IVI-controller module, usually + replaced by IVI system vendors. + Uses ivi-layout.so to perform essentially window manager tasks. + This implementation keeps all window management inside the module, + while IVI-systems may use another module that exposes all window + management via Wayland or other protocol for an external process + to control. + +- ivi-hmi-controller.xml: + Wayland protocol extension for IVI display control; the private + shell protocol for weston-ivi-shell-user-interface client + (the same concept as desktop-shell.xml). + Implemented by hmi-controller.so, and usually replaced by IVI + system vendors. + +- weston-ivi-shell-user-interface: + A sample implementation of an IVI shell helper client, usually + replaced by IVI system vendors. + A helper client for basic display content, similar to + weston-desktop-shell. + + +How to compile: +same as weston. To disable, use option: --disable-ivi-shell for configure. + +How to configure weston.ini: +reference ini file will be generated in /ivi-shell. + +How to run: +same as weston. exec weston. + +How to use UI: +http://lists.freedesktop.org/archives/wayland-devel/attachments/20140625/abbfc064/attachment-0001.png diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c new file mode 100644 index 0000000..230e788 --- /dev/null +++ b/ivi-shell/hmi-controller.c @@ -0,0 +1,2006 @@ +/* + * Copyright (C) 2014 DENSO CORPORATION + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * A reference implementation how to use ivi-layout APIs in order to manage + * layout of ivi_surfaces/ivi_layers. Layout change is triggered by + * ivi-hmi-controller protocol, ivi-hmi-controller.xml. A reference how to + * use the protocol, see hmi-controller-homescreen. + * + * In-Vehicle Infotainment system usually manage properties of + * ivi_surfaces/ivi_layers by only a central component which decide where + * ivi_surfaces/ivi_layers shall be. This reference show examples to + * implement the central component as a module of weston. + * + * Default Scene graph of UI is defined in hmi_controller_create. It + * consists of + * - In the bottom, a base ivi_layer to group ivi_surfaces of background, + * panel, and buttons + * - Next, an application ivi_layer to show application ivi_surfaces. + * - Workspace background ivi_layer to show a ivi_surface of background image. + * - Workspace ivi_layer to show launcher to launch application with icons. + * Paths to binary and icon are defined in weston.ini. The width of this + * ivi_layer is longer than the size of ivi_screen because a workspace has + * several pages and is controlled by motion of input. + * + * TODO: animation method shall be refined + * TODO: support fade-in when UI is ready + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ivi-layout-export.h" +#include "ivi-hmi-controller-server-protocol.h" +#include "shared/helpers.h" +#include "shared/xalloc.h" +#include "compositor/weston.h" + +/***************************************************************************** + * structure, globals + ****************************************************************************/ +struct hmi_controller_layer { + struct ivi_layout_layer *ivilayer; + uint32_t id_layer; + int32_t x; + int32_t y; + int32_t width; + int32_t height; + struct wl_list link; +}; + +struct link_layer { + struct ivi_layout_layer *layout_layer; + struct wl_list link; +}; + +struct hmi_controller_fade { + uint32_t is_fade_in; + struct wl_list layer_list; +}; + +struct hmi_server_setting { + uint32_t base_layer_id; + uint32_t application_layer_id; + uint32_t workspace_background_layer_id; + uint32_t workspace_layer_id; + uint32_t base_layer_id_offset; + int32_t panel_height; + uint32_t transition_duration; + char *ivi_homescreen; +}; + +struct ui_setting { + uint32_t background_id; + uint32_t panel_id; + uint32_t tiling_id; + uint32_t sidebyside_id; + uint32_t fullscreen_id; + uint32_t random_id; + uint32_t home_id; + uint32_t workspace_background_id; + uint32_t surface_id_offset; +}; + +struct hmi_controller { + struct hmi_server_setting *hmi_setting; + /* List of struct hmi_controller_layer */ + struct wl_list base_layer_list; + struct wl_list application_layer_list; + struct hmi_controller_layer workspace_background_layer; + struct hmi_controller_layer workspace_layer; + enum ivi_hmi_controller_layout_mode layout_mode; + + struct hmi_controller_fade workspace_fade; + + int32_t workspace_count; + struct wl_array ui_widgets; + int32_t is_initialized; + + struct weston_compositor *compositor; + struct wl_listener destroy_listener; + + struct wl_listener surface_removed; + struct wl_listener surface_configured; + struct wl_listener desktop_surface_configured; + + struct wl_client *user_interface; + struct ui_setting ui_setting; + + struct weston_output * workspace_background_output; + int32_t screen_num; + + const struct ivi_layout_interface *interface; +}; + +struct launcher_info { + uint32_t surface_id; + uint32_t workspace_id; + int32_t index; +}; + +/***************************************************************************** + * local functions + ****************************************************************************/ +static void * +mem_alloc(size_t size, char *file, int32_t line) +{ + return fail_on_null(calloc(1, size), size, file, line); +} + +#define MEM_ALLOC(s) mem_alloc((s),__FILE__,__LINE__) + +static int32_t +is_surf_in_ui_widget(struct hmi_controller *hmi_ctrl, + struct ivi_layout_surface *ivisurf) +{ + uint32_t id = hmi_ctrl->interface->get_id_of_surface(ivisurf); + + uint32_t *ui_widget_id = NULL; + wl_array_for_each(ui_widget_id, &hmi_ctrl->ui_widgets) { + if (*ui_widget_id == id) + return 1; + } + + return 0; +} + +static int +compare_launcher_info(const void *lhs, const void *rhs) +{ + const struct launcher_info *left = lhs; + const struct launcher_info *right = rhs; + + if (left->workspace_id < right->workspace_id) + return -1; + + if (left->workspace_id > right->workspace_id) + return 1; + + if (left->index < right->index) + return -1; + + if (left->index > right->index) + return 1; + + return 0; +} + +/** + * Internal methods called by mainly ivi_hmi_controller_switch_mode + * This reference shows 4 examples how to use ivi_layout APIs. + */ +static void +mode_divided_into_tiling(struct hmi_controller *hmi_ctrl, + struct ivi_layout_surface **pp_surface, + int32_t surface_length, + struct wl_list *layer_list) +{ + struct hmi_controller_layer *layer = wl_container_of(layer_list->prev, layer, link); + const float surface_width = (float)layer->width * 0.25; + const float surface_height = (float)layer->height * 0.5; + int32_t surface_x = 0; + int32_t surface_y = 0; + struct ivi_layout_surface *ivisurf = NULL; + struct ivi_layout_surface **surfaces; + struct ivi_layout_surface **new_order; + const uint32_t duration = hmi_ctrl->hmi_setting->transition_duration; + struct ivi_layout_layer *ivilayer = NULL; + + int32_t i = 0; + int32_t surf_num = 0; + int32_t idx = 0; + + surfaces = MEM_ALLOC(sizeof(*surfaces) * surface_length); + new_order = MEM_ALLOC(sizeof(*surfaces) * surface_length); + + for (i = 0; i < surface_length; i++) { + ivisurf = pp_surface[i]; + + /* skip ui widgets */ + if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) + continue; + + surfaces[surf_num++] = ivisurf; + } + + wl_list_for_each_reverse(layer, layer_list, link) { + if (idx >= surf_num) + break; + + ivilayer = layer->ivilayer; + + for (i = 0; i < 8; i++, idx++) { + if (idx >= surf_num) + break; + + ivisurf = surfaces[idx]; + new_order[i] = ivisurf; + if (i < 4) { + surface_x = (int32_t)(i * (surface_width)); + surface_y = 0; + } else { + surface_x = (int32_t)((i - 4) * (surface_width)); + surface_y = (int32_t)surface_height; + } + + hmi_ctrl->interface->surface_set_transition(ivisurf, + IVI_LAYOUT_TRANSITION_VIEW_DEFAULT, + duration); + hmi_ctrl->interface->surface_set_visibility(ivisurf, true); + hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, + surface_x, surface_y, + (int32_t)surface_width, + (int32_t)surface_height); + + } + hmi_ctrl->interface->layer_set_render_order(ivilayer, new_order, i); + + hmi_ctrl->interface->layer_set_transition(ivilayer, + IVI_LAYOUT_TRANSITION_LAYER_VIEW_ORDER, + duration); + } + for (i = idx; i < surf_num; i++) + hmi_ctrl->interface->surface_set_visibility(surfaces[i], false); + + free(surfaces); + free(new_order); +} + +static void +mode_divided_into_sidebyside(struct hmi_controller *hmi_ctrl, + struct ivi_layout_surface **pp_surface, + int32_t surface_length, + struct wl_list *layer_list) +{ + struct hmi_controller_layer *layer = wl_container_of(layer_list->prev, layer, link); + int32_t surface_width = layer->width / 2; + int32_t surface_height = layer->height; + struct ivi_layout_surface *ivisurf = NULL; + + const uint32_t duration = hmi_ctrl->hmi_setting->transition_duration; + int32_t i = 0; + struct ivi_layout_surface **surfaces; + struct ivi_layout_surface **new_order; + struct ivi_layout_layer *ivilayer = NULL; + int32_t surf_num = 0; + int32_t idx = 0; + + surfaces = MEM_ALLOC(sizeof(*surfaces) * surface_length); + new_order = MEM_ALLOC(sizeof(*surfaces) * surface_length); + + for (i = 0; i < surface_length; i++) { + ivisurf = pp_surface[i]; + + /* skip ui widgets */ + if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) + continue; + + surfaces[surf_num++] = ivisurf; + } + + wl_list_for_each_reverse(layer, layer_list, link) { + if (idx >= surf_num) + break; + + ivilayer = layer->ivilayer; + + for (i = 0; i < 2; i++, idx++) { + if (idx >= surf_num) + break; + + ivisurf = surfaces[idx]; + new_order[i] = ivisurf; + + hmi_ctrl->interface->surface_set_transition(ivisurf, + IVI_LAYOUT_TRANSITION_VIEW_DEFAULT, + duration); + hmi_ctrl->interface->surface_set_visibility(ivisurf, true); + + hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, + i * surface_width, 0, + surface_width, + surface_height); + } + hmi_ctrl->interface->layer_set_render_order(ivilayer, new_order, i); + } + + for (i = idx; i < surf_num; i++) { + hmi_ctrl->interface->surface_set_transition(surfaces[i], + IVI_LAYOUT_TRANSITION_VIEW_FADE_ONLY, + duration); + hmi_ctrl->interface->surface_set_visibility(surfaces[i], false); + } + + free(surfaces); + free(new_order); +} + +static void +mode_fullscreen_someone(struct hmi_controller *hmi_ctrl, + struct ivi_layout_surface **pp_surface, + int32_t surface_length, + struct wl_list *layer_list) +{ + struct hmi_controller_layer *layer = wl_container_of(layer_list->prev, layer, link); + const int32_t surface_width = layer->width; + const int32_t surface_height = layer->height; + struct ivi_layout_surface *ivisurf = NULL; + int32_t i = 0; + const uint32_t duration = hmi_ctrl->hmi_setting->transition_duration; + int32_t surf_num = 0; + struct ivi_layout_surface **surfaces; + + surfaces = MEM_ALLOC(sizeof(*surfaces) * surface_length); + + for (i = 0; i < surface_length; i++) { + ivisurf = pp_surface[i]; + + /* skip ui widgets */ + if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) + continue; + + surfaces[surf_num++] = ivisurf; + } + hmi_ctrl->interface->layer_set_render_order(layer->ivilayer, surfaces, surf_num); + + for (i = 0; i < surf_num; i++) { + ivisurf = surfaces[i]; + + if ((i > 0) && (i < hmi_ctrl->screen_num)) { + layer = wl_container_of(layer->link.prev, layer, link); + hmi_ctrl->interface->layer_set_render_order(layer->ivilayer, &ivisurf, 1); + } + + hmi_ctrl->interface->surface_set_transition(ivisurf, + IVI_LAYOUT_TRANSITION_VIEW_DEFAULT, + duration); + hmi_ctrl->interface->surface_set_visibility(ivisurf, true); + hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, 0, 0, + surface_width, + surface_height); + } + + free(surfaces); +} + +static void +mode_random_replace(struct hmi_controller *hmi_ctrl, + struct ivi_layout_surface **pp_surface, + int32_t surface_length, + struct wl_list *layer_list) +{ + struct hmi_controller_layer *application_layer = NULL; + struct hmi_controller_layer **layers = NULL; + int32_t surface_width = 0; + int32_t surface_height = 0; + int32_t surface_x = 0; + int32_t surface_y = 0; + struct ivi_layout_surface *ivisurf = NULL; + const uint32_t duration = hmi_ctrl->hmi_setting->transition_duration; + int32_t i = 0; + int32_t layer_idx = 0; + + layers = MEM_ALLOC(sizeof(*layers) * hmi_ctrl->screen_num); + + wl_list_for_each(application_layer, layer_list, link) { + layers[layer_idx] = application_layer; + layer_idx++; + } + + for (i = 0; i < surface_length; i++) { + ivisurf = pp_surface[i]; + + /* skip ui widgets */ + if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) + continue; + + /* surface determined at random a layer that belongs */ + layer_idx = rand() % hmi_ctrl->screen_num; + + hmi_ctrl->interface->surface_set_transition(ivisurf, + IVI_LAYOUT_TRANSITION_VIEW_DEFAULT, + duration); + + hmi_ctrl->interface->surface_set_visibility(ivisurf, true); + + surface_width = (int32_t)(layers[layer_idx]->width * 0.25f); + surface_height = (int32_t)(layers[layer_idx]->height * 0.25f); + surface_x = rand() % (layers[layer_idx]->width - surface_width); + surface_y = rand() % (layers[layer_idx]->height - surface_height); + + hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, + surface_x, + surface_y, + surface_width, + surface_height); + + hmi_ctrl->interface->layer_add_surface(layers[layer_idx]->ivilayer, ivisurf); + } + + free(layers); +} + +static int32_t +has_application_surface(struct hmi_controller *hmi_ctrl, + struct ivi_layout_surface **pp_surface, + int32_t surface_length) +{ + struct ivi_layout_surface *ivisurf = NULL; + int32_t i = 0; + + for (i = 0; i < surface_length; i++) { + ivisurf = pp_surface[i]; + + /* skip ui widgets */ + if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) + continue; + + return 1; + } + + return 0; +} + +/** + * Supports 4 example to layout of application ivi_surfaces; + * tiling, side by side, fullscreen, and random. + */ +static void +switch_mode(struct hmi_controller *hmi_ctrl, + enum ivi_hmi_controller_layout_mode layout_mode) +{ + struct wl_list *layer = &hmi_ctrl->application_layer_list; + struct ivi_layout_surface **pp_surface = NULL; + int32_t surface_length = 0; + int32_t ret = 0; + + if (!hmi_ctrl->is_initialized) + return; + + hmi_ctrl->layout_mode = layout_mode; + + ret = hmi_ctrl->interface->get_surfaces(&surface_length, &pp_surface); + assert(!ret); + + if (!has_application_surface(hmi_ctrl, pp_surface, surface_length)) { + free(pp_surface); + pp_surface = NULL; + return; + } + + switch (layout_mode) { + case IVI_HMI_CONTROLLER_LAYOUT_MODE_TILING: + mode_divided_into_tiling(hmi_ctrl, pp_surface, surface_length, + layer); + break; + case IVI_HMI_CONTROLLER_LAYOUT_MODE_SIDE_BY_SIDE: + mode_divided_into_sidebyside(hmi_ctrl, pp_surface, + surface_length, layer); + break; + case IVI_HMI_CONTROLLER_LAYOUT_MODE_FULL_SCREEN: + mode_fullscreen_someone(hmi_ctrl, pp_surface, surface_length, + layer); + break; + case IVI_HMI_CONTROLLER_LAYOUT_MODE_RANDOM: + mode_random_replace(hmi_ctrl, pp_surface, surface_length, + layer); + break; + } + + hmi_ctrl->interface->commit_changes(); + free(pp_surface); +} + +/** + * Internal method for transition + */ +static void +hmi_controller_fade_run(struct hmi_controller *hmi_ctrl, uint32_t is_fade_in, + struct hmi_controller_fade *fade) +{ + double tint = is_fade_in ? 1.0 : 0.0; + struct link_layer *linklayer = NULL; + const uint32_t duration = hmi_ctrl->hmi_setting->transition_duration; + + fade->is_fade_in = is_fade_in; + + wl_list_for_each(linklayer, &fade->layer_list, link) { + hmi_ctrl->interface->layer_set_transition(linklayer->layout_layer, + IVI_LAYOUT_TRANSITION_LAYER_FADE, + duration); + hmi_ctrl->interface->layer_set_fade_info(linklayer->layout_layer, + is_fade_in, 1.0 - tint, tint); + } +} + +/** + * Internal method to create ivi_layer with hmi_controller_layer and + * add to a weston_output + */ +static void +create_layer(struct weston_output *output, + struct hmi_controller_layer *layer, + struct hmi_controller *hmi_ctrl) +{ + int32_t ret = 0; + + layer->ivilayer = + hmi_ctrl->interface->layer_create_with_dimension(layer->id_layer, + layer->width, + layer->height); + assert(layer->ivilayer != NULL); + + ret = hmi_ctrl->interface->screen_add_layer(output, layer->ivilayer); + assert(!ret); + + ret = hmi_ctrl->interface->layer_set_destination_rectangle(layer->ivilayer, + layer->x, layer->y, + layer->width, + layer->height); + assert(!ret); + + ret = hmi_ctrl->interface->layer_set_visibility(layer->ivilayer, true); + assert(!ret); +} + +/** + * Internal set notification + */ +static void +set_notification_remove_surface(struct wl_listener *listener, void *data) +{ + struct hmi_controller *hmi_ctrl = + wl_container_of(listener, hmi_ctrl, + surface_removed); + (void)data; + + switch_mode(hmi_ctrl, hmi_ctrl->layout_mode); +} + +static void +set_notification_configure_surface(struct wl_listener *listener, void *data) +{ + struct hmi_controller *hmi_ctrl = + wl_container_of(listener, hmi_ctrl, + surface_configured); + struct ivi_layout_surface *ivisurf = data; + struct hmi_controller_layer *layer_link = NULL; + struct ivi_layout_layer *application_layer = NULL; + struct weston_surface *surface; + struct ivi_layout_surface **ivisurfs = NULL; + int32_t length = 0; + int32_t i; + + /* return if the surface is not application content */ + if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) { + return; + } + + /* + * if application changes size of wl_buffer. The source rectangle shall be + * fit to the size. + */ + surface = hmi_ctrl->interface->surface_get_weston_surface(ivisurf); + if (surface) { + hmi_ctrl->interface->surface_set_source_rectangle( + ivisurf, 0, 0, surface->width, + surface->height); + } + + /* + * search if the surface is already added to layer. + * If not yet, it is newly invoded application to go to switch_mode. + */ + wl_list_for_each_reverse(layer_link, &hmi_ctrl->application_layer_list, link) { + application_layer = layer_link->ivilayer; + hmi_ctrl->interface->get_surfaces_on_layer(application_layer, + &length, &ivisurfs); + for (i = 0; i < length; i++) { + if (ivisurf == ivisurfs[i]) { + /* + * if it is non new invoked application, just call + * commit_changes to apply source_rectangle. + */ + hmi_ctrl->interface->commit_changes(); + free(ivisurfs); + return; + } + } + free(ivisurfs); + ivisurfs = NULL; + } + + switch_mode(hmi_ctrl, hmi_ctrl->layout_mode); +} + +static void +set_notification_configure_desktop_surface(struct wl_listener *listener, void *data) +{ + struct hmi_controller *hmi_ctrl = + wl_container_of(listener, hmi_ctrl, + desktop_surface_configured); + struct ivi_layout_surface *ivisurf = data; + struct hmi_controller_layer *layer_link = + wl_container_of(hmi_ctrl->application_layer_list.prev, + layer_link, + link); + struct ivi_layout_layer *application_layer = layer_link->ivilayer; + struct weston_surface *surface; + int32_t ret = 0; + + /* skip ui widgets */ + if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) + return; + + ret = hmi_ctrl->interface->layer_add_surface(application_layer, ivisurf); + assert(!ret); + + /* + * if application changes size of wl_buffer. The source rectangle shall be + * fit to the size. + */ + surface = hmi_ctrl->interface->surface_get_weston_surface(ivisurf); + if (surface) { + hmi_ctrl->interface->surface_set_source_rectangle(ivisurf, 0, + 0, surface->width, surface->height); + } + + hmi_ctrl->interface->commit_changes(); + switch_mode(hmi_ctrl, hmi_ctrl->layout_mode); +} + +/** + * A hmi_controller used 4 ivi_layers to manage ivi_surfaces. The IDs of + * corresponding ivi_layer are defined in weston.ini. Default scene graph + * of ivi_layers are initialized in hmi_controller_create + */ +static struct hmi_server_setting * +hmi_server_setting_create(struct weston_compositor *ec) +{ + struct hmi_server_setting *setting = MEM_ALLOC(sizeof(*setting)); + struct weston_config *config = wet_get_config(ec); + struct weston_config_section *shell_section = NULL; + char *ivi_ui_config; + + shell_section = weston_config_get_section(config, "ivi-shell", + NULL, NULL); + + weston_config_section_get_uint(shell_section, "base-layer-id", + &setting->base_layer_id, 1000); + + weston_config_section_get_uint(shell_section, + "workspace-background-layer-id", + &setting->workspace_background_layer_id, + 2000); + + weston_config_section_get_uint(shell_section, "workspace-layer-id", + &setting->workspace_layer_id, 3000); + + weston_config_section_get_uint(shell_section, "application-layer-id", + &setting->application_layer_id, 4000); + + weston_config_section_get_uint(shell_section, "base-layer-id-offset", + &setting->base_layer_id_offset, 10000); + + weston_config_section_get_uint(shell_section, "transition-duration", + &setting->transition_duration, 300); + + setting->panel_height = 70; + + weston_config_section_get_string(shell_section, + "ivi-shell-user-interface", + &ivi_ui_config, NULL); + if (ivi_ui_config && ivi_ui_config[0] != '/') { + setting->ivi_homescreen = wet_get_libexec_path(ivi_ui_config); + if (setting->ivi_homescreen) + free(ivi_ui_config); + else + setting->ivi_homescreen = ivi_ui_config; + } else { + setting->ivi_homescreen = ivi_ui_config; + } + + return setting; +} + +static void +hmi_controller_destroy(struct wl_listener *listener, void *data) +{ + struct link_layer *link = NULL; + struct link_layer *next = NULL; + struct hmi_controller_layer *ctrl_layer_link = NULL; + struct hmi_controller_layer *ctrl_layer_next = NULL; + struct hmi_controller *hmi_ctrl = + container_of(listener, struct hmi_controller, destroy_listener); + + wl_list_for_each_safe(link, next, + &hmi_ctrl->workspace_fade.layer_list, link) { + wl_list_remove(&link->link); + free(link); + } + + /* clear base_layer_list */ + wl_list_for_each_safe(ctrl_layer_link, ctrl_layer_next, + &hmi_ctrl->base_layer_list, link) { + wl_list_remove(&ctrl_layer_link->link); + free(ctrl_layer_link); + } + + /* clear application_layer_list */ + wl_list_for_each_safe(ctrl_layer_link, ctrl_layer_next, + &hmi_ctrl->application_layer_list, link) { + wl_list_remove(&ctrl_layer_link->link); + free(ctrl_layer_link); + } + + wl_array_release(&hmi_ctrl->ui_widgets); + free(hmi_ctrl->hmi_setting); + free(hmi_ctrl); +} + +/** + * This is a starting method called from module_init. + * This sets up scene graph of ivi_layers; base, application, workspace + * background, and workspace. These ivi_layers are created/added to + * ivi_screen in create_layer + * + * base: to group ivi_surfaces of panel and background + * application: to group ivi_surfaces of ivi_applications + * workspace background: to group a ivi_surface of background in workspace + * workspace: to group ivi_surfaces for launching ivi_applications + * + * ivi_layers of workspace background and workspace is set to invisible at + * first. The properties of it is updated with animation when + * ivi_hmi_controller_home is requested. + */ +static struct hmi_controller * +hmi_controller_create(struct weston_compositor *ec) +{ + struct link_layer *tmp_link_layer = NULL; + int32_t panel_height = 0; + struct hmi_controller *hmi_ctrl; + const struct ivi_layout_interface *interface; + struct hmi_controller_layer *base_layer = NULL; + struct hmi_controller_layer *application_layer = NULL; + struct weston_output *output; + int32_t i; + + interface = ivi_layout_get_api(ec); + + if (!interface) { + weston_log("Cannot use ivi_layout_interface.\n"); + return NULL; + } + + hmi_ctrl = MEM_ALLOC(sizeof(*hmi_ctrl)); + i = 0; + + wl_array_init(&hmi_ctrl->ui_widgets); + hmi_ctrl->layout_mode = IVI_HMI_CONTROLLER_LAYOUT_MODE_TILING; + hmi_ctrl->hmi_setting = hmi_server_setting_create(ec); + hmi_ctrl->compositor = ec; + hmi_ctrl->screen_num = wl_list_length(&ec->output_list); + hmi_ctrl->interface = interface; + + /* init base ivi_layer*/ + wl_list_init(&hmi_ctrl->base_layer_list); + wl_list_for_each(output, &ec->output_list, link) { + base_layer = MEM_ALLOC(1 * sizeof(struct hmi_controller_layer)); + base_layer->x = 0; + base_layer->y = 0; + base_layer->width = output->current_mode->width; + base_layer->height = output->current_mode->height; + base_layer->id_layer = + hmi_ctrl->hmi_setting->base_layer_id + + (i * hmi_ctrl->hmi_setting->base_layer_id_offset); + wl_list_insert(&hmi_ctrl->base_layer_list, &base_layer->link); + + create_layer(output, base_layer, hmi_ctrl); + i++; + } + + i = 0; + panel_height = hmi_ctrl->hmi_setting->panel_height; + + /* init application ivi_layer */ + wl_list_init(&hmi_ctrl->application_layer_list); + wl_list_for_each(output, &ec->output_list, link) { + application_layer = MEM_ALLOC(1 * sizeof(struct hmi_controller_layer)); + application_layer->x = 0; + application_layer->y = 0; + application_layer->width = output->current_mode->width; + application_layer->height = output->current_mode->height - panel_height; + application_layer->id_layer = + hmi_ctrl->hmi_setting->application_layer_id + + (i * hmi_ctrl->hmi_setting->base_layer_id_offset); + wl_list_insert(&hmi_ctrl->application_layer_list, &application_layer->link); + + create_layer(output, application_layer, hmi_ctrl); + i++; + } + + /* init workspace background ivi_layer */ + output = wl_container_of(ec->output_list.next, output, link); + hmi_ctrl->workspace_background_output = output; + hmi_ctrl->workspace_background_layer.x = 0; + hmi_ctrl->workspace_background_layer.y = 0; + hmi_ctrl->workspace_background_layer.width = + output->current_mode->width; + hmi_ctrl->workspace_background_layer.height = + output->current_mode->height - panel_height; + + hmi_ctrl->workspace_background_layer.id_layer = + hmi_ctrl->hmi_setting->workspace_background_layer_id; + + create_layer(output, &hmi_ctrl->workspace_background_layer, hmi_ctrl); + hmi_ctrl->interface->layer_set_opacity( + hmi_ctrl->workspace_background_layer.ivilayer, 0); + hmi_ctrl->interface->layer_set_visibility( + hmi_ctrl->workspace_background_layer.ivilayer, false); + + + wl_list_init(&hmi_ctrl->workspace_fade.layer_list); + tmp_link_layer = MEM_ALLOC(sizeof(*tmp_link_layer)); + tmp_link_layer->layout_layer = + hmi_ctrl->workspace_background_layer.ivilayer; + wl_list_insert(&hmi_ctrl->workspace_fade.layer_list, + &tmp_link_layer->link); + + hmi_ctrl->surface_removed.notify = set_notification_remove_surface; + hmi_ctrl->interface->add_listener_remove_surface(&hmi_ctrl->surface_removed); + + hmi_ctrl->surface_configured.notify = set_notification_configure_surface; + hmi_ctrl->interface->add_listener_configure_surface(&hmi_ctrl->surface_configured); + + hmi_ctrl->desktop_surface_configured.notify = set_notification_configure_desktop_surface; + hmi_ctrl->interface->add_listener_configure_desktop_surface(&hmi_ctrl->desktop_surface_configured); + + hmi_ctrl->destroy_listener.notify = hmi_controller_destroy; + wl_signal_add(&hmi_ctrl->compositor->destroy_signal, + &hmi_ctrl->destroy_listener); + + return hmi_ctrl; +} + +/** + * Implementations of ivi-hmi-controller.xml + */ + +/** + * A ivi_surface drawing background is identified by id_surface. + * Properties of the ivi_surface is set by using ivi_layout APIs according to + * the scene graph of UI defined in hmi_controller_create. + * + * UI ivi_layer is used to add this ivi_surface. + */ +static void +ivi_hmi_controller_set_background(struct hmi_controller *hmi_ctrl, + uint32_t id_surface) +{ + struct ivi_layout_surface *ivisurf = NULL; + struct hmi_controller_layer *base_layer = NULL; + struct ivi_layout_layer *ivilayer = NULL; + int32_t dstx; + int32_t dsty; + int32_t width; + int32_t height; + int32_t ret = 0; + int32_t i = 0; + + wl_list_for_each_reverse(base_layer, &hmi_ctrl->base_layer_list, link) { + uint32_t *add_surface_id = wl_array_add(&hmi_ctrl->ui_widgets, + sizeof(*add_surface_id)); + *add_surface_id = id_surface + (i * hmi_ctrl->ui_setting.surface_id_offset); + dstx = base_layer->x; + dsty = base_layer->y; + width = base_layer->width; + height = base_layer->height; + ivilayer = base_layer->ivilayer; + + ivisurf = hmi_ctrl->interface->get_surface_from_id(*add_surface_id); + assert(ivisurf != NULL); + + ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); + assert(!ret); + + ret = hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, + dstx, dsty, width, height); + assert(!ret); + + ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); + assert(!ret); + + i++; + } +} + +/** + * A ivi_surface drawing panel is identified by id_surface. + * Properties of the ivi_surface is set by using ivi_layout APIs according to + * the scene graph of UI defined in hmi_controller_create. + * + * UI ivi_layer is used to add this ivi_surface. + */ +static void +ivi_hmi_controller_set_panel(struct hmi_controller *hmi_ctrl, + uint32_t id_surface) +{ + struct ivi_layout_surface *ivisurf = NULL; + struct hmi_controller_layer *base_layer; + struct ivi_layout_layer *ivilayer = NULL; + int32_t width; + int32_t ret = 0; + int32_t panel_height = hmi_ctrl->hmi_setting->panel_height; + const int32_t dstx = 0; + int32_t dsty = 0; + int32_t i = 0; + + wl_list_for_each_reverse(base_layer, &hmi_ctrl->base_layer_list, link) { + uint32_t *add_surface_id = wl_array_add(&hmi_ctrl->ui_widgets, + sizeof(*add_surface_id)); + *add_surface_id = id_surface + (i * hmi_ctrl->ui_setting.surface_id_offset); + + ivilayer = base_layer->ivilayer; + ivisurf = hmi_ctrl->interface->get_surface_from_id(*add_surface_id); + assert(ivisurf != NULL); + + ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); + assert(!ret); + + dsty = base_layer->height - panel_height; + width = base_layer->width; + + ret = hmi_ctrl->interface->surface_set_destination_rectangle( + ivisurf, dstx, dsty, width, panel_height); + assert(!ret); + + ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); + assert(!ret); + + i++; + } +} + +/** + * A ivi_surface drawing buttons in panel is identified by id_surface. + * It can set several buttons. Properties of the ivi_surface is set by + * using ivi_layout APIs according to the scene graph of UI defined in + * hmi_controller_create. Additionally, the position of it is shifted to + * right when new one is requested. + * + * UI ivi_layer is used to add these ivi_surfaces. + */ +static void +ivi_hmi_controller_set_button(struct hmi_controller *hmi_ctrl, + uint32_t id_surface, int32_t number) +{ + struct ivi_layout_surface *ivisurf = NULL; + struct hmi_controller_layer *base_layer = + wl_container_of(hmi_ctrl->base_layer_list.prev, + base_layer, + link); + struct ivi_layout_layer *ivilayer = base_layer->ivilayer; + const int32_t width = 48; + const int32_t height = 48; + int32_t ret = 0; + int32_t panel_height = 0; + int32_t dstx = 0; + int32_t dsty = 0; + uint32_t *add_surface_id = wl_array_add(&hmi_ctrl->ui_widgets, + sizeof(*add_surface_id)); + *add_surface_id = id_surface; + + ivisurf = hmi_ctrl->interface->get_surface_from_id(id_surface); + assert(ivisurf != NULL); + + ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); + assert(!ret); + + panel_height = hmi_ctrl->hmi_setting->panel_height; + + dstx = (60 * number) + 15; + dsty = (base_layer->height - panel_height) + 5; + + ret = hmi_ctrl->interface->surface_set_destination_rectangle( + ivisurf,dstx, dsty, width, height); + assert(!ret); + + ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); + assert(!ret); +} + +/** + * A ivi_surface drawing home button in panel is identified by id_surface. + * Properties of the ivi_surface is set by using ivi_layout APIs according to + * the scene graph of UI defined in hmi_controller_create. + * + * UI ivi_layer is used to add these ivi_surfaces. + */ +static void +ivi_hmi_controller_set_home_button(struct hmi_controller *hmi_ctrl, + uint32_t id_surface) +{ + struct ivi_layout_surface *ivisurf = NULL; + struct hmi_controller_layer *base_layer = + wl_container_of(hmi_ctrl->base_layer_list.prev, + base_layer, + link); + struct ivi_layout_layer *ivilayer = base_layer->ivilayer; + int32_t ret = 0; + int32_t size = 48; + int32_t panel_height = hmi_ctrl->hmi_setting->panel_height; + const int32_t dstx = (base_layer->width - size) / 2; + const int32_t dsty = (base_layer->height - panel_height) + 5; + + uint32_t *add_surface_id = wl_array_add(&hmi_ctrl->ui_widgets, + sizeof(*add_surface_id)); + *add_surface_id = id_surface; + + ivisurf = hmi_ctrl->interface->get_surface_from_id(id_surface); + assert(ivisurf != NULL); + + ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); + assert(!ret); + + ret = hmi_ctrl->interface->surface_set_destination_rectangle( + ivisurf, dstx, dsty, size, size); + assert(!ret); + + ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); + assert(!ret); +} + +/** + * A ivi_surface drawing background of workspace is identified by id_surface. + * Properties of the ivi_surface is set by using ivi_layout APIs according to + * the scene graph of UI defined in hmi_controller_create. + * + * A ivi_layer of workspace_background is used to add this ivi_surface. + */ +static void +ivi_hmi_controller_set_workspacebackground(struct hmi_controller *hmi_ctrl, + uint32_t id_surface) +{ + struct ivi_layout_surface *ivisurf = NULL; + struct ivi_layout_layer *ivilayer = NULL; + const int32_t width = hmi_ctrl->workspace_background_layer.width; + const int32_t height = hmi_ctrl->workspace_background_layer.height; + int32_t ret = 0; + + uint32_t *add_surface_id = wl_array_add(&hmi_ctrl->ui_widgets, + sizeof(*add_surface_id)); + *add_surface_id = id_surface; + ivilayer = hmi_ctrl->workspace_background_layer.ivilayer; + + ivisurf = hmi_ctrl->interface->get_surface_from_id(id_surface); + assert(ivisurf != NULL); + + ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); + assert(!ret); + + ret = hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, + 0, 0, width, height); + assert(!ret); + + ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); + assert(!ret); +} + +/** + * A list of ivi_surfaces drawing launchers in workspace is identified by + * id_surfaces. Properties of the ivi_surface is set by using ivi_layout + * APIs according to the scene graph of UI defined in hmi_controller_create. + * + * The workspace can have several pages to group ivi_surfaces of launcher. + * Each call of this interface increments a number of page to add a group + * of ivi_surfaces + */ +static void +ivi_hmi_controller_add_launchers(struct hmi_controller *hmi_ctrl, + int32_t icon_size) +{ + int32_t minspace_x = 10; + int32_t minspace_y = minspace_x; + + int32_t width = hmi_ctrl->workspace_background_layer.width; + int32_t height = hmi_ctrl->workspace_background_layer.height; + + int32_t x_count = (width - minspace_x) / (minspace_x + icon_size); + int32_t space_x = (int32_t)((width - x_count * icon_size) / (1.0 + x_count)); + float fcell_size_x = icon_size + space_x; + + int32_t y_count = (height - minspace_y) / (minspace_y + icon_size); + int32_t space_y = (int32_t)((height - y_count * icon_size) / (1.0 + y_count)); + float fcell_size_y = icon_size + space_y; + + struct weston_config *config = NULL; + struct weston_config_section *section = NULL; + const char *name = NULL; + int launcher_count = 0; + struct wl_array launchers; + int32_t nx = 0; + int32_t ny = 0; + int32_t prev = -1; + struct launcher_info *data = NULL; + + uint32_t surfaceid = 0; + uint32_t workspaceid = 0; + struct launcher_info *info = NULL; + + int32_t x = 0; + int32_t y = 0; + int32_t ret = 0; + struct ivi_layout_surface* layout_surface = NULL; + uint32_t *add_surface_id = NULL; + + struct link_layer *tmp_link_layer = NULL; + + if (0 == x_count) + x_count = 1; + + if (0 == y_count) + y_count = 1; + + config = wet_get_config(hmi_ctrl->compositor); + if (!config) + return; + + section = weston_config_get_section(config, "ivi-shell", NULL, NULL); + if (!section) + return; + + wl_array_init(&launchers); + + while (weston_config_next_section(config, §ion, &name)) { + surfaceid = 0; + workspaceid = 0; + info = NULL; + if (0 != strcmp(name, "ivi-launcher")) + continue; + + if (0 != weston_config_section_get_uint(section, "icon-id", + &surfaceid, 0)) + continue; + + if (0 != weston_config_section_get_uint(section, + "workspace-id", + &workspaceid, 0)) + continue; + + info = wl_array_add(&launchers, sizeof(*info)); + + if (info) { + info->surface_id = surfaceid; + info->workspace_id = workspaceid; + info->index = launcher_count; + ++launcher_count; + } + } + + qsort(launchers.data, launcher_count, sizeof(struct launcher_info), + compare_launcher_info); + + wl_array_for_each(data, &launchers) { + add_surface_id = wl_array_add(&hmi_ctrl->ui_widgets, + sizeof(*add_surface_id)); + + *add_surface_id = data->surface_id; + + if (0 > prev || (uint32_t)prev != data->workspace_id) { + nx = 0; + ny = 0; + prev = data->workspace_id; + + if (0 <= prev) + hmi_ctrl->workspace_count++; + } + + if (y_count == ny) { + ny = 0; + hmi_ctrl->workspace_count++; + } + + x = nx * fcell_size_x + (hmi_ctrl->workspace_count - 1) * width + space_x; + y = ny * fcell_size_y + space_y; + + layout_surface = + hmi_ctrl->interface->get_surface_from_id(data->surface_id); + assert(layout_surface); + + ret = hmi_ctrl->interface->surface_set_destination_rectangle( + layout_surface, x, y, icon_size, icon_size); + assert(!ret); + + nx++; + + if (x_count == nx) { + ny++; + nx = 0; + } + } + + /* init workspace ivi_layer */ + hmi_ctrl->workspace_layer.x = hmi_ctrl->workspace_background_layer.x; + hmi_ctrl->workspace_layer.y = hmi_ctrl->workspace_background_layer.y; + hmi_ctrl->workspace_layer.width = + hmi_ctrl->workspace_background_layer.width * hmi_ctrl->workspace_count; + hmi_ctrl->workspace_layer.height = + hmi_ctrl->workspace_background_layer.height; + hmi_ctrl->workspace_layer.id_layer = + hmi_ctrl->hmi_setting->workspace_layer_id; + + create_layer(hmi_ctrl->workspace_background_output, + &hmi_ctrl->workspace_layer, hmi_ctrl); + hmi_ctrl->interface->layer_set_opacity(hmi_ctrl->workspace_layer.ivilayer, 0); + hmi_ctrl->interface->layer_set_visibility(hmi_ctrl->workspace_layer.ivilayer, + false); + + tmp_link_layer = MEM_ALLOC(sizeof(*tmp_link_layer)); + tmp_link_layer->layout_layer = hmi_ctrl->workspace_layer.ivilayer; + wl_list_insert(&hmi_ctrl->workspace_fade.layer_list, + &tmp_link_layer->link); + + /* Add surface to layer */ + wl_array_for_each(data, &launchers) { + layout_surface = + hmi_ctrl->interface->get_surface_from_id(data->surface_id); + assert(layout_surface); + + ret = hmi_ctrl->interface->layer_add_surface(hmi_ctrl->workspace_layer.ivilayer, + layout_surface); + assert(!ret); + + ret = hmi_ctrl->interface->surface_set_visibility(layout_surface, true); + assert(!ret); + } + + wl_array_release(&launchers); + hmi_ctrl->interface->commit_changes(); +} + +static void +ivi_hmi_controller_UI_ready(struct wl_client *client, + struct wl_resource *resource) +{ + struct hmi_controller *hmi_ctrl = wl_resource_get_user_data(resource); + + ivi_hmi_controller_set_background(hmi_ctrl, hmi_ctrl->ui_setting.background_id); + ivi_hmi_controller_set_panel(hmi_ctrl, hmi_ctrl->ui_setting.panel_id); + ivi_hmi_controller_set_button(hmi_ctrl, hmi_ctrl->ui_setting.tiling_id, 0); + ivi_hmi_controller_set_button(hmi_ctrl, hmi_ctrl->ui_setting.sidebyside_id, 1); + ivi_hmi_controller_set_button(hmi_ctrl, hmi_ctrl->ui_setting.fullscreen_id, 2); + ivi_hmi_controller_set_button(hmi_ctrl, hmi_ctrl->ui_setting.random_id, 3); + ivi_hmi_controller_set_home_button(hmi_ctrl, hmi_ctrl->ui_setting.home_id); + ivi_hmi_controller_set_workspacebackground(hmi_ctrl, hmi_ctrl->ui_setting.workspace_background_id); + hmi_ctrl->interface->commit_changes(); + + ivi_hmi_controller_add_launchers(hmi_ctrl, 256); + hmi_ctrl->is_initialized = 1; +} + +/** + * Implementation of request and event of ivi_hmi_controller_workspace_control + * and controlling workspace. + * + * When motion of input is detected in a ivi_surface of workspace background, + * ivi_hmi_controller_workspace_control shall be invoked and to start + * controlling of workspace. The workspace has several pages to show several + * groups of applications. + * The workspace is slid by using ivi-layout to select a page in layer_set_pos + * according to motion. When motion finished, e.g. touch up detected, control is + * terminated and event:ivi_hmi_controller_workspace_control is notified. + */ +struct pointer_grab { + struct weston_pointer_grab grab; + struct ivi_layout_layer *layer; + struct wl_resource *resource; +}; + +struct touch_grab { + struct weston_touch_grab grab; + struct ivi_layout_layer *layer; + struct wl_resource *resource; +}; + +struct move_grab { + wl_fixed_t dst[2]; + wl_fixed_t rgn[2][2]; + double v[2]; + struct timespec start_time; + struct timespec pre_time; + wl_fixed_t start_pos[2]; + wl_fixed_t pos[2]; + int32_t is_moved; +}; + +struct pointer_move_grab { + struct pointer_grab base; + struct move_grab move; +}; + +struct touch_move_grab { + struct touch_grab base; + struct move_grab move; + int32_t is_active; +}; + +static void +pointer_grab_start(struct pointer_grab *grab, + struct ivi_layout_layer *layer, + const struct weston_pointer_grab_interface *interface, + struct weston_pointer *pointer) +{ + grab->grab.interface = interface; + grab->layer = layer; + weston_pointer_start_grab(pointer, &grab->grab); +} + +static void +touch_grab_start(struct touch_grab *grab, + struct ivi_layout_layer *layer, + const struct weston_touch_grab_interface *interface, + struct weston_touch* touch) +{ + grab->grab.interface = interface; + grab->layer = layer; + weston_touch_start_grab(touch, &grab->grab); +} + +static int32_t +clamp(int32_t val, int32_t min, int32_t max) +{ + if (val < min) + return min; + + if (max < val) + return max; + + return val; +} + +static void +move_workspace_grab_end(struct move_grab *move, struct wl_resource* resource, + wl_fixed_t grab_x, struct ivi_layout_layer *layer) +{ + struct hmi_controller *hmi_ctrl = wl_resource_get_user_data(resource); + int32_t width = hmi_ctrl->workspace_background_layer.width; + const struct ivi_layout_layer_properties *prop; + + struct timespec time = {0}; + double grab_time = 0.0; + double from_motion_time = 0.0; + double pointer_v = 0.0; + int32_t is_flick = 0; + int32_t pos_x = 0; + int32_t pos_y = 0; + int page_no = 0; + double end_pos = 0.0; + uint32_t duration = 0; + + clock_gettime(CLOCK_MONOTONIC, &time); + + grab_time = 1e+3 * (time.tv_sec - move->start_time.tv_sec) + + 1e-6 * (time.tv_nsec - move->start_time.tv_nsec); + + from_motion_time = 1e+3 * (time.tv_sec - move->pre_time.tv_sec) + + 1e-6 * (time.tv_nsec - move->pre_time.tv_nsec); + + pointer_v = move->v[0]; + + is_flick = grab_time < 400 && 0.4 < fabs(pointer_v); + if (200 < from_motion_time) + pointer_v = 0.0; + + prop = hmi_ctrl->interface->get_properties_of_layer(layer); + pos_x = prop->dest_x; + pos_y = prop->dest_y; + + if (is_flick) { + int orgx = wl_fixed_to_int(move->dst[0] + grab_x); + page_no = (-orgx + width / 2) / width; + + if (pointer_v < 0.0) + page_no++; + else + page_no--; + } else { + page_no = (-pos_x + width / 2) / width; + } + + page_no = clamp(page_no, 0, hmi_ctrl->workspace_count - 1); + end_pos = -page_no * width; + + duration = hmi_ctrl->hmi_setting->transition_duration; + ivi_hmi_controller_send_workspace_end_control(resource, move->is_moved); + hmi_ctrl->interface->layer_set_transition(layer, + IVI_LAYOUT_TRANSITION_LAYER_MOVE, + duration); + hmi_ctrl->interface->layer_set_destination_rectangle(layer, + end_pos, pos_y, + hmi_ctrl->workspace_layer.width, + hmi_ctrl->workspace_layer.height); + hmi_ctrl->interface->commit_changes(); +} + +static void +pointer_move_workspace_grab_end(struct pointer_grab *grab) +{ + struct pointer_move_grab *pnt_move_grab = + (struct pointer_move_grab *)grab; + struct ivi_layout_layer *layer = pnt_move_grab->base.layer; + + move_workspace_grab_end(&pnt_move_grab->move, grab->resource, + grab->grab.pointer->grab_x, layer); + + weston_pointer_end_grab(grab->grab.pointer); +} + +static void +touch_move_workspace_grab_end(struct touch_grab *grab) +{ + struct touch_move_grab *tch_move_grab = (struct touch_move_grab *)grab; + struct ivi_layout_layer *layer = tch_move_grab->base.layer; + + move_workspace_grab_end(&tch_move_grab->move, grab->resource, + grab->grab.touch->grab_x, layer); + + weston_touch_end_grab(grab->grab.touch); +} + +static void +pointer_noop_grab_focus(struct weston_pointer_grab *grab) +{ +} + +static void +pointer_default_grab_axis(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_axis_event *event) +{ + weston_pointer_send_axis(grab->pointer, time, event); +} + +static void +pointer_default_grab_axis_source(struct weston_pointer_grab *grab, + uint32_t source) +{ + weston_pointer_send_axis_source(grab->pointer, source); +} + +static void +pointer_default_grab_frame(struct weston_pointer_grab *grab) +{ + weston_pointer_send_frame(grab->pointer); +} + +static void +move_grab_update(struct move_grab *move, wl_fixed_t pointer[2]) +{ + struct timespec timestamp = {0}; + int32_t ii = 0; + double dt = 0.0; + + clock_gettime(CLOCK_MONOTONIC, ×tamp); //FIXME + dt = (1e+3 * (timestamp.tv_sec - move->pre_time.tv_sec) + + 1e-6 * (timestamp.tv_nsec - move->pre_time.tv_nsec)); + + if (dt < 1e-6) + dt = 1e-6; + + move->pre_time = timestamp; + + for (ii = 0; ii < 2; ii++) { + wl_fixed_t prepos = move->pos[ii]; + move->pos[ii] = pointer[ii] + move->dst[ii]; + + if (move->pos[ii] < move->rgn[0][ii]) { + move->pos[ii] = move->rgn[0][ii]; + move->dst[ii] = move->pos[ii] - pointer[ii]; + } else if (move->rgn[1][ii] < move->pos[ii]) { + move->pos[ii] = move->rgn[1][ii]; + move->dst[ii] = move->pos[ii] - pointer[ii]; + } + + move->v[ii] = wl_fixed_to_double(move->pos[ii] - prepos) / dt; + + if (!move->is_moved && + 0 < wl_fixed_to_int(move->pos[ii] - move->start_pos[ii])) + move->is_moved = 1; + } +} + +static void +layer_set_pos(struct hmi_controller *hmi_ctrl, struct ivi_layout_layer *layer, + wl_fixed_t pos_x, wl_fixed_t pos_y) +{ + const struct ivi_layout_layer_properties *prop; + int32_t layout_pos_x = 0; + int32_t layout_pos_y = 0; + + prop = hmi_ctrl->interface->get_properties_of_layer(layer); + + layout_pos_x = wl_fixed_to_int(pos_x); + layout_pos_y = wl_fixed_to_int(pos_y); + hmi_ctrl->interface->layer_set_destination_rectangle(layer, + layout_pos_x, layout_pos_y, prop->dest_width, prop->dest_height); + hmi_ctrl->interface->commit_changes(); +} + +static void +pointer_move_grab_motion(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + struct pointer_move_grab *pnt_move_grab = + (struct pointer_move_grab *)grab; + struct hmi_controller *hmi_ctrl = + wl_resource_get_user_data(pnt_move_grab->base.resource); + wl_fixed_t pointer_pos[2]; + + weston_pointer_motion_to_abs(grab->pointer, event, + &pointer_pos[0], &pointer_pos[1]); + move_grab_update(&pnt_move_grab->move, pointer_pos); + layer_set_pos(hmi_ctrl, pnt_move_grab->base.layer, + pnt_move_grab->move.pos[0], pnt_move_grab->move.pos[1]); + weston_pointer_move(pnt_move_grab->base.grab.pointer, event); +} + +static void +touch_move_grab_motion(struct weston_touch_grab *grab, + const struct timespec *time, int touch_id, + wl_fixed_t x, wl_fixed_t y) +{ + struct touch_move_grab *tch_move_grab = (struct touch_move_grab *)grab; + struct hmi_controller *hmi_ctrl = + wl_resource_get_user_data(tch_move_grab->base.resource); + + if (!tch_move_grab->is_active) + return; + + wl_fixed_t pointer_pos[2] = { + grab->touch->grab_x, + grab->touch->grab_y + }; + + move_grab_update(&tch_move_grab->move, pointer_pos); + layer_set_pos(hmi_ctrl, tch_move_grab->base.layer, + tch_move_grab->move.pos[0], tch_move_grab->move.pos[1]); +} + +static void +pointer_move_workspace_grab_button(struct weston_pointer_grab *grab, + const struct timespec *time, uint32_t button, + uint32_t state_w) +{ + if (BTN_LEFT == button && + WL_POINTER_BUTTON_STATE_RELEASED == state_w) { + struct pointer_grab *pg = (struct pointer_grab *)grab; + + pointer_move_workspace_grab_end(pg); + free(grab); + } +} + +static void +touch_nope_grab_down(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id, wl_fixed_t sx, wl_fixed_t sy) +{ +} + +static void +touch_move_workspace_grab_up(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id) +{ + struct touch_move_grab *tch_move_grab = (struct touch_move_grab *)grab; + + if (0 == touch_id) + tch_move_grab->is_active = 0; + + if (0 == grab->touch->num_tp) { + touch_move_workspace_grab_end(&tch_move_grab->base); + free(grab); + } +} + +static void +pointer_move_workspace_grab_cancel(struct weston_pointer_grab *grab) +{ + struct pointer_grab *pg = (struct pointer_grab *)grab; + + pointer_move_workspace_grab_end(pg); + free(grab); +} + +static void +touch_move_workspace_grab_frame(struct weston_touch_grab *grab) +{ +} + +static void +touch_move_workspace_grab_cancel(struct weston_touch_grab *grab) +{ + struct touch_grab *tg = (struct touch_grab *)grab; + + touch_move_workspace_grab_end(tg); + free(grab); +} + +static const struct weston_pointer_grab_interface pointer_move_grab_workspace_interface = { + pointer_noop_grab_focus, + pointer_move_grab_motion, + pointer_move_workspace_grab_button, + pointer_default_grab_axis, + pointer_default_grab_axis_source, + pointer_default_grab_frame, + pointer_move_workspace_grab_cancel +}; + +static const struct weston_touch_grab_interface touch_move_grab_workspace_interface = { + touch_nope_grab_down, + touch_move_workspace_grab_up, + touch_move_grab_motion, + touch_move_workspace_grab_frame, + touch_move_workspace_grab_cancel +}; + +enum HMI_GRAB_DEVICE { + HMI_GRAB_DEVICE_NONE, + HMI_GRAB_DEVICE_POINTER, + HMI_GRAB_DEVICE_TOUCH +}; + +static enum HMI_GRAB_DEVICE +get_hmi_grab_device(struct weston_seat *seat, uint32_t serial) +{ + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_touch *touch = weston_seat_get_touch(seat); + + if (pointer && + pointer->focus && + pointer->button_count && + pointer->grab_serial == serial) + return HMI_GRAB_DEVICE_POINTER; + + if (touch && + touch->focus && + touch->grab_serial == serial) + return HMI_GRAB_DEVICE_TOUCH; + + return HMI_GRAB_DEVICE_NONE; +} + +static void +move_grab_init(struct move_grab* move, wl_fixed_t start_pos[2], + wl_fixed_t grab_pos[2], wl_fixed_t rgn[2][2], + struct wl_resource* resource) +{ + clock_gettime(CLOCK_MONOTONIC, &move->start_time); //FIXME + move->pre_time = move->start_time; + move->pos[0] = start_pos[0]; + move->pos[1] = start_pos[1]; + move->start_pos[0] = start_pos[0]; + move->start_pos[1] = start_pos[1]; + move->dst[0] = start_pos[0] - grab_pos[0]; + move->dst[1] = start_pos[1] - grab_pos[1]; + memcpy(move->rgn, rgn, sizeof(move->rgn)); +} + +static void +move_grab_init_workspace(struct move_grab* move, + wl_fixed_t grab_x, wl_fixed_t grab_y, + struct wl_resource *resource) +{ + struct hmi_controller *hmi_ctrl = wl_resource_get_user_data(resource); + struct ivi_layout_layer *layer = hmi_ctrl->workspace_layer.ivilayer; + const struct ivi_layout_layer_properties *prop; + int32_t workspace_count = hmi_ctrl->workspace_count; + int32_t workspace_width = hmi_ctrl->workspace_background_layer.width; + int32_t layer_pos_x = 0; + int32_t layer_pos_y = 0; + wl_fixed_t start_pos[2] = {0}; + wl_fixed_t rgn[2][2] = {{0}}; + wl_fixed_t grab_pos[2] = { grab_x, grab_y }; + + prop = hmi_ctrl->interface->get_properties_of_layer(layer); + layer_pos_x = prop->dest_x; + layer_pos_y = prop->dest_y; + + start_pos[0] = wl_fixed_from_int(layer_pos_x); + start_pos[1] = wl_fixed_from_int(layer_pos_y); + + rgn[0][0] = wl_fixed_from_int(-workspace_width * (workspace_count - 1)); + + rgn[0][1] = wl_fixed_from_int(0); + rgn[1][0] = wl_fixed_from_int(0); + rgn[1][1] = wl_fixed_from_int(0); + + move_grab_init(move, start_pos, grab_pos, rgn, resource); +} + +static struct pointer_move_grab * +create_workspace_pointer_move(struct weston_pointer *pointer, + struct wl_resource* resource) +{ + struct pointer_move_grab *pnt_move_grab = + MEM_ALLOC(sizeof(*pnt_move_grab)); + + pnt_move_grab->base.resource = resource; + move_grab_init_workspace(&pnt_move_grab->move, pointer->grab_x, + pointer->grab_y, resource); + + return pnt_move_grab; +} + +static struct touch_move_grab * +create_workspace_touch_move(struct weston_touch *touch, + struct wl_resource* resource) +{ + struct touch_move_grab *tch_move_grab = + MEM_ALLOC(sizeof(*tch_move_grab)); + + tch_move_grab->base.resource = resource; + tch_move_grab->is_active = 1; + move_grab_init_workspace(&tch_move_grab->move, touch->grab_x, + touch->grab_y, resource); + + return tch_move_grab; +} + +static void +ivi_hmi_controller_workspace_control(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial) +{ + struct hmi_controller *hmi_ctrl = wl_resource_get_user_data(resource); + struct ivi_layout_layer *layer = NULL; + struct pointer_move_grab *pnt_move_grab = NULL; + struct touch_move_grab *tch_move_grab = NULL; + struct weston_seat *seat = NULL; + struct weston_pointer *pointer; + struct weston_touch *touch; + + enum HMI_GRAB_DEVICE device; + + if (hmi_ctrl->workspace_count < 2) + return; + + seat = wl_resource_get_user_data(seat_resource); + device = get_hmi_grab_device(seat, serial); + + if (HMI_GRAB_DEVICE_POINTER != device && + HMI_GRAB_DEVICE_TOUCH != device) + return; + + layer = hmi_ctrl->workspace_layer.ivilayer; + + hmi_ctrl->interface->transition_move_layer_cancel(layer); + + switch (device) { + case HMI_GRAB_DEVICE_POINTER: + pointer = weston_seat_get_pointer(seat); + pnt_move_grab = create_workspace_pointer_move(pointer, + resource); + + pointer_grab_start(&pnt_move_grab->base, layer, + &pointer_move_grab_workspace_interface, + pointer); + break; + + case HMI_GRAB_DEVICE_TOUCH: + touch = weston_seat_get_touch(seat); + tch_move_grab = create_workspace_touch_move(touch, + resource); + + touch_grab_start(&tch_move_grab->base, layer, + &touch_move_grab_workspace_interface, + touch); + break; + + default: + break; + } +} + +/** + * Implementation of switch_mode + */ +static void +ivi_hmi_controller_switch_mode(struct wl_client *client, + struct wl_resource *resource, + uint32_t layout_mode) +{ + struct hmi_controller *hmi_ctrl = wl_resource_get_user_data(resource); + + switch_mode(hmi_ctrl, layout_mode); +} + +/** + * Implementation of on/off displaying workspace and workspace background + * ivi_layers. + */ +static void +ivi_hmi_controller_home(struct wl_client *client, + struct wl_resource *resource, + uint32_t home) +{ + struct hmi_controller *hmi_ctrl = wl_resource_get_user_data(resource); + uint32_t is_fade_in; + + if ((IVI_HMI_CONTROLLER_HOME_ON == home && + !hmi_ctrl->workspace_fade.is_fade_in) || + (IVI_HMI_CONTROLLER_HOME_OFF == home && + hmi_ctrl->workspace_fade.is_fade_in)) { + is_fade_in = !hmi_ctrl->workspace_fade.is_fade_in; + hmi_controller_fade_run(hmi_ctrl, is_fade_in, + &hmi_ctrl->workspace_fade); + } + + hmi_ctrl->interface->commit_changes(); +} + +/** + * binding ivi-hmi-controller implementation + */ +static const struct ivi_hmi_controller_interface ivi_hmi_controller_implementation = { + ivi_hmi_controller_UI_ready, + ivi_hmi_controller_workspace_control, + ivi_hmi_controller_switch_mode, + ivi_hmi_controller_home +}; + +static void +unbind_hmi_controller(struct wl_resource *resource) +{ +} + +static void +bind_hmi_controller(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct wl_resource *resource = NULL; + struct hmi_controller *hmi_ctrl = data; + + if (hmi_ctrl->user_interface != client) { + struct wl_resource *res = wl_client_get_object(client, 1); + wl_resource_post_error(res, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "hmi-controller failed: permission denied"); + return; + } + + resource = wl_resource_create( + client, &ivi_hmi_controller_interface, 1, id); + + wl_resource_set_implementation( + resource, &ivi_hmi_controller_implementation, + hmi_ctrl, unbind_hmi_controller); +} + +static int32_t +initialize(struct hmi_controller *hmi_ctrl) +{ + struct config_command { + char *key; + uint32_t *dest; + }; + + struct weston_config *config = wet_get_config(hmi_ctrl->compositor); + struct weston_config_section *section = NULL; + int result = 0; + int i = 0; + + const struct config_command uint_commands[] = { + { "background-id", &hmi_ctrl->ui_setting.background_id }, + { "panel-id", &hmi_ctrl->ui_setting.panel_id }, + { "tiling-id", &hmi_ctrl->ui_setting.tiling_id }, + { "sidebyside-id", &hmi_ctrl->ui_setting.sidebyside_id }, + { "fullscreen-id", &hmi_ctrl->ui_setting.fullscreen_id }, + { "random-id", &hmi_ctrl->ui_setting.random_id }, + { "home-id", &hmi_ctrl->ui_setting.home_id }, + { "workspace-background-id", &hmi_ctrl->ui_setting.workspace_background_id }, + { "surface-id-offset", &hmi_ctrl->ui_setting.surface_id_offset }, + { NULL, NULL } + }; + + section = weston_config_get_section(config, "ivi-shell", NULL, NULL); + + for (i = 0; -1 != result; ++i) { + const struct config_command *command = &uint_commands[i]; + + if (!command->key) + break; + + if (weston_config_section_get_uint( + section, command->key, command->dest, 0) != 0) + result = -1; + } + + if (-1 == result) { + weston_log("Failed to initialize hmi-controller\n"); + return 0; + } + + return 1; +} + +static void +launch_hmi_client_process(void *data) +{ + struct hmi_controller *hmi_ctrl = + (struct hmi_controller *)data; + + hmi_ctrl->user_interface = + weston_client_start(hmi_ctrl->compositor, + hmi_ctrl->hmi_setting->ivi_homescreen); + + free(hmi_ctrl->hmi_setting->ivi_homescreen); +} + +/***************************************************************************** + * exported functions + ****************************************************************************/ +WL_EXPORT int +wet_module_init(struct weston_compositor *ec, + int *argc, char *argv[]) +{ + struct hmi_controller *hmi_ctrl = NULL; + struct wl_event_loop *loop = NULL; + + /* ad hoc weston_compositor_add_destroy_listener_once() */ + if (wl_signal_get(&ec->destroy_signal, hmi_controller_destroy)) + return 0; + + hmi_ctrl = hmi_controller_create(ec); + if (hmi_ctrl == NULL) + return -1; + + if (!initialize(hmi_ctrl)) { + return -1; + } + + if (wl_global_create(ec->wl_display, + &ivi_hmi_controller_interface, 1, + hmi_ctrl, bind_hmi_controller) == NULL) { + return -1; + } + + loop = wl_display_get_event_loop(ec->wl_display); + wl_event_loop_add_idle(loop, launch_hmi_client_process, hmi_ctrl); + + return 0; +} diff --git a/ivi-shell/ivi-layout-export.h b/ivi-shell/ivi-layout-export.h new file mode 100644 index 0000000..740e1ac --- /dev/null +++ b/ivi-shell/ivi-layout-export.h @@ -0,0 +1,610 @@ +/* + * Copyright (C) 2013 DENSO CORPORATION + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * The ivi-layout library supports API set of controlling properties of + * surface and layer which groups surfaces. A unique ID whose type is integer is + * required to create surface and layer. With the unique ID, surface and layer + * are identified to control them. The API set consists of APIs to control + * properties of surface and layers about the following: + * - visibility. + * - opacity. + * - clipping (x,y,width,height). + * - position and size of it to be displayed. + * - orientation per 90 degree. + * - add or remove surfaces to a layer. + * - order of surfaces/layers in layer/screen to be displayed. + * - commit to apply property changes. + * - notifications of property change. + * + * Management of surfaces and layers grouping these surfaces are common + * way in In-Vehicle Infotainment system, which integrate several domains + * in one system. A layer is allocated to a domain in order to control + * application surfaces grouped to the layer all together. + * + * This API and ABI follow following specifications. + * https://at.projects.genivi.org/wiki/display/PROJ/Wayland+IVI+Extension+Design + */ + +#ifndef _IVI_LAYOUT_EXPORT_H_ +#define _IVI_LAYOUT_EXPORT_H_ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include +#include + +#include "stdbool.h" +#include +#include + +#define IVI_SUCCEEDED (0) +#define IVI_FAILED (-1) +#define IVI_INVALID_ID UINT_MAX + +struct ivi_layout_layer; +struct ivi_layout_screen; +struct ivi_layout_surface; + +struct ivi_layout_surface_properties +{ + wl_fixed_t opacity; + int32_t source_x; + int32_t source_y; + int32_t source_width; + int32_t source_height; + int32_t start_x; + int32_t start_y; + int32_t start_width; + int32_t start_height; + int32_t dest_x; + int32_t dest_y; + int32_t dest_width; + int32_t dest_height; + enum wl_output_transform orientation; + bool visibility; + int32_t transition_type; + uint32_t transition_duration; + uint32_t event_mask; +}; + +struct ivi_layout_layer_properties +{ + wl_fixed_t opacity; + int32_t source_x; + int32_t source_y; + int32_t source_width; + int32_t source_height; + int32_t dest_x; + int32_t dest_y; + int32_t dest_width; + int32_t dest_height; + enum wl_output_transform orientation; + bool visibility; + int32_t transition_type; + uint32_t transition_duration; + double start_alpha; + double end_alpha; + uint32_t is_fade_in; + uint32_t event_mask; +}; + +enum ivi_layout_notification_mask { + IVI_NOTIFICATION_NONE = 0, + IVI_NOTIFICATION_OPACITY = (1 << 1), + IVI_NOTIFICATION_SOURCE_RECT = (1 << 2), + IVI_NOTIFICATION_DEST_RECT = (1 << 3), + IVI_NOTIFICATION_DIMENSION = (1 << 4), + IVI_NOTIFICATION_POSITION = (1 << 5), + IVI_NOTIFICATION_ORIENTATION = (1 << 6), + IVI_NOTIFICATION_VISIBILITY = (1 << 7), + IVI_NOTIFICATION_PIXELFORMAT = (1 << 8), + IVI_NOTIFICATION_ADD = (1 << 9), + IVI_NOTIFICATION_REMOVE = (1 << 10), + IVI_NOTIFICATION_CONFIGURE = (1 << 11), + IVI_NOTIFICATION_ALL = 0xFFFF +}; + +enum ivi_layout_transition_type{ + IVI_LAYOUT_TRANSITION_NONE, + IVI_LAYOUT_TRANSITION_VIEW_DEFAULT, + IVI_LAYOUT_TRANSITION_VIEW_DEST_RECT_ONLY, + IVI_LAYOUT_TRANSITION_VIEW_FADE_ONLY, + IVI_LAYOUT_TRANSITION_LAYER_FADE, + IVI_LAYOUT_TRANSITION_LAYER_MOVE, + IVI_LAYOUT_TRANSITION_LAYER_VIEW_ORDER, + IVI_LAYOUT_TRANSITION_VIEW_MOVE_RESIZE, + IVI_LAYOUT_TRANSITION_VIEW_RESIZE, + IVI_LAYOUT_TRANSITION_VIEW_FADE, + IVI_LAYOUT_TRANSITION_MAX, +}; + +#define IVI_LAYOUT_API_NAME "ivi_layout_api_v1" + +struct ivi_layout_interface { + + /** + * \brief Commit all changes and execute all enqueued commands since + * last commit. + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*commit_changes)(void); + + /** + * surface controller interface + */ + + /** + * \brief add a listener for notification when ivi_surface is created + * + * When an ivi_surface is created, a signal is emitted + * to the listening controller plugins. + * The pointer of the created ivi_surface is sent as the void *data argument + * to the wl_listener::notify callback function of the listener. + */ + int32_t (*add_listener_create_surface)(struct wl_listener *listener); + + /** + * \brief add a listener for notification when ivi_surface is removed + * + * When an ivi_surface is removed, a signal is emitted + * to the listening controller plugins. + * The pointer of the removed ivi_surface is sent as the void *data argument + * to the wl_listener::notify callback function of the listener. + */ + int32_t (*add_listener_remove_surface)(struct wl_listener *listener); + + /** + * \brief add a listener for notification when ivi_surface is configured + * + * When an ivi_surface is configured, a signal is emitted + * to the listening controller plugins. + * The pointer of the configured ivi_surface is sent as the void *data argument + * to the wl_listener::notify callback function of the listener. + */ + int32_t (*add_listener_configure_surface)(struct wl_listener *listener); + + /** + * \brief add a listener for notification when desktop_surface is configured + * + * When an desktop_surface is configured, a signal is emitted + * to the listening controller plugins. + * The pointer of the configured desktop_surface is sent as the void *data argument + * to the wl_listener::notify callback function of the listener. + */ + int32_t (*add_listener_configure_desktop_surface)(struct wl_listener *listener); + + /** + * \brief Get all ivi_surfaces which are currently registered and managed + * by the services + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*get_surfaces)(int32_t *pLength, struct ivi_layout_surface ***ppArray); + + /** + * \brief get id of ivi_surface from ivi_layout_surface + * + * \return id of ivi_surface + */ + uint32_t (*get_id_of_surface)(struct ivi_layout_surface *ivisurf); + + /** + * \brief get ivi_layout_surface from id of ivi_surface + * + * \return (struct ivi_layout_surface *) + * if the method call was successful + * \return NULL if the method call was failed + */ + struct ivi_layout_surface * + (*get_surface_from_id)(uint32_t id_surface); + + /** + * \brief get ivi_layout_surface_properties from ivisurf + * + * \return (struct ivi_layout_surface_properties *) + * if the method call was successful + * \return NULL if the method call was failed + */ + const struct ivi_layout_surface_properties * + (*get_properties_of_surface)(struct ivi_layout_surface *ivisurf); + + /** + * \brief Get all Surfaces which are currently registered to a given + * layer and are managed by the services + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*get_surfaces_on_layer)(struct ivi_layout_layer *ivilayer, + int32_t *pLength, + struct ivi_layout_surface ***ppArray); + + /** + * \brief Set the visibility of a ivi_surface. + * + * If a surface is not visible it will not be rendered. + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*surface_set_visibility)(struct ivi_layout_surface *ivisurf, + bool newVisibility); + + /** + * \brief Set the opacity of a surface. + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*surface_set_opacity)(struct ivi_layout_surface *ivisurf, + wl_fixed_t opacity); + + /** + * \brief Set the area of a ivi_surface which should be used for the rendering. + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*surface_set_source_rectangle)(struct ivi_layout_surface *ivisurf, + int32_t x, int32_t y, + int32_t width, int32_t height); + + /** + * \brief Set the destination area of a ivi_surface within a ivi_layer + * for rendering. + * + * The surface will be scaled to this rectangle for rendering. + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*surface_set_destination_rectangle)(struct ivi_layout_surface *ivisurf, + int32_t x, int32_t y, + int32_t width, int32_t height); + + /** + * \brief add a listener to listen property changes of ivi_surface + * + * When a property of the ivi_surface is changed, the property_changed + * signal is emitted to the listening controller plugins. + * The pointer of the ivi_surface is sent as the void *data argument + * to the wl_listener::notify callback function of the listener. + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*surface_add_listener)(struct ivi_layout_surface *ivisurf, + struct wl_listener *listener); + + /** + * \brief get weston_surface of ivi_surface + */ + struct weston_surface * + (*surface_get_weston_surface)(struct ivi_layout_surface *ivisurf); + + /** + * \brief set type of transition animation + */ + int32_t (*surface_set_transition)(struct ivi_layout_surface *ivisurf, + enum ivi_layout_transition_type type, + uint32_t duration); + + /** + * \brief set duration of transition animation + */ + int32_t (*surface_set_transition_duration)( + struct ivi_layout_surface *ivisurf, + uint32_t duration); + + /** + * \brief set id of ivi_layout_surface + */ + int32_t (*surface_set_id)(struct ivi_layout_surface *ivisurf, + uint32_t id_surface); + + /** + * layer controller interface + */ + + /** + * \brief add a listener for notification when ivi_layer is created + * + * When an ivi_layer is created, a signal is emitted + * to the listening controller plugins. + * The pointer of the created ivi_layer is sent as the void *data argument + * to the wl_listener::notify callback function of the listener. + */ + int32_t (*add_listener_create_layer)(struct wl_listener *listener); + + /** + * \brief add a listener for notification when ivi_layer is removed + * + * When an ivi_layer is removed, a signal is emitted + * to the listening controller plugins. + * The pointer of the removed ivi_layer is sent as the void *data argument + * to the wl_listener::notify callback function of the listener. + */ + int32_t (*add_listener_remove_layer)(struct wl_listener *listener); + + /** + * \brief Create a ivi_layer which should be managed by the service + * + * \return (struct ivi_layout_layer *) + * if the method call was successful + * \return NULL if the method call was failed + */ + struct ivi_layout_layer * + (*layer_create_with_dimension)(uint32_t id_layer, + int32_t width, int32_t height); + + /** + * \brief Removes a ivi_layer which is currently managed by the service + */ + void (*layer_destroy)(struct ivi_layout_layer *ivilayer); + + /** + * \brief Get all ivi_layers which are currently registered and managed + * by the services + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*get_layers)(int32_t *pLength, struct ivi_layout_layer ***ppArray); + + /** + * \brief get id of ivi_layer from ivi_layout_layer + * + * + * \return id of ivi_layer + */ + uint32_t (*get_id_of_layer)(struct ivi_layout_layer *ivilayer); + + /** + * \brief get ivi_layout_layer from id of layer + * + * \return (struct ivi_layout_layer *) + * if the method call was successful + * \return NULL if the method call was failed + */ + struct ivi_layout_layer * (*get_layer_from_id)(uint32_t id_layer); + + /** + * \brief Get the ivi_layer properties + * + * \return (const struct ivi_layout_layer_properties *) + * if the method call was successful + * \return NULL if the method call was failed + */ + const struct ivi_layout_layer_properties * + (*get_properties_of_layer)(struct ivi_layout_layer *ivilayer); + + /** + * \brief Get all ivi-layers under the given ivi-surface + * + * This means all the ivi-layers the ivi-surface was added to. It has + * no relation to geometric overlaps. + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*get_layers_under_surface)(struct ivi_layout_surface *ivisurf, + int32_t *pLength, + struct ivi_layout_layer ***ppArray); + + /** + * \brief Get all Layers of the given weston_output + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*get_layers_on_screen)(struct weston_output *output, + int32_t *pLength, + struct ivi_layout_layer ***ppArray); + + /** + * \brief Set the visibility of a ivi_layer. If a ivi_layer is not visible, + * the ivi_layer and its ivi_surfaces will not be rendered. + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*layer_set_visibility)(struct ivi_layout_layer *ivilayer, + bool newVisibility); + + /** + * \brief Set the opacity of a ivi_layer. + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*layer_set_opacity)(struct ivi_layout_layer *ivilayer, + wl_fixed_t opacity); + + /** + * \brief Set the area of a ivi_layer which should be used for the rendering. + * + * Only this part will be visible. + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*layer_set_source_rectangle)(struct ivi_layout_layer *ivilayer, + int32_t x, int32_t y, + int32_t width, int32_t height); + + /** + * \brief Set the destination area on the display for a ivi_layer. + * + * The ivi_layer will be scaled and positioned to this rectangle + * for rendering + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*layer_set_destination_rectangle)(struct ivi_layout_layer *ivilayer, + int32_t x, int32_t y, + int32_t width, int32_t height); + + /** + * \brief Add a ivi_surface to a ivi_layer which is currently managed by the service + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*layer_add_surface)(struct ivi_layout_layer *ivilayer, + struct ivi_layout_surface *addsurf); + + /** + * \brief Removes a surface from a layer which is currently managed by the service + */ + void (*layer_remove_surface)(struct ivi_layout_layer *ivilayer, + struct ivi_layout_surface *remsurf); + + /** + * \brief Sets render order of ivi_surfaces within a ivi_layer + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*layer_set_render_order)(struct ivi_layout_layer *ivilayer, + struct ivi_layout_surface **pSurface, + int32_t number); + + /** + * \brief add a listener to listen property changes of ivi_layer + * + * When a property of the ivi_layer is changed, the property_changed + * signal is emitted to the listening controller plugins. + * The pointer of the ivi_layer is sent as the void *data argument + * to the wl_listener::notify callback function of the listener. + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*layer_add_listener)(struct ivi_layout_layer *ivilayer, + struct wl_listener *listener); + + /** + * \brief set type of transition animation + */ + int32_t (*layer_set_transition)(struct ivi_layout_layer *ivilayer, + enum ivi_layout_transition_type type, + uint32_t duration); + + /** + * screen controller interface + */ + + /** + * \brief Get the weston_outputs under the given ivi_layer + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*get_screens_under_layer)(struct ivi_layout_layer *ivilayer, + int32_t *pLength, + struct weston_output ***ppArray); + + /** + * \brief Add a ivi_layer to a weston_output which is currently managed + * by the service + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*screen_add_layer)(struct weston_output *output, + struct ivi_layout_layer *addlayer); + + /** + * \brief Sets render order of ivi_layers on a weston_output + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*screen_set_render_order)(struct weston_output *output, + struct ivi_layout_layer **pLayer, + const int32_t number); + + /** + * transition animation for layer + */ + void (*transition_move_layer_cancel)(struct ivi_layout_layer *layer); + int32_t (*layer_set_fade_info)(struct ivi_layout_layer* ivilayer, + uint32_t is_fade_in, + double start_alpha, double end_alpha); + + /** + * surface content dumping for debugging + */ + int32_t (*surface_get_size)(struct ivi_layout_surface *ivisurf, + int32_t *width, int32_t *height, + int32_t *stride); + + int32_t (*surface_dump)(struct weston_surface *surface, + void *target, size_t size, + int32_t x, int32_t y, + int32_t width, int32_t height); + + /** + * Returns the ivi_layout_surface or NULL + * + * NULL is returned if there is no ivi_layout_surface corresponding + * to the given weston_surface. + */ + struct ivi_layout_surface * + (*get_surface)(struct weston_surface *surface); + + /** + * \brief Remove a ivi_layer to a weston_output which is currently managed + * by the service + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*screen_remove_layer)(struct weston_output *output, + struct ivi_layout_layer *removelayer); +}; + +static inline const struct ivi_layout_interface * +ivi_layout_get_api(struct weston_compositor *compositor) +{ + const void *api; + api = weston_plugin_api_get(compositor, IVI_LAYOUT_API_NAME, + sizeof(struct ivi_layout_interface)); + + return (const struct ivi_layout_interface *)api; +} + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _IVI_LAYOUT_EXPORT_H_ */ diff --git a/ivi-shell/ivi-layout-private.h b/ivi-shell/ivi-layout-private.h new file mode 100644 index 0000000..5a0119c --- /dev/null +++ b/ivi-shell/ivi-layout-private.h @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2014 DENSO CORPORATION + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _ivi_layout_PRIVATE_H_ +#define _ivi_layout_PRIVATE_H_ + +#include + +#include +#include "ivi-layout-export.h" +#include + +struct ivi_layout_view { + struct wl_list link; /* ivi_layout::view_list */ + struct wl_list surf_link; /*ivi_layout_surface::view_list */ + struct wl_list pending_link; /* ivi_layout_layer::pending.view_list */ + struct wl_list order_link; /* ivi_layout_layer::order.view_list */ + + struct weston_view *view; + struct weston_transform transform; + + struct ivi_layout_surface *ivisurf; + struct ivi_layout_layer *on_layer; +}; + +struct ivi_layout_surface { + struct wl_list link; /* ivi_layout::surface_list */ + struct wl_signal property_changed; + int32_t update_count; + uint32_t id_surface; + + struct ivi_layout *layout; + struct weston_surface *surface; + struct weston_desktop_surface *weston_desktop_surface; + + struct ivi_layout_surface_properties prop; + + struct { + struct ivi_layout_surface_properties prop; + } pending; + + struct wl_list view_list; /* ivi_layout_view::surf_link */ +}; + +struct ivi_layout_layer { + struct wl_list link; /* ivi_layout::layer_list */ + struct wl_signal property_changed; + uint32_t id_layer; + + struct ivi_layout *layout; + struct ivi_layout_screen *on_screen; + + struct ivi_layout_layer_properties prop; + + struct { + struct ivi_layout_layer_properties prop; + struct wl_list view_list; /* ivi_layout_view::pending_link */ + struct wl_list link; /* ivi_layout_screen::pending.layer_list */ + } pending; + + struct { + int dirty; + struct wl_list view_list; /* ivi_layout_view::order_link */ + struct wl_list link; /* ivi_layout_screen::order.layer_list */ + } order; + + int32_t ref_count; +}; + +struct ivi_layout { + struct weston_compositor *compositor; + + struct wl_list surface_list; /* ivi_layout_surface::link */ + struct wl_list layer_list; /* ivi_layout_layer::link */ + struct wl_list screen_list; /* ivi_layout_screen::link */ + struct wl_list view_list; /* ivi_layout_view::link */ + + struct { + struct wl_signal created; + struct wl_signal removed; + } layer_notification; + + struct { + struct wl_signal created; + struct wl_signal removed; + struct wl_signal configure_changed; + struct wl_signal configure_desktop_changed; + } surface_notification; + + struct weston_layer layout_layer; + + struct ivi_layout_transition_set *transitions; + struct wl_list pending_transition_list; /* transition_node::link */ +}; + +struct ivi_layout *get_instance(void); + +struct ivi_layout_transition; + +struct ivi_layout_transition_set { + struct wl_event_source *event_source; + struct wl_list transition_list; +}; + +typedef void (*ivi_layout_transition_destroy_user_func)(void *user_data); + +struct ivi_layout_transition_set * +ivi_layout_transition_set_create(struct weston_compositor *ec); + +void +ivi_layout_transition_move_resize_view(struct ivi_layout_surface *surface, + int32_t dest_x, int32_t dest_y, + int32_t dest_width, int32_t dest_height, + uint32_t duration); + +void +ivi_layout_transition_visibility_on(struct ivi_layout_surface *surface, + uint32_t duration); + +void +ivi_layout_transition_visibility_off(struct ivi_layout_surface *surface, + uint32_t duration); + + +void +ivi_layout_transition_move_layer(struct ivi_layout_layer *layer, + int32_t dest_x, int32_t dest_y, + uint32_t duration); + +void +ivi_layout_transition_fade_layer(struct ivi_layout_layer *layer, + uint32_t is_fade_in, + double start_alpha, double end_alpha, + void *user_data, + ivi_layout_transition_destroy_user_func destroy_func, + uint32_t duration); + +int32_t +is_surface_transition(struct ivi_layout_surface *surface); + +void +ivi_layout_remove_all_surface_transitions(struct ivi_layout_surface *surface); + +/** + * methods of interaction between transition animation with ivi-layout + */ +int32_t +ivi_layout_commit_changes(void); +uint32_t +ivi_layout_get_id_of_surface(struct ivi_layout_surface *ivisurf); +int32_t +ivi_layout_surface_set_destination_rectangle(struct ivi_layout_surface *ivisurf, + int32_t x, int32_t y, + int32_t width, int32_t height); +int32_t +ivi_layout_surface_set_opacity(struct ivi_layout_surface *ivisurf, + wl_fixed_t opacity); +int32_t +ivi_layout_surface_set_visibility(struct ivi_layout_surface *ivisurf, + bool newVisibility); +void +ivi_layout_surface_set_size(struct ivi_layout_surface *ivisurf, + int32_t width, int32_t height); +struct ivi_layout_surface * +ivi_layout_get_surface_from_id(uint32_t id_surface); +int32_t +ivi_layout_layer_set_opacity(struct ivi_layout_layer *ivilayer, + wl_fixed_t opacity); +int32_t +ivi_layout_layer_set_visibility(struct ivi_layout_layer *ivilayer, + bool newVisibility); +int32_t +ivi_layout_layer_set_destination_rectangle(struct ivi_layout_layer *ivilayer, + int32_t x, int32_t y, + int32_t width, int32_t height); +int32_t +ivi_layout_layer_set_render_order(struct ivi_layout_layer *ivilayer, + struct ivi_layout_surface **pSurface, + int32_t number); +void +ivi_layout_transition_move_layer_cancel(struct ivi_layout_layer *layer); + +#endif diff --git a/ivi-shell/ivi-layout-shell.h b/ivi-shell/ivi-layout-shell.h new file mode 100644 index 0000000..bbbb77d --- /dev/null +++ b/ivi-shell/ivi-layout-shell.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2014 DENSO CORPORATION + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef IVI_LAYOUT_SHELL_H +#define IVI_LAYOUT_SHELL_H + +#include + +/* + * This is the interface that ivi-layout exposes to ivi-shell. + * It is private to ivi-shell.so plugin. + */ + +struct wl_listener; +struct weston_compositor; +struct weston_view; +struct weston_surface; +struct ivi_layout_surface; + +void +ivi_layout_desktop_surface_configure(struct ivi_layout_surface *ivisurf, + int32_t width, int32_t height); + +struct ivi_layout_surface* +ivi_layout_desktop_surface_create(struct weston_surface *wl_surface); + +void +ivi_layout_surface_configure(struct ivi_layout_surface *ivisurf, + int32_t width, int32_t height); + +struct ivi_layout_surface* +ivi_layout_surface_create(struct weston_surface *wl_surface, + uint32_t id_surface); + +void +ivi_layout_init_with_compositor(struct weston_compositor *ec); + +void +ivi_layout_surface_destroy(struct ivi_layout_surface *ivisurf); + +int +load_controller_modules(struct weston_compositor *compositor, const char *modules, + int *argc, char *argv[]); + +#endif /* IVI_LAYOUT_SHELL_H */ diff --git a/ivi-shell/ivi-layout-transition.c b/ivi-shell/ivi-layout-transition.c new file mode 100644 index 0000000..b07c9d0 --- /dev/null +++ b/ivi-shell/ivi-layout-transition.c @@ -0,0 +1,908 @@ +/* + * Copyright (C) 2014 DENSO CORPORATION + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "ivi-shell.h" +#include "ivi-layout-export.h" +#include "ivi-layout-private.h" + +struct ivi_layout_transition; + +typedef void (*ivi_layout_transition_frame_func)( + struct ivi_layout_transition *transition); +typedef void (*ivi_layout_transition_destroy_func)( + struct ivi_layout_transition *transition); +typedef int32_t (*ivi_layout_is_transition_func)(void *private_data, void *id); + +struct ivi_layout_transition { + enum ivi_layout_transition_type type; + void *private_data; + void *user_data; + + uint32_t time_start; + uint32_t time_duration; + uint32_t time_elapsed; + uint32_t is_done; + ivi_layout_is_transition_func is_transition_func; + ivi_layout_transition_frame_func frame_func; + ivi_layout_transition_destroy_func destroy_func; +}; + +struct transition_node { + struct ivi_layout_transition *transition; + + /* ivi_layout::pending_transition_list + * ivi_layout_transition_set::transition_list + */ + struct wl_list link; +}; + +static void layout_transition_destroy(struct ivi_layout_transition *transition); + +static struct ivi_layout_transition * +get_transition_from_type_and_id(enum ivi_layout_transition_type type, + void *id_data) +{ + struct ivi_layout *layout = get_instance(); + struct transition_node *node; + struct ivi_layout_transition *tran; + + wl_list_for_each(node, &layout->transitions->transition_list, link) { + tran = node->transition; + + if (tran->type == type && + tran->is_transition_func(tran->private_data, id_data)) + return tran; + } + + return NULL; +} + +int32_t +is_surface_transition(struct ivi_layout_surface *surface) +{ + struct ivi_layout *layout = get_instance(); + struct transition_node *node; + struct ivi_layout_transition *tran; + + wl_list_for_each(node, &layout->transitions->transition_list, link) { + tran = node->transition; + + if ((tran->type == IVI_LAYOUT_TRANSITION_VIEW_MOVE_RESIZE || + tran->type == IVI_LAYOUT_TRANSITION_VIEW_RESIZE) && + tran->is_transition_func(tran->private_data, surface)) + return 1; + } + + return 0; +} + +void +ivi_layout_remove_all_surface_transitions(struct ivi_layout_surface *surface) +{ + struct ivi_layout *layout = get_instance(); + struct transition_node *node; + struct transition_node *tmp; + struct ivi_layout_transition *tran; + + wl_list_for_each_safe(node, tmp, &layout->transitions->transition_list, link) { + tran = node->transition; + if (tran->is_transition_func(tran->private_data, surface)) { + layout_transition_destroy(tran); + } + }; +} + +static void +tick_transition(struct ivi_layout_transition *transition, uint32_t timestamp) +{ + const double t = timestamp - transition->time_start; + + if (transition->time_duration <= t) { + transition->time_elapsed = transition->time_duration; + transition->is_done = 1; + } else { + transition->time_elapsed = t; + } +} + +static float time_to_nowpos(struct ivi_layout_transition *transition) +{ + return sin((float)transition->time_elapsed / + (float)transition->time_duration * M_PI_2); +} + +static void +do_transition_frame(struct ivi_layout_transition *transition, + uint32_t timestamp) +{ + if (0 == transition->time_start) + transition->time_start = timestamp; + + tick_transition(transition, timestamp); + transition->frame_func(transition); + + if (transition->is_done) + layout_transition_destroy(transition); +} + +static int32_t +layout_transition_frame(void *data) +{ + struct ivi_layout_transition_set *transitions = data; + uint32_t fps = 30; + struct timespec timestamp = {}; + uint32_t msec = 0; + struct transition_node *node = NULL; + struct transition_node *next = NULL; + + if (wl_list_empty(&transitions->transition_list)) { + wl_event_source_timer_update(transitions->event_source, 0); + return 1; + } + + wl_event_source_timer_update(transitions->event_source, 1000 / fps); + + clock_gettime(CLOCK_MONOTONIC, ×tamp);/* FIXME */ + msec = (1e+3 * timestamp.tv_sec + 1e-6 * timestamp.tv_nsec); + + wl_list_for_each_safe(node, next, &transitions->transition_list, link) { + do_transition_frame(node->transition, msec); + } + + ivi_layout_commit_changes(); + return 1; +} + +struct ivi_layout_transition_set * +ivi_layout_transition_set_create(struct weston_compositor *ec) +{ + struct ivi_layout_transition_set *transitions; + struct wl_event_loop *loop; + + transitions = malloc(sizeof(*transitions)); + if (transitions == NULL) { + weston_log("%s: memory allocation fails\n", __func__); + return NULL; + } + + wl_list_init(&transitions->transition_list); + + loop = wl_display_get_event_loop(ec->wl_display); + transitions->event_source = + wl_event_loop_add_timer(loop, layout_transition_frame, + transitions); + + return transitions; +} + +static bool +layout_transition_register(struct ivi_layout_transition *trans) +{ + struct ivi_layout *layout = get_instance(); + struct transition_node *node; + + node = malloc(sizeof(*node)); + if (node == NULL) { + weston_log("%s: memory allocation fails\n", __func__); + return false; + } + + node->transition = trans; + wl_list_insert(&layout->pending_transition_list, &node->link); + return true; +} + +static void +remove_transition(struct ivi_layout *layout, + struct ivi_layout_transition *trans) +{ + struct transition_node *node; + struct transition_node *next; + + wl_list_for_each_safe(node, next, + &layout->transitions->transition_list, link) { + if (node->transition == trans) { + wl_list_remove(&node->link); + free(node); + return; + } + } + + wl_list_for_each_safe(node, next, + &layout->pending_transition_list, link) { + if (node->transition == trans) { + wl_list_remove(&node->link); + free(node); + return; + } + } +} + +static void +layout_transition_destroy(struct ivi_layout_transition *transition) +{ + struct ivi_layout *layout = get_instance(); + + remove_transition(layout, transition); + if (transition->destroy_func) + transition->destroy_func(transition); + free(transition); +} + +static struct ivi_layout_transition * +create_layout_transition(void) +{ + struct ivi_layout_transition *transition = malloc(sizeof(*transition)); + + if (transition == NULL) { + weston_log("%s: memory allocation fails\n", __func__); + return NULL; + } + + transition->type = IVI_LAYOUT_TRANSITION_MAX; + transition->time_start = 0; + transition->time_duration = 300; /* 300ms */ + transition->time_elapsed = 0; + + transition->is_done = 0; + + transition->is_transition_func = NULL; + transition->private_data = NULL; + transition->user_data = NULL; + + transition->frame_func = NULL; + transition->destroy_func = NULL; + + return transition; +} + +/* move and resize view transition */ + +struct move_resize_view_data { + struct ivi_layout_surface *surface; + int32_t start_x; + int32_t start_y; + int32_t end_x; + int32_t end_y; + int32_t start_width; + int32_t start_height; + int32_t end_width; + int32_t end_height; +}; + +static void +transition_move_resize_view_destroy(struct ivi_layout_transition *transition) +{ + struct move_resize_view_data *data = + (struct move_resize_view_data *)transition->private_data; + struct ivi_layout_surface *layout_surface = data->surface; + + ivi_layout_surface_set_size(layout_surface, + layout_surface->prop.dest_width, + layout_surface->prop.dest_height); + + if (transition->private_data) { + free(transition->private_data); + transition->private_data = NULL; + } +} + +static void +transition_move_resize_view_user_frame(struct ivi_layout_transition *transition) +{ + struct move_resize_view_data *mrv = transition->private_data; + const double current = time_to_nowpos(transition); + + const int32_t destx = mrv->start_x + + (mrv->end_x - mrv->start_x) * current; + + const int32_t desty = mrv->start_y + + (mrv->end_y - mrv->start_y) * current; + + const int32_t dest_width = mrv->start_width + + (mrv->end_width - mrv->start_width) * current; + + const int32_t dest_height = mrv->start_height + + (mrv->end_height - mrv->start_height) * current; + + ivi_layout_surface_set_destination_rectangle(mrv->surface, + destx, desty, + dest_width, dest_height); +} + +static int32_t +is_transition_move_resize_view_func(struct move_resize_view_data *data, + struct ivi_layout_surface *view) +{ + return data->surface == view; +} + +static struct ivi_layout_transition * +create_move_resize_view_transition( + struct ivi_layout_surface *surface, + int32_t start_x, int32_t start_y, + int32_t end_x, int32_t end_y, + int32_t start_width, int32_t start_height, + int32_t end_width, int32_t end_height, + ivi_layout_transition_frame_func frame_func, + ivi_layout_transition_destroy_func destroy_func, + uint32_t duration) +{ + struct ivi_layout_transition *transition; + struct move_resize_view_data *data; + + transition = create_layout_transition(); + if (transition == NULL) + return NULL; + + data = malloc(sizeof(*data)); + if (data == NULL) { + weston_log("%s: memory allocation fails\n", __func__); + free(transition); + return NULL; + } + + transition->type = IVI_LAYOUT_TRANSITION_VIEW_MOVE_RESIZE; + transition->is_transition_func = (ivi_layout_is_transition_func)is_transition_move_resize_view_func; + + transition->frame_func = frame_func; + transition->destroy_func = destroy_func; + transition->private_data = data; + + if (duration != 0) + transition->time_duration = duration; + + data->surface = surface; + data->start_x = start_x; + data->start_y = start_y; + data->end_x = end_x; + data->end_y = end_y; + + data->start_width = start_width; + data->start_height = start_height; + data->end_width = end_width; + data->end_height = end_height; + + return transition; +} + +void +ivi_layout_transition_move_resize_view(struct ivi_layout_surface *surface, + int32_t dest_x, int32_t dest_y, + int32_t dest_width, int32_t dest_height, + uint32_t duration) +{ + struct ivi_layout_transition *transition; + int32_t start_pos[2] = { + surface->pending.prop.start_x, + surface->pending.prop.start_y + }; + + int32_t start_size[2] = { + surface->pending.prop.start_width, + surface->pending.prop.start_height + }; + + transition = get_transition_from_type_and_id( + IVI_LAYOUT_TRANSITION_VIEW_MOVE_RESIZE, + surface); + if (transition) { + struct move_resize_view_data *data = transition->private_data; + transition->time_start = 0; + transition->time_duration = duration; + + data->start_x = start_pos[0]; + data->start_y = start_pos[1]; + data->end_x = dest_x; + data->end_y = dest_y; + + data->start_width = start_size[0]; + data->start_height = start_size[1]; + data->end_width = dest_width; + data->end_height = dest_height; + return; + } + + transition = create_move_resize_view_transition( + surface, + start_pos[0], start_pos[1], + dest_x, dest_y, + start_size[0], start_size[1], + dest_width, dest_height, + transition_move_resize_view_user_frame, + transition_move_resize_view_destroy, + duration); + + if (transition && layout_transition_register(transition)) + return; + layout_transition_destroy(transition); +} + +/* fade transition */ +struct fade_view_data { + struct ivi_layout_surface *surface; + double start_alpha; + double end_alpha; +}; + +struct store_alpha{ + double alpha; +}; + +static void +fade_view_user_frame(struct ivi_layout_transition *transition) +{ + struct fade_view_data *fade = transition->private_data; + struct ivi_layout_surface *surface = fade->surface; + + const double current = time_to_nowpos(transition); + const double alpha = fade->start_alpha + + (fade->end_alpha - fade->start_alpha) * current; + + ivi_layout_surface_set_opacity(surface, wl_fixed_from_double(alpha)); + ivi_layout_surface_set_visibility(surface, true); +} + +static int32_t +is_transition_fade_view_func(struct fade_view_data *data, + struct ivi_layout_surface *view) +{ + return data->surface == view; +} + +static struct ivi_layout_transition * +create_fade_view_transition( + struct ivi_layout_surface *surface, + double start_alpha, double end_alpha, + ivi_layout_transition_frame_func frame_func, + void *user_data, + ivi_layout_transition_destroy_func destroy_func, + uint32_t duration) +{ + struct ivi_layout_transition *transition; + struct fade_view_data *data; + + transition = create_layout_transition(); + if (transition == NULL) + return NULL; + + data = malloc(sizeof(*data)); + if (data == NULL) { + weston_log("%s: memory allocation fails\n", __func__); + free(transition); + return NULL; + } + + transition->type = IVI_LAYOUT_TRANSITION_VIEW_FADE; + transition->is_transition_func = (ivi_layout_is_transition_func)is_transition_fade_view_func; + + transition->user_data = user_data; + transition->private_data = data; + transition->frame_func = frame_func; + transition->destroy_func = destroy_func; + + if (duration != 0) + transition->time_duration = duration; + + data->surface = surface; + data->start_alpha = start_alpha; + data->end_alpha = end_alpha; + + return transition; +} + +static void +create_visibility_transition(struct ivi_layout_surface *surface, + double start_alpha, + double dest_alpha, + void *user_data, + ivi_layout_transition_destroy_func destroy_func, + uint32_t duration) +{ + struct ivi_layout_transition *transition = NULL; + + transition = create_fade_view_transition( + surface, + start_alpha, dest_alpha, + fade_view_user_frame, + user_data, + destroy_func, + duration); + + if (transition && layout_transition_register(transition)) + return; + layout_transition_destroy(transition); +} + +static void +visibility_on_transition_destroy(struct ivi_layout_transition *transition) +{ + struct fade_view_data *data = transition->private_data; + struct store_alpha *user_data = transition->user_data; + + ivi_layout_surface_set_visibility(data->surface, true); + + free(data); + transition->private_data = NULL; + + free(user_data); + transition->user_data = NULL; +} + +void +ivi_layout_transition_visibility_on(struct ivi_layout_surface *surface, + uint32_t duration) +{ + struct ivi_layout_transition *transition; + bool is_visible = surface->prop.visibility; + wl_fixed_t dest_alpha = surface->prop.opacity; + struct store_alpha *user_data = NULL; + wl_fixed_t start_alpha = 0.0; + struct fade_view_data *data = NULL; + + transition = get_transition_from_type_and_id( + IVI_LAYOUT_TRANSITION_VIEW_FADE, + surface); + if (transition) { + start_alpha = surface->prop.opacity; + user_data = transition->user_data; + data = transition->private_data; + + transition->time_start = 0; + transition->time_duration = duration; + transition->destroy_func = visibility_on_transition_destroy; + + data->start_alpha = wl_fixed_to_double(start_alpha); + data->end_alpha = user_data->alpha; + return; + } + + if (is_visible) + return; + + user_data = malloc(sizeof(*user_data)); + if (user_data == NULL) { + weston_log("%s: memory allocation fails\n", __func__); + return; + } + + user_data->alpha = wl_fixed_to_double(dest_alpha); + + create_visibility_transition(surface, + 0.0, // start_alpha + wl_fixed_to_double(dest_alpha), + user_data, + visibility_on_transition_destroy, + duration); +} + +static void +visibility_off_transition_destroy(struct ivi_layout_transition *transition) +{ + struct fade_view_data *data = transition->private_data; + struct store_alpha *user_data = transition->user_data; + + ivi_layout_surface_set_visibility(data->surface, false); + + ivi_layout_surface_set_opacity(data->surface, + wl_fixed_from_double(user_data->alpha)); + + free(data); + transition->private_data = NULL; + + free(user_data); + transition->user_data= NULL; +} + +void +ivi_layout_transition_visibility_off(struct ivi_layout_surface *surface, + uint32_t duration) +{ + struct ivi_layout_transition *transition; + wl_fixed_t start_alpha = surface->prop.opacity; + struct store_alpha* user_data = NULL; + struct fade_view_data* data = NULL; + + transition = + get_transition_from_type_and_id(IVI_LAYOUT_TRANSITION_VIEW_FADE, + surface); + if (transition) { + data = transition->private_data; + + transition->time_start = 0; + transition->time_duration = duration; + transition->destroy_func = visibility_off_transition_destroy; + + data->start_alpha = wl_fixed_to_double(start_alpha); + data->end_alpha = 0; + return; + } + + user_data = malloc(sizeof(*user_data)); + if (user_data == NULL) { + weston_log("%s: memory allocation fails\n", __func__); + return; + } + + user_data->alpha = wl_fixed_to_double(start_alpha); + + create_visibility_transition(surface, + wl_fixed_to_double(start_alpha), + 0.0, // dest_alpha + user_data, + visibility_off_transition_destroy, + duration); +} + +/* move layer transition */ + +struct move_layer_data { + struct ivi_layout_layer *layer; + int32_t start_x; + int32_t start_y; + int32_t end_x; + int32_t end_y; + ivi_layout_transition_destroy_user_func destroy_func; +}; + +static void +transition_move_layer_user_frame(struct ivi_layout_transition *transition) +{ + struct move_layer_data *data = transition->private_data; + struct ivi_layout_layer *layer = data->layer; + + const float current = time_to_nowpos(transition); + + const int32_t dest_x = data->start_x + + (data->end_x - data->start_x) * current; + + const int32_t dest_y = data->start_y + + (data->end_y - data->start_y) * current; + + ivi_layout_layer_set_destination_rectangle(layer, dest_x, dest_y, + layer->prop.dest_width, layer->prop.dest_height); +} + +static void +transition_move_layer_destroy(struct ivi_layout_transition *transition) +{ + struct move_layer_data *data = transition->private_data; + + if (data->destroy_func) + data->destroy_func(transition->user_data); + + free(data); + transition->private_data = NULL; +} + +static int32_t +is_transition_move_layer_func(struct move_layer_data *data, + struct ivi_layout_layer *layer) +{ + return data->layer == layer; +} + + +static struct ivi_layout_transition * +create_move_layer_transition( + struct ivi_layout_layer *layer, + int32_t start_x, int32_t start_y, + int32_t end_x, int32_t end_y, + void *user_data, + ivi_layout_transition_destroy_user_func destroy_user_func, + uint32_t duration) +{ + struct ivi_layout_transition *transition; + struct move_layer_data *data; + + transition = create_layout_transition(); + if (transition == NULL) + return NULL; + + data = malloc(sizeof(*data)); + if (data == NULL) { + weston_log("%s: memory allocation fails\n", __func__); + free(transition); + return NULL; + } + + transition->type = IVI_LAYOUT_TRANSITION_LAYER_MOVE; + transition->is_transition_func = (ivi_layout_is_transition_func)is_transition_move_layer_func; + + transition->frame_func = transition_move_layer_user_frame; + transition->destroy_func = transition_move_layer_destroy; + transition->private_data = data; + transition->user_data = user_data; + + if (duration != 0) + transition->time_duration = duration; + + data->layer = layer; + data->start_x = start_x; + data->start_y = start_y; + data->end_x = end_x; + data->end_y = end_y; + data->destroy_func = destroy_user_func; + + return transition; +} + +void +ivi_layout_transition_move_layer(struct ivi_layout_layer *layer, + int32_t dest_x, int32_t dest_y, + uint32_t duration) +{ + int32_t start_pos_x = layer->prop.dest_x; + int32_t start_pos_y = layer->prop.dest_y; + struct ivi_layout_transition *transition = NULL; + + transition = create_move_layer_transition( + layer, + start_pos_x, start_pos_y, + dest_x, dest_y, + NULL, NULL, + duration); + + if (transition && layout_transition_register(transition)) + return; + + free(transition); +} + +void +ivi_layout_transition_move_layer_cancel(struct ivi_layout_layer *layer) +{ + struct ivi_layout_transition *transition = + get_transition_from_type_and_id( + IVI_LAYOUT_TRANSITION_LAYER_MOVE, + layer); + if (transition) { + layout_transition_destroy(transition); + } +} + +/* fade layer transition */ +struct fade_layer_data { + struct ivi_layout_layer *layer; + uint32_t is_fade_in; + double start_alpha; + double end_alpha; + ivi_layout_transition_destroy_user_func destroy_func; +}; + +static void +transition_fade_layer_destroy(struct ivi_layout_transition *transition) +{ + struct fade_layer_data *data = transition->private_data; + transition->private_data = NULL; + + free(data); +} + +static void +transition_fade_layer_user_frame(struct ivi_layout_transition *transition) +{ + double current = time_to_nowpos(transition); + struct fade_layer_data *data = transition->private_data; + double alpha = data->start_alpha + + (data->end_alpha - data->start_alpha) * current; + wl_fixed_t fixed_alpha = wl_fixed_from_double(alpha); + + int32_t is_done = transition->is_done; + bool is_visible = !is_done || data->is_fade_in; + + ivi_layout_layer_set_opacity(data->layer, fixed_alpha); + ivi_layout_layer_set_visibility(data->layer, is_visible); +} + +static int32_t +is_transition_fade_layer_func(struct fade_layer_data *data, + struct ivi_layout_layer *layer) +{ + return data->layer == layer; +} + +void +ivi_layout_transition_fade_layer( + struct ivi_layout_layer *layer, + uint32_t is_fade_in, + double start_alpha, double end_alpha, + void* user_data, + ivi_layout_transition_destroy_user_func destroy_func, + uint32_t duration) +{ + struct ivi_layout_transition *transition; + struct fade_layer_data *data; + wl_fixed_t fixed_opacity; + double now_opacity; + double remain; + + transition = get_transition_from_type_and_id( + IVI_LAYOUT_TRANSITION_LAYER_FADE, + layer); + if (transition) { + /* transition update */ + data = transition->private_data; + + /* FIXME */ + fixed_opacity = layer->prop.opacity; + now_opacity = wl_fixed_to_double(fixed_opacity); + + data->is_fade_in = is_fade_in; + data->start_alpha = now_opacity; + data->end_alpha = end_alpha; + + remain = is_fade_in? 1.0 - now_opacity : now_opacity; + transition->time_start = 0; + transition->time_elapsed = 0; + transition->time_duration = duration * remain; + + return; + } + + transition = create_layout_transition(); + if (transition == NULL) + return; + + data = malloc(sizeof(*data)); + if (data == NULL) { + weston_log("%s: memory allocation fails\n", __func__); + free(transition); + return; + } + + transition->type = IVI_LAYOUT_TRANSITION_LAYER_FADE; + transition->is_transition_func = (ivi_layout_is_transition_func)is_transition_fade_layer_func; + + transition->private_data = data; + transition->user_data = user_data; + + transition->frame_func = transition_fade_layer_user_frame; + transition->destroy_func = transition_fade_layer_destroy; + + if (duration != 0) + transition->time_duration = duration; + + data->layer = layer; + data->is_fade_in = is_fade_in; + data->start_alpha = start_alpha; + data->end_alpha = end_alpha; + data->destroy_func = destroy_func; + + if (!layout_transition_register(transition)) + layout_transition_destroy(transition); + + return; +} + diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c new file mode 100644 index 0000000..b2f8323 --- /dev/null +++ b/ivi-shell/ivi-layout.c @@ -0,0 +1,2127 @@ +/* + * Copyright (C) 2013 DENSO CORPORATION + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * Implementation of ivi-layout library. The actual view on ivi_screen is + * not updated until ivi_layout_commit_changes is called. An overview from + * calling API for updating properties of ivi_surface/ivi_layer to asking + * compositor to compose them by using weston_view_schedule_repaint, + * 0/ initialize this library by ivi_layout_init_with_compositor + * with (struct weston_compositor *ec) from ivi-shell. + * 1/ When an API for updating properties of ivi_surface/ivi_layer, it updates + * pending prop of ivi_surface/ivi_layer/ivi_screen which are structure to + * store properties. + * 2/ Before calling commitChanges, in case of calling an API to get a property, + * return current property, not pending property. + * 3/ At the timing of calling ivi_layout_commitChanges, pending properties + * are applied to properties. + * + * *) ivi_layout_commitChanges is also called by transition animation + * per each frame. See ivi-layout-transition.c in details. Transition + * animation interpolates frames between previous properties of ivi_surface + * and new ones. + * For example, when a property of ivi_surface is changed from invisible + * to visible, it behaves like fade-in. When ivi_layout_commitChange is + * called during transition animation, it cancels the transition and + * re-start transition to new properties from current properties of final + * frame just before the cancellation. + * + * 4/ According properties, set transformation by using weston_matrix and + * weston_view per ivi_surfaces and ivi_layers in while loop. + * 5/ Set damage and trigger transform by using weston_view_geometry_dirty. + * 6/ Schedule repaint for each view by using weston_view_schedule_repaint. + * 7/ Notify update of properties. + * + */ +#include "config.h" + +#include +#include +#include + +#include "compositor/weston.h" +#include +#include "ivi-shell.h" +#include "ivi-layout-export.h" +#include "ivi-layout-private.h" +#include "ivi-layout-shell.h" + +#include "shared/helpers.h" +#include "shared/os-compatibility.h" + +#define max(a, b) ((a) > (b) ? (a) : (b)) + +struct ivi_layout; + +struct ivi_layout_screen { + struct wl_list link; /* ivi_layout::screen_list */ + + struct ivi_layout *layout; + struct weston_output *output; + + struct { + struct wl_list layer_list; /* ivi_layout_layer::pending.link */ + } pending; + + struct { + int dirty; + struct wl_list layer_list; /* ivi_layout_layer::order.link */ + } order; +}; + +struct ivi_rectangle +{ + int32_t x; + int32_t y; + int32_t width; + int32_t height; +}; + +static struct ivi_layout ivilayout = {0}; + +struct ivi_layout * +get_instance(void) +{ + return &ivilayout; +} + +/** + * Internal API to add/remove an ivi_layer to/from ivi_screen. + */ +static struct ivi_layout_surface * +get_surface(struct wl_list *surf_list, uint32_t id_surface) +{ + struct ivi_layout_surface *ivisurf; + + wl_list_for_each(ivisurf, surf_list, link) { + if (ivisurf->id_surface == id_surface) { + return ivisurf; + } + } + + return NULL; +} + +static struct ivi_layout_layer * +get_layer(struct wl_list *layer_list, uint32_t id_layer) +{ + struct ivi_layout_layer *ivilayer; + + wl_list_for_each(ivilayer, layer_list, link) { + if (ivilayer->id_layer == id_layer) { + return ivilayer; + } + } + + return NULL; +} + +static bool +ivi_view_is_rendered(struct ivi_layout_view *view) +{ + return !wl_list_empty(&view->order_link); +} + +static void +ivi_view_destroy(struct ivi_layout_view *ivi_view) +{ + wl_list_remove(&ivi_view->transform.link); + wl_list_remove(&ivi_view->link); + wl_list_remove(&ivi_view->surf_link); + wl_list_remove(&ivi_view->pending_link); + wl_list_remove(&ivi_view->order_link); + + if (weston_surface_is_desktop_surface(ivi_view->ivisurf->surface)) + weston_desktop_surface_unlink_view(ivi_view->view); + weston_view_destroy(ivi_view->view); + + free(ivi_view); +} + +static struct ivi_layout_view* +ivi_view_create(struct ivi_layout_layer *ivilayer, + struct ivi_layout_surface *ivisurf) +{ + struct ivi_layout_view *ivi_view; + + ivi_view = calloc(1, sizeof *ivi_view); + if (ivi_view == NULL) { + weston_log("fails to allocate memory\n"); + return NULL; + } + + if (weston_surface_is_desktop_surface(ivisurf->surface)) { + ivi_view->view = weston_desktop_surface_create_view( + ivisurf->weston_desktop_surface); + } else { + ivi_view->view = weston_view_create(ivisurf->surface); + } + + if (ivi_view->view == NULL) { + weston_log("fails to allocate memory\n"); + free(ivi_view); + return NULL; + } + + weston_matrix_init(&ivi_view->transform.matrix); + wl_list_init(&ivi_view->transform.link); + + ivi_view->ivisurf = ivisurf; + ivi_view->on_layer = ivilayer; + wl_list_insert(&ivilayer->layout->view_list, + &ivi_view->link); + wl_list_insert(&ivisurf->view_list, + &ivi_view->surf_link); + + wl_list_init(&ivi_view->pending_link); + wl_list_init(&ivi_view->order_link); + + return ivi_view; +} + +static struct ivi_layout_view * +get_ivi_view(struct ivi_layout_layer *ivilayer, + struct ivi_layout_surface *ivisurf) +{ + struct ivi_layout_view *ivi_view; + + assert(ivisurf->surface != NULL); + + wl_list_for_each(ivi_view, &ivisurf->view_list, surf_link) { + if (ivi_view->on_layer == ivilayer) + return ivi_view; + } + + return NULL; +} + +static struct ivi_layout_screen * +get_screen_from_output(struct weston_output *output) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_screen *iviscrn = NULL; + + wl_list_for_each(iviscrn, &layout->screen_list, link) { + if (iviscrn->output == output) + return iviscrn; + } + + return NULL; +} + +/** + * Called at destruction of wl_surface/ivi_surface + */ +void +ivi_layout_surface_destroy(struct ivi_layout_surface *ivisurf) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_view *ivi_view ,*next; + + if (ivisurf == NULL) { + weston_log("%s: invalid argument\n", __func__); + return; + } + + wl_list_remove(&ivisurf->link); + + wl_list_for_each_safe(ivi_view, next, &ivisurf->view_list, surf_link) { + ivi_view_destroy(ivi_view); + } + + wl_signal_emit(&layout->surface_notification.removed, ivisurf); + + ivi_layout_remove_all_surface_transitions(ivisurf); + + free(ivisurf); +} + +/** + * Internal API to initialize ivi_screens found from output_list of weston_compositor. + * Called by ivi_layout_init_with_compositor. + */ +static void +create_screen(struct weston_compositor *ec) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_screen *iviscrn = NULL; + struct weston_output *output = NULL; + + wl_list_for_each(output, &ec->output_list, link) { + iviscrn = calloc(1, sizeof *iviscrn); + if (iviscrn == NULL) { + weston_log("fails to allocate memory\n"); + continue; + } + + iviscrn->layout = layout; + + iviscrn->output = output; + + wl_list_init(&iviscrn->pending.layer_list); + + wl_list_init(&iviscrn->order.layer_list); + + wl_list_insert(&layout->screen_list, &iviscrn->link); + } +} + +/** + * Internal APIs to initialize properties of ivi_surface/ivi_layer when they are created. + */ +static void +init_layer_properties(struct ivi_layout_layer_properties *prop, + int32_t width, int32_t height) +{ + memset(prop, 0, sizeof *prop); + prop->opacity = wl_fixed_from_double(1.0); + prop->source_width = width; + prop->source_height = height; + prop->dest_width = width; + prop->dest_height = height; +} + +static void +init_surface_properties(struct ivi_layout_surface_properties *prop) +{ + memset(prop, 0, sizeof *prop); + prop->opacity = wl_fixed_from_double(1.0); + /* + * FIXME: this shall be fixed by ivi-layout-transition. + */ + prop->dest_width = 1; + prop->dest_height = 1; +} + +/** + * Internal APIs to be called from ivi_layout_commit_changes. + */ +static void +update_opacity(struct ivi_layout_layer *ivilayer, + struct ivi_layout_surface *ivisurf, + struct weston_view *view) +{ + double layer_alpha = wl_fixed_to_double(ivilayer->prop.opacity); + double surf_alpha = wl_fixed_to_double(ivisurf->prop.opacity); + + view->alpha = layer_alpha * surf_alpha; +} + +static void +calc_transformation_matrix(struct ivi_rectangle *source_rect, + struct ivi_rectangle *dest_rect, + struct weston_matrix *m) +{ + float source_center_x; + float source_center_y; + float scale_x; + float scale_y; + float translate_x; + float translate_y; + + source_center_x = source_rect->x + source_rect->width * 0.5f; + source_center_y = source_rect->y + source_rect->height * 0.5f; + weston_matrix_translate(m, -source_center_x, -source_center_y, 0.0f); + + if ((dest_rect->width != source_rect->width) || + (dest_rect->height != source_rect->height)) + { + scale_x = (float) dest_rect->width / (float) source_rect->width; + scale_y = (float) dest_rect->height / (float) source_rect->height; + weston_matrix_scale(m, scale_x, scale_y, 1.0f); + } + + translate_x = dest_rect->width * 0.5f + dest_rect->x; + translate_y = dest_rect->height * 0.5f + dest_rect->y; + weston_matrix_translate(m, translate_x, translate_y, 0.0f); +} + +/* + * This computes intersected rect_output from two ivi_rectangles + */ +static void +ivi_rectangle_intersect(const struct ivi_rectangle *rect1, + const struct ivi_rectangle *rect2, + struct ivi_rectangle *rect_output) +{ + int32_t rect1_right = rect1->x + rect1->width; + int32_t rect1_bottom = rect1->y + rect1->height; + int32_t rect2_right = rect2->x + rect2->width; + int32_t rect2_bottom = rect2->y + rect2->height; + + rect_output->x = max(rect1->x, rect2->x); + rect_output->y = max(rect1->y, rect2->y); + rect_output->width = rect1_right < rect2_right ? + rect1_right - rect_output->x : + rect2_right - rect_output->x; + rect_output->height = rect1_bottom < rect2_bottom ? + rect1_bottom - rect_output->y : + rect2_bottom - rect_output->y; + + if (rect_output->width < 0 || rect_output->height < 0) { + rect_output->width = 0; + rect_output->height = 0; + } +} + +/* + * Transform rect_input by the inverse of matrix, intersect with boundingbox, + * and store the result in rect_output. + * The boundingbox must be given in the same coordinate space as rect_output. + * Additionally, there are the following restrictions on the matrix: + * - no projective transformations + * - no skew + * - only multiples of 90-degree rotations supported + * + * In failure case of weston_matrix_invert, rect_output is set to boundingbox + * as a fail-safe with log. + */ +static void +calc_inverse_matrix_transform(const struct weston_matrix *matrix, + const struct ivi_rectangle *rect_input, + const struct ivi_rectangle *boundingbox, + struct ivi_rectangle *rect_output) +{ + struct weston_matrix m; + struct weston_vector top_left; + struct weston_vector bottom_right; + + assert(boundingbox != rect_output); + + if (weston_matrix_invert(&m, matrix) < 0) { + weston_log("ivi-shell: calc_inverse_matrix_transform fails to invert a matrix.\n"); + weston_log("ivi-shell: boundingbox is set to the rect_output.\n"); + rect_output->x = boundingbox->x; + rect_output->y = boundingbox->y; + rect_output->width = boundingbox->width; + rect_output->height = boundingbox->height; + } + + /* The vectors and matrices involved will always produce f[3] == 1.0. */ + top_left.f[0] = rect_input->x; + top_left.f[1] = rect_input->y; + top_left.f[2] = 0.0f; + top_left.f[3] = 1.0f; + + bottom_right.f[0] = rect_input->x + rect_input->width; + bottom_right.f[1] = rect_input->y + rect_input->height; + bottom_right.f[2] = 0.0f; + bottom_right.f[3] = 1.0f; + + weston_matrix_transform(&m, &top_left); + weston_matrix_transform(&m, &bottom_right); + + if (top_left.f[0] < bottom_right.f[0]) { + rect_output->x = top_left.f[0]; + rect_output->width = bottom_right.f[0] - rect_output->x; + } else { + rect_output->x = bottom_right.f[0]; + rect_output->width = top_left.f[0] - rect_output->x; + } + + if (top_left.f[1] < bottom_right.f[1]) { + rect_output->y = top_left.f[1]; + rect_output->height = bottom_right.f[1] - rect_output->y; + } else { + rect_output->y = bottom_right.f[1]; + rect_output->height = top_left.f[1] - rect_output->y; + } + + ivi_rectangle_intersect(rect_output, boundingbox, rect_output); +} + +/** + * This computes the whole transformation matrix:m from surface-local + * coordinates to multi-screen coordinates, which are global coordinates. + * It is assumed that weston_view::geometry.{x,y} are zero. + * + * Additionally, this computes the mask on surface-local coordinates as an + * ivi_rectangle. This can be set to weston_view_set_mask. + * + * The mask is computed by following steps + * - destination rectangle of layer is transformed to multi-screen coordinates, + * global coordinates. This is done by adding weston_output.{x,y} in simple + * because there is no scaled and rotated transformation. + * - destination rectangle of layer in multi-screen coordinates needs to be + * intersected inside of a screen the layer is assigned to. This is because + * overlapped region of weston surface in another screen shall not be + * displayed according to ivi use case. + * - destination rectangle of layer + * - in multi-screen coordinates, + * - and intersected inside of an assigned screen, + * is inversed to surface-local coordinates by inversed matrix:m. + * - the area is intersected by intersected area between weston_surface and + * source rectangle of ivi_surface. + */ +static void +calc_surface_to_global_matrix_and_mask_to_weston_surface( + struct ivi_layout_screen *iviscrn, + struct ivi_layout_layer *ivilayer, + struct ivi_layout_surface *ivisurf, + struct weston_matrix *m, + struct ivi_rectangle *result) +{ + const struct ivi_layout_surface_properties *sp = &ivisurf->prop; + const struct ivi_layout_layer_properties *lp = &ivilayer->prop; + struct weston_output *output = iviscrn->output; + struct ivi_rectangle weston_surface_rect = { 0, + 0, + ivisurf->surface->width, + ivisurf->surface->height }; + struct ivi_rectangle surface_source_rect = { sp->source_x, + sp->source_y, + sp->source_width, + sp->source_height }; + struct ivi_rectangle surface_dest_rect = { sp->dest_x, + sp->dest_y, + sp->dest_width, + sp->dest_height }; + struct ivi_rectangle layer_source_rect = { lp->source_x, + lp->source_y, + lp->source_width, + lp->source_height }; + struct ivi_rectangle layer_dest_rect = { lp->dest_x, + lp->dest_y, + lp->dest_width, + lp->dest_height }; + struct ivi_rectangle screen_dest_rect = { output->x, + output->y, + output->width, + output->height }; + struct ivi_rectangle layer_dest_rect_in_global = + { lp->dest_x + output->x, + lp->dest_y + output->y, + lp->dest_width, + lp->dest_height }; + struct ivi_rectangle surface_result; + struct ivi_rectangle layer_dest_rect_in_global_intersected; + + /* + * the whole transformation matrix:m from surface-local + * coordinates to global coordinates, which is computed by + * two steps, + * - surface-local coordinates to layer-local coordinates + * - layer-local coordinates to single screen-local coordinates + * - single screen-local coordinates to multi-screen coordinates, + * which are global coordinates. + */ + calc_transformation_matrix(&surface_source_rect, &surface_dest_rect, m); + calc_transformation_matrix(&layer_source_rect, &layer_dest_rect, m); + + weston_matrix_translate(m, output->x, output->y, 0.0f); + + /* this intersected ivi_rectangle would be used for masking + * weston_surface + */ + ivi_rectangle_intersect(&surface_source_rect, &weston_surface_rect, + &surface_result); + + /* + * destination rectangle of layer in multi screens coordinate + * is intersected to avoid displaying outside of an assigned screen. + */ + ivi_rectangle_intersect(&layer_dest_rect_in_global, &screen_dest_rect, + &layer_dest_rect_in_global_intersected); + + /* calc masking area of weston_surface from m */ + calc_inverse_matrix_transform(m, + &layer_dest_rect_in_global_intersected, + &surface_result, + result); +} + +static void +update_prop(struct ivi_layout_view *ivi_view) +{ + struct ivi_layout_surface *ivisurf = ivi_view->ivisurf; + struct ivi_layout_layer *ivilayer = ivi_view->on_layer; + struct ivi_layout_screen *iviscrn = ivilayer->on_screen; + struct ivi_rectangle r; + bool can_calc = true; + + /*In case of no prop change, this just returns*/ + if (!ivilayer->prop.event_mask && !ivisurf->prop.event_mask) + return; + + update_opacity(ivilayer, ivisurf, ivi_view->view); + + if (ivisurf->prop.source_width == 0 || ivisurf->prop.source_height == 0) { + weston_log("ivi-shell: source rectangle is not yet set by ivi_layout_surface_set_source_rectangle\n"); + can_calc = false; + } + + if (ivisurf->prop.dest_width == 0 || ivisurf->prop.dest_height == 0) { + weston_log("ivi-shell: destination rectangle is not yet set by ivi_layout_surface_set_destination_rectangle\n"); + can_calc = false; + } + + if (can_calc) { + wl_list_remove(&ivi_view->transform.link); + weston_matrix_init(&ivi_view->transform.matrix); + + calc_surface_to_global_matrix_and_mask_to_weston_surface( + iviscrn, ivilayer, ivisurf, &ivi_view->transform.matrix, &r); + + weston_view_set_mask(ivi_view->view, r.x, r.y, r.width, r.height); + wl_list_insert(&ivi_view->view->geometry.transformation_list, + &ivi_view->transform.link); + + weston_view_set_transform_parent(ivi_view->view, NULL); + weston_view_geometry_dirty(ivi_view->view); + weston_view_update_transform(ivi_view->view); + } + + ivisurf->update_count++; + + weston_view_schedule_repaint(ivi_view->view); +} + +static bool +ivi_view_is_mapped(struct ivi_layout_view *ivi_view) +{ + return (!wl_list_empty(&ivi_view->order_link) && + ivi_view->on_layer->on_screen && + ivi_view->on_layer->prop.visibility && + ivi_view->ivisurf->prop.visibility); +} + +static void +commit_changes(struct ivi_layout *layout) +{ + struct ivi_layout_view *ivi_view = NULL; + + wl_list_for_each(ivi_view, &layout->view_list, link) { + /* + * If the view is not on the currently rendered scenegraph, + * we do not need to update its properties. + */ + if (!ivi_view_is_mapped(ivi_view)) + continue; + + update_prop(ivi_view); + } +} + +static void +commit_surface_list(struct ivi_layout *layout) +{ + struct ivi_layout_surface *ivisurf = NULL; + int32_t dest_x = 0; + int32_t dest_y = 0; + int32_t dest_width = 0; + int32_t dest_height = 0; + int32_t configured = 0; + + wl_list_for_each(ivisurf, &layout->surface_list, link) { + if (ivisurf->pending.prop.transition_type == IVI_LAYOUT_TRANSITION_VIEW_DEFAULT) { + dest_x = ivisurf->prop.dest_x; + dest_y = ivisurf->prop.dest_y; + dest_width = ivisurf->prop.dest_width; + dest_height = ivisurf->prop.dest_height; + + ivi_layout_transition_move_resize_view(ivisurf, + ivisurf->pending.prop.dest_x, + ivisurf->pending.prop.dest_y, + ivisurf->pending.prop.dest_width, + ivisurf->pending.prop.dest_height, + ivisurf->pending.prop.transition_duration); + + if (ivisurf->pending.prop.visibility) { + ivi_layout_transition_visibility_on(ivisurf, ivisurf->pending.prop.transition_duration); + } else { + ivi_layout_transition_visibility_off(ivisurf, ivisurf->pending.prop.transition_duration); + } + + ivisurf->prop = ivisurf->pending.prop; + ivisurf->prop.dest_x = dest_x; + ivisurf->prop.dest_y = dest_y; + ivisurf->prop.dest_width = dest_width; + ivisurf->prop.dest_height = dest_height; + ivisurf->prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; + ivisurf->pending.prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; + + } else if (ivisurf->pending.prop.transition_type == IVI_LAYOUT_TRANSITION_VIEW_DEST_RECT_ONLY) { + dest_x = ivisurf->prop.dest_x; + dest_y = ivisurf->prop.dest_y; + dest_width = ivisurf->prop.dest_width; + dest_height = ivisurf->prop.dest_height; + + ivi_layout_transition_move_resize_view(ivisurf, + ivisurf->pending.prop.dest_x, + ivisurf->pending.prop.dest_y, + ivisurf->pending.prop.dest_width, + ivisurf->pending.prop.dest_height, + ivisurf->pending.prop.transition_duration); + + ivisurf->prop = ivisurf->pending.prop; + ivisurf->prop.dest_x = dest_x; + ivisurf->prop.dest_y = dest_y; + ivisurf->prop.dest_width = dest_width; + ivisurf->prop.dest_height = dest_height; + + ivisurf->prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; + ivisurf->pending.prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; + + } else if (ivisurf->pending.prop.transition_type == IVI_LAYOUT_TRANSITION_VIEW_FADE_ONLY) { + configured = 0; + if (ivisurf->pending.prop.visibility) { + ivi_layout_transition_visibility_on(ivisurf, ivisurf->pending.prop.transition_duration); + } else { + ivi_layout_transition_visibility_off(ivisurf, ivisurf->pending.prop.transition_duration); + } + + if (ivisurf->prop.dest_width != ivisurf->pending.prop.dest_width || + ivisurf->prop.dest_height != ivisurf->pending.prop.dest_height) { + configured = 1; + } + + ivisurf->prop = ivisurf->pending.prop; + ivisurf->prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; + ivisurf->pending.prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; + + if (configured && !is_surface_transition(ivisurf)) { + ivi_layout_surface_set_size(ivisurf, + ivisurf->prop.dest_width, + ivisurf->prop.dest_height); + } + } else { + configured = 0; + if (ivisurf->prop.dest_width != ivisurf->pending.prop.dest_width || + ivisurf->prop.dest_height != ivisurf->pending.prop.dest_height) { + configured = 1; + } + + ivisurf->prop = ivisurf->pending.prop; + ivisurf->prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; + ivisurf->pending.prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; + + if (configured && !is_surface_transition(ivisurf)) { + ivi_layout_surface_set_size(ivisurf, + ivisurf->prop.dest_width, + ivisurf->prop.dest_height); + } + } + } +} + +static void +commit_layer_list(struct ivi_layout *layout) +{ + struct ivi_layout_view *ivi_view = NULL; + struct ivi_layout_layer *ivilayer = NULL; + struct ivi_layout_view *next = NULL; + + wl_list_for_each(ivilayer, &layout->layer_list, link) { + if (ivilayer->pending.prop.transition_type == IVI_LAYOUT_TRANSITION_LAYER_MOVE) { + ivi_layout_transition_move_layer(ivilayer, ivilayer->pending.prop.dest_x, ivilayer->pending.prop.dest_y, ivilayer->pending.prop.transition_duration); + } else if (ivilayer->pending.prop.transition_type == IVI_LAYOUT_TRANSITION_LAYER_FADE) { + ivi_layout_transition_fade_layer(ivilayer,ivilayer->pending.prop.is_fade_in, + ivilayer->pending.prop.start_alpha,ivilayer->pending.prop.end_alpha, + NULL, NULL, + ivilayer->pending.prop.transition_duration); + } + ivilayer->pending.prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; + + ivilayer->prop = ivilayer->pending.prop; + + if (!ivilayer->order.dirty) { + continue; + } + + wl_list_for_each_safe(ivi_view, next, &ivilayer->order.view_list, + order_link) { + wl_list_remove(&ivi_view->order_link); + wl_list_init(&ivi_view->order_link); + ivi_view->ivisurf->prop.event_mask |= IVI_NOTIFICATION_REMOVE; + } + + assert(wl_list_empty(&ivilayer->order.view_list)); + + wl_list_for_each(ivi_view, &ivilayer->pending.view_list, + pending_link) { + wl_list_remove(&ivi_view->order_link); + wl_list_insert(&ivilayer->order.view_list, &ivi_view->order_link); + ivi_view->ivisurf->prop.event_mask |= IVI_NOTIFICATION_ADD; + } + + ivilayer->order.dirty = 0; + } +} + +static void +commit_screen_list(struct ivi_layout *layout) +{ + struct ivi_layout_screen *iviscrn = NULL; + struct ivi_layout_layer *ivilayer = NULL; + struct ivi_layout_layer *next = NULL; + + wl_list_for_each(iviscrn, &layout->screen_list, link) { + if (iviscrn->order.dirty) { + wl_list_for_each_safe(ivilayer, next, + &iviscrn->order.layer_list, order.link) { + ivilayer->on_screen = NULL; + wl_list_remove(&ivilayer->order.link); + wl_list_init(&ivilayer->order.link); + ivilayer->prop.event_mask |= IVI_NOTIFICATION_REMOVE; + } + + assert(wl_list_empty(&iviscrn->order.layer_list)); + + wl_list_for_each(ivilayer, &iviscrn->pending.layer_list, + pending.link) { + /* FIXME: avoid to insert order.link to multiple screens */ + wl_list_remove(&ivilayer->order.link); + + wl_list_insert(&iviscrn->order.layer_list, + &ivilayer->order.link); + ivilayer->on_screen = iviscrn; + ivilayer->prop.event_mask |= IVI_NOTIFICATION_ADD; + } + + iviscrn->order.dirty = 0; + } + } +} + +static void +build_view_list(struct ivi_layout *layout) +{ + struct ivi_layout_screen *iviscrn; + struct ivi_layout_layer *ivilayer; + struct ivi_layout_view *ivi_view; + + /* If ivi_view is not part of the scenegrapgh, we have to unmap + * weston_views + */ + wl_list_for_each(ivi_view, &layout->view_list, link) { + if (!ivi_view_is_mapped(ivi_view)) + weston_view_unmap(ivi_view->view); + } + + /* Clear view list of layout ivi_layer */ + wl_list_init(&layout->layout_layer.view_list.link); + + wl_list_for_each(iviscrn, &layout->screen_list, link) { + wl_list_for_each(ivilayer, &iviscrn->order.layer_list, order.link) { + if (ivilayer->prop.visibility == false) + continue; + + wl_list_for_each(ivi_view, &ivilayer->order.view_list, order_link) { + if (ivi_view->ivisurf->prop.visibility == false) + continue; + + weston_layer_entry_insert(&layout->layout_layer.view_list, + &ivi_view->view->layer_link); + + ivi_view->ivisurf->surface->is_mapped = true; + ivi_view->view->is_mapped = true; + } + } + } +} + +static void +commit_transition(struct ivi_layout* layout) +{ + if (wl_list_empty(&layout->pending_transition_list)) { + return; + } + + wl_list_insert_list(&layout->transitions->transition_list, + &layout->pending_transition_list); + + wl_list_init(&layout->pending_transition_list); + + wl_event_source_timer_update(layout->transitions->event_source, 1); +} + +static void +send_surface_prop(struct ivi_layout_surface *ivisurf) +{ + wl_signal_emit(&ivisurf->property_changed, ivisurf); + ivisurf->pending.prop.event_mask = 0; +} + +static void +send_layer_prop(struct ivi_layout_layer *ivilayer) +{ + wl_signal_emit(&ivilayer->property_changed, ivilayer); + ivilayer->pending.prop.event_mask = 0; +} + +static void +send_prop(struct ivi_layout *layout) +{ + struct ivi_layout_layer *ivilayer = NULL; + struct ivi_layout_surface *ivisurf = NULL; + + wl_list_for_each_reverse(ivilayer, &layout->layer_list, link) { + if (ivilayer->prop.event_mask) + send_layer_prop(ivilayer); + } + + wl_list_for_each_reverse(ivisurf, &layout->surface_list, link) { + if (ivisurf->prop.event_mask) + send_surface_prop(ivisurf); + } +} + +static void +clear_view_pending_list(struct ivi_layout_layer *ivilayer) +{ + struct ivi_layout_view *view_link = NULL; + struct ivi_layout_view *view_next = NULL; + + wl_list_for_each_safe(view_link, view_next, + &ivilayer->pending.view_list, pending_link) { + wl_list_remove(&view_link->pending_link); + wl_list_init(&view_link->pending_link); + } +} + +/** + * Exported APIs of ivi-layout library are implemented from here. + * Brief of APIs is described in ivi-layout-export.h. + */ +static int32_t +ivi_layout_add_listener_create_layer(struct wl_listener *listener) +{ + struct ivi_layout *layout = get_instance(); + + if (listener == NULL) { + weston_log("ivi_layout_add_listener_create_layer: invalid argument\n"); + return IVI_FAILED; + } + + wl_signal_add(&layout->layer_notification.created, listener); + + return IVI_SUCCEEDED; +} + +static int32_t +ivi_layout_add_listener_remove_layer(struct wl_listener *listener) +{ + struct ivi_layout *layout = get_instance(); + + if (listener == NULL) { + weston_log("ivi_layout_add_listener_remove_layer: invalid argument\n"); + return IVI_FAILED; + } + + wl_signal_add(&layout->layer_notification.removed, listener); + + return IVI_SUCCEEDED; +} + +static int32_t +ivi_layout_add_listener_create_surface(struct wl_listener *listener) +{ + struct ivi_layout *layout = get_instance(); + + if (listener == NULL) { + weston_log("ivi_layout_add_listener_create_surface: invalid argument\n"); + return IVI_FAILED; + } + + wl_signal_add(&layout->surface_notification.created, listener); + + return IVI_SUCCEEDED; +} + +static int32_t +ivi_layout_add_listener_remove_surface(struct wl_listener *listener) +{ + struct ivi_layout *layout = get_instance(); + + if (listener == NULL) { + weston_log("ivi_layout_add_listener_remove_surface: invalid argument\n"); + return IVI_FAILED; + } + + wl_signal_add(&layout->surface_notification.removed, listener); + + return IVI_SUCCEEDED; +} + +static int32_t +ivi_layout_add_listener_configure_surface(struct wl_listener *listener) +{ + struct ivi_layout *layout = get_instance(); + + if (listener == NULL) { + weston_log("ivi_layout_add_listener_configure_surface: invalid argument\n"); + return IVI_FAILED; + } + + wl_signal_add(&layout->surface_notification.configure_changed, listener); + + return IVI_SUCCEEDED; +} + +static int32_t +ivi_layout_add_listener_configure_desktop_surface(struct wl_listener *listener) +{ + struct ivi_layout *layout = get_instance(); + + if (!listener) { + weston_log("ivi_layout_add_listener_configure_desktop_surface: invalid argument\n"); + return IVI_FAILED; + } + + wl_signal_add(&layout->surface_notification.configure_desktop_changed, listener); + + return IVI_SUCCEEDED; +} + +uint32_t +ivi_layout_get_id_of_surface(struct ivi_layout_surface *ivisurf) +{ + return ivisurf->id_surface; +} + +static uint32_t +ivi_layout_get_id_of_layer(struct ivi_layout_layer *ivilayer) +{ + return ivilayer->id_layer; +} + +static struct ivi_layout_layer * +ivi_layout_get_layer_from_id(uint32_t id_layer) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_layer *ivilayer = NULL; + + wl_list_for_each(ivilayer, &layout->layer_list, link) { + if (ivilayer->id_layer == id_layer) { + return ivilayer; + } + } + + return NULL; +} + +struct ivi_layout_surface * +ivi_layout_get_surface_from_id(uint32_t id_surface) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_surface *ivisurf = NULL; + + wl_list_for_each(ivisurf, &layout->surface_list, link) { + if (ivisurf->id_surface == id_surface) { + return ivisurf; + } + } + + return NULL; +} + +static int32_t +ivi_layout_surface_add_listener(struct ivi_layout_surface *ivisurf, + struct wl_listener *listener) +{ + if (ivisurf == NULL || listener == NULL) { + weston_log("ivi_layout_surface_add_listener: invalid argument\n"); + return IVI_FAILED; + } + + wl_signal_add(&ivisurf->property_changed, listener); + + return IVI_SUCCEEDED; +} + +static const struct ivi_layout_layer_properties * +ivi_layout_get_properties_of_layer(struct ivi_layout_layer *ivilayer) +{ + if (ivilayer == NULL) { + weston_log("ivi_layout_get_properties_of_layer: invalid argument\n"); + return NULL; + } + + return &ivilayer->prop; +} + +static int32_t +ivi_layout_get_screens_under_layer(struct ivi_layout_layer *ivilayer, + int32_t *pLength, + struct weston_output ***ppArray) +{ + int32_t length = 0; + int32_t n = 0; + + if (ivilayer == NULL || pLength == NULL || ppArray == NULL) { + weston_log("ivi_layout_get_screens_under_layer: invalid argument\n"); + return IVI_FAILED; + } + + if (ivilayer->on_screen != NULL) + length = 1; + + if (length != 0) { + /* the Array must be free by module which called this function */ + *ppArray = calloc(length, sizeof(struct weston_output *)); + if (*ppArray == NULL) { + weston_log("fails to allocate memory\n"); + return IVI_FAILED; + } + + (*ppArray)[n++] = ivilayer->on_screen->output; + } + + *pLength = length; + + return IVI_SUCCEEDED; +} + +static int32_t +ivi_layout_get_layers(int32_t *pLength, struct ivi_layout_layer ***ppArray) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_layer *ivilayer = NULL; + int32_t length = 0; + int32_t n = 0; + + if (pLength == NULL || ppArray == NULL) { + weston_log("ivi_layout_get_layers: invalid argument\n"); + return IVI_FAILED; + } + + length = wl_list_length(&layout->layer_list); + + if (length != 0) { + /* the Array must be freed by module which called this function */ + *ppArray = calloc(length, sizeof(struct ivi_layout_layer *)); + if (*ppArray == NULL) { + weston_log("fails to allocate memory\n"); + return IVI_FAILED; + } + + wl_list_for_each(ivilayer, &layout->layer_list, link) { + (*ppArray)[n++] = ivilayer; + } + } + + *pLength = length; + + return IVI_SUCCEEDED; +} + +static int32_t +ivi_layout_get_layers_on_screen(struct weston_output *output, + int32_t *pLength, + struct ivi_layout_layer ***ppArray) +{ + struct ivi_layout_screen *iviscrn = NULL; + struct ivi_layout_layer *ivilayer = NULL; + int32_t length = 0; + int32_t n = 0; + + if (output == NULL || pLength == NULL || ppArray == NULL) { + weston_log("ivi_layout_get_layers_on_screen: invalid argument\n"); + return IVI_FAILED; + } + + iviscrn = get_screen_from_output(output); + length = wl_list_length(&iviscrn->order.layer_list); + + if (length != 0) { + /* the Array must be freed by module which called this function */ + *ppArray = calloc(length, sizeof(struct ivi_layout_layer *)); + if (*ppArray == NULL) { + weston_log("fails to allocate memory\n"); + return IVI_FAILED; + } + + wl_list_for_each(ivilayer, &iviscrn->order.layer_list, order.link) { + (*ppArray)[n++] = ivilayer; + } + } + + *pLength = length; + + return IVI_SUCCEEDED; +} + +static int32_t +ivi_layout_get_layers_under_surface(struct ivi_layout_surface *ivisurf, + int32_t *pLength, + struct ivi_layout_layer ***ppArray) +{ + struct ivi_layout_view *ivi_view; + int32_t length = 0; + int32_t n = 0; + + if (ivisurf == NULL || pLength == NULL || ppArray == NULL) { + weston_log("ivi_layout_getLayers: invalid argument\n"); + return IVI_FAILED; + } + + if (!wl_list_empty(&ivisurf->view_list)) { + /* the Array must be free by module which called this function */ + length = wl_list_length(&ivisurf->view_list); + *ppArray = calloc(length, sizeof(struct ivi_layout_layer *)); + if (*ppArray == NULL) { + weston_log("fails to allocate memory\n"); + return IVI_FAILED; + } + + wl_list_for_each_reverse(ivi_view, &ivisurf->view_list, surf_link) { + if (ivi_view_is_rendered(ivi_view)) + (*ppArray)[n++] = ivi_view->on_layer; + else + length--; + } + } + + *pLength = length; + + if (!length) { + free(*ppArray); + *ppArray = NULL; + } + + return IVI_SUCCEEDED; +} + +static +int32_t +ivi_layout_get_surfaces(int32_t *pLength, struct ivi_layout_surface ***ppArray) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_surface *ivisurf = NULL; + int32_t length = 0; + int32_t n = 0; + + if (pLength == NULL || ppArray == NULL) { + weston_log("ivi_layout_get_surfaces: invalid argument\n"); + return IVI_FAILED; + } + + length = wl_list_length(&layout->surface_list); + + if (length != 0) { + /* the Array must be freed by module which called this function */ + *ppArray = calloc(length, sizeof(struct ivi_layout_surface *)); + if (*ppArray == NULL) { + weston_log("fails to allocate memory\n"); + return IVI_FAILED; + } + + wl_list_for_each(ivisurf, &layout->surface_list, link) { + (*ppArray)[n++] = ivisurf; + } + } + + *pLength = length; + + return IVI_SUCCEEDED; +} + +static int32_t +ivi_layout_get_surfaces_on_layer(struct ivi_layout_layer *ivilayer, + int32_t *pLength, + struct ivi_layout_surface ***ppArray) +{ + struct ivi_layout_view *ivi_view = NULL; + int32_t length = 0; + int32_t n = 0; + + if (ivilayer == NULL || pLength == NULL || ppArray == NULL) { + weston_log("ivi_layout_getSurfaceIDsOnLayer: invalid argument\n"); + return IVI_FAILED; + } + + length = wl_list_length(&ivilayer->order.view_list); + + if (length != 0) { + /* the Array must be freed by module which called this function */ + *ppArray = calloc(length, sizeof(struct ivi_layout_surface *)); + if (*ppArray == NULL) { + weston_log("fails to allocate memory\n"); + return IVI_FAILED; + } + + wl_list_for_each(ivi_view, &ivilayer->order.view_list, order_link) { + (*ppArray)[n++] = ivi_view->ivisurf; + } + } + + *pLength = length; + + return IVI_SUCCEEDED; +} + +static struct ivi_layout_layer * +ivi_layout_layer_create_with_dimension(uint32_t id_layer, + int32_t width, int32_t height) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_layer *ivilayer = NULL; + + ivilayer = get_layer(&layout->layer_list, id_layer); + if (ivilayer != NULL) { + weston_log("id_layer is already created\n"); + ++ivilayer->ref_count; + return ivilayer; + } + + ivilayer = calloc(1, sizeof *ivilayer); + if (ivilayer == NULL) { + weston_log("fails to allocate memory\n"); + return NULL; + } + + ivilayer->ref_count = 1; + wl_signal_init(&ivilayer->property_changed); + ivilayer->layout = layout; + ivilayer->id_layer = id_layer; + + init_layer_properties(&ivilayer->prop, width, height); + + wl_list_init(&ivilayer->pending.view_list); + wl_list_init(&ivilayer->pending.link); + ivilayer->pending.prop = ivilayer->prop; + + wl_list_init(&ivilayer->order.view_list); + wl_list_init(&ivilayer->order.link); + + wl_list_insert(&layout->layer_list, &ivilayer->link); + + wl_signal_emit(&layout->layer_notification.created, ivilayer); + + return ivilayer; +} + +static void +ivi_layout_layer_destroy(struct ivi_layout_layer *ivilayer) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_view *ivi_view, *next; + + if (ivilayer == NULL) { + weston_log("ivi_layout_layer_destroy: invalid argument\n"); + return; + } + + if (--ivilayer->ref_count > 0) + return; + + /*Destroy all ivi_views*/ + wl_list_for_each_safe(ivi_view, next, &layout->view_list, link) { + if (ivi_view->on_layer == ivilayer) + ivi_view_destroy(ivi_view); + } + + wl_signal_emit(&layout->layer_notification.removed, ivilayer); + + wl_list_remove(&ivilayer->pending.link); + wl_list_remove(&ivilayer->order.link); + wl_list_remove(&ivilayer->link); + + free(ivilayer); +} + +int32_t +ivi_layout_layer_set_visibility(struct ivi_layout_layer *ivilayer, + bool newVisibility) +{ + struct ivi_layout_layer_properties *prop = NULL; + + if (ivilayer == NULL) { + weston_log("ivi_layout_layer_set_visibility: invalid argument\n"); + return IVI_FAILED; + } + + prop = &ivilayer->pending.prop; + prop->visibility = newVisibility; + + if (ivilayer->prop.visibility != newVisibility) + prop->event_mask |= IVI_NOTIFICATION_VISIBILITY; + else + prop->event_mask &= ~IVI_NOTIFICATION_VISIBILITY; + + return IVI_SUCCEEDED; +} + +int32_t +ivi_layout_layer_set_opacity(struct ivi_layout_layer *ivilayer, + wl_fixed_t opacity) +{ + struct ivi_layout_layer_properties *prop = NULL; + + if (ivilayer == NULL || + opacity < wl_fixed_from_double(0.0) || + wl_fixed_from_double(1.0) < opacity) { + weston_log("ivi_layout_layer_set_opacity: invalid argument\n"); + return IVI_FAILED; + } + + prop = &ivilayer->pending.prop; + prop->opacity = opacity; + + if (ivilayer->prop.opacity != opacity) + prop->event_mask |= IVI_NOTIFICATION_OPACITY; + else + prop->event_mask &= ~IVI_NOTIFICATION_OPACITY; + + return IVI_SUCCEEDED; +} + +static int32_t +ivi_layout_layer_set_source_rectangle(struct ivi_layout_layer *ivilayer, + int32_t x, int32_t y, + int32_t width, int32_t height) +{ + struct ivi_layout_layer_properties *prop = NULL; + + if (ivilayer == NULL) { + weston_log("ivi_layout_layer_set_source_rectangle: invalid argument\n"); + return IVI_FAILED; + } + + prop = &ivilayer->pending.prop; + prop->source_x = x; + prop->source_y = y; + prop->source_width = width; + prop->source_height = height; + + if (ivilayer->prop.source_x != x || ivilayer->prop.source_y != y || + ivilayer->prop.source_width != width || + ivilayer->prop.source_height != height) + prop->event_mask |= IVI_NOTIFICATION_SOURCE_RECT; + else + prop->event_mask &= ~IVI_NOTIFICATION_SOURCE_RECT; + + return IVI_SUCCEEDED; +} + +int32_t +ivi_layout_layer_set_destination_rectangle(struct ivi_layout_layer *ivilayer, + int32_t x, int32_t y, + int32_t width, int32_t height) +{ + struct ivi_layout_layer_properties *prop = NULL; + + if (ivilayer == NULL) { + weston_log("ivi_layout_layer_set_destination_rectangle: invalid argument\n"); + return IVI_FAILED; + } + + prop = &ivilayer->pending.prop; + prop->dest_x = x; + prop->dest_y = y; + prop->dest_width = width; + prop->dest_height = height; + + if (ivilayer->prop.dest_x != x || ivilayer->prop.dest_y != y || + ivilayer->prop.dest_width != width || + ivilayer->prop.dest_height != height) + prop->event_mask |= IVI_NOTIFICATION_DEST_RECT; + else + prop->event_mask &= ~IVI_NOTIFICATION_DEST_RECT; + + return IVI_SUCCEEDED; +} + +int32_t +ivi_layout_layer_set_render_order(struct ivi_layout_layer *ivilayer, + struct ivi_layout_surface **pSurface, + int32_t number) +{ + int32_t i = 0; + struct ivi_layout_view * ivi_view; + + if (ivilayer == NULL) { + weston_log("ivi_layout_layer_set_render_order: invalid argument\n"); + return IVI_FAILED; + } + + clear_view_pending_list(ivilayer); + + for (i = 0; i < number; i++) { + ivi_view = get_ivi_view(ivilayer, pSurface[i]); + if (!ivi_view) + ivi_view = ivi_view_create(ivilayer, pSurface[i]); + + assert(ivi_view != NULL); + + wl_list_remove(&ivi_view->pending_link); + wl_list_insert(&ivilayer->pending.view_list, &ivi_view->pending_link); + } + + ivilayer->order.dirty = 1; + + return IVI_SUCCEEDED; +} + +int32_t +ivi_layout_surface_set_visibility(struct ivi_layout_surface *ivisurf, + bool newVisibility) +{ + struct ivi_layout_surface_properties *prop = NULL; + + if (ivisurf == NULL) { + weston_log("ivi_layout_surface_set_visibility: invalid argument\n"); + return IVI_FAILED; + } + + prop = &ivisurf->pending.prop; + prop->visibility = newVisibility; + + if (ivisurf->prop.visibility != newVisibility) + prop->event_mask |= IVI_NOTIFICATION_VISIBILITY; + else + prop->event_mask &= ~IVI_NOTIFICATION_VISIBILITY; + + return IVI_SUCCEEDED; +} + +int32_t +ivi_layout_surface_set_opacity(struct ivi_layout_surface *ivisurf, + wl_fixed_t opacity) +{ + struct ivi_layout_surface_properties *prop = NULL; + + if (ivisurf == NULL || + opacity < wl_fixed_from_double(0.0) || + wl_fixed_from_double(1.0) < opacity) { + weston_log("ivi_layout_surface_set_opacity: invalid argument\n"); + return IVI_FAILED; + } + + prop = &ivisurf->pending.prop; + prop->opacity = opacity; + + if (ivisurf->prop.opacity != opacity) + prop->event_mask |= IVI_NOTIFICATION_OPACITY; + else + prop->event_mask &= ~IVI_NOTIFICATION_OPACITY; + + return IVI_SUCCEEDED; +} + +int32_t +ivi_layout_surface_set_destination_rectangle(struct ivi_layout_surface *ivisurf, + int32_t x, int32_t y, + int32_t width, int32_t height) +{ + struct ivi_layout_surface_properties *prop = NULL; + + if (ivisurf == NULL) { + weston_log("ivi_layout_surface_set_destination_rectangle: invalid argument\n"); + return IVI_FAILED; + } + + prop = &ivisurf->pending.prop; + prop->start_x = prop->dest_x; + prop->start_y = prop->dest_y; + prop->dest_x = x; + prop->dest_y = y; + prop->start_width = prop->dest_width; + prop->start_height = prop->dest_height; + prop->dest_width = width; + prop->dest_height = height; + + if (ivisurf->prop.dest_x != x || ivisurf->prop.dest_y != y || + ivisurf->prop.dest_width != width || + ivisurf->prop.dest_height != height) + prop->event_mask |= IVI_NOTIFICATION_DEST_RECT; + else + prop->event_mask &= ~IVI_NOTIFICATION_DEST_RECT; + + return IVI_SUCCEEDED; +} + +void +ivi_layout_surface_set_size(struct ivi_layout_surface *ivisurf, + int32_t width, int32_t height) +{ + if (weston_surface_is_desktop_surface(ivisurf->surface)) { + weston_desktop_surface_set_size(ivisurf->weston_desktop_surface, + width, height); + } else { + shell_surface_send_configure(ivisurf->surface, + width, height); + } +} + +static int32_t +ivi_layout_screen_add_layer(struct weston_output *output, + struct ivi_layout_layer *addlayer) +{ + struct ivi_layout_screen *iviscrn; + + if (output == NULL || addlayer == NULL) { + weston_log("ivi_layout_screen_add_layer: invalid argument\n"); + return IVI_FAILED; + } + + iviscrn = get_screen_from_output(output); + + /*if layer is already assigned to screen make order of it dirty + * we are going to remove it (in commit_screen_list)*/ + if (addlayer->on_screen) + addlayer->on_screen->order.dirty = 1; + + wl_list_remove(&addlayer->pending.link); + wl_list_insert(&iviscrn->pending.layer_list, &addlayer->pending.link); + + iviscrn->order.dirty = 1; + + return IVI_SUCCEEDED; +} + +static int32_t +ivi_layout_screen_remove_layer(struct weston_output *output, + struct ivi_layout_layer *removelayer) +{ + struct ivi_layout_screen *iviscrn; + + if (output == NULL || removelayer == NULL) { + weston_log("ivi_layout_screen_remove_layer: invalid argument\n"); + return IVI_FAILED; + } + + iviscrn = get_screen_from_output(output); + + wl_list_remove(&removelayer->pending.link); + wl_list_init(&removelayer->pending.link); + + iviscrn->order.dirty = 1; + + return IVI_SUCCEEDED; +} + +static int32_t +ivi_layout_screen_set_render_order(struct weston_output *output, + struct ivi_layout_layer **pLayer, + const int32_t number) +{ + struct ivi_layout_screen *iviscrn; + struct ivi_layout_layer *ivilayer = NULL; + struct ivi_layout_layer *next = NULL; + int32_t i = 0; + + if (output == NULL) { + weston_log("ivi_layout_screen_set_render_order: invalid argument\n"); + return IVI_FAILED; + } + + iviscrn = get_screen_from_output(output); + + wl_list_for_each_safe(ivilayer, next, + &iviscrn->pending.layer_list, pending.link) { + wl_list_remove(&ivilayer->pending.link); + wl_list_init(&ivilayer->pending.link); + } + + assert(wl_list_empty(&iviscrn->pending.layer_list)); + + for (i = 0; i < number; i++) { + wl_list_remove(&pLayer[i]->pending.link); + wl_list_insert(&iviscrn->pending.layer_list, + &pLayer[i]->pending.link); + } + + iviscrn->order.dirty = 1; + + return IVI_SUCCEEDED; +} + +/** + * This function is used by the additional ivi-module because of dumping ivi_surface sceenshot. + * The ivi-module, e.g. ivi-controller.so, is in wayland-ivi-extension of Genivi's Layer Management. + * This function is used to get the result of drawing by clients. + */ +static struct weston_surface * +ivi_layout_surface_get_weston_surface(struct ivi_layout_surface *ivisurf) +{ + return ivisurf != NULL ? ivisurf->surface : NULL; +} + +static int32_t +ivi_layout_surface_get_size(struct ivi_layout_surface *ivisurf, + int32_t *width, int32_t *height, + int32_t *stride) +{ + int32_t w; + int32_t h; + const size_t bytespp = 4; /* PIXMAN_a8b8g8r8 */ + + if (ivisurf == NULL || ivisurf->surface == NULL) { + weston_log("%s: invalid argument\n", __func__); + return IVI_FAILED; + } + + weston_surface_get_content_size(ivisurf->surface, &w, &h); + + if (width != NULL) + *width = w; + + if (height != NULL) + *height = h; + + if (stride != NULL) + *stride = w * bytespp; + + return IVI_SUCCEEDED; +} + +static int32_t +ivi_layout_layer_add_listener(struct ivi_layout_layer *ivilayer, + struct wl_listener *listener) +{ + if (ivilayer == NULL || listener == NULL) { + weston_log("ivi_layout_layer_add_listener: invalid argument\n"); + return IVI_FAILED; + } + + wl_signal_add(&ivilayer->property_changed, listener); + + return IVI_SUCCEEDED; +} + +static const struct ivi_layout_surface_properties * +ivi_layout_get_properties_of_surface(struct ivi_layout_surface *ivisurf) +{ + if (ivisurf == NULL) { + weston_log("ivi_layout_get_properties_of_surface: invalid argument\n"); + return NULL; + } + + return &ivisurf->prop; +} + +static int32_t +ivi_layout_layer_add_surface(struct ivi_layout_layer *ivilayer, + struct ivi_layout_surface *addsurf) +{ + struct ivi_layout_view *ivi_view; + + if (ivilayer == NULL || addsurf == NULL) { + weston_log("ivi_layout_layer_add_surface: invalid argument\n"); + return IVI_FAILED; + } + + ivi_view = get_ivi_view(ivilayer, addsurf); + if (!ivi_view) + ivi_view = ivi_view_create(ivilayer, addsurf); + + wl_list_remove(&ivi_view->pending_link); + wl_list_insert(&ivilayer->pending.view_list, &ivi_view->pending_link); + + ivilayer->order.dirty = 1; + + return IVI_SUCCEEDED; +} + +static void +ivi_layout_layer_remove_surface(struct ivi_layout_layer *ivilayer, + struct ivi_layout_surface *remsurf) +{ + struct ivi_layout_view *ivi_view; + + if (ivilayer == NULL || remsurf == NULL) { + weston_log("ivi_layout_layer_remove_surface: invalid argument\n"); + return; + } + + ivi_view = get_ivi_view(ivilayer, remsurf); + if (ivi_view) { + wl_list_remove(&ivi_view->pending_link); + wl_list_init(&ivi_view->pending_link); + + ivilayer->order.dirty = 1; + } +} + +static int32_t +ivi_layout_surface_set_source_rectangle(struct ivi_layout_surface *ivisurf, + int32_t x, int32_t y, + int32_t width, int32_t height) +{ + struct ivi_layout_surface_properties *prop = NULL; + + if (ivisurf == NULL) { + weston_log("ivi_layout_surface_set_source_rectangle: invalid argument\n"); + return IVI_FAILED; + } + + prop = &ivisurf->pending.prop; + prop->source_x = x; + prop->source_y = y; + prop->source_width = width; + prop->source_height = height; + + if (ivisurf->prop.source_x != x || ivisurf->prop.source_y != y || + ivisurf->prop.source_width != width || + ivisurf->prop.source_height != height) + prop->event_mask |= IVI_NOTIFICATION_SOURCE_RECT; + else + prop->event_mask &= ~IVI_NOTIFICATION_SOURCE_RECT; + + return IVI_SUCCEEDED; +} + +int32_t +ivi_layout_commit_changes(void) +{ + struct ivi_layout *layout = get_instance(); + + commit_surface_list(layout); + commit_layer_list(layout); + commit_screen_list(layout); + build_view_list(layout); + + commit_transition(layout); + + commit_changes(layout); + send_prop(layout); + + return IVI_SUCCEEDED; +} + +static int32_t +ivi_layout_layer_set_transition(struct ivi_layout_layer *ivilayer, + enum ivi_layout_transition_type type, + uint32_t duration) +{ + if (ivilayer == NULL) { + weston_log("%s: invalid argument\n", __func__); + return -1; + } + + ivilayer->pending.prop.transition_type = type; + ivilayer->pending.prop.transition_duration = duration; + + return 0; +} + +static int32_t +ivi_layout_layer_set_fade_info(struct ivi_layout_layer* ivilayer, + uint32_t is_fade_in, + double start_alpha, double end_alpha) +{ + if (ivilayer == NULL) { + weston_log("%s: invalid argument\n", __func__); + return -1; + } + + ivilayer->pending.prop.is_fade_in = is_fade_in; + ivilayer->pending.prop.start_alpha = start_alpha; + ivilayer->pending.prop.end_alpha = end_alpha; + + return 0; +} + +static int32_t +ivi_layout_surface_set_transition_duration(struct ivi_layout_surface *ivisurf, + uint32_t duration) +{ + struct ivi_layout_surface_properties *prop; + + if (ivisurf == NULL) { + weston_log("%s: invalid argument\n", __func__); + return -1; + } + + prop = &ivisurf->pending.prop; + prop->transition_duration = duration*10; + return 0; +} + +/* + * This interface enables e.g. an id agent to set the id of an ivi-layout + * surface, that has been created by a desktop application. This can only be + * done once as long as the initial surface id equals IVI_INVALID_ID. Afterwards + * two events are emitted, namely surface_created and surface_configured. + */ +static int32_t +ivi_layout_surface_set_id(struct ivi_layout_surface *ivisurf, + uint32_t id_surface) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_surface *search_ivisurf = NULL; + + if (!ivisurf) { + weston_log("%s: invalid argument\n", __func__); + return IVI_FAILED; + } + + if (ivisurf->id_surface != IVI_INVALID_ID) { + weston_log("surface id can only be set once\n"); + return IVI_FAILED; + } + + search_ivisurf = get_surface(&layout->surface_list, id_surface); + if (search_ivisurf) { + weston_log("id_surface(%d) is already created\n", id_surface); + return IVI_FAILED; + } + + ivisurf->id_surface = id_surface; + + wl_signal_emit(&layout->surface_notification.created, ivisurf); + wl_signal_emit(&layout->surface_notification.configure_changed, + ivisurf); + + return IVI_SUCCEEDED; +} + +static int32_t +ivi_layout_surface_set_transition(struct ivi_layout_surface *ivisurf, + enum ivi_layout_transition_type type, + uint32_t duration) +{ + struct ivi_layout_surface_properties *prop; + + if (ivisurf == NULL) { + weston_log("%s: invalid argument\n", __func__); + return -1; + } + + prop = &ivisurf->pending.prop; + prop->transition_type = type; + prop->transition_duration = duration; + return 0; +} + +static int32_t +ivi_layout_surface_dump(struct weston_surface *surface, + void *target, size_t size,int32_t x, int32_t y, + int32_t width, int32_t height) +{ + int result = 0; + + if (surface == NULL) { + weston_log("%s: invalid argument\n", __func__); + return IVI_FAILED; + } + + result = weston_surface_copy_content( + surface, target, size, + x, y, width, height); + + return result == 0 ? IVI_SUCCEEDED : IVI_FAILED; +} + +/** + * methods of interaction between ivi-shell with ivi-layout + */ + +static struct ivi_layout_surface* +surface_create(struct weston_surface *wl_surface, uint32_t id_surface) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_surface *ivisurf = NULL; + + if (wl_surface == NULL) { + weston_log("ivi_layout_surface_create: invalid argument\n"); + return NULL; + } + + ivisurf = calloc(1, sizeof *ivisurf); + if (ivisurf == NULL) { + weston_log("fails to allocate memory\n"); + return NULL; + } + + wl_signal_init(&ivisurf->property_changed); + ivisurf->id_surface = id_surface; + ivisurf->layout = layout; + + ivisurf->surface = wl_surface; + + ivisurf->surface->width_from_buffer = 0; + ivisurf->surface->height_from_buffer = 0; + + init_surface_properties(&ivisurf->prop); + + ivisurf->pending.prop = ivisurf->prop; + + wl_list_init(&ivisurf->view_list); + + wl_list_insert(&layout->surface_list, &ivisurf->link); + + return ivisurf; +} + +void +ivi_layout_desktop_surface_configure(struct ivi_layout_surface *ivisurf, + int32_t width, int32_t height) +{ + struct ivi_layout *layout = get_instance(); + + /* emit callback which is set by ivi-layout api user */ + wl_signal_emit(&layout->surface_notification.configure_desktop_changed, + ivisurf); +} + +struct ivi_layout_surface* +ivi_layout_desktop_surface_create(struct weston_surface *wl_surface) +{ + return surface_create(wl_surface, IVI_INVALID_ID); +} + +void +ivi_layout_surface_configure(struct ivi_layout_surface *ivisurf, + int32_t width, int32_t height) +{ + struct ivi_layout *layout = get_instance(); + + /* emit callback which is set by ivi-layout api user */ + wl_signal_emit(&layout->surface_notification.configure_changed, + ivisurf); +} + +struct ivi_layout_surface* +ivi_layout_surface_create(struct weston_surface *wl_surface, + uint32_t id_surface) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_surface *ivisurf = NULL; + + ivisurf = get_surface(&layout->surface_list, id_surface); + if (ivisurf) { + weston_log("id_surface(%d) is already created\n", id_surface); + return NULL; + } + + ivisurf = surface_create(wl_surface, id_surface); + + if (ivisurf) + wl_signal_emit(&layout->surface_notification.created, ivisurf); + + return ivisurf; +} + +static struct ivi_layout_interface ivi_layout_interface; + +void +ivi_layout_init_with_compositor(struct weston_compositor *ec) +{ + struct ivi_layout *layout = get_instance(); + + layout->compositor = ec; + + wl_list_init(&layout->surface_list); + wl_list_init(&layout->layer_list); + wl_list_init(&layout->screen_list); + wl_list_init(&layout->view_list); + + wl_signal_init(&layout->layer_notification.created); + wl_signal_init(&layout->layer_notification.removed); + + wl_signal_init(&layout->surface_notification.created); + wl_signal_init(&layout->surface_notification.removed); + wl_signal_init(&layout->surface_notification.configure_changed); + wl_signal_init(&layout->surface_notification.configure_desktop_changed); + + /* Add layout_layer at the last of weston_compositor.layer_list */ + weston_layer_init(&layout->layout_layer, ec); + weston_layer_set_position(&layout->layout_layer, + WESTON_LAYER_POSITION_NORMAL); + + create_screen(ec); + + layout->transitions = ivi_layout_transition_set_create(ec); + wl_list_init(&layout->pending_transition_list); + + weston_plugin_api_register(ec, IVI_LAYOUT_API_NAME, + &ivi_layout_interface, + sizeof(struct ivi_layout_interface)); +} + +static struct ivi_layout_interface ivi_layout_interface = { + /** + * commit all changes + */ + .commit_changes = ivi_layout_commit_changes, + + /** + * surface controller interfaces + */ + .add_listener_create_surface = ivi_layout_add_listener_create_surface, + .add_listener_remove_surface = ivi_layout_add_listener_remove_surface, + .add_listener_configure_surface = ivi_layout_add_listener_configure_surface, + .add_listener_configure_desktop_surface = ivi_layout_add_listener_configure_desktop_surface, + .get_surface = shell_get_ivi_layout_surface, + .get_surfaces = ivi_layout_get_surfaces, + .get_id_of_surface = ivi_layout_get_id_of_surface, + .get_surface_from_id = ivi_layout_get_surface_from_id, + .get_properties_of_surface = ivi_layout_get_properties_of_surface, + .get_surfaces_on_layer = ivi_layout_get_surfaces_on_layer, + .surface_set_visibility = ivi_layout_surface_set_visibility, + .surface_set_opacity = ivi_layout_surface_set_opacity, + .surface_set_source_rectangle = ivi_layout_surface_set_source_rectangle, + .surface_set_destination_rectangle = ivi_layout_surface_set_destination_rectangle, + .surface_add_listener = ivi_layout_surface_add_listener, + .surface_get_weston_surface = ivi_layout_surface_get_weston_surface, + .surface_set_transition = ivi_layout_surface_set_transition, + .surface_set_transition_duration = ivi_layout_surface_set_transition_duration, + .surface_set_id = ivi_layout_surface_set_id, + + /** + * layer controller interfaces + */ + .add_listener_create_layer = ivi_layout_add_listener_create_layer, + .add_listener_remove_layer = ivi_layout_add_listener_remove_layer, + .layer_create_with_dimension = ivi_layout_layer_create_with_dimension, + .layer_destroy = ivi_layout_layer_destroy, + .get_layers = ivi_layout_get_layers, + .get_id_of_layer = ivi_layout_get_id_of_layer, + .get_layer_from_id = ivi_layout_get_layer_from_id, + .get_properties_of_layer = ivi_layout_get_properties_of_layer, + .get_layers_under_surface = ivi_layout_get_layers_under_surface, + .get_layers_on_screen = ivi_layout_get_layers_on_screen, + .layer_set_visibility = ivi_layout_layer_set_visibility, + .layer_set_opacity = ivi_layout_layer_set_opacity, + .layer_set_source_rectangle = ivi_layout_layer_set_source_rectangle, + .layer_set_destination_rectangle = ivi_layout_layer_set_destination_rectangle, + .layer_add_surface = ivi_layout_layer_add_surface, + .layer_remove_surface = ivi_layout_layer_remove_surface, + .layer_set_render_order = ivi_layout_layer_set_render_order, + .layer_add_listener = ivi_layout_layer_add_listener, + .layer_set_transition = ivi_layout_layer_set_transition, + + /** + * screen controller interfaces + */ + .get_screens_under_layer = ivi_layout_get_screens_under_layer, + .screen_add_layer = ivi_layout_screen_add_layer, + .screen_remove_layer = ivi_layout_screen_remove_layer, + .screen_set_render_order = ivi_layout_screen_set_render_order, + + /** + * animation + */ + .transition_move_layer_cancel = ivi_layout_transition_move_layer_cancel, + .layer_set_fade_info = ivi_layout_layer_set_fade_info, + + /** + * surface content dumping for debugging + */ + .surface_get_size = ivi_layout_surface_get_size, + .surface_dump = ivi_layout_surface_dump, +}; diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c new file mode 100644 index 0000000..0fb6b29 --- /dev/null +++ b/ivi-shell/ivi-shell.c @@ -0,0 +1,660 @@ +/* + * Copyright (C) 2013 DENSO CORPORATION + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * ivi-shell supports a type of shell for In-Vehicle Infotainment system. + * In-Vehicle Infotainment system traditionally manages surfaces with global + * identification. A protocol, ivi_application, supports such a feature + * by implementing a request, ivi_application::surface_creation defined in + * ivi_application.xml. + * + * The ivi-shell explicitly loads a module to add business logic like how to + * layout surfaces by using internal ivi-layout APIs. + */ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "ivi-shell.h" +#include "ivi-application-server-protocol.h" +#include "ivi-layout-private.h" +#include "ivi-layout-shell.h" +#include "shared/helpers.h" +#include "compositor/weston.h" + +/* Representation of ivi_surface protocol object. */ +struct ivi_shell_surface +{ + struct wl_resource* resource; + struct ivi_shell *shell; + struct ivi_layout_surface *layout_surface; + + struct weston_surface *surface; + struct wl_listener surface_destroy_listener; + + uint32_t id_surface; + + int32_t width; + int32_t height; + + struct wl_list link; +}; + +/* + * Implementation of ivi_surface + */ + +static void +ivi_shell_surface_committed(struct weston_surface *, int32_t, int32_t); + +static struct ivi_shell_surface * +get_ivi_shell_surface(struct weston_surface *surface) +{ + struct ivi_shell_surface *shsurf; + + if (surface->committed != ivi_shell_surface_committed) + return NULL; + + shsurf = surface->committed_private; + assert(shsurf); + assert(shsurf->surface == surface); + + return shsurf; +} + +struct ivi_layout_surface * +shell_get_ivi_layout_surface(struct weston_surface *surface) +{ + struct ivi_shell_surface *shsurf; + + shsurf = get_ivi_shell_surface(surface); + if (!shsurf) + return NULL; + + return shsurf->layout_surface; +} + +void +shell_surface_send_configure(struct weston_surface *surface, + int32_t width, int32_t height) +{ + struct ivi_shell_surface *shsurf; + + shsurf = get_ivi_shell_surface(surface); + if (!shsurf) + return; + + if (shsurf->resource) + ivi_surface_send_configure(shsurf->resource, width, height); +} + +static void +ivi_shell_surface_committed(struct weston_surface *surface, + int32_t sx, int32_t sy) +{ + struct ivi_shell_surface *ivisurf = get_ivi_shell_surface(surface); + + assert(ivisurf); + if (!ivisurf) + return; + + if (surface->width == 0 || surface->height == 0) + return; + + if (ivisurf->width != surface->width || + ivisurf->height != surface->height) { + ivisurf->width = surface->width; + ivisurf->height = surface->height; + + ivi_layout_surface_configure(ivisurf->layout_surface, + surface->width, surface->height); + } +} + +static int +ivi_shell_surface_get_label(struct weston_surface *surface, + char *buf, + size_t len) +{ + struct ivi_shell_surface *shell_surf = get_ivi_shell_surface(surface); + + if (!shell_surf) + return snprintf(buf, len, "unidentified window in ivi-shell"); + + return snprintf(buf, len, "ivi-surface %#x", shell_surf->id_surface); +} + +static void +layout_surface_cleanup(struct ivi_shell_surface *ivisurf) +{ + assert(ivisurf->layout_surface != NULL); + + /* destroy weston_surface destroy signal. */ + if (!ivisurf->layout_surface->weston_desktop_surface) + wl_list_remove(&ivisurf->surface_destroy_listener.link); + + ivi_layout_surface_destroy(ivisurf->layout_surface); + ivisurf->layout_surface = NULL; + + ivisurf->surface->committed = NULL; + ivisurf->surface->committed_private = NULL; + weston_surface_set_label_func(ivisurf->surface, NULL); + ivisurf->surface = NULL; +} + +/* + * The ivi_surface wl_resource destructor. + * + * Gets called via ivi_surface.destroy request or automatic wl_client clean-up. + */ +static void +shell_destroy_shell_surface(struct wl_resource *resource) +{ + struct ivi_shell_surface *ivisurf = wl_resource_get_user_data(resource); + + if (ivisurf == NULL) + return; + + assert(ivisurf->resource == resource); + + if (ivisurf->layout_surface != NULL) + layout_surface_cleanup(ivisurf); + + wl_list_remove(&ivisurf->link); + + free(ivisurf); +} + +/* Gets called through the weston_surface destroy signal. */ +static void +shell_handle_surface_destroy(struct wl_listener *listener, void *data) +{ + struct ivi_shell_surface *ivisurf = + container_of(listener, struct ivi_shell_surface, + surface_destroy_listener); + + assert(ivisurf != NULL); + + if (ivisurf->layout_surface != NULL) + layout_surface_cleanup(ivisurf); +} + +/* Gets called, when a client sends ivi_surface.destroy request. */ +static void +surface_destroy(struct wl_client *client, struct wl_resource *resource) +{ + /* + * Fires the wl_resource destroy signal, and then calls + * ivi_surface wl_resource destructor: shell_destroy_shell_surface() + */ + wl_resource_destroy(resource); +} + +static const struct ivi_surface_interface surface_implementation = { + surface_destroy, +}; + +/** + * Request handler for ivi_application.surface_create. + * + * Creates an ivi_surface protocol object associated with the given wl_surface. + * ivi_surface protocol object is represented by struct ivi_shell_surface. + * + * \param client The client. + * \param resource The ivi_application protocol object. + * \param id_surface The IVI surface ID. + * \param surface_resource The wl_surface protocol object. + * \param id The protocol object id for the new ivi_surface protocol object. + * + * The wl_surface is given the ivi_surface role and associated with a unique + * IVI ID which is used to identify the surface in a controller + * (window manager). + */ +static void +application_surface_create(struct wl_client *client, + struct wl_resource *resource, + uint32_t id_surface, + struct wl_resource *surface_resource, + uint32_t id) +{ + struct ivi_shell *shell = wl_resource_get_user_data(resource); + struct ivi_shell_surface *ivisurf; + struct ivi_layout_surface *layout_surface; + struct weston_surface *weston_surface = + wl_resource_get_user_data(surface_resource); + struct wl_resource *res; + + if (weston_surface_set_role(weston_surface, "ivi_surface", + resource, IVI_APPLICATION_ERROR_ROLE) < 0) + return; + + layout_surface = ivi_layout_surface_create(weston_surface, id_surface); + + /* check if id_ivi is already used for wl_surface*/ + if (layout_surface == NULL) { + wl_resource_post_error(resource, + IVI_APPLICATION_ERROR_IVI_ID, + "surface_id is already assigned " + "by another app"); + return; + } + + layout_surface->weston_desktop_surface = NULL; + + ivisurf = zalloc(sizeof *ivisurf); + if (ivisurf == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + wl_list_init(&ivisurf->link); + wl_list_insert(&shell->ivi_surface_list, &ivisurf->link); + + ivisurf->shell = shell; + ivisurf->id_surface = id_surface; + + ivisurf->width = 0; + ivisurf->height = 0; + ivisurf->layout_surface = layout_surface; + + /* + * The following code relies on wl_surface destruction triggering + * immediateweston_surface destruction + */ + ivisurf->surface_destroy_listener.notify = shell_handle_surface_destroy; + wl_signal_add(&weston_surface->destroy_signal, + &ivisurf->surface_destroy_listener); + + ivisurf->surface = weston_surface; + + weston_surface->committed = ivi_shell_surface_committed; + weston_surface->committed_private = ivisurf; + weston_surface_set_label_func(weston_surface, + ivi_shell_surface_get_label); + + res = wl_resource_create(client, &ivi_surface_interface, 1, id); + if (res == NULL) { + wl_client_post_no_memory(client); + return; + } + + ivisurf->resource = res; + + wl_resource_set_implementation(res, &surface_implementation, + ivisurf, shell_destroy_shell_surface); +} + +static const struct ivi_application_interface application_implementation = { + application_surface_create +}; + +/* + * Handle wl_registry.bind of ivi_application global singleton. + */ +static void +bind_ivi_application(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct ivi_shell *shell = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, &ivi_application_interface, + 1, id); + + wl_resource_set_implementation(resource, + &application_implementation, + shell, NULL); +} + +/* + * Called through the compositor's destroy signal. + */ +static void +shell_destroy(struct wl_listener *listener, void *data) +{ + struct ivi_shell *shell = + container_of(listener, struct ivi_shell, destroy_listener); + struct ivi_shell_surface *ivisurf, *next; + + wl_list_remove(&shell->destroy_listener.link); + wl_list_remove(&shell->wake_listener.link); + + wl_list_for_each_safe(ivisurf, next, &shell->ivi_surface_list, link) { + wl_list_remove(&ivisurf->link); + free(ivisurf); + } + + free(shell); +} + +/* + * Called through the compositor's wake signal. + */ +static void +wake_handler(struct wl_listener *listener, void *data) +{ + struct weston_compositor *compositor = data; + + weston_compositor_damage_all(compositor); +} + +static void +terminate_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) +{ + struct weston_compositor *compositor = data; + + weston_compositor_exit(compositor); +} + +static void +init_ivi_shell(struct weston_compositor *compositor, struct ivi_shell *shell) +{ + struct weston_config *config = wet_get_config(compositor); + struct weston_config_section *section; + bool developermode; + + shell->compositor = compositor; + + wl_list_init(&shell->ivi_surface_list); + + section = weston_config_get_section(config, "ivi-shell", NULL, NULL); + + weston_config_section_get_bool(section, "developermode", + &developermode, 0); + + if (developermode) { + weston_install_debug_key_binding(compositor, MODIFIER_SUPER); + + weston_compositor_add_key_binding(compositor, KEY_BACKSPACE, + MODIFIER_CTRL | MODIFIER_ALT, + terminate_binding, + compositor); + } +} + +static void +activate_binding(struct weston_seat *seat, + struct weston_view *focus_view) +{ + struct weston_surface *focus = focus_view->surface; + struct weston_surface *main_surface = + weston_surface_get_main_surface(focus); + + if (get_ivi_shell_surface(main_surface) == NULL) + return; + + weston_seat_set_keyboard_focus(seat, focus); +} + +static void +click_to_activate_binding(struct weston_pointer *pointer, + const struct timespec *time, + uint32_t button, void *data) +{ + if (pointer->grab != &pointer->default_grab) + return; + if (pointer->focus == NULL) + return; + + activate_binding(pointer->seat, pointer->focus); +} + +static void +touch_to_activate_binding(struct weston_touch *touch, + const struct timespec *time, + void *data) +{ + if (touch->grab != &touch->default_grab) + return; + if (touch->focus == NULL) + return; + + activate_binding(touch->seat, touch->focus); +} + +static void +shell_add_bindings(struct weston_compositor *compositor, + struct ivi_shell *shell) +{ + weston_compositor_add_button_binding(compositor, BTN_LEFT, 0, + click_to_activate_binding, + shell); + weston_compositor_add_button_binding(compositor, BTN_RIGHT, 0, + click_to_activate_binding, + shell); + weston_compositor_add_touch_binding(compositor, 0, + touch_to_activate_binding, + shell); +} + +/* + * libweston-desktop + */ + +static void +desktop_surface_ping_timeout(struct weston_desktop_client *client, + void *user_data) +{ + /* Not supported */ +} + +static void +desktop_surface_pong(struct weston_desktop_client *client, + void *user_data) +{ + /* Not supported */ +} + +static void +desktop_surface_added(struct weston_desktop_surface *surface, + void *user_data) +{ + struct ivi_shell *shell = (struct ivi_shell *) user_data; + struct ivi_layout_surface *layout_surface; + struct ivi_shell_surface *ivisurf; + struct weston_surface *weston_surf = + weston_desktop_surface_get_surface(surface); + + layout_surface = ivi_layout_desktop_surface_create(weston_surf); + if (!layout_surface) { + return; + } + + layout_surface->weston_desktop_surface = surface; + + ivisurf = zalloc(sizeof *ivisurf); + if (!ivisurf) { + return; + } + + ivisurf->shell = shell; + ivisurf->id_surface = IVI_INVALID_ID; + + ivisurf->width = 0; + ivisurf->height = 0; + ivisurf->layout_surface = layout_surface; + ivisurf->surface = weston_surf; + + weston_desktop_surface_set_user_data(surface, ivisurf); +} + +static void +desktop_surface_removed(struct weston_desktop_surface *surface, + void *user_data) +{ + struct ivi_shell_surface *ivisurf = (struct ivi_shell_surface *) + weston_desktop_surface_get_user_data(surface); + + assert(ivisurf != NULL); + + if (ivisurf->layout_surface) + layout_surface_cleanup(ivisurf); +} + +static void +desktop_surface_committed(struct weston_desktop_surface *surface, + int32_t sx, int32_t sy, void *user_data) +{ + struct ivi_shell_surface *ivisurf = (struct ivi_shell_surface *) + weston_desktop_surface_get_user_data(surface); + struct weston_surface *weston_surf = + weston_desktop_surface_get_surface(surface); + + if(!ivisurf) + return; + + if (weston_surf->width == 0 || weston_surf->height == 0) + return; + + if (ivisurf->width != weston_surf->width || + ivisurf->height != weston_surf->height) { + ivisurf->width = weston_surf->width; + ivisurf->height = weston_surf->height; + + ivi_layout_desktop_surface_configure(ivisurf->layout_surface, + weston_surf->width, + weston_surf->height); + } +} + +static void +desktop_surface_move(struct weston_desktop_surface *surface, + struct weston_seat *seat, uint32_t serial, void *user_data) +{ + /* Not supported */ +} + +static void +desktop_surface_resize(struct weston_desktop_surface *surface, + struct weston_seat *seat, uint32_t serial, + enum weston_desktop_surface_edge edges, void *user_data) +{ + /* Not supported */ +} + +static void +desktop_surface_fullscreen_requested(struct weston_desktop_surface *surface, + bool fullscreen, + struct weston_output *output, + void *user_data) +{ + /* Not supported */ +} + +static void +desktop_surface_maximized_requested(struct weston_desktop_surface *surface, + bool maximized, void *user_data) +{ + /* Not supported */ +} + +static void +desktop_surface_minimized_requested(struct weston_desktop_surface *surface, + void *user_data) +{ + /* Not supported */ +} + +static void +desktop_surface_set_xwayland_position(struct weston_desktop_surface *surface, + int32_t x, int32_t y, void *user_data) +{ + /* Not supported */ +} + +static const struct weston_desktop_api shell_desktop_api = { + .struct_size = sizeof(struct weston_desktop_api), + .ping_timeout = desktop_surface_ping_timeout, + .pong = desktop_surface_pong, + .surface_added = desktop_surface_added, + .surface_removed = desktop_surface_removed, + .committed = desktop_surface_committed, + + .move = desktop_surface_move, + .resize = desktop_surface_resize, + .fullscreen_requested = desktop_surface_fullscreen_requested, + .maximized_requested = desktop_surface_maximized_requested, + .minimized_requested = desktop_surface_minimized_requested, + .set_xwayland_position = desktop_surface_set_xwayland_position, +}; + +/* + * end of libweston-desktop + */ + +/* + * Initialization of ivi-shell. + */ +WL_EXPORT int +wet_shell_init(struct weston_compositor *compositor, + int *argc, char *argv[]) +{ + struct ivi_shell *shell; + + shell = zalloc(sizeof *shell); + if (shell == NULL) + return -1; + + if (!weston_compositor_add_destroy_listener_once(compositor, + &shell->destroy_listener, + shell_destroy)) { + free(shell); + return 0; + } + + init_ivi_shell(compositor, shell); + + shell->wake_listener.notify = wake_handler; + wl_signal_add(&compositor->wake_signal, &shell->wake_listener); + + shell->desktop = weston_desktop_create(compositor, &shell_desktop_api, shell); + if (!shell->desktop) + goto err_shell; + + if (wl_global_create(compositor->wl_display, + &ivi_application_interface, 1, + shell, bind_ivi_application) == NULL) + goto err_desktop; + + ivi_layout_init_with_compositor(compositor); + shell_add_bindings(compositor, shell); + + return IVI_SUCCEEDED; + +err_desktop: + weston_desktop_destroy(shell->desktop); + +err_shell: + wl_list_remove(&shell->destroy_listener.link); + free(shell); + + return IVI_FAILED; +} diff --git a/ivi-shell/ivi-shell.h b/ivi-shell/ivi-shell.h new file mode 100644 index 0000000..d7f1cdb --- /dev/null +++ b/ivi-shell/ivi-shell.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2013 DENSO CORPORATION + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_IVI_SHELL_H +#define WESTON_IVI_SHELL_H + +#include +#include + +#include +#include + +struct ivi_shell +{ + struct wl_listener destroy_listener; + struct wl_listener wake_listener; + + struct weston_compositor *compositor; + + struct weston_desktop *desktop; + struct wl_list ivi_surface_list; /* struct ivi_shell_surface::link */ +}; + +void +shell_surface_send_configure(struct weston_surface *surface, + int32_t width, int32_t height); + +struct ivi_layout_surface; + +struct ivi_layout_surface * +shell_get_ivi_layout_surface(struct weston_surface *surface); + +#endif /* WESTON_IVI_SHELL_H */ diff --git a/ivi-shell/meson.build b/ivi-shell/meson.build new file mode 100644 index 0000000..03fc717 --- /dev/null +++ b/ivi-shell/meson.build @@ -0,0 +1,61 @@ +if get_option('shell-ivi') + srcs_shell_ivi = [ + 'ivi-shell.c', + 'ivi-layout.c', + 'ivi-layout-transition.c', + ivi_application_server_protocol_h, + ivi_application_protocol_c, + input_method_unstable_v1_server_protocol_h, + input_method_unstable_v1_protocol_c, + ] + plugin_shell_ivi = shared_library( + 'ivi-shell', + srcs_shell_ivi, + include_directories: common_inc, + dependencies: [ + dep_libm, + dep_libexec_weston, + dep_lib_desktop, + dep_libweston_public + ], + name_prefix: '', + install: true, + install_dir: dir_module_weston, + install_rpath: '$ORIGIN' + ) + env_modmap += 'ivi-shell.so=@0@;'.format(plugin_shell_ivi.full_path()) + + install_headers('ivi-layout-export.h', subdir: 'weston') + + srcs_ivi_hmi = [ + 'hmi-controller.c', + ivi_hmi_controller_server_protocol_h, + ivi_hmi_controller_protocol_c, + ] + plugin_ivi_hmi = shared_library( + 'hmi-controller', + srcs_ivi_hmi, + include_directories: common_inc, + dependencies: [ + dep_libexec_weston, + dep_libweston_public, + dep_libshared + ], + name_prefix: '', + install: true, + install_dir: dir_module_weston, + install_rpath: '$ORIGIN' + ) + env_modmap += 'hmi-controller.so=@0@;'.format(plugin_ivi_hmi.full_path()) + + ivi_test_config = configuration_data() + ivi_test_config.set('bindir', dir_bin) + ivi_test_config.set('libexecdir', dir_libexec) + ivi_test_config.set('plugin_prefix', meson.current_build_dir()) + ivi_test_config.set('westondatadir', join_paths(dir_data, 'weston')) + ivi_test_ini = configure_file( + input: '../ivi-shell/weston.ini.in', + output: 'weston-ivi-test.ini', + configuration: ivi_test_config + ) +endif diff --git a/ivi-shell/weston.ini.in b/ivi-shell/weston.ini.in new file mode 100644 index 0000000..3bdfbeb --- /dev/null +++ b/ivi-shell/weston.ini.in @@ -0,0 +1,98 @@ +[core] +shell=ivi-shell.so +modules=hmi-controller.so + +[ivi-shell] +ivi-shell-user-interface=weston-ivi-shell-user-interface + +#developermode=true + +cursor-theme=default +cursor-size=32 + +base-layer-id=1000 +base-layer-id-offset=10000 + +workspace-background-layer-id=2000 +workspace-layer-id=3000 +application-layer-id=4000 + +transition-duration=300 + +background-image=@westondatadir@/background.png +background-id=1001 +panel-image=@westondatadir@/panel.png +panel-id=1002 +surface-id-offset=10 +tiling-image=@westondatadir@/tiling.png +tiling-id=1003 +sidebyside-image=@westondatadir@/sidebyside.png +sidebyside-id=1004 +fullscreen-image=@westondatadir@/fullscreen.png +fullscreen-id=1005 +random-image=@westondatadir@/random.png +random-id=1006 +home-image=@westondatadir@/home.png +home-id=1007 +workspace-background-color=0x99000000 +workspace-background-id=2001 + +[ivi-launcher] +workspace-id=0 +icon-id=4001 +icon=@westondatadir@/icon_ivi_flower.png +path=@bindir@/weston-flower + +[ivi-launcher] +workspace-id=0 +icon-id=4002 +icon=@westondatadir@/icon_ivi_clickdot.png +path=@bindir@/weston-clickdot + +[ivi-launcher] +workspace-id=1 +icon-id=4003 +icon=@westondatadir@/icon_ivi_simple-egl.png +path=@bindir@/weston-simple-egl + +[ivi-launcher] +workspace-id=1 +icon-id=4004 +icon=@westondatadir@/icon_ivi_simple-shm.png +path=@bindir@/weston-simple-shm + +[ivi-launcher] +workspace-id=2 +icon-id=4005 +icon=@westondatadir@/icon_ivi_smoke.png +path=@bindir@/weston-smoke + +[ivi-launcher] +workspace-id=3 +icon-id=4006 +icon=@westondatadir@/icon_ivi_flower.png +path=@bindir@/weston-flower + +[ivi-launcher] +workspace-id=3 +icon-id=4007 +icon=@westondatadir@/icon_ivi_clickdot.png +path=@bindir@/weston-clickdot + +[ivi-launcher] +workspace-id=3 +icon-id=4008 +icon=@westondatadir@/icon_ivi_simple-egl.png +path=@bindir@/weston-simple-egl + +[ivi-launcher] +workspace-id=3 +icon-id=4009 +icon=@westondatadir@/icon_ivi_simple-shm.png +path=@bindir@/weston-simple-shm + +[ivi-launcher] +workspace-id=3 +icon-id=4010 +icon=@westondatadir@/icon_ivi_smoke.png +path=@bindir@/weston-smoke diff --git a/kiosk-shell/kiosk-shell-grab.c b/kiosk-shell/kiosk-shell-grab.c new file mode 100644 index 0000000..3ea0156 --- /dev/null +++ b/kiosk-shell/kiosk-shell-grab.c @@ -0,0 +1,314 @@ +/* + * Copyright 2010-2012 Intel Corporation + * Copyright 2013 Raspberry Pi Foundation + * Copyright 2011-2012,2020 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "kiosk-shell-grab.h" +#include "shared/helpers.h" + +struct kiosk_shell_grab { + struct kiosk_shell_surface *shsurf; + struct wl_listener shsurf_destroy_listener; + + struct weston_pointer_grab pointer_grab; + struct weston_touch_grab touch_grab; + wl_fixed_t dx, dy; + bool active; +}; + +static void +kiosk_shell_grab_destroy(struct kiosk_shell_grab *shgrab); + +/* + * pointer_move_grab_interface + */ + +static void +pointer_move_grab_focus(struct weston_pointer_grab *grab) +{ +} + +static void +pointer_move_grab_axis(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_axis_event *event) +{ +} + +static void +pointer_move_grab_axis_source(struct weston_pointer_grab *grab, + uint32_t source) +{ +} + +static void +pointer_move_grab_frame(struct weston_pointer_grab *grab) +{ +} + +static void +pointer_move_grab_motion(struct weston_pointer_grab *pointer_grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + struct kiosk_shell_grab *shgrab = + container_of(pointer_grab, struct kiosk_shell_grab, pointer_grab); + struct weston_pointer *pointer = pointer_grab->pointer; + struct kiosk_shell_surface *shsurf = shgrab->shsurf; + struct weston_surface *surface; + int dx, dy; + + weston_pointer_move(pointer, event); + + if (!shsurf) + return; + + surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); + + dx = wl_fixed_to_int(pointer->x + shgrab->dx); + dy = wl_fixed_to_int(pointer->y + shgrab->dy); + + weston_view_set_position(shsurf->view, dx, dy); + + weston_compositor_schedule_repaint(surface->compositor); +} + +static void +pointer_move_grab_button(struct weston_pointer_grab *pointer_grab, + const struct timespec *time, + uint32_t button, uint32_t state_w) +{ + struct kiosk_shell_grab *shgrab = + container_of(pointer_grab, struct kiosk_shell_grab, pointer_grab); + struct weston_pointer *pointer = pointer_grab->pointer; + enum wl_pointer_button_state state = state_w; + + if (pointer->button_count == 0 && + state == WL_POINTER_BUTTON_STATE_RELEASED) + kiosk_shell_grab_destroy(shgrab); +} + +static void +pointer_move_grab_cancel(struct weston_pointer_grab *pointer_grab) +{ + struct kiosk_shell_grab *shgrab = + container_of(pointer_grab, struct kiosk_shell_grab, pointer_grab); + + kiosk_shell_grab_destroy(shgrab); +} + +static const struct weston_pointer_grab_interface pointer_move_grab_interface = { + pointer_move_grab_focus, + pointer_move_grab_motion, + pointer_move_grab_button, + pointer_move_grab_axis, + pointer_move_grab_axis_source, + pointer_move_grab_frame, + pointer_move_grab_cancel, +}; + +/* + * touch_move_grab_interface + */ + +static void +touch_move_grab_down(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id, wl_fixed_t x, wl_fixed_t y) +{ +} + +static void +touch_move_grab_up(struct weston_touch_grab *touch_grab, + const struct timespec *time, int touch_id) +{ + struct kiosk_shell_grab *shgrab = + container_of(touch_grab, struct kiosk_shell_grab, touch_grab); + + if (touch_id == 0) + shgrab->active = false; + + if (touch_grab->touch->num_tp == 0) + kiosk_shell_grab_destroy(shgrab); +} + +static void +touch_move_grab_motion(struct weston_touch_grab *touch_grab, + const struct timespec *time, int touch_id, + wl_fixed_t x, wl_fixed_t y) +{ + struct kiosk_shell_grab *shgrab = + container_of(touch_grab, struct kiosk_shell_grab, touch_grab); + struct weston_touch *touch = touch_grab->touch; + struct kiosk_shell_surface *shsurf = shgrab->shsurf; + struct weston_surface *surface; + int dx, dy; + + if (!shsurf || !shgrab->active) + return; + + surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); + + dx = wl_fixed_to_int(touch->grab_x + shgrab->dx); + dy = wl_fixed_to_int(touch->grab_y + shgrab->dy); + + weston_view_set_position(shsurf->view, dx, dy); + + weston_compositor_schedule_repaint(surface->compositor); +} + +static void +touch_move_grab_frame(struct weston_touch_grab *grab) +{ +} + +static void +touch_move_grab_cancel(struct weston_touch_grab *touch_grab) +{ + struct kiosk_shell_grab *shgrab = + container_of(touch_grab, struct kiosk_shell_grab, touch_grab); + + kiosk_shell_grab_destroy(shgrab); +} + +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_frame, + touch_move_grab_cancel, +}; + +/* + * kiosk_shell_grab + */ + +static void +kiosk_shell_grab_handle_shsurf_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell_grab *shgrab = + container_of(listener, struct kiosk_shell_grab, + shsurf_destroy_listener); + + shgrab->shsurf = NULL; +} + +static struct kiosk_shell_grab * +kiosk_shell_grab_create(struct kiosk_shell_surface *shsurf) +{ + struct kiosk_shell_grab *shgrab; + + shgrab = zalloc(sizeof *shgrab); + if (!shgrab) + return NULL; + + shgrab->shsurf = shsurf; + shgrab->shsurf_destroy_listener.notify = + kiosk_shell_grab_handle_shsurf_destroy; + wl_signal_add(&shsurf->destroy_signal, + &shgrab->shsurf_destroy_listener); + + shsurf->grabbed = true; + + return shgrab; +} + +enum kiosk_shell_grab_result +kiosk_shell_grab_start_for_pointer_move(struct kiosk_shell_surface *shsurf, + struct weston_pointer *pointer) +{ + struct kiosk_shell_grab *shgrab; + + if (!shsurf) + return KIOSK_SHELL_GRAB_RESULT_ERROR; + + if (shsurf->grabbed || + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return KIOSK_SHELL_GRAB_RESULT_IGNORED; + + shgrab = kiosk_shell_grab_create(shsurf); + if (!shgrab) + return KIOSK_SHELL_GRAB_RESULT_ERROR; + + shgrab->dx = wl_fixed_from_double(shsurf->view->geometry.x) - + pointer->grab_x; + shgrab->dy = wl_fixed_from_double(shsurf->view->geometry.y) - + pointer->grab_y; + shgrab->active = true; + + weston_seat_break_desktop_grabs(pointer->seat); + + shgrab->pointer_grab.interface = &pointer_move_grab_interface; + weston_pointer_start_grab(pointer, &shgrab->pointer_grab); + + return KIOSK_SHELL_GRAB_RESULT_OK; +} + +enum kiosk_shell_grab_result +kiosk_shell_grab_start_for_touch_move(struct kiosk_shell_surface *shsurf, + struct weston_touch *touch) +{ + struct kiosk_shell_grab *shgrab; + + if (!shsurf) + return KIOSK_SHELL_GRAB_RESULT_ERROR; + + if (shsurf->grabbed || + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return KIOSK_SHELL_GRAB_RESULT_IGNORED; + + shgrab = kiosk_shell_grab_create(shsurf); + if (!shgrab) + return KIOSK_SHELL_GRAB_RESULT_ERROR; + + shgrab->dx = wl_fixed_from_double(shsurf->view->geometry.x) - + touch->grab_x; + shgrab->dy = wl_fixed_from_double(shsurf->view->geometry.y) - + touch->grab_y; + shgrab->active = true; + + weston_seat_break_desktop_grabs(touch->seat); + + shgrab->touch_grab.interface = &touch_move_grab_interface; + weston_touch_start_grab(touch, &shgrab->touch_grab); + + return KIOSK_SHELL_GRAB_RESULT_OK; +} + +static void +kiosk_shell_grab_destroy(struct kiosk_shell_grab *shgrab) +{ + if (shgrab->shsurf) { + wl_list_remove(&shgrab->shsurf_destroy_listener.link); + shgrab->shsurf->grabbed = false; + } + + if (shgrab->pointer_grab.pointer) + weston_pointer_end_grab(shgrab->pointer_grab.pointer); + else if (shgrab->touch_grab.touch) + weston_touch_end_grab(shgrab->touch_grab.touch); + + free(shgrab); +} diff --git a/kiosk-shell/kiosk-shell-grab.h b/kiosk-shell/kiosk-shell-grab.h new file mode 100644 index 0000000..bf78384 --- /dev/null +++ b/kiosk-shell/kiosk-shell-grab.h @@ -0,0 +1,43 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef WESTON_KIOSK_SHELL_GRAB_H +#define WESTON_KIOSK_SHELL_GRAB_H + +#include "kiosk-shell.h" + +enum kiosk_shell_grab_result { + KIOSK_SHELL_GRAB_RESULT_OK, + KIOSK_SHELL_GRAB_RESULT_IGNORED, + KIOSK_SHELL_GRAB_RESULT_ERROR, +}; + +enum kiosk_shell_grab_result +kiosk_shell_grab_start_for_pointer_move(struct kiosk_shell_surface *shsurf, + struct weston_pointer *pointer); + +enum kiosk_shell_grab_result +kiosk_shell_grab_start_for_touch_move(struct kiosk_shell_surface *shsurf, + struct weston_touch *touch); + +#endif /* WESTON_KIOSK_SHELL_GRAB_H */ diff --git a/kiosk-shell/kiosk-shell.c b/kiosk-shell/kiosk-shell.c new file mode 100644 index 0000000..ac9c868 --- /dev/null +++ b/kiosk-shell/kiosk-shell.c @@ -0,0 +1,1071 @@ +/* + * Copyright 2010-2012 Intel Corporation + * Copyright 2013 Raspberry Pi Foundation + * Copyright 2011-2012,2020 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "kiosk-shell.h" +#include "kiosk-shell-grab.h" +#include "compositor/weston.h" +#include "shared/helpers.h" +#include "util.h" + +static struct kiosk_shell_surface * +get_kiosk_shell_surface(struct weston_surface *surface) +{ + struct weston_desktop_surface *desktop_surface = + weston_surface_get_desktop_surface(surface); + + if (desktop_surface) + return weston_desktop_surface_get_user_data(desktop_surface); + + return NULL; +} + +static void +kiosk_shell_seat_handle_destroy(struct wl_listener *listener, void *data); + +static struct kiosk_shell_seat * +get_kiosk_shell_seat(struct weston_seat *seat) +{ + struct wl_listener *listener; + + listener = wl_signal_get(&seat->destroy_signal, + kiosk_shell_seat_handle_destroy); + assert(listener != NULL); + + return container_of(listener, + struct kiosk_shell_seat, seat_destroy_listener); +} + +/* + * kiosk_shell_surface + */ + +static void +kiosk_shell_surface_set_output(struct kiosk_shell_surface *shsurf, + struct weston_output *output); +static void +kiosk_shell_surface_set_parent(struct kiosk_shell_surface *shsurf, + struct kiosk_shell_surface *parent); + +static void +kiosk_shell_surface_notify_parent_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell_surface *shsurf = + container_of(listener, + struct kiosk_shell_surface, parent_destroy_listener); + + kiosk_shell_surface_set_parent(shsurf, shsurf->parent->parent); +} + +static void +kiosk_shell_surface_notify_output_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell_surface *shsurf = + container_of(listener, + struct kiosk_shell_surface, output_destroy_listener); + + kiosk_shell_surface_set_output(shsurf, NULL); +} + +static struct kiosk_shell_surface * +kiosk_shell_surface_get_parent_root(struct kiosk_shell_surface *shsurf) +{ + struct kiosk_shell_surface *root = shsurf; + while (root->parent) + root = root->parent; + return root; +} + +static bool +kiosk_shell_output_has_app_id(struct kiosk_shell_output *shoutput, + const char *app_id); + +static struct weston_output * +kiosk_shell_surface_find_best_output(struct kiosk_shell_surface *shsurf) +{ + struct weston_output *output; + struct kiosk_shell_output *shoutput; + struct kiosk_shell_surface *root; + const char *app_id; + + /* Always use current output if any. */ + if (shsurf->output) + return shsurf->output; + + /* Check if we have a designated output for this app. */ + app_id = weston_desktop_surface_get_app_id(shsurf->desktop_surface); + if (app_id) { + wl_list_for_each(shoutput, &shsurf->shell->output_list, link) { + if (kiosk_shell_output_has_app_id(shoutput, app_id)) + return shoutput->output; + } + } + + /* Group all related windows in the same output. */ + root = kiosk_shell_surface_get_parent_root(shsurf); + if (root->output) + return root->output; + + output = get_focused_output(shsurf->shell->compositor); + if (output) + return output; + + output = get_default_output(shsurf->shell->compositor); + if (output) + return output; + + return NULL; +} + +static void +kiosk_shell_surface_set_output(struct kiosk_shell_surface *shsurf, + struct weston_output *output) +{ + shsurf->output = output; + + if (shsurf->output_destroy_listener.notify) { + wl_list_remove(&shsurf->output_destroy_listener.link); + shsurf->output_destroy_listener.notify = NULL; + } + + if (!shsurf->output) + return; + + shsurf->output_destroy_listener.notify = + kiosk_shell_surface_notify_output_destroy; + wl_signal_add(&shsurf->output->destroy_signal, + &shsurf->output_destroy_listener); +} + +static void +kiosk_shell_surface_set_fullscreen(struct kiosk_shell_surface *shsurf, + struct weston_output *output) +{ + if (!output) + output = kiosk_shell_surface_find_best_output(shsurf); + + kiosk_shell_surface_set_output(shsurf, output); + + weston_desktop_surface_set_fullscreen(shsurf->desktop_surface, true); + if (shsurf->output) + weston_desktop_surface_set_size(shsurf->desktop_surface, + shsurf->output->width, + shsurf->output->height); +} + +static void +kiosk_shell_surface_set_maximized(struct kiosk_shell_surface *shsurf) +{ + struct weston_output *output = + kiosk_shell_surface_find_best_output(shsurf); + + kiosk_shell_surface_set_output(shsurf, output); + + weston_desktop_surface_set_maximized(shsurf->desktop_surface, true); + if (shsurf->output) + weston_desktop_surface_set_size(shsurf->desktop_surface, + shsurf->output->width, + shsurf->output->height); +} + +static void +kiosk_shell_surface_set_normal(struct kiosk_shell_surface *shsurf) +{ + if (!shsurf->output) + kiosk_shell_surface_set_output(shsurf, + kiosk_shell_surface_find_best_output(shsurf)); + + weston_desktop_surface_set_fullscreen(shsurf->desktop_surface, false); + weston_desktop_surface_set_maximized(shsurf->desktop_surface, false); + weston_desktop_surface_set_size(shsurf->desktop_surface, 0, 0); +} + +static void +kiosk_shell_surface_set_parent(struct kiosk_shell_surface *shsurf, + struct kiosk_shell_surface *parent) +{ + if (shsurf->parent_destroy_listener.notify) { + wl_list_remove(&shsurf->parent_destroy_listener.link); + shsurf->parent_destroy_listener.notify = NULL; + } + + shsurf->parent = parent; + + if (shsurf->parent) { + shsurf->parent_destroy_listener.notify = + kiosk_shell_surface_notify_parent_destroy; + wl_signal_add(&shsurf->parent->destroy_signal, + &shsurf->parent_destroy_listener); + kiosk_shell_surface_set_output(shsurf, NULL); + kiosk_shell_surface_set_normal(shsurf); + } else { + kiosk_shell_surface_set_fullscreen(shsurf, shsurf->output); + } +} + +static void +kiosk_shell_surface_reconfigure_for_output(struct kiosk_shell_surface *shsurf) +{ + struct weston_desktop_surface *desktop_surface; + + if (!shsurf->output) + return; + + desktop_surface = shsurf->desktop_surface; + + if (weston_desktop_surface_get_maximized(desktop_surface) || + weston_desktop_surface_get_fullscreen(desktop_surface)) { + weston_desktop_surface_set_size(desktop_surface, + shsurf->output->width, + shsurf->output->height); + } + + center_on_output(shsurf->view, shsurf->output); + weston_view_update_transform(shsurf->view); +} + +static void +kiosk_shell_surface_destroy(struct kiosk_shell_surface *shsurf) +{ + wl_signal_emit(&shsurf->destroy_signal, shsurf); + + weston_desktop_surface_set_user_data(shsurf->desktop_surface, NULL); + shsurf->desktop_surface = NULL; + + weston_desktop_surface_unlink_view(shsurf->view); + + weston_view_destroy(shsurf->view); + + if (shsurf->output_destroy_listener.notify) { + wl_list_remove(&shsurf->output_destroy_listener.link); + shsurf->output_destroy_listener.notify = NULL; + } + + if (shsurf->parent_destroy_listener.notify) { + wl_list_remove(&shsurf->parent_destroy_listener.link); + shsurf->parent_destroy_listener.notify = NULL; + shsurf->parent = NULL; + } + + free(shsurf); +} + +static struct kiosk_shell_surface * +kiosk_shell_surface_create(struct kiosk_shell *shell, + struct weston_desktop_surface *desktop_surface) +{ + struct weston_desktop_client *client = + weston_desktop_surface_get_client(desktop_surface); + struct wl_client *wl_client = + weston_desktop_client_get_client(client); + struct weston_view *view; + struct kiosk_shell_surface *shsurf; + + view = weston_desktop_surface_create_view(desktop_surface); + if (!view) + return NULL; + + shsurf = zalloc(sizeof *shsurf); + if (!shsurf) { + if (wl_client) + wl_client_post_no_memory(wl_client); + else + weston_log("no memory to allocate shell surface\n"); + return NULL; + } + + shsurf->desktop_surface = desktop_surface; + shsurf->view = view; + shsurf->shell = shell; + + weston_desktop_surface_set_user_data(desktop_surface, shsurf); + + wl_signal_init(&shsurf->destroy_signal); + + return shsurf; +} + +/* + * kiosk_shell_seat + */ + +static void +kiosk_shell_seat_handle_keyboard_focus(struct wl_listener *listener, void *data) +{ + struct weston_keyboard *keyboard = data; + struct kiosk_shell_seat *shseat = get_kiosk_shell_seat(keyboard->seat); + + if (shseat->focused_surface) { + struct kiosk_shell_surface *shsurf = + get_kiosk_shell_surface(shseat->focused_surface); + if (shsurf && --shsurf->focus_count == 0) + weston_desktop_surface_set_activated(shsurf->desktop_surface, + false); + } + + shseat->focused_surface = weston_surface_get_main_surface(keyboard->focus); + + if (shseat->focused_surface) { + struct kiosk_shell_surface *shsurf = + get_kiosk_shell_surface(shseat->focused_surface); + if (shsurf && shsurf->focus_count++ == 0) + weston_desktop_surface_set_activated(shsurf->desktop_surface, + true); + } +} + +static void +kiosk_shell_seat_handle_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell_seat *shseat = + container_of(listener, + struct kiosk_shell_seat, seat_destroy_listener); + + wl_list_remove(&shseat->keyboard_focus_listener.link); + wl_list_remove(&shseat->caps_changed_listener.link); + wl_list_remove(&shseat->seat_destroy_listener.link); + free(shseat); +} + +static void +kiosk_shell_seat_handle_caps_changed(struct wl_listener *listener, void *data) +{ + struct weston_keyboard *keyboard; + struct kiosk_shell_seat *shseat; + + shseat = container_of(listener, struct kiosk_shell_seat, + caps_changed_listener); + keyboard = weston_seat_get_keyboard(shseat->seat); + + if (keyboard && + wl_list_empty(&shseat->keyboard_focus_listener.link)) { + wl_signal_add(&keyboard->focus_signal, + &shseat->keyboard_focus_listener); + } else if (!keyboard) { + wl_list_remove(&shseat->keyboard_focus_listener.link); + wl_list_init(&shseat->keyboard_focus_listener.link); + } +} + +static struct kiosk_shell_seat * +kiosk_shell_seat_create(struct weston_seat *seat) +{ + struct kiosk_shell_seat *shseat; + + shseat = zalloc(sizeof *shseat); + if (!shseat) { + weston_log("no memory to allocate shell seat\n"); + return NULL; + } + + shseat->seat = seat; + + shseat->seat_destroy_listener.notify = kiosk_shell_seat_handle_destroy; + wl_signal_add(&seat->destroy_signal, &shseat->seat_destroy_listener); + + shseat->keyboard_focus_listener.notify = kiosk_shell_seat_handle_keyboard_focus; + wl_list_init(&shseat->keyboard_focus_listener.link); + + shseat->caps_changed_listener.notify = kiosk_shell_seat_handle_caps_changed; + wl_signal_add(&seat->updated_caps_signal, + &shseat->caps_changed_listener); + kiosk_shell_seat_handle_caps_changed(&shseat->caps_changed_listener, NULL); + + return shseat; +} + +/* + * kiosk_shell_output + */ + +static int +kiosk_shell_background_surface_get_label(struct weston_surface *surface, + char *buf, size_t len) +{ + return snprintf(buf, len, "kiosk shell background surface"); +} + +static void +kiosk_shell_output_recreate_background(struct kiosk_shell_output *shoutput) +{ + struct kiosk_shell *shell = shoutput->shell; + struct weston_output *output = shoutput->output; + + if (shoutput->background_view) + weston_surface_destroy(shoutput->background_view->surface); + + if (!output) + return; + + shoutput->background_view = + create_colored_surface(shoutput->shell->compositor, + 0.5, 0.5, 0.5, + output->x, output->y, + output->width, + output->height); + + weston_surface_set_role(shoutput->background_view->surface, + "kiosk-shell-background", NULL, 0); + weston_surface_set_label_func(shoutput->background_view->surface, + kiosk_shell_background_surface_get_label); + + weston_layer_entry_insert(&shell->background_layer.view_list, + &shoutput->background_view->layer_link); + + shoutput->background_view->is_mapped = true; + shoutput->background_view->surface->is_mapped = true; + shoutput->background_view->surface->output = output; + weston_view_set_output(shoutput->background_view, output); +} + +static void +kiosk_shell_output_destroy(struct kiosk_shell_output *shoutput) +{ + shoutput->output = NULL; + shoutput->output_destroy_listener.notify = NULL; + + if (shoutput->background_view) + weston_surface_destroy(shoutput->background_view->surface); + + wl_list_remove(&shoutput->output_destroy_listener.link); + wl_list_remove(&shoutput->link); + + free(shoutput->app_ids); + + free(shoutput); +} + +static bool +kiosk_shell_output_has_app_id(struct kiosk_shell_output *shoutput, + const char *app_id) +{ + char *cur; + size_t app_id_len; + + if (!shoutput->app_ids) + return false; + + cur = shoutput->app_ids; + app_id_len = strlen(app_id); + + while ((cur = strstr(cur, app_id))) { + /* Check whether we have found a complete match of app_id. */ + if ((cur[app_id_len] == ',' || cur[app_id_len] == '\0') && + (cur == shoutput->app_ids || cur[-1] == ',')) + return true; + cur++; + } + + return false; +} + +static void +kiosk_shell_output_configure(struct kiosk_shell_output *shoutput) +{ + struct weston_config *wc = wet_get_config(shoutput->shell->compositor); + struct weston_config_section *section = + weston_config_get_section(wc, "output", "name", shoutput->output->name); + + assert(shoutput->app_ids == NULL); + + if (section) { + weston_config_section_get_string(section, "app-ids", + &shoutput->app_ids, NULL); + } +} + +static void +kiosk_shell_output_notify_output_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell_output *shoutput = + container_of(listener, + struct kiosk_shell_output, output_destroy_listener); + + kiosk_shell_output_destroy(shoutput); +} + +static struct kiosk_shell_output * +kiosk_shell_output_create(struct kiosk_shell *shell, struct weston_output *output) +{ + struct kiosk_shell_output *shoutput; + + shoutput = zalloc(sizeof *shoutput); + if (shoutput == NULL) + return NULL; + + shoutput->output = output; + shoutput->shell = shell; + + shoutput->output_destroy_listener.notify = + kiosk_shell_output_notify_output_destroy; + wl_signal_add(&shoutput->output->destroy_signal, + &shoutput->output_destroy_listener); + + wl_list_insert(shell->output_list.prev, &shoutput->link); + + kiosk_shell_output_recreate_background(shoutput); + kiosk_shell_output_configure(shoutput); + + return shoutput; +} + +/* + * libweston-desktop + */ + +static void +desktop_surface_added(struct weston_desktop_surface *desktop_surface, + void *data) +{ + struct kiosk_shell *shell = data; + struct kiosk_shell_surface *shsurf; + struct weston_seat *seat; + + shsurf = kiosk_shell_surface_create(shell, desktop_surface); + if (!shsurf) + return; + + kiosk_shell_surface_set_fullscreen(shsurf, NULL); + + wl_list_for_each(seat, &shell->compositor->seat_list, link) + weston_view_activate(shsurf->view, seat, 0); +} + +/* Return the view that should gain focus after the specified shsurf is + * destroyed. We prefer the top remaining view from the same parent surface, + * but if we can't find one we fall back to the top view regardless of + * parentage. */ +static struct weston_view * +find_focus_successor(struct weston_layer *layer, + struct kiosk_shell_surface *shsurf) +{ + struct kiosk_shell_surface *parent_root = + kiosk_shell_surface_get_parent_root(shsurf); + struct weston_view *top_view = NULL; + struct weston_view *view; + + wl_list_for_each(view, &layer->view_list.link, layer_link.link) { + struct kiosk_shell_surface *view_shsurf; + struct kiosk_shell_surface *root; + + if (!view->is_mapped || view == shsurf->view) + continue; + + view_shsurf = get_kiosk_shell_surface(view->surface); + if (!view_shsurf) + continue; + + if (!top_view) + top_view = view; + + root = kiosk_shell_surface_get_parent_root(view_shsurf); + if (root == parent_root) + return view; + } + + return top_view; +} + +static void +desktop_surface_removed(struct weston_desktop_surface *desktop_surface, + void *data) +{ + struct kiosk_shell *shell = data; + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + struct weston_view *focus_view; + struct weston_seat *seat; + + if (!shsurf) + return; + + focus_view = find_focus_successor(&shell->normal_layer, shsurf); + + if (focus_view) { + wl_list_for_each(seat, &shell->compositor->seat_list, link) { + struct weston_keyboard *keyboard = seat->keyboard_state; + if (keyboard && keyboard->focus == surface) + weston_view_activate(focus_view, seat, 0); + } + } + + kiosk_shell_surface_destroy(shsurf); +} + +static void +desktop_surface_committed(struct weston_desktop_surface *desktop_surface, + int32_t sx, int32_t sy, void *data) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + bool is_resized; + bool is_fullscreen; + + if (surface->width == 0) + return; + + /* TODO: When the top-level surface is committed with a new size after an + * output resize, sometimes the view appears scaled. What state are we not + * updating? + */ + + is_resized = surface->width != shsurf->last_width || + surface->height != shsurf->last_height; + is_fullscreen = weston_desktop_surface_get_maximized(desktop_surface) || + weston_desktop_surface_get_fullscreen(desktop_surface); + + if (!weston_surface_is_mapped(surface) || (is_resized && is_fullscreen)) { + if (is_fullscreen || !shsurf->xwayland.is_set) { + center_on_output(shsurf->view, shsurf->output); + } else { + struct weston_geometry geometry = + weston_desktop_surface_get_geometry(desktop_surface); + float x = shsurf->xwayland.x - geometry.x; + float y = shsurf->xwayland.y - geometry.y; + + weston_view_set_position(shsurf->view, x, y); + } + + weston_view_update_transform(shsurf->view); + } + + if (!weston_surface_is_mapped(surface)) { + weston_layer_entry_insert(&shsurf->shell->normal_layer.view_list, + &shsurf->view->layer_link); + shsurf->view->is_mapped = true; + surface->is_mapped = true; + } + + if (!is_fullscreen && (sx != 0 || sy != 0)) { + float from_x, from_y; + float to_x, to_y; + float x, y; + + weston_view_to_global_float(shsurf->view, 0, 0, &from_x, &from_y); + weston_view_to_global_float(shsurf->view, sx, sy, &to_x, &to_y); + x = shsurf->view->geometry.x + to_x - from_x; + y = shsurf->view->geometry.y + to_y - from_y; + + weston_view_set_position(shsurf->view, x, y); + weston_view_update_transform(shsurf->view); + } + + shsurf->last_width = surface->width; + shsurf->last_height = surface->height; +} + +static void +desktop_surface_move(struct weston_desktop_surface *desktop_surface, + struct weston_seat *seat, uint32_t serial, void *shell) +{ + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_touch *touch = weston_seat_get_touch(seat); + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_surface *focus; + + if (pointer && + pointer->focus && + pointer->button_count > 0 && + pointer->grab_serial == serial) { + focus = weston_surface_get_main_surface(pointer->focus->surface); + if ((focus == surface) && + (kiosk_shell_grab_start_for_pointer_move(shsurf, pointer) == + KIOSK_SHELL_GRAB_RESULT_ERROR)) + wl_resource_post_no_memory(surface->resource); + } + else if (touch && + touch->focus && + touch->grab_serial == serial) { + focus = weston_surface_get_main_surface(touch->focus->surface); + if ((focus == surface) && + (kiosk_shell_grab_start_for_touch_move(shsurf, touch) == + KIOSK_SHELL_GRAB_RESULT_ERROR)) + wl_resource_post_no_memory(surface->resource); + } +} + +static void +desktop_surface_resize(struct weston_desktop_surface *desktop_surface, + struct weston_seat *seat, uint32_t serial, + enum weston_desktop_surface_edge edges, void *shell) +{ +} + +static void +desktop_surface_set_parent(struct weston_desktop_surface *desktop_surface, + struct weston_desktop_surface *parent, + void *shell) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct kiosk_shell_surface *shsurf_parent = + parent ? weston_desktop_surface_get_user_data(parent) : NULL; + + kiosk_shell_surface_set_parent(shsurf, shsurf_parent); +} + +static void +desktop_surface_fullscreen_requested(struct weston_desktop_surface *desktop_surface, + bool fullscreen, + struct weston_output *output, void *shell) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + /* We should normally be able to ignore fullscreen requests for + * top-level surfaces, since we set them as fullscreen at creation + * time. However, xwayland surfaces set their internal WM state + * regardless of what the shell wants, so they may remove fullscreen + * state before informing weston-desktop of this request. Since we + * always want top-level surfaces to be fullscreen, we need to reapply + * the fullscreen state to force the correct xwayland WM state. + * + * TODO: Explore a model where the XWayland WM doesn't set the internal + * WM surface state itself, rather letting the shell make the decision. + */ + + if (!shsurf->parent || fullscreen) + kiosk_shell_surface_set_fullscreen(shsurf, output); + else + kiosk_shell_surface_set_normal(shsurf); +} + +static void +desktop_surface_maximized_requested(struct weston_desktop_surface *desktop_surface, + bool maximized, void *shell) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + /* Since xwayland surfaces may have already applied the max/min states + * internally, reapply fullscreen to force the correct xwayland WM state. + * Also see comment in desktop_surface_fullscreen_requested(). */ + if (!shsurf->parent) + kiosk_shell_surface_set_fullscreen(shsurf, NULL); + else if (maximized) + kiosk_shell_surface_set_maximized(shsurf); + else + kiosk_shell_surface_set_normal(shsurf); +} + +static void +desktop_surface_minimized_requested(struct weston_desktop_surface *desktop_surface, + void *shell) +{ +} + +static void +desktop_surface_ping_timeout(struct weston_desktop_client *desktop_client, + void *shell_) +{ +} + +static void +desktop_surface_pong(struct weston_desktop_client *desktop_client, + void *shell_) +{ +} + +static void +desktop_surface_set_xwayland_position(struct weston_desktop_surface *desktop_surface, + int32_t x, int32_t y, void *shell) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + shsurf->xwayland.x = x; + shsurf->xwayland.y = y; + shsurf->xwayland.is_set = true; +} + +static const struct weston_desktop_api kiosk_shell_desktop_api = { + .struct_size = sizeof(struct weston_desktop_api), + .surface_added = desktop_surface_added, + .surface_removed = desktop_surface_removed, + .committed = desktop_surface_committed, + .move = desktop_surface_move, + .resize = desktop_surface_resize, + .set_parent = desktop_surface_set_parent, + .fullscreen_requested = desktop_surface_fullscreen_requested, + .maximized_requested = desktop_surface_maximized_requested, + .minimized_requested = desktop_surface_minimized_requested, + .ping_timeout = desktop_surface_ping_timeout, + .pong = desktop_surface_pong, + .set_xwayland_position = desktop_surface_set_xwayland_position, +}; + +/* + * kiosk_shell + */ + +static struct kiosk_shell_output * +kiosk_shell_find_shell_output(struct kiosk_shell *shell, + struct weston_output *output) +{ + struct kiosk_shell_output *shoutput; + + wl_list_for_each(shoutput, &shell->output_list, link) { + if (shoutput->output == output) + return shoutput; + } + + return NULL; +} + +static void +kiosk_shell_activate_view(struct kiosk_shell *shell, + struct weston_view *view, + struct weston_seat *seat, + uint32_t flags) +{ + struct weston_surface *main_surface = + weston_surface_get_main_surface(view->surface); + struct kiosk_shell_surface *shsurf = + get_kiosk_shell_surface(main_surface); + + if (!shsurf) + return; + + /* If the view belongs to a child window bring it to the front. + * We don't do this for the parent top-level, since that would + * obscure all children. + */ + if (shsurf->parent) { + weston_layer_entry_remove(&view->layer_link); + weston_layer_entry_insert(&shell->normal_layer.view_list, + &view->layer_link); + weston_view_geometry_dirty(view); + weston_surface_damage(view->surface); + } + + weston_view_activate(view, seat, flags); +} + +static void +kiosk_shell_click_to_activate_binding(struct weston_pointer *pointer, + const struct timespec *time, + uint32_t button, void *data) +{ + struct kiosk_shell *shell = data; + + if (pointer->grab != &pointer->default_grab) + return; + if (pointer->focus == NULL) + return; + + kiosk_shell_activate_view(shell, pointer->focus, pointer->seat, + WESTON_ACTIVATE_FLAG_CLICKED); +} + +static void +kiosk_shell_touch_to_activate_binding(struct weston_touch *touch, + const struct timespec *time, + void *data) +{ + struct kiosk_shell *shell = data; + + if (touch->grab != &touch->default_grab) + return; + if (touch->focus == NULL) + return; + + kiosk_shell_activate_view(shell, touch->focus, touch->seat, + WESTON_ACTIVATE_FLAG_NONE); +} + +static void +kiosk_shell_add_bindings(struct kiosk_shell *shell) +{ + weston_compositor_add_button_binding(shell->compositor, BTN_LEFT, 0, + kiosk_shell_click_to_activate_binding, + shell); + weston_compositor_add_button_binding(shell->compositor, BTN_RIGHT, 0, + kiosk_shell_click_to_activate_binding, + shell); + weston_compositor_add_touch_binding(shell->compositor, 0, + kiosk_shell_touch_to_activate_binding, + shell); +} + +static void +kiosk_shell_handle_output_created(struct wl_listener *listener, void *data) +{ + struct kiosk_shell *shell = + container_of(listener, struct kiosk_shell, output_created_listener); + struct weston_output *output = data; + + kiosk_shell_output_create(shell, output); +} + +static void +kiosk_shell_handle_output_resized(struct wl_listener *listener, void *data) +{ + struct kiosk_shell *shell = + container_of(listener, struct kiosk_shell, output_resized_listener); + struct weston_output *output = data; + struct kiosk_shell_output *shoutput = + kiosk_shell_find_shell_output(shell, output); + struct weston_view *view; + + kiosk_shell_output_recreate_background(shoutput); + + wl_list_for_each(view, &shell->normal_layer.view_list.link, + layer_link.link) { + struct kiosk_shell_surface *shsurf; + if (view->output != output) + continue; + shsurf = get_kiosk_shell_surface(view->surface); + if (!shsurf) + continue; + kiosk_shell_surface_reconfigure_for_output(shsurf); + } +} + +static void +kiosk_shell_handle_output_moved(struct wl_listener *listener, void *data) +{ + struct kiosk_shell *shell = + container_of(listener, struct kiosk_shell, output_moved_listener); + struct weston_output *output = data; + struct weston_view *view; + + wl_list_for_each(view, &shell->background_layer.view_list.link, + layer_link.link) { + if (view->output != output) + continue; + weston_view_set_position(view, + view->geometry.x + output->move_x, + view->geometry.y + output->move_y); + } + + wl_list_for_each(view, &shell->normal_layer.view_list.link, + layer_link.link) { + if (view->output != output) + continue; + weston_view_set_position(view, + view->geometry.x + output->move_x, + view->geometry.y + output->move_y); + } +} + +static void +kiosk_shell_handle_seat_created(struct wl_listener *listener, void *data) +{ + struct weston_seat *seat = data; + kiosk_shell_seat_create(seat); +} + +static void +kiosk_shell_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell *shell = + container_of(listener, struct kiosk_shell, destroy_listener); + struct kiosk_shell_output *shoutput, *tmp; + + wl_list_remove(&shell->destroy_listener.link); + wl_list_remove(&shell->output_created_listener.link); + wl_list_remove(&shell->output_resized_listener.link); + wl_list_remove(&shell->output_moved_listener.link); + wl_list_remove(&shell->seat_created_listener.link); + + wl_list_for_each_safe(shoutput, tmp, &shell->output_list, link) { + kiosk_shell_output_destroy(shoutput); + } + + weston_desktop_destroy(shell->desktop); + + free(shell); +} + +WL_EXPORT int +wet_shell_init(struct weston_compositor *ec, + int *argc, char *argv[]) +{ + struct kiosk_shell *shell; + struct weston_seat *seat; + struct weston_output *output; + + shell = zalloc(sizeof *shell); + if (shell == NULL) + return -1; + + shell->compositor = ec; + + if (!weston_compositor_add_destroy_listener_once(ec, + &shell->destroy_listener, + kiosk_shell_destroy)) { + free(shell); + return 0; + } + + weston_layer_init(&shell->background_layer, ec); + weston_layer_init(&shell->normal_layer, ec); + + weston_layer_set_position(&shell->background_layer, + WESTON_LAYER_POSITION_BACKGROUND); + /* We use the NORMAL layer position, so that xwayland surfaces, which + * are placed at NORMAL+1, are visible. */ + weston_layer_set_position(&shell->normal_layer, + WESTON_LAYER_POSITION_NORMAL); + + shell->desktop = weston_desktop_create(ec, &kiosk_shell_desktop_api, + shell); + if (!shell->desktop) + return -1; + + wl_list_for_each(seat, &ec->seat_list, link) + kiosk_shell_seat_create(seat); + shell->seat_created_listener.notify = kiosk_shell_handle_seat_created; + wl_signal_add(&ec->seat_created_signal, &shell->seat_created_listener); + + wl_list_init(&shell->output_list); + wl_list_for_each(output, &ec->output_list, link) + kiosk_shell_output_create(shell, output); + + shell->output_created_listener.notify = kiosk_shell_handle_output_created; + wl_signal_add(&ec->output_created_signal, &shell->output_created_listener); + + shell->output_resized_listener.notify = kiosk_shell_handle_output_resized; + wl_signal_add(&ec->output_resized_signal, &shell->output_resized_listener); + + shell->output_moved_listener.notify = kiosk_shell_handle_output_moved; + wl_signal_add(&ec->output_moved_signal, &shell->output_moved_listener); + + kiosk_shell_add_bindings(shell); + + return 0; +} diff --git a/kiosk-shell/kiosk-shell.h b/kiosk-shell/kiosk-shell.h new file mode 100644 index 0000000..09f5a77 --- /dev/null +++ b/kiosk-shell/kiosk-shell.h @@ -0,0 +1,91 @@ +/* + * Copyright 2020 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef WESTON_KIOSK_SHELL_H +#define WESTON_KIOSK_SHELL_H + +#include +#include + +struct kiosk_shell { + struct weston_compositor *compositor; + struct weston_desktop *desktop; + + struct wl_listener destroy_listener; + struct wl_listener output_created_listener; + struct wl_listener output_resized_listener; + struct wl_listener output_moved_listener; + struct wl_listener seat_created_listener; + + struct weston_layer background_layer; + struct weston_layer normal_layer; + + struct wl_list output_list; +}; + +struct kiosk_shell_surface { + struct weston_desktop_surface *desktop_surface; + struct weston_view *view; + + struct kiosk_shell *shell; + + struct weston_output *output; + struct wl_listener output_destroy_listener; + + struct wl_signal destroy_signal; + struct wl_listener parent_destroy_listener; + struct kiosk_shell_surface *parent; + + int focus_count; + + int32_t last_width, last_height; + bool grabbed; + + struct { + bool is_set; + int32_t x; + int32_t y; + } xwayland; +}; + +struct kiosk_shell_seat { + struct weston_seat *seat; + struct wl_listener seat_destroy_listener; + struct weston_surface *focused_surface; + + struct wl_listener caps_changed_listener; + struct wl_listener keyboard_focus_listener; +}; + +struct kiosk_shell_output { + struct weston_output *output; + struct wl_listener output_destroy_listener; + struct weston_view *background_view; + + struct kiosk_shell *shell; + struct wl_list link; + + char *app_ids; +}; + +#endif /* WESTON_KIOSK_SHELL_H */ diff --git a/kiosk-shell/meson.build b/kiosk-shell/meson.build new file mode 100644 index 0000000..e838614 --- /dev/null +++ b/kiosk-shell/meson.build @@ -0,0 +1,29 @@ +if get_option('shell-kiosk') + srcs_shell_kiosk = [ + 'kiosk-shell.c', + 'kiosk-shell-grab.c', + 'util.c', + weston_desktop_shell_server_protocol_h, + weston_desktop_shell_protocol_c, + input_method_unstable_v1_server_protocol_h, + input_method_unstable_v1_protocol_c, + ] + deps_shell_kiosk = [ + dep_libm, + dep_libexec_weston, + dep_libshared, + dep_lib_desktop, + dep_libweston_public, + ] + plugin_shell_kiosk = shared_library( + 'kiosk-shell', + srcs_shell_kiosk, + include_directories: common_inc, + dependencies: deps_shell_kiosk, + name_prefix: '', + install: true, + install_dir: dir_module_weston, + install_rpath: '$ORIGIN' + ) + env_modmap += 'kiosk-shell.so=@0@;'.format(plugin_shell_kiosk.full_path()) +endif diff --git a/kiosk-shell/util.c b/kiosk-shell/util.c new file mode 100644 index 0000000..ad3e0d9 --- /dev/null +++ b/kiosk-shell/util.c @@ -0,0 +1,168 @@ +/* + * Copyright 2010-2012 Intel Corporation + * Copyright 2013 Raspberry Pi Foundation + * Copyright 2011-2012,2020 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* Helper functions for kiosk-shell */ + +/* TODO: These functions are useful to many shells, and, in fact, + * much of content in this file was copied from desktop-shell. We should + * create a shared shell utility collection to deduplicate this code. */ + +#include "util.h" +#include "shared/helpers.h" +#include + +struct weston_output * +get_default_output(struct weston_compositor *compositor) +{ + if (wl_list_empty(&compositor->output_list)) + return NULL; + + return container_of(compositor->output_list.next, + struct weston_output, link); +} + +struct weston_output * +get_focused_output(struct weston_compositor *compositor) +{ + struct weston_seat *seat; + struct weston_output *output = NULL; + + wl_list_for_each(seat, &compositor->seat_list, link) { + struct weston_touch *touch = weston_seat_get_touch(seat); + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(seat); + + /* Priority has touch focus, then pointer and + * then keyboard focus. We should probably have + * three for loops and check first for touch, + * then for pointer, etc. but unless somebody has some + * objections, I think this is sufficient. */ + if (touch && touch->focus) + output = touch->focus->output; + else if (pointer && pointer->focus) + output = pointer->focus->output; + else if (keyboard && keyboard->focus) + output = keyboard->focus->output; + + if (output) + break; + } + + return output; +} + +/* This is a copy of the same function from desktop-shell. + * TODO: Fix this function to take into account nested subsurfaces. */ +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->width, + surface->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->width, + subsurface->surface->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); +} + +void +center_on_output(struct weston_view *view, struct weston_output *output) +{ + int32_t surf_x, surf_y, width, height; + float x, y; + + if (!output) { + weston_view_set_position(view, 0, 0); + return; + } + + surface_subsurfaces_boundingbox(view->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_view_set_position(view, x, y); +} + +static void +colored_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) +{ +} + +struct weston_view * +create_colored_surface(struct weston_compositor *compositor, + float r, float g, float b, + float x, float y, int w, int h) +{ + struct weston_surface *surface = NULL; + struct weston_view *view; + + surface = weston_surface_create(compositor); + if (surface == NULL) { + weston_log("no memory\n"); + return NULL; + } + view = weston_view_create(surface); + if (surface == NULL) { + weston_log("no memory\n"); + weston_surface_destroy(surface); + return NULL; + } + + surface->committed = colored_surface_committed; + surface->committed_private = NULL; + + weston_surface_set_color(surface, r, g, b, 1.0); + 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); + + weston_surface_set_size(surface, w, h); + weston_view_set_position(view, x, y); + + return view; +} diff --git a/kiosk-shell/util.h b/kiosk-shell/util.h new file mode 100644 index 0000000..e60aa3b --- /dev/null +++ b/kiosk-shell/util.h @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2012 Intel Corporation + * Copyright 2013 Raspberry Pi Foundation + * Copyright 2011-2012,2020 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* Helper functions adapted from desktop-shell */ + +#include + +struct weston_compositor; +struct weston_layer; +struct weston_output; +struct weston_surface; +struct weston_view; + +struct weston_output * +get_default_output(struct weston_compositor *compositor); + +struct weston_output * +get_focused_output(struct weston_compositor *compositor); + +void +center_on_output(struct weston_view *view, struct weston_output *output); + +struct weston_view * +create_colored_surface(struct weston_compositor *compositor, + float r, float g, float b, + float x, float y, int w, int h); diff --git a/libweston-desktop/client.c b/libweston-desktop/client.c new file mode 100644 index 0000000..56413f7 --- /dev/null +++ b/libweston-desktop/client.c @@ -0,0 +1,212 @@ +/* + * Copyright © 2016 Quentin "Sardem FF7" Glidic + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include + +#include +#include + +#include +#include "internal.h" + +struct weston_desktop_client { + struct weston_desktop *desktop; + struct wl_client *client; + struct wl_resource *resource; + struct wl_list surface_list; + uint32_t ping_serial; + struct wl_event_source *ping_timer; + struct wl_signal destroy_signal; +}; + +void +weston_desktop_client_add_destroy_listener(struct weston_desktop_client *client, + struct wl_listener *listener) +{ + wl_signal_add(&client->destroy_signal, listener); +} + +static void +weston_desktop_client_destroy(struct wl_resource *resource) +{ + struct weston_desktop_client *client = + wl_resource_get_user_data(resource); + struct wl_list *list = &client->surface_list; + struct wl_list *link, *tmp; + + wl_signal_emit(&client->destroy_signal, client); + + for (link = list->next, tmp = link->next; + link != list; + link = tmp, tmp = link->next) { + wl_list_remove(link); + wl_list_init(link); + } + + if (client->ping_timer != NULL) + wl_event_source_remove(client->ping_timer); + + free(client); +} + +static int +weston_desktop_client_ping_timeout(void *user_data) +{ + struct weston_desktop_client *client = user_data; + + weston_desktop_api_ping_timeout(client->desktop, client); + return 1; +} + +struct weston_desktop_client * +weston_desktop_client_create(struct weston_desktop *desktop, + struct wl_client *wl_client, + wl_dispatcher_func_t dispatcher, + const struct wl_interface *interface, + const void *implementation, uint32_t version, + uint32_t id) +{ + struct weston_desktop_client *client; + struct wl_display *display; + struct wl_event_loop *loop; + + client = zalloc(sizeof(struct weston_desktop_client)); + if (client == NULL) { + if (wl_client != NULL) + wl_client_post_no_memory(wl_client); + return NULL; + } + + client->desktop = desktop; + client->client = wl_client; + + wl_list_init(&client->surface_list); + wl_signal_init(&client->destroy_signal); + + if (wl_client == NULL) + return client; + + client->resource = wl_resource_create(wl_client, interface, version, id); + if (client->resource == NULL) { + wl_client_post_no_memory(wl_client); + free(client); + return NULL; + } + + if (dispatcher != NULL) + wl_resource_set_dispatcher(client->resource, dispatcher, + weston_desktop_client_destroy, client, + weston_desktop_client_destroy); + else + wl_resource_set_implementation(client->resource, implementation, + client, + weston_desktop_client_destroy); + + + display = wl_client_get_display(client->client); + loop = wl_display_get_event_loop(display); + client->ping_timer = + wl_event_loop_add_timer(loop, + weston_desktop_client_ping_timeout, + client); + if (client->ping_timer == NULL) + wl_client_post_no_memory(wl_client); + + return client; +} + +struct weston_desktop * +weston_desktop_client_get_desktop(struct weston_desktop_client *client) +{ + return client->desktop; +} + +struct wl_resource * +weston_desktop_client_get_resource(struct weston_desktop_client *client) +{ + return client->resource; +} + +struct wl_list * +weston_desktop_client_get_surface_list(struct weston_desktop_client *client) +{ + return &client->surface_list; +} + +WL_EXPORT struct wl_client * +weston_desktop_client_get_client(struct weston_desktop_client *client) +{ + return client->client; +} + +WL_EXPORT void +weston_desktop_client_for_each_surface(struct weston_desktop_client *client, + void (*callback)(struct weston_desktop_surface *surface, void *user_data), + void *user_data) +{ + struct wl_list *list = &client->surface_list; + struct wl_list *link; + + for (link = list->next; link != list; link = link->next) + callback(weston_desktop_surface_from_client_link(link), + user_data); +} + +WL_EXPORT int +weston_desktop_client_ping(struct weston_desktop_client *client) +{ + struct weston_desktop_surface *surface = + weston_desktop_surface_from_client_link(client->surface_list.next); + const struct weston_desktop_surface_implementation *implementation = + weston_desktop_surface_get_implementation(surface); + void *implementation_data = + weston_desktop_surface_get_implementation_data(surface); + + if (implementation->ping == NULL) + return -1; + + if (client->ping_serial != 0) + return 1; + + client->ping_serial = + wl_display_next_serial(wl_client_get_display(client->client)); + wl_event_source_timer_update(client->ping_timer, 10000); + + implementation->ping(surface, client->ping_serial, implementation_data); + + return 0; +} + +void +weston_desktop_client_pong(struct weston_desktop_client *client, uint32_t serial) +{ + if (client->ping_serial != serial) + return; + + weston_desktop_api_pong(client->desktop, client); + + wl_event_source_timer_update(client->ping_timer, 0); + client->ping_serial = 0; +} diff --git a/libweston-desktop/internal.h b/libweston-desktop/internal.h new file mode 100644 index 0000000..e4ab270 --- /dev/null +++ b/libweston-desktop/internal.h @@ -0,0 +1,242 @@ +/* + * Copyright © 2016 Quentin "Sardem FF7" Glidic + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef WESTON_DESKTOP_INTERNAL_H +#define WESTON_DESKTOP_INTERNAL_H + +#include + +struct weston_desktop_seat; +struct weston_desktop_client; + +struct weston_compositor * +weston_desktop_get_compositor(struct weston_desktop *desktop); +struct wl_display * +weston_desktop_get_display(struct weston_desktop *desktop); + +void +weston_desktop_api_ping_timeout(struct weston_desktop *desktop, + struct weston_desktop_client *client); +void +weston_desktop_api_pong(struct weston_desktop *desktop, + struct weston_desktop_client *client); +void +weston_desktop_api_surface_added(struct weston_desktop *desktop, + struct weston_desktop_surface *surface); +void +weston_desktop_api_surface_removed(struct weston_desktop *desktop, + struct weston_desktop_surface *surface); +void +weston_desktop_api_committed(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + int32_t sx, int32_t sy); +void +weston_desktop_api_show_window_menu(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + struct weston_seat *seat, + int32_t x, int32_t y); +void +weston_desktop_api_set_parent(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + struct weston_desktop_surface *parent); +void +weston_desktop_api_move(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + struct weston_seat *seat, uint32_t serial); +void +weston_desktop_api_resize(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + struct weston_seat *seat, uint32_t serial, + enum weston_desktop_surface_edge edges); +void +weston_desktop_api_fullscreen_requested(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + bool fullscreen, + struct weston_output *output); +void +weston_desktop_api_maximized_requested(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + bool maximized); +void +weston_desktop_api_minimized_requested(struct weston_desktop *desktop, + struct weston_desktop_surface *surface); + +void +weston_desktop_api_set_xwayland_position(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + int32_t x, int32_t y); + +struct weston_desktop_seat * +weston_desktop_seat_from_seat(struct weston_seat *wseat); + +struct weston_desktop_surface_implementation { + void (*set_activated)(struct weston_desktop_surface *surface, + void *user_data, bool activated); + void (*set_fullscreen)(struct weston_desktop_surface *surface, + void *user_data, bool fullscreen); + void (*set_maximized)(struct weston_desktop_surface *surface, + void *user_data, bool maximized); + void (*set_resizing)(struct weston_desktop_surface *surface, + void *user_data, bool resizing); + void (*set_size)(struct weston_desktop_surface *surface, + void *user_data, int32_t width, int32_t height); + void (*committed)(struct weston_desktop_surface *surface, void *user_data, + int32_t sx, int32_t sy); + void (*update_position)(struct weston_desktop_surface *surface, + void *user_data); + void (*ping)(struct weston_desktop_surface *surface, uint32_t serial, + void *user_data); + void (*close)(struct weston_desktop_surface *surface, void *user_data); + + bool (*get_activated)(struct weston_desktop_surface *surface, + void *user_data); + bool (*get_fullscreen)(struct weston_desktop_surface *surface, + void *user_data); + bool (*get_maximized)(struct weston_desktop_surface *surface, + void *user_data); + bool (*get_resizing)(struct weston_desktop_surface *surface, + void *user_data); + struct weston_size + (*get_max_size)(struct weston_desktop_surface *surface, + void *user_data); + struct weston_size + (*get_min_size)(struct weston_desktop_surface *surface, + void *user_data); + + void (*destroy)(struct weston_desktop_surface *surface, + void *user_data); +}; + +struct weston_desktop_client * +weston_desktop_client_create(struct weston_desktop *desktop, + struct wl_client *client, + wl_dispatcher_func_t dispatcher, + const struct wl_interface *interface, + const void *implementation, uint32_t version, + uint32_t id); + +void +weston_desktop_client_add_destroy_listener(struct weston_desktop_client *client, + struct wl_listener *listener); +struct weston_desktop * +weston_desktop_client_get_desktop(struct weston_desktop_client *client); +struct wl_resource * +weston_desktop_client_get_resource(struct weston_desktop_client *client); +struct wl_list * +weston_desktop_client_get_surface_list(struct weston_desktop_client *client); + +void +weston_desktop_client_pong(struct weston_desktop_client *client, + uint32_t serial); + +struct weston_desktop_surface * +weston_desktop_surface_create(struct weston_desktop *desktop, + struct weston_desktop_client *client, + struct weston_surface *surface, + const struct weston_desktop_surface_implementation *implementation, + void *implementation_data); +void +weston_desktop_surface_destroy(struct weston_desktop_surface *surface); +void +weston_desktop_surface_resource_destroy(struct wl_resource *resource); +struct wl_resource * +weston_desktop_surface_add_resource(struct weston_desktop_surface *surface, + const struct wl_interface *interface, + const void *implementation, uint32_t id, + wl_resource_destroy_func_t destroy); +struct weston_desktop_surface * +weston_desktop_surface_from_grab_link(struct wl_list *grab_link); + +struct wl_list * +weston_desktop_surface_get_client_link(struct weston_desktop_surface *surface); +struct weston_desktop_surface * +weston_desktop_surface_from_client_link(struct wl_list *link); +bool +weston_desktop_surface_has_implementation(struct weston_desktop_surface *surface, + const struct weston_desktop_surface_implementation *implementation); +const struct weston_desktop_surface_implementation * +weston_desktop_surface_get_implementation(struct weston_desktop_surface *surface); +void * +weston_desktop_surface_get_implementation_data(struct weston_desktop_surface *surface); +struct weston_desktop_surface * +weston_desktop_surface_get_parent(struct weston_desktop_surface *surface); +bool +weston_desktop_surface_get_grab(struct weston_desktop_surface *surface); + +void +weston_desktop_surface_set_title(struct weston_desktop_surface *surface, + const char *title); +void +weston_desktop_surface_set_app_id(struct weston_desktop_surface *surface, + const char *app_id); +void +weston_desktop_surface_set_pid(struct weston_desktop_surface *surface, + pid_t pid); +void +weston_desktop_surface_set_geometry(struct weston_desktop_surface *surface, + struct weston_geometry geometry); +void +weston_desktop_surface_set_relative_to(struct weston_desktop_surface *surface, + struct weston_desktop_surface *parent, + int32_t x, int32_t y, bool use_geometry); +void +weston_desktop_surface_unset_relative_to(struct weston_desktop_surface *surface); +void +weston_desktop_surface_popup_grab(struct weston_desktop_surface *popup, + struct weston_desktop_seat *seat, + uint32_t serial); +void +weston_desktop_surface_popup_ungrab(struct weston_desktop_surface *popup, + struct weston_desktop_seat *seat); +void +weston_desktop_surface_popup_dismiss(struct weston_desktop_surface *surface); + +struct weston_desktop_surface * +weston_desktop_seat_popup_grab_get_topmost_surface(struct weston_desktop_seat *seat); +bool +weston_desktop_seat_popup_grab_start(struct weston_desktop_seat *seat, + struct wl_client *client, uint32_t serial); +void +weston_desktop_seat_popup_grab_add_surface(struct weston_desktop_seat *seat, + struct wl_list *link); +void +weston_desktop_seat_popup_grab_remove_surface(struct weston_desktop_seat *seat, + struct wl_list *link); + +void +weston_desktop_destroy_request(struct wl_client *client, + struct wl_resource *resource); +struct wl_global * +weston_desktop_xdg_wm_base_create(struct weston_desktop *desktop, + struct wl_display *display); +struct wl_global * +weston_desktop_xdg_shell_v6_create(struct weston_desktop *desktop, + struct wl_display *display); +struct wl_global * +weston_desktop_wl_shell_create(struct weston_desktop *desktop, + struct wl_display *display); + +void +weston_desktop_xwayland_init(struct weston_desktop *desktop); + +#endif /* WESTON_DESKTOP_INTERNAL_H */ diff --git a/libweston-desktop/libweston-desktop.c b/libweston-desktop/libweston-desktop.c new file mode 100755 index 0000000..0744ec6 --- /dev/null +++ b/libweston-desktop/libweston-desktop.c @@ -0,0 +1,255 @@ +/* + * Copyright © 2016 Quentin "Sardem FF7" Glidic + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include + +#include +#include + +#include +#include +#include "shared/helpers.h" + +#include +#include "internal.h" + + +struct weston_desktop { + struct weston_compositor *compositor; + struct weston_desktop_api api; + void *user_data; + struct wl_global *xdg_wm_base; /* Stable protocol xdg_shell replaces xdg_shell_unstable_v6 */ + struct wl_global *xdg_shell_v6; /* Unstable xdg_shell_unstable_v6 protocol. */ + struct wl_global *wl_shell; +}; + +void +weston_desktop_destroy_request(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +WL_EXPORT struct weston_desktop * +weston_desktop_create(struct weston_compositor *compositor, + const struct weston_desktop_api *api, void *user_data) +{ + struct weston_desktop *desktop; + struct wl_display *display = compositor->wl_display; + + assert(api->surface_added); + assert(api->surface_removed); + + desktop = zalloc(sizeof(struct weston_desktop)); + desktop->compositor = compositor; + desktop->user_data = user_data; + + desktop->api.struct_size = + MIN(sizeof(struct weston_desktop_api), api->struct_size); + memcpy(&desktop->api, api, desktop->api.struct_size); + + desktop->xdg_wm_base = + weston_desktop_xdg_wm_base_create(desktop, display); + if (desktop->xdg_wm_base == NULL) { + weston_desktop_destroy(desktop); + return NULL; + } + + desktop->xdg_shell_v6 = + weston_desktop_xdg_shell_v6_create(desktop, display); + if (desktop->xdg_shell_v6 == NULL) { + weston_desktop_destroy(desktop); + return NULL; + } + + desktop->wl_shell = + weston_desktop_wl_shell_create(desktop, display); + if (desktop->wl_shell == NULL) { + weston_desktop_destroy(desktop); + return NULL; + } + +// OHOS +// weston_desktop_xwayland_init(desktop); + + return desktop; +} + +WL_EXPORT void +weston_desktop_destroy(struct weston_desktop *desktop) +{ + if (desktop == NULL) + return; + + if (desktop->wl_shell != NULL) + wl_global_destroy(desktop->wl_shell); + if (desktop->xdg_shell_v6 != NULL) + wl_global_destroy(desktop->xdg_shell_v6); + if (desktop->xdg_wm_base != NULL) + wl_global_destroy(desktop->xdg_wm_base); + + free(desktop); +} + + +struct weston_compositor * +weston_desktop_get_compositor(struct weston_desktop *desktop) +{ + return desktop->compositor; +} + +struct wl_display * +weston_desktop_get_display(struct weston_desktop *desktop) +{ + return desktop->compositor->wl_display; +} + +void +weston_desktop_api_ping_timeout(struct weston_desktop *desktop, + struct weston_desktop_client *client) +{ + if (desktop->api.ping_timeout != NULL) + desktop->api.ping_timeout(client, desktop->user_data); +} + +void +weston_desktop_api_pong(struct weston_desktop *desktop, + struct weston_desktop_client *client) +{ + if (desktop->api.pong != NULL) + desktop->api.pong(client, desktop->user_data); +} + +void +weston_desktop_api_surface_added(struct weston_desktop *desktop, + struct weston_desktop_surface *surface) +{ + struct weston_desktop_client *client = + weston_desktop_surface_get_client(surface); + struct wl_list *list = weston_desktop_client_get_surface_list(client); + struct wl_list *link = weston_desktop_surface_get_client_link(surface); + + desktop->api.surface_added(surface, desktop->user_data); + wl_list_insert(list, link); +} + +void +weston_desktop_api_surface_removed(struct weston_desktop *desktop, + struct weston_desktop_surface *surface) +{ + struct wl_list *link = weston_desktop_surface_get_client_link(surface); + + wl_list_remove(link); + wl_list_init(link); + desktop->api.surface_removed(surface, desktop->user_data); +} + +void +weston_desktop_api_committed(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + int32_t sx, int32_t sy) +{ + if (desktop->api.committed != NULL) + desktop->api.committed(surface, sx, sy, desktop->user_data); +} + +void +weston_desktop_api_show_window_menu(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + struct weston_seat *seat, + int32_t x, int32_t y) +{ + if (desktop->api.show_window_menu != NULL) + desktop->api.show_window_menu(surface, seat, x, y, + desktop->user_data); +} + +void +weston_desktop_api_set_parent(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + struct weston_desktop_surface *parent) +{ + if (desktop->api.set_parent != NULL) + desktop->api.set_parent(surface, parent, desktop->user_data); +} + +void +weston_desktop_api_move(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + struct weston_seat *seat, uint32_t serial) +{ + if (desktop->api.move != NULL) + desktop->api.move(surface, seat, serial, desktop->user_data); +} + +void +weston_desktop_api_resize(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + struct weston_seat *seat, uint32_t serial, + enum weston_desktop_surface_edge edges) +{ + if (desktop->api.resize != NULL) + desktop->api.resize(surface, seat, serial, edges, + desktop->user_data); +} + +void +weston_desktop_api_fullscreen_requested(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + bool fullscreen, + struct weston_output *output) +{ + if (desktop->api.fullscreen_requested != NULL) + desktop->api.fullscreen_requested(surface, fullscreen, output, + desktop->user_data); +} + +void +weston_desktop_api_maximized_requested(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + bool maximized) +{ + if (desktop->api.maximized_requested != NULL) + desktop->api.maximized_requested(surface, maximized, + desktop->user_data); +} + +void +weston_desktop_api_minimized_requested(struct weston_desktop *desktop, + struct weston_desktop_surface *surface) +{ + if (desktop->api.minimized_requested != NULL) + desktop->api.minimized_requested(surface, desktop->user_data); +} + +void +weston_desktop_api_set_xwayland_position(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + int32_t x, int32_t y) +{ + if (desktop->api.set_xwayland_position != NULL) + desktop->api.set_xwayland_position(surface, x, y, + desktop->user_data); +} diff --git a/libweston-desktop/meson.build b/libweston-desktop/meson.build new file mode 100644 index 0000000..0a45d94 --- /dev/null +++ b/libweston-desktop/meson.build @@ -0,0 +1,36 @@ +srcs_libdesktop = [ + 'libweston-desktop.c', + 'client.c', + 'seat.c', + 'surface.c', + 'xwayland.c', + 'wl-shell.c', + 'xdg-shell.c', + 'xdg-shell-v6.c', + xdg_shell_unstable_v6_server_protocol_h, + xdg_shell_unstable_v6_protocol_c, + xdg_shell_server_protocol_h, + xdg_shell_protocol_c, +] +lib_desktop = shared_library( + 'weston-desktop-@0@'.format(libweston_major), + srcs_libdesktop, + include_directories: common_inc, + install: true, + version: '0.0.@0@'.format(libweston_revision), + dependencies: dep_libweston_public +) +dep_lib_desktop = declare_dependency( + link_with: lib_desktop, + dependencies: dep_libweston_public +) + +pkgconfig.generate( + lib_desktop, + filebase: 'libweston-desktop-@0@'.format(libweston_major), + name: 'libweston-desktop', + version: version_weston, + description: 'Desktop shells abstraction library for libweston compositors', + requires_private: [ lib_weston, dep_wayland_server ], + subdirs: dir_include_libweston +) diff --git a/libweston-desktop/seat.c b/libweston-desktop/seat.c new file mode 100644 index 0000000..b92546d --- /dev/null +++ b/libweston-desktop/seat.c @@ -0,0 +1,381 @@ +/* + * Copyright © 2010-2012 Intel Corporation + * Copyright © 2011-2012 Collabora, Ltd. + * Copyright © 2013 Raspberry Pi Foundation + * Copyright © 2016 Quentin "Sardem FF7" Glidic + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include + +#include + +#include +#include + +#include +#include "internal.h" +#include "shared/timespec-util.h" + +struct weston_desktop_seat { + struct wl_listener seat_destroy_listener; + struct weston_seat *seat; + struct { + struct weston_keyboard_grab keyboard; + struct weston_pointer_grab pointer; + struct weston_touch_grab touch; + bool initial_up; + struct wl_client *client; + struct wl_list surfaces; + } popup_grab; +}; + +static void weston_desktop_seat_popup_grab_end(struct weston_desktop_seat *seat); + +static void +weston_desktop_seat_popup_grab_keyboard_key(struct weston_keyboard_grab *grab, + const struct timespec *time, + uint32_t key, + enum wl_keyboard_key_state state) +{ + weston_keyboard_send_key(grab->keyboard, time, key, state); +} + +static void +weston_desktop_seat_popup_grab_keyboard_modifiers(struct weston_keyboard_grab *grab, + uint32_t serial, + uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, + uint32_t group) +{ + weston_keyboard_send_modifiers(grab->keyboard, serial, mods_depressed, + mods_latched, mods_locked, group); +} + +static void +weston_desktop_seat_popup_grab_keyboard_cancel(struct weston_keyboard_grab *grab) +{ + struct weston_desktop_seat *seat = + wl_container_of(grab, seat, popup_grab.keyboard); + + weston_desktop_seat_popup_grab_end(seat); +} + +static const struct weston_keyboard_grab_interface weston_desktop_seat_keyboard_popup_grab_interface = { + .key = weston_desktop_seat_popup_grab_keyboard_key, + .modifiers = weston_desktop_seat_popup_grab_keyboard_modifiers, + .cancel = weston_desktop_seat_popup_grab_keyboard_cancel, +}; + +static void +weston_desktop_seat_popup_grab_pointer_focus(struct weston_pointer_grab *grab) +{ + struct weston_desktop_seat *seat = + wl_container_of(grab, seat, popup_grab.pointer); + struct weston_pointer *pointer = grab->pointer; + struct weston_view *view; + wl_fixed_t sx, sy; + + view = weston_compositor_pick_view(pointer->seat->compositor, + pointer->x, pointer->y, &sx, &sy); + + if (view != NULL && + view->surface->resource != NULL && + wl_resource_get_client(view->surface->resource) == seat->popup_grab.client) + weston_pointer_set_focus(pointer, view, sx, sy); + else + weston_pointer_clear_focus(pointer); +} + +static void +weston_desktop_seat_popup_grab_pointer_motion(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + weston_pointer_send_motion(grab->pointer, time, event); +} + +static void +weston_desktop_seat_popup_grab_pointer_button(struct weston_pointer_grab *grab, + const struct timespec *time, + uint32_t button, + enum wl_pointer_button_state state) +{ + struct weston_desktop_seat *seat = + wl_container_of(grab, seat, popup_grab.pointer); + struct weston_pointer *pointer = grab->pointer; + bool initial_up = seat->popup_grab.initial_up; + + if (state == WL_POINTER_BUTTON_STATE_RELEASED) + seat->popup_grab.initial_up = true; + + if (weston_pointer_has_focus_resource(pointer)) + weston_pointer_send_button(pointer, time, button, state); + else if (state == WL_POINTER_BUTTON_STATE_RELEASED && + (initial_up || + (timespec_sub_to_msec(time, &grab->pointer->grab_time) > 500))) + weston_desktop_seat_popup_grab_end(seat); +} + +static void +weston_desktop_seat_popup_grab_pointer_axis(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_axis_event *event) +{ + weston_pointer_send_axis(grab->pointer, time, event); +} + +static void +weston_desktop_seat_popup_grab_pointer_axis_source(struct weston_pointer_grab *grab, + uint32_t source) +{ + weston_pointer_send_axis_source(grab->pointer, source); +} + +static void +weston_desktop_seat_popup_grab_pointer_frame(struct weston_pointer_grab *grab) +{ + weston_pointer_send_frame(grab->pointer); +} + +static void +weston_desktop_seat_popup_grab_pointer_cancel(struct weston_pointer_grab *grab) +{ + struct weston_desktop_seat *seat = + wl_container_of(grab, seat, popup_grab.pointer); + + weston_desktop_seat_popup_grab_end(seat); +} + +static const struct weston_pointer_grab_interface weston_desktop_seat_pointer_popup_grab_interface = { + .focus = weston_desktop_seat_popup_grab_pointer_focus, + .motion = weston_desktop_seat_popup_grab_pointer_motion, + .button = weston_desktop_seat_popup_grab_pointer_button, + .axis = weston_desktop_seat_popup_grab_pointer_axis, + .axis_source = weston_desktop_seat_popup_grab_pointer_axis_source, + .frame = weston_desktop_seat_popup_grab_pointer_frame, + .cancel = weston_desktop_seat_popup_grab_pointer_cancel, +}; + +static void +weston_desktop_seat_popup_grab_touch_down(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id, + wl_fixed_t sx, wl_fixed_t sy) +{ + weston_touch_send_down(grab->touch, time, touch_id, sx, sy); +} + +static void +weston_desktop_seat_popup_grab_touch_up(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id) +{ + weston_touch_send_up(grab->touch, time, touch_id); +} + +static void +weston_desktop_seat_popup_grab_touch_motion(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id, + wl_fixed_t sx, wl_fixed_t sy) +{ + weston_touch_send_motion(grab->touch, time, touch_id, sx, sy); +} + +static void +weston_desktop_seat_popup_grab_touch_frame(struct weston_touch_grab *grab) +{ + weston_touch_send_frame(grab->touch); +} + +static void +weston_desktop_seat_popup_grab_touch_cancel(struct weston_touch_grab *grab) +{ + struct weston_desktop_seat *seat = + wl_container_of(grab, seat, popup_grab.touch); + + weston_desktop_seat_popup_grab_end(seat); +} + +static const struct weston_touch_grab_interface weston_desktop_seat_touch_popup_grab_interface = { + .down = weston_desktop_seat_popup_grab_touch_down, + .up = weston_desktop_seat_popup_grab_touch_up, + .motion = weston_desktop_seat_popup_grab_touch_motion, + .frame = weston_desktop_seat_popup_grab_touch_frame, + .cancel = weston_desktop_seat_popup_grab_touch_cancel, +}; + +static void +weston_desktop_seat_destroy(struct wl_listener *listener, void *data) +{ + struct weston_desktop_seat *seat = + wl_container_of(listener, seat, seat_destroy_listener); + + free(seat); +} + +struct weston_desktop_seat * +weston_desktop_seat_from_seat(struct weston_seat *wseat) +{ + struct wl_listener *listener; + struct weston_desktop_seat *seat; + + if (wseat == NULL) + return NULL; + + listener = wl_signal_get(&wseat->destroy_signal, + weston_desktop_seat_destroy); + if (listener != NULL) + return wl_container_of(listener, seat, seat_destroy_listener); + + seat = zalloc(sizeof(struct weston_desktop_seat)); + if (seat == NULL) + return NULL; + + seat->seat = wseat; + + seat->seat_destroy_listener.notify = weston_desktop_seat_destroy; + wl_signal_add(&wseat->destroy_signal, &seat->seat_destroy_listener); + + seat->popup_grab.keyboard.interface = + &weston_desktop_seat_keyboard_popup_grab_interface; + seat->popup_grab.pointer.interface = + &weston_desktop_seat_pointer_popup_grab_interface; + seat->popup_grab.touch.interface = + &weston_desktop_seat_touch_popup_grab_interface; + wl_list_init(&seat->popup_grab.surfaces); + + return seat; +} + +struct weston_desktop_surface * +weston_desktop_seat_popup_grab_get_topmost_surface(struct weston_desktop_seat *seat) +{ + if (seat == NULL || wl_list_empty(&seat->popup_grab.surfaces)) + return NULL; + + struct wl_list *grab_link = seat->popup_grab.surfaces.next; + + return weston_desktop_surface_from_grab_link(grab_link); +} + +bool +weston_desktop_seat_popup_grab_start(struct weston_desktop_seat *seat, + struct wl_client *client, uint32_t serial) +{ + assert(seat == NULL || seat->popup_grab.client == NULL || + seat->popup_grab.client == client); + + struct weston_seat *wseat = seat != NULL ? seat->seat : NULL; + /* weston_seat_get_* functions can properly handle a NULL wseat */ + struct weston_keyboard *keyboard = weston_seat_get_keyboard(wseat); + struct weston_pointer *pointer = weston_seat_get_pointer(wseat); + struct weston_touch *touch = weston_seat_get_touch(wseat); + + if ((keyboard == NULL || keyboard->grab_serial != serial) && + (pointer == NULL || pointer->grab_serial != serial) && + (touch == NULL || touch->grab_serial != serial)) { + return false; + } + + if (keyboard != NULL && + keyboard->grab->interface != &weston_desktop_seat_keyboard_popup_grab_interface) + weston_keyboard_start_grab(keyboard, &seat->popup_grab.keyboard); + + if (pointer != NULL && + pointer->grab->interface != &weston_desktop_seat_pointer_popup_grab_interface) + weston_pointer_start_grab(pointer, &seat->popup_grab.pointer); + + if (touch != NULL && + touch->grab->interface != &weston_desktop_seat_touch_popup_grab_interface) + weston_touch_start_grab(touch, &seat->popup_grab.touch); + + seat->popup_grab.initial_up = + (pointer == NULL || pointer->button_count == 0); + seat->popup_grab.client = client; + + return true; +} + +static void +weston_desktop_seat_popup_grab_end(struct weston_desktop_seat *seat) +{ + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat->seat); + struct weston_pointer *pointer = weston_seat_get_pointer(seat->seat); + struct weston_touch *touch = weston_seat_get_touch(seat->seat); + + while (!wl_list_empty(&seat->popup_grab.surfaces)) { + struct wl_list *link = seat->popup_grab.surfaces.prev; + struct weston_desktop_surface *surface = + weston_desktop_surface_from_grab_link(link); + + wl_list_remove(link); + wl_list_init(link); + weston_desktop_surface_popup_dismiss(surface); + } + + if (keyboard != NULL && + keyboard->grab->interface == &weston_desktop_seat_keyboard_popup_grab_interface) + weston_keyboard_end_grab(keyboard); + + if (pointer != NULL && + pointer->grab->interface == &weston_desktop_seat_pointer_popup_grab_interface) + weston_pointer_end_grab(pointer); + + if (touch != NULL && + touch->grab->interface == &weston_desktop_seat_touch_popup_grab_interface) + weston_touch_end_grab(touch); + + seat->popup_grab.client = NULL; +} + +void +weston_desktop_seat_popup_grab_add_surface(struct weston_desktop_seat *seat, + struct wl_list *link) +{ + assert(seat->popup_grab.client != NULL); + + wl_list_insert(&seat->popup_grab.surfaces, link); +} + +void +weston_desktop_seat_popup_grab_remove_surface(struct weston_desktop_seat *seat, + struct wl_list *link) +{ + assert(seat->popup_grab.client != NULL); + + wl_list_remove(link); + wl_list_init(link); + if (wl_list_empty(&seat->popup_grab.surfaces)) + weston_desktop_seat_popup_grab_end(seat); +} + +WL_EXPORT void +weston_seat_break_desktop_grabs(struct weston_seat *wseat) +{ + struct weston_desktop_seat *seat = weston_desktop_seat_from_seat(wseat); + + weston_desktop_seat_popup_grab_end(seat); +} diff --git a/libweston-desktop/surface.c b/libweston-desktop/surface.c new file mode 100644 index 0000000..433f08a --- /dev/null +++ b/libweston-desktop/surface.c @@ -0,0 +1,830 @@ +/* + * Copyright © 2016 Quentin "Sardem FF7" Glidic + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include + +#include +#include + +#include +#include "internal.h" + +struct weston_desktop_view { + struct wl_list link; + struct weston_view *view; + struct weston_desktop_view *parent; + struct wl_list children_list; + struct wl_list children_link; +}; + +struct weston_desktop_surface { + struct weston_desktop *desktop; + struct weston_desktop_client *client; + struct wl_list client_link; + const struct weston_desktop_surface_implementation *implementation; + void *implementation_data; + void *user_data; + struct weston_surface *surface; + struct wl_list view_list; + struct weston_position buffer_move; + struct wl_listener surface_commit_listener; + struct wl_listener surface_destroy_listener; + struct wl_listener client_destroy_listener; + struct wl_list children_list; + + struct wl_list resource_list; + bool has_geometry; + struct weston_geometry geometry; + struct { + char *title; + char *app_id; + pid_t pid; + struct wl_signal metadata_signal; + }; + struct { + struct weston_desktop_surface *parent; + struct wl_list children_link; + struct weston_position position; + bool use_geometry; + }; + struct { + struct wl_list grab_link; + }; +}; + +static void +weston_desktop_surface_update_view_position(struct weston_desktop_surface *surface) +{ + struct weston_desktop_view *view; + int32_t x, y; + + x = surface->position.x; + y = surface->position.y; + + if (surface->use_geometry) { + struct weston_desktop_surface *parent = + weston_desktop_surface_get_parent(surface); + struct weston_geometry geometry, parent_geometry; + + geometry = weston_desktop_surface_get_geometry(surface); + parent_geometry = weston_desktop_surface_get_geometry(parent); + + x += parent_geometry.x - geometry.x; + y += parent_geometry.y - geometry.y; + } + wl_list_for_each(view, &surface->view_list, link) + weston_view_set_position(view->view, x, y); +} + + +static void +weston_desktop_view_propagate_layer(struct weston_desktop_view *view); + +static void +weston_desktop_view_destroy(struct weston_desktop_view *view) +{ + struct weston_desktop_view *child_view, *tmp; + + wl_list_for_each_safe(child_view, tmp, &view->children_list, children_link) + weston_desktop_view_destroy(child_view); + + wl_list_remove(&view->children_link); + wl_list_remove(&view->link); + + weston_view_damage_below(view->view); + if (view->parent != NULL) + weston_view_destroy(view->view); + + free(view); +} + +void +weston_desktop_surface_destroy(struct weston_desktop_surface *surface) +{ + struct weston_desktop_view *view, *next_view; + struct weston_desktop_surface *child, *next_child; + + wl_list_remove(&surface->surface_commit_listener.link); + wl_list_remove(&surface->surface_destroy_listener.link); + wl_list_remove(&surface->client_destroy_listener.link); + + if (!wl_list_empty(&surface->resource_list)) { + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &surface->resource_list) { + wl_resource_set_user_data(resource, NULL); + wl_list_remove(wl_resource_get_link(resource)); + } + } + + surface->implementation->destroy(surface, surface->implementation_data); + + surface->surface->committed = NULL; + surface->surface->committed_private = NULL; + + weston_desktop_surface_unset_relative_to(surface); + wl_list_remove(&surface->client_link); + + wl_list_for_each_safe(child, next_child, + &surface->children_list, + children_link) + weston_desktop_surface_unset_relative_to(child); + + wl_list_for_each_safe(view, next_view, &surface->view_list, link) + weston_desktop_view_destroy(view); + + free(surface->title); + free(surface->app_id); + + free(surface); +} + +static void +weston_desktop_surface_surface_committed(struct wl_listener *listener, + void *data) +{ + struct weston_desktop_surface *surface = + wl_container_of(listener, surface, surface_commit_listener); + + if (surface->implementation->committed != NULL) + surface->implementation->committed(surface, + surface->implementation_data, + surface->buffer_move.x, + surface->buffer_move.y); + + if (surface->parent != NULL) { + struct weston_desktop_view *view; + + wl_list_for_each(view, &surface->view_list, link) { + weston_view_set_transform_parent(view->view, + view->parent->view); + weston_desktop_view_propagate_layer(view->parent); + } + weston_desktop_surface_update_view_position(surface); + } + + if (!wl_list_empty(&surface->children_list)) { + struct weston_desktop_surface *child; + + wl_list_for_each(child, &surface->children_list, children_link) + weston_desktop_surface_update_view_position(child); + } + + surface->buffer_move.x = 0; + surface->buffer_move.y = 0; +} + +static void +weston_desktop_surface_surface_destroyed(struct wl_listener *listener, + void *data) +{ + struct weston_desktop_surface *surface = + wl_container_of(listener, surface, surface_destroy_listener); + + weston_desktop_surface_destroy(surface); +} + +void +weston_desktop_surface_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *surface = + wl_resource_get_user_data(resource); + + if (surface != NULL) + weston_desktop_surface_destroy(surface); +} + +static void +weston_desktop_surface_committed(struct weston_surface *wsurface, + int32_t sx, int32_t sy) +{ + struct weston_desktop_surface *surface = wsurface->committed_private; + + surface->buffer_move.x = sx; + surface->buffer_move.y = sy; +} + +static void +weston_desktop_surface_client_destroyed(struct wl_listener *listener, + void *data) +{ + struct weston_desktop_surface *surface = + wl_container_of(listener, surface, client_destroy_listener); + + weston_desktop_surface_destroy(surface); +} + +struct weston_desktop_surface * +weston_desktop_surface_create(struct weston_desktop *desktop, + struct weston_desktop_client *client, + struct weston_surface *wsurface, + const struct weston_desktop_surface_implementation *implementation, + void *implementation_data) +{ + assert(implementation->destroy != NULL); + + struct weston_desktop_surface *surface; + + surface = zalloc(sizeof(struct weston_desktop_surface)); + if (surface == NULL) { + if (client != NULL) + wl_client_post_no_memory(weston_desktop_client_get_client(client)); + return NULL; + } + + surface->desktop = desktop; + surface->implementation = implementation; + surface->implementation_data = implementation_data; + surface->surface = wsurface; + + surface->client = client; + surface->client_destroy_listener.notify = + weston_desktop_surface_client_destroyed; + weston_desktop_client_add_destroy_listener( + client, &surface->client_destroy_listener); + + wsurface->committed = weston_desktop_surface_committed; + wsurface->committed_private = surface; + + surface->pid = -1; + + surface->surface_commit_listener.notify = + weston_desktop_surface_surface_committed; + wl_signal_add(&surface->surface->commit_signal, + &surface->surface_commit_listener); + surface->surface_destroy_listener.notify = + weston_desktop_surface_surface_destroyed; + wl_signal_add(&surface->surface->destroy_signal, + &surface->surface_destroy_listener); + + wl_list_init(&surface->client_link); + wl_list_init(&surface->resource_list); + wl_list_init(&surface->children_list); + wl_list_init(&surface->children_link); + wl_list_init(&surface->view_list); + wl_list_init(&surface->grab_link); + + wl_signal_init(&surface->metadata_signal); + + return surface; +} + +struct wl_resource * +weston_desktop_surface_add_resource(struct weston_desktop_surface *surface, + const struct wl_interface *interface, + const void *implementation, uint32_t id, + wl_resource_destroy_func_t destroy) +{ + struct wl_resource *client_resource = + weston_desktop_client_get_resource(surface->client); + struct wl_client *wl_client = + weston_desktop_client_get_client(surface->client); + struct wl_resource *resource; + + resource = wl_resource_create(wl_client, + interface, + wl_resource_get_version(client_resource), + id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + weston_desktop_surface_destroy(surface); + return NULL; + } + if (destroy == NULL) + destroy = weston_desktop_surface_resource_destroy; + wl_resource_set_implementation(resource, implementation, surface, destroy); + wl_list_insert(&surface->resource_list, wl_resource_get_link(resource)); + + return resource; +} + +struct weston_desktop_surface * +weston_desktop_surface_from_grab_link(struct wl_list *grab_link) +{ + struct weston_desktop_surface *surface = + wl_container_of(grab_link, surface, grab_link); + + return surface; +} + +WL_EXPORT bool +weston_surface_is_desktop_surface(struct weston_surface *wsurface) +{ + return wsurface->committed == weston_desktop_surface_committed; +} + +WL_EXPORT struct weston_desktop_surface * +weston_surface_get_desktop_surface(struct weston_surface *wsurface) +{ + if (!weston_surface_is_desktop_surface(wsurface)) + return NULL; + return wsurface->committed_private; +} + +WL_EXPORT void +weston_desktop_surface_set_user_data(struct weston_desktop_surface *surface, + void *user_data) +{ + surface->user_data = user_data; +} + +static struct weston_desktop_view * +weston_desktop_surface_create_desktop_view(struct weston_desktop_surface *surface) +{ + struct wl_client *wl_client= + weston_desktop_client_get_client(surface->client); + struct weston_desktop_view *view, *child_view; + struct weston_view *wview; + struct weston_desktop_surface *child; + + wview = weston_view_create(surface->surface); + if (wview == NULL) { + if (wl_client != NULL) + wl_client_post_no_memory(wl_client); + return NULL; + } + + view = zalloc(sizeof(struct weston_desktop_view)); + if (view == NULL) { + if (wl_client != NULL) + wl_client_post_no_memory(wl_client); + return NULL; + } + + view->view = wview; + wl_list_init(&view->children_list); + wl_list_init(&view->children_link); + wl_list_insert(surface->view_list.prev, &view->link); + + wl_list_for_each(child, &surface->children_list, children_link) { + child_view = + weston_desktop_surface_create_desktop_view(child); + if (child_view == NULL) { + weston_desktop_view_destroy(view); + return NULL; + } + + child_view->parent = view; + wl_list_insert(view->children_list.prev, + &child_view->children_link); + } + + return view; +} + +WL_EXPORT struct weston_view * +weston_desktop_surface_create_view(struct weston_desktop_surface *surface) +{ + struct weston_desktop_view *view; + + view = weston_desktop_surface_create_desktop_view(surface); + if (view == NULL) + return NULL; + + return view->view; +} + +WL_EXPORT void +weston_desktop_surface_unlink_view(struct weston_view *wview) +{ + struct weston_desktop_surface *surface; + struct weston_desktop_view *view; + + if (!weston_surface_is_desktop_surface(wview->surface)) + return; + + surface = weston_surface_get_desktop_surface(wview->surface); + wl_list_for_each(view, &surface->view_list, link) { + if (view->view == wview) { + weston_desktop_view_destroy(view); + return; + } + } +} + +static void +weston_desktop_view_propagate_layer(struct weston_desktop_view *view) +{ + struct weston_desktop_view *child; + struct wl_list *link = &view->view->layer_link.link; + + wl_list_for_each_reverse(child, &view->children_list, children_link) { + struct weston_layer_entry *prev = + wl_container_of(link->prev, prev, link); + + if (prev == &child->view->layer_link) + continue; + + child->view->is_mapped = true; + weston_view_damage_below(child->view); + weston_view_geometry_dirty(child->view); + weston_layer_entry_remove(&child->view->layer_link); + weston_layer_entry_insert(prev, &child->view->layer_link); + weston_view_geometry_dirty(child->view); + weston_surface_damage(child->view->surface); + weston_view_update_transform(child->view); + + weston_desktop_view_propagate_layer(child); + } +} + +WL_EXPORT void +weston_desktop_surface_propagate_layer(struct weston_desktop_surface *surface) +{ + struct weston_desktop_view *view; + + wl_list_for_each(view, &surface->view_list, link) + weston_desktop_view_propagate_layer(view); +} + +WL_EXPORT void +weston_desktop_surface_set_activated(struct weston_desktop_surface *surface, bool activated) +{ + if (surface->implementation->set_activated != NULL) + surface->implementation->set_activated(surface, + surface->implementation_data, + activated); +} + +WL_EXPORT void +weston_desktop_surface_set_fullscreen(struct weston_desktop_surface *surface, bool fullscreen) +{ + if (surface->implementation->set_fullscreen != NULL) + surface->implementation->set_fullscreen(surface, + surface->implementation_data, + fullscreen); +} + +WL_EXPORT void +weston_desktop_surface_set_maximized(struct weston_desktop_surface *surface, bool maximized) +{ + if (surface->implementation->set_maximized != NULL) + surface->implementation->set_maximized(surface, + surface->implementation_data, + maximized); +} + +WL_EXPORT void +weston_desktop_surface_set_resizing(struct weston_desktop_surface *surface, bool resizing) +{ + if (surface->implementation->set_resizing != NULL) + surface->implementation->set_resizing(surface, + surface->implementation_data, + resizing); +} + +WL_EXPORT void +weston_desktop_surface_set_size(struct weston_desktop_surface *surface, int32_t width, int32_t height) +{ + if (surface->implementation->set_size != NULL) + surface->implementation->set_size(surface, + surface->implementation_data, + width, height); +} + +WL_EXPORT void +weston_desktop_surface_close(struct weston_desktop_surface *surface) +{ + if (surface->implementation->close != NULL) + surface->implementation->close(surface, + surface->implementation_data); +} + +WL_EXPORT void +weston_desktop_surface_add_metadata_listener(struct weston_desktop_surface *surface, + struct wl_listener *listener) +{ + wl_signal_add(&surface->metadata_signal, listener); +} + +struct weston_desktop_surface * +weston_desktop_surface_from_client_link(struct wl_list *link) +{ + struct weston_desktop_surface *surface; + + surface = wl_container_of(link, surface, client_link); + return surface; +} + +struct wl_list * +weston_desktop_surface_get_client_link(struct weston_desktop_surface *surface) +{ + return &surface->client_link; +} + +bool +weston_desktop_surface_has_implementation(struct weston_desktop_surface *surface, + const struct weston_desktop_surface_implementation *implementation) +{ + return surface->implementation == implementation; +} + +const struct weston_desktop_surface_implementation * +weston_desktop_surface_get_implementation(struct weston_desktop_surface *surface) +{ + return surface->implementation; +} + +void * +weston_desktop_surface_get_implementation_data(struct weston_desktop_surface *surface) +{ + return surface->implementation_data; +} + +struct weston_desktop_surface * +weston_desktop_surface_get_parent(struct weston_desktop_surface *surface) +{ + return surface->parent; +} + +bool +weston_desktop_surface_get_grab(struct weston_desktop_surface *surface) +{ + return !wl_list_empty(&surface->grab_link); +} + +WL_EXPORT struct weston_desktop_client * +weston_desktop_surface_get_client(struct weston_desktop_surface *surface) +{ + return surface->client; +} + +WL_EXPORT void * +weston_desktop_surface_get_user_data(struct weston_desktop_surface *surface) +{ + return surface->user_data; +} + +WL_EXPORT struct weston_surface * +weston_desktop_surface_get_surface(struct weston_desktop_surface *surface) +{ + return surface->surface; +} + +WL_EXPORT const char * +weston_desktop_surface_get_title(struct weston_desktop_surface *surface) +{ + return surface->title; +} + +WL_EXPORT const char * +weston_desktop_surface_get_app_id(struct weston_desktop_surface *surface) +{ + return surface->app_id; +} + +WL_EXPORT pid_t +weston_desktop_surface_get_pid(struct weston_desktop_surface *surface) +{ + pid_t pid; + + if (surface->pid != -1) { + pid = surface->pid; + } else { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(surface); + struct wl_client *wl_client = + weston_desktop_client_get_client(client); + + /* wl_client should always be valid, because only in the + * xwayland case it wouldn't be, but in that case we won't + * reach here, as the pid is initialized to 0. */ + assert(wl_client); + wl_client_get_credentials(wl_client, &pid, NULL, NULL); + } + return pid; +} + +WL_EXPORT bool +weston_desktop_surface_get_activated(struct weston_desktop_surface *surface) +{ + if (surface->implementation->get_activated == NULL) + return false; + return surface->implementation->get_activated(surface, + surface->implementation_data); +} + +WL_EXPORT bool +weston_desktop_surface_get_resizing(struct weston_desktop_surface *surface) +{ + if (surface->implementation->get_resizing == NULL) + return false; + return surface->implementation->get_resizing(surface, + surface->implementation_data); +} + +WL_EXPORT bool +weston_desktop_surface_get_maximized(struct weston_desktop_surface *surface) +{ + if (surface->implementation->get_maximized == NULL) + return false; + return surface->implementation->get_maximized(surface, + surface->implementation_data); +} + +WL_EXPORT bool +weston_desktop_surface_get_fullscreen(struct weston_desktop_surface *surface) +{ + if (surface->implementation->get_fullscreen == NULL) + return false; + return surface->implementation->get_fullscreen(surface, + surface->implementation_data); +} + +WL_EXPORT struct weston_geometry +weston_desktop_surface_get_geometry(struct weston_desktop_surface *surface) +{ + if (surface->has_geometry) + return surface->geometry; + return weston_surface_get_bounding_box(surface->surface); +} + +WL_EXPORT struct weston_size +weston_desktop_surface_get_max_size(struct weston_desktop_surface *surface) +{ + struct weston_size size = { 0, 0 }; + + if (surface->implementation->get_max_size == NULL) + return size; + return surface->implementation->get_max_size(surface, + surface->implementation_data); +} + +WL_EXPORT struct weston_size +weston_desktop_surface_get_min_size(struct weston_desktop_surface *surface) +{ + struct weston_size size = { 0, 0 }; + + if (surface->implementation->get_min_size == NULL) + return size; + return surface->implementation->get_min_size(surface, + surface->implementation_data); +} + +void +weston_desktop_surface_set_title(struct weston_desktop_surface *surface, + const char *title) +{ + char *tmp, *old; + + tmp = strdup(title); + if (tmp == NULL) + return; + + old = surface->title; + surface->title = tmp; + wl_signal_emit(&surface->metadata_signal, surface); + free(old); +} + +void +weston_desktop_surface_set_app_id(struct weston_desktop_surface *surface, + const char *app_id) +{ + char *tmp, *old; + + tmp = strdup(app_id); + if (tmp == NULL) + return; + + old = surface->app_id; + surface->app_id = tmp; + wl_signal_emit(&surface->metadata_signal, surface); + free(old); +} + +void +weston_desktop_surface_set_pid(struct weston_desktop_surface *surface, + pid_t pid) +{ + surface->pid = pid; +} + +void +weston_desktop_surface_set_geometry(struct weston_desktop_surface *surface, + struct weston_geometry geometry) +{ + surface->has_geometry = true; + surface->geometry = geometry; +} + +void +weston_desktop_surface_set_relative_to(struct weston_desktop_surface *surface, + struct weston_desktop_surface *parent, + int32_t x, int32_t y, bool use_geometry) +{ + struct weston_desktop_view *view, *parent_view; + struct wl_list *link, *tmp; + + assert(parent); + + surface->position.x = x; + surface->position.y = y; + surface->use_geometry = use_geometry; + + if (surface->parent == parent) + return; + + surface->parent = parent; + wl_list_remove(&surface->children_link); + wl_list_insert(surface->parent->children_list.prev, + &surface->children_link); + + link = surface->view_list.next; + tmp = link->next; + wl_list_for_each(parent_view, &parent->view_list, link) { + if (link == &surface->view_list) { + view = weston_desktop_surface_create_desktop_view(surface); + if (view == NULL) + return; + tmp = &surface->view_list; + } else { + view = wl_container_of(link, view, link); + wl_list_remove(&view->children_link); + } + + view->parent = parent_view; + wl_list_insert(parent_view->children_list.prev, + &view->children_link); + weston_desktop_view_propagate_layer(view); + + link = tmp; + tmp = link->next; + } + for (; link != &surface->view_list; link = tmp, tmp = link->next) { + view = wl_container_of(link, view, link); + weston_desktop_view_destroy(view); + } +} + +void +weston_desktop_surface_unset_relative_to(struct weston_desktop_surface *surface) +{ + struct weston_desktop_view *view, *tmp; + + if (surface->parent == NULL) + return; + + surface->parent = NULL; + wl_list_remove(&surface->children_link); + wl_list_init(&surface->children_link); + + wl_list_for_each_safe(view, tmp, &surface->view_list, link) + weston_desktop_view_destroy(view); +} + +void +weston_desktop_surface_popup_grab(struct weston_desktop_surface *surface, + struct weston_desktop_seat *seat, + uint32_t serial) +{ + struct wl_client *wl_client = + weston_desktop_client_get_client(surface->client); + if (weston_desktop_seat_popup_grab_start(seat, wl_client, serial)) + weston_desktop_seat_popup_grab_add_surface(seat, &surface->grab_link); + else + weston_desktop_surface_popup_dismiss(surface); +} + +void +weston_desktop_surface_popup_ungrab(struct weston_desktop_surface *surface, + struct weston_desktop_seat *seat) +{ + weston_desktop_seat_popup_grab_remove_surface(seat, &surface->grab_link); +} + +void +weston_desktop_surface_popup_dismiss(struct weston_desktop_surface *surface) +{ + struct weston_desktop_view *view, *tmp; + + wl_list_for_each_safe(view, tmp, &surface->view_list, link) + weston_desktop_view_destroy(view); + wl_list_remove(&surface->grab_link); + wl_list_init(&surface->grab_link); + weston_desktop_surface_close(surface); +} diff --git a/libweston-desktop/wl-shell.c b/libweston-desktop/wl-shell.c new file mode 100644 index 0000000..9efec89 --- /dev/null +++ b/libweston-desktop/wl-shell.c @@ -0,0 +1,497 @@ +/* + * Copyright © 2010-2012 Intel Corporation + * Copyright © 2011-2012 Collabora, Ltd. + * Copyright © 2013 Raspberry Pi Foundation + * Copyright © 2016 Quentin "Sardem FF7" Glidic + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include + +#include + +#include +#include + +#include +#include "internal.h" + +#define WD_WL_SHELL_PROTOCOL_VERSION 1 + +enum weston_desktop_wl_shell_surface_state { + NONE, + TOPLEVEL, + MAXIMIZED, + FULLSCREEN, + TRANSIENT, + POPUP, +}; + +struct weston_desktop_wl_shell_surface { + struct wl_resource *resource; + struct weston_desktop *desktop; + struct wl_display *display; + struct weston_desktop_surface *surface; + struct weston_desktop_surface *parent; + bool added; + struct weston_desktop_seat *popup_seat; + enum weston_desktop_wl_shell_surface_state state; + struct wl_listener wl_surface_resource_destroy_listener; +}; + +static void +weston_desktop_wl_shell_surface_set_size(struct weston_desktop_surface *dsurface, + void *user_data, + int32_t width, int32_t height) +{ + struct weston_desktop_wl_shell_surface *surface = user_data; + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(surface->surface); + + if ((wsurface->width == width && wsurface->height == height) || + (width == 0 && height == 0)) + return; + + wl_shell_surface_send_configure(surface->resource, + WL_SHELL_SURFACE_RESIZE_NONE, + width, height); +} + +static void +weston_desktop_wl_shell_surface_maybe_ungrab(struct weston_desktop_wl_shell_surface *surface) +{ + if (surface->state != POPUP || + !weston_desktop_surface_get_grab(surface->surface)) + return; + + weston_desktop_surface_popup_ungrab(surface->surface, + surface->popup_seat); + surface->popup_seat = NULL; +} + +static void +weston_desktop_wl_shell_surface_committed(struct weston_desktop_surface *dsurface, + void *user_data, + int32_t sx, int32_t sy) +{ + struct weston_desktop_wl_shell_surface *surface = user_data; + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(dsurface); + + if (wsurface->buffer_ref.buffer == NULL) + weston_desktop_wl_shell_surface_maybe_ungrab(surface); + + if (surface->added) + weston_desktop_api_committed(surface->desktop, surface->surface, + sx, sy); +} + +static void +weston_desktop_wl_shell_surface_ping(struct weston_desktop_surface *dsurface, + uint32_t serial, void *user_data) +{ + struct weston_desktop_wl_shell_surface *surface = user_data; + + wl_shell_surface_send_ping(surface->resource, serial); +} + +static void +weston_desktop_wl_shell_surface_close(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_wl_shell_surface *surface = user_data; + + if (surface->state == POPUP) + wl_shell_surface_send_popup_done(surface->resource); +} + +static bool +weston_desktop_wl_shell_surface_get_maximized(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_wl_shell_surface *surface = user_data; + + return surface->state == MAXIMIZED; +} + +static bool +weston_desktop_wl_shell_surface_get_fullscreen(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_wl_shell_surface *surface = user_data; + + return surface->state == FULLSCREEN; +} + +static void +weston_desktop_wl_shell_change_state(struct weston_desktop_wl_shell_surface *surface, + enum weston_desktop_wl_shell_surface_state state, + struct weston_desktop_surface *parent, + int32_t x, int32_t y) +{ + bool to_add = (parent == NULL); + + assert(state != NONE); + + if (to_add && surface->added) { + surface->state = state; + return; + } + + if (surface->state != state) { + if (surface->state == POPUP) + weston_desktop_wl_shell_surface_maybe_ungrab(surface); + + if (to_add) { + weston_desktop_surface_unset_relative_to(surface->surface); + weston_desktop_api_surface_added(surface->desktop, + surface->surface); + } else if (surface->added) { + weston_desktop_api_surface_removed(surface->desktop, + surface->surface); + } + + surface->state = state; + surface->added = to_add; + } + + if (parent != NULL) + weston_desktop_surface_set_relative_to(surface->surface, parent, + x, y, false); +} + +static void +weston_desktop_wl_shell_surface_destroy(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_wl_shell_surface *surface = user_data; + + wl_list_remove(&surface->wl_surface_resource_destroy_listener.link); + + weston_desktop_wl_shell_surface_maybe_ungrab(surface); + weston_desktop_surface_unset_relative_to(surface->surface); + if (surface->added) + weston_desktop_api_surface_removed(surface->desktop, + surface->surface); + + free(surface); +} + +static void +weston_desktop_wl_shell_surface_protocol_pong(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t serial) +{ + struct weston_desktop_surface *surface = wl_resource_get_user_data(resource); + + weston_desktop_client_pong(weston_desktop_surface_get_client(surface), serial); +} + +static void +weston_desktop_wl_shell_surface_protocol_move(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_seat *seat = + wl_resource_get_user_data(seat_resource); + struct weston_desktop_wl_shell_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + + if (seat == NULL) + return; + + weston_desktop_api_move(surface->desktop, dsurface, seat, serial); +} + +static void +weston_desktop_wl_shell_surface_protocol_resize(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial, + enum wl_shell_surface_resize edges) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_seat *seat = wl_resource_get_user_data(seat_resource); + struct weston_desktop_wl_shell_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + enum weston_desktop_surface_edge surf_edges = + (enum weston_desktop_surface_edge) edges; + + if (seat == NULL) + return; + + weston_desktop_api_resize(surface->desktop, dsurface, seat, serial, surf_edges); +} + +static void +weston_desktop_wl_shell_surface_protocol_set_toplevel(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_wl_shell_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_wl_shell_change_state(surface, TOPLEVEL, NULL, 0, 0); + if (surface->parent == NULL) + return; + surface->parent = NULL; + weston_desktop_api_set_parent(surface->desktop, surface->surface, NULL); +} + +static void +weston_desktop_wl_shell_surface_protocol_set_transient(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *parent_resource, + int32_t x, int32_t y, + enum wl_shell_surface_transient flags) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_surface *wparent = + wl_resource_get_user_data(parent_resource); + struct weston_desktop_surface *parent; + struct weston_desktop_wl_shell_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + + if (!weston_surface_is_desktop_surface(wparent)) + return; + + parent = weston_surface_get_desktop_surface(wparent); + if (flags & WL_SHELL_SURFACE_TRANSIENT_INACTIVE) { + weston_desktop_wl_shell_change_state(surface, TRANSIENT, parent, + x, y); + } else { + weston_desktop_wl_shell_change_state(surface, TOPLEVEL, NULL, + 0, 0); + surface->parent = parent; + weston_desktop_api_set_parent(surface->desktop, + surface->surface, parent); + } +} + +static void +weston_desktop_wl_shell_surface_protocol_set_fullscreen(struct wl_client *wl_client, + struct wl_resource *resource, + enum wl_shell_surface_fullscreen_method method, + uint32_t framerate, + struct wl_resource *output_resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_wl_shell_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_output *output = NULL; + + if (output_resource != NULL) + output = weston_head_from_resource(output_resource)->output; + + weston_desktop_wl_shell_change_state(surface, FULLSCREEN, NULL, 0, 0); + weston_desktop_api_fullscreen_requested(surface->desktop, dsurface, + true, output); +} + +static void +weston_desktop_wl_shell_surface_protocol_set_popup(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial, + struct wl_resource *parent_resource, + int32_t x, int32_t y, + enum wl_shell_surface_transient flags) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_seat *wseat = wl_resource_get_user_data(seat_resource); + struct weston_desktop_seat *seat = weston_desktop_seat_from_seat(wseat); + struct weston_surface *parent = + wl_resource_get_user_data(parent_resource); + struct weston_desktop_surface *parent_surface; + struct weston_desktop_wl_shell_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + + /* Check that if we have a valid wseat we also got a valid desktop seat */ + if (wseat != NULL && seat == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + if (!weston_surface_is_desktop_surface(parent)) + return; + + parent_surface = weston_surface_get_desktop_surface(parent); + + weston_desktop_wl_shell_change_state(surface, POPUP, + parent_surface, x, y); + weston_desktop_surface_popup_grab(surface->surface, seat, serial); + surface->popup_seat = seat; +} + +static void +weston_desktop_wl_shell_surface_protocol_set_maximized(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *output_resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_wl_shell_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_wl_shell_change_state(surface, MAXIMIZED, NULL, 0, 0); + weston_desktop_api_maximized_requested(surface->desktop, dsurface, true); +} + +static void +weston_desktop_wl_shell_surface_protocol_set_title(struct wl_client *wl_client, + struct wl_resource *resource, + const char *title) +{ + struct weston_desktop_surface *surface = + wl_resource_get_user_data(resource); + + weston_desktop_surface_set_title(surface, title); +} + +static void +weston_desktop_wl_shell_surface_protocol_set_class(struct wl_client *wl_client, + struct wl_resource *resource, + const char *class_) +{ + struct weston_desktop_surface *surface = + wl_resource_get_user_data(resource); + + weston_desktop_surface_set_app_id(surface, class_); +} + + +static const struct wl_shell_surface_interface weston_desktop_wl_shell_surface_implementation = { + .pong = weston_desktop_wl_shell_surface_protocol_pong, + .move = weston_desktop_wl_shell_surface_protocol_move, + .resize = weston_desktop_wl_shell_surface_protocol_resize, + .set_toplevel = weston_desktop_wl_shell_surface_protocol_set_toplevel, + .set_transient = weston_desktop_wl_shell_surface_protocol_set_transient, + .set_fullscreen = weston_desktop_wl_shell_surface_protocol_set_fullscreen, + .set_popup = weston_desktop_wl_shell_surface_protocol_set_popup, + .set_maximized = weston_desktop_wl_shell_surface_protocol_set_maximized, + .set_title = weston_desktop_wl_shell_surface_protocol_set_title, + .set_class = weston_desktop_wl_shell_surface_protocol_set_class, +}; + +static const struct weston_desktop_surface_implementation weston_desktop_wl_shell_surface_internal_implementation = { + .set_size = weston_desktop_wl_shell_surface_set_size, + .committed = weston_desktop_wl_shell_surface_committed, + .ping = weston_desktop_wl_shell_surface_ping, + .close = weston_desktop_wl_shell_surface_close, + + .get_maximized = weston_desktop_wl_shell_surface_get_maximized, + .get_fullscreen = weston_desktop_wl_shell_surface_get_fullscreen, + + .destroy = weston_desktop_wl_shell_surface_destroy, +}; + +static void +wl_surface_resource_destroyed(struct wl_listener *listener, + void *data) +{ + struct weston_desktop_wl_shell_surface *surface = + wl_container_of(listener, surface, + wl_surface_resource_destroy_listener); + + /* the wl_shell_surface spec says that wl_shell_surfaces are to be + * destroyed automatically when the wl_surface is destroyed. */ + weston_desktop_surface_destroy(surface->surface); +} + +static void +weston_desktop_wl_shell_protocol_get_shell_surface(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface_resource) +{ + struct weston_desktop_client *client = wl_resource_get_user_data(resource); + struct weston_surface *wsurface = wl_resource_get_user_data(surface_resource); + struct weston_desktop_wl_shell_surface *surface; + + + if (weston_surface_set_role(wsurface, "wl_shell_surface", resource, WL_SHELL_ERROR_ROLE) < 0) + return; + + surface = zalloc(sizeof(struct weston_desktop_wl_shell_surface)); + if (surface == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + surface->desktop = weston_desktop_client_get_desktop(client); + surface->display = weston_desktop_get_display(surface->desktop); + + surface->surface = + weston_desktop_surface_create(surface->desktop, client, wsurface, + &weston_desktop_wl_shell_surface_internal_implementation, + surface); + if (surface->surface == NULL) { + free(surface); + return; + } + + surface->wl_surface_resource_destroy_listener.notify = + wl_surface_resource_destroyed; + wl_resource_add_destroy_listener(wsurface->resource, + &surface->wl_surface_resource_destroy_listener); + + surface->resource = + weston_desktop_surface_add_resource(surface->surface, + &wl_shell_surface_interface, + &weston_desktop_wl_shell_surface_implementation, + id, NULL); +} + + +static const struct wl_shell_interface weston_desktop_wl_shell_implementation = { + .get_shell_surface = weston_desktop_wl_shell_protocol_get_shell_surface, +}; + +static void +weston_desktop_wl_shell_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct weston_desktop *desktop = data; + + weston_desktop_client_create(desktop, client, NULL, &wl_shell_interface, + &weston_desktop_wl_shell_implementation, + version, id); +} + +struct wl_global * +weston_desktop_wl_shell_create(struct weston_desktop *desktop, + struct wl_display *display) +{ + return wl_global_create(display, + &wl_shell_interface, + WD_WL_SHELL_PROTOCOL_VERSION, desktop, + weston_desktop_wl_shell_bind); +} diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c new file mode 100644 index 0000000..955fcca --- /dev/null +++ b/libweston-desktop/xdg-shell-v6.c @@ -0,0 +1,1444 @@ +/* + * Copyright © 2010-2012 Intel Corporation + * Copyright © 2011-2012 Collabora, Ltd. + * Copyright © 2013 Raspberry Pi Foundation + * Copyright © 2016 Quentin "Sardem FF7" Glidic + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include + +#include +#include +#include "xdg-shell-unstable-v6-server-protocol.h" + +#include +#include "internal.h" + +#define WD_XDG_SHELL_PROTOCOL_VERSION 1 + +static const char *weston_desktop_xdg_toplevel_role = "xdg_toplevel"; +static const char *weston_desktop_xdg_popup_role = "xdg_popup"; + +struct weston_desktop_xdg_positioner { + struct weston_desktop *desktop; + struct weston_desktop_client *client; + struct wl_resource *resource; + + struct weston_size size; + struct weston_geometry anchor_rect; + enum zxdg_positioner_v6_anchor anchor; + enum zxdg_positioner_v6_gravity gravity; + enum zxdg_positioner_v6_constraint_adjustment constraint_adjustment; + struct weston_position offset; +}; + +enum weston_desktop_xdg_surface_role { + WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE, + WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL, + WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP, +}; + +struct weston_desktop_xdg_surface { + struct wl_resource *resource; + struct weston_desktop *desktop; + struct weston_surface *surface; + struct weston_desktop_surface *desktop_surface; + bool configured; + struct wl_event_source *configure_idle; + struct wl_list configure_list; /* weston_desktop_xdg_surface_configure::link */ + + bool has_next_geometry; + struct weston_geometry next_geometry; + + enum weston_desktop_xdg_surface_role role; +}; + +struct weston_desktop_xdg_surface_configure { + struct wl_list link; /* weston_desktop_xdg_surface::configure_list */ + uint32_t serial; +}; + +struct weston_desktop_xdg_toplevel_state { + bool maximized; + bool fullscreen; + bool resizing; + bool activated; +}; + +struct weston_desktop_xdg_toplevel_configure { + struct weston_desktop_xdg_surface_configure base; + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; +}; + +struct weston_desktop_xdg_toplevel { + struct weston_desktop_xdg_surface base; + + struct wl_resource *resource; + bool added; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + } pending; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + struct weston_size min_size, max_size; + } next; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size min_size, max_size; + } current; +}; + +struct weston_desktop_xdg_popup { + struct weston_desktop_xdg_surface base; + + struct wl_resource *resource; + bool committed; + struct weston_desktop_xdg_surface *parent; + struct weston_desktop_seat *seat; + struct weston_geometry geometry; +}; + +#define weston_desktop_surface_role_biggest_size (sizeof(struct weston_desktop_xdg_toplevel)) +#define weston_desktop_surface_configure_biggest_size (sizeof(struct weston_desktop_xdg_toplevel)) + + +static struct weston_geometry +weston_desktop_xdg_positioner_get_geometry(struct weston_desktop_xdg_positioner *positioner, + struct weston_desktop_surface *dsurface, + struct weston_desktop_surface *parent) +{ + struct weston_geometry geometry = { + .x = positioner->offset.x, + .y = positioner->offset.y, + .width = positioner->size.width, + .height = positioner->size.height, + }; + + if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP) + geometry.y += positioner->anchor_rect.y; + else if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM) + geometry.y += positioner->anchor_rect.y + positioner->anchor_rect.height; + else + geometry.y += positioner->anchor_rect.y + positioner->anchor_rect.height / 2; + + if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT) + geometry.x += positioner->anchor_rect.x; + else if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT) + geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width; + else + geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width / 2; + + if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_TOP) + geometry.y -= geometry.height; + else if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_BOTTOM) + geometry.y = geometry.y; + else + geometry.y -= geometry.height / 2; + + if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_LEFT) + geometry.x -= geometry.width; + else if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_RIGHT) + geometry.x = geometry.x; + else + geometry.x -= geometry.width / 2; + + if (positioner->constraint_adjustment == ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_NONE) + return geometry; + + /* TODO: add compositor policy configuration and the code here */ + + return geometry; +} + +static void +weston_desktop_xdg_positioner_protocol_set_size(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t width, int32_t height) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + if (width < 1 || height < 1) { + wl_resource_post_error(resource, + ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, + "width and height must be positives and non-zero"); + return; + } + + positioner->size.width = width; + positioner->size.height = height; +} + +static void +weston_desktop_xdg_positioner_protocol_set_anchor_rect(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t x, int32_t y, + int32_t width, int32_t height) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + if (width < 1 || height < 1) { + wl_resource_post_error(resource, + ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, + "width and height must be positives and non-zero"); + return; + } + + positioner->anchor_rect.x = x; + positioner->anchor_rect.y = y; + positioner->anchor_rect.width = width; + positioner->anchor_rect.height = height; +} + +static void +weston_desktop_xdg_positioner_protocol_set_anchor(struct wl_client *wl_client, + struct wl_resource *resource, + enum zxdg_positioner_v6_anchor anchor) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + if (((anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP ) && + (anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM)) || + ((anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT) && + (anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT))) { + wl_resource_post_error(resource, + ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, + "same-axis values are not allowed"); + return; + } + + positioner->anchor = anchor; +} + +static void +weston_desktop_xdg_positioner_protocol_set_gravity(struct wl_client *wl_client, + struct wl_resource *resource, + enum zxdg_positioner_v6_gravity gravity) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + if (((gravity & ZXDG_POSITIONER_V6_GRAVITY_TOP) && + (gravity & ZXDG_POSITIONER_V6_GRAVITY_BOTTOM)) || + ((gravity & ZXDG_POSITIONER_V6_GRAVITY_LEFT) && + (gravity & ZXDG_POSITIONER_V6_GRAVITY_RIGHT))) { + wl_resource_post_error(resource, + ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, + "same-axis values are not allowed"); + return; + } + + positioner->gravity = gravity; +} + +static void +weston_desktop_xdg_positioner_protocol_set_constraint_adjustment(struct wl_client *wl_client, + struct wl_resource *resource, + enum zxdg_positioner_v6_constraint_adjustment constraint_adjustment) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + positioner->constraint_adjustment = constraint_adjustment; +} + +static void +weston_desktop_xdg_positioner_protocol_set_offset(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t x, int32_t y) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + positioner->offset.x = x; + positioner->offset.y = y; +} + +static void +weston_desktop_xdg_positioner_destroy(struct wl_resource *resource) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + free(positioner); +} + +static const struct zxdg_positioner_v6_interface weston_desktop_xdg_positioner_implementation = { + .destroy = weston_desktop_destroy_request, + .set_size = weston_desktop_xdg_positioner_protocol_set_size, + .set_anchor_rect = weston_desktop_xdg_positioner_protocol_set_anchor_rect, + .set_anchor = weston_desktop_xdg_positioner_protocol_set_anchor, + .set_gravity = weston_desktop_xdg_positioner_protocol_set_gravity, + .set_constraint_adjustment = weston_desktop_xdg_positioner_protocol_set_constraint_adjustment, + .set_offset = weston_desktop_xdg_positioner_protocol_set_offset, +}; + +static void +weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface); + +static void +weston_desktop_xdg_toplevel_ensure_added(struct weston_desktop_xdg_toplevel *toplevel) +{ + if (toplevel->added) + return; + + weston_desktop_api_surface_added(toplevel->base.desktop, + toplevel->base.desktop_surface); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); + toplevel->added = true; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_parent(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *parent_resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_surface *parent = NULL; + + if (parent_resource != NULL) + parent = wl_resource_get_user_data(parent_resource); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_set_parent(toplevel->base.desktop, dsurface, parent); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_title(struct wl_client *wl_client, + struct wl_resource *resource, + const char *title) +{ + struct weston_desktop_surface *toplevel = + wl_resource_get_user_data(resource); + + weston_desktop_surface_set_title(toplevel, title); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_app_id(struct wl_client *wl_client, + struct wl_resource *resource, + const char *app_id) +{ + struct weston_desktop_surface *toplevel = + wl_resource_get_user_data(resource); + + weston_desktop_surface_set_app_id(toplevel, app_id); +} + +static void +weston_desktop_xdg_toplevel_protocol_show_window_menu(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial, + int32_t x, int32_t y) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_seat *seat = + wl_resource_get_user_data(seat_resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + if (!toplevel->base.configured) { + wl_resource_post_error(toplevel->resource, + ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, + "Surface has not been configured yet"); + return; + } + + if (seat == NULL) + return; + + weston_desktop_api_show_window_menu(toplevel->base.desktop, + dsurface, seat, x, y); +} + +static void +weston_desktop_xdg_toplevel_protocol_move(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_seat *seat = + wl_resource_get_user_data(seat_resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + if (!toplevel->base.configured) { + wl_resource_post_error(toplevel->resource, + ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, + "Surface has not been configured yet"); + return; + } + + if (seat == NULL) + return; + + weston_desktop_api_move(toplevel->base.desktop, dsurface, seat, serial); +} + +static void +weston_desktop_xdg_toplevel_protocol_resize(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial, + enum zxdg_toplevel_v6_resize_edge edges) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_seat *seat = + wl_resource_get_user_data(seat_resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + enum weston_desktop_surface_edge surf_edges = + (enum weston_desktop_surface_edge) edges; + + if (!toplevel->base.configured) { + wl_resource_post_error(toplevel->resource, + ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, + "Surface has not been configured yet"); + return; + } + + if (seat == NULL) + return; + + weston_desktop_api_resize(toplevel->base.desktop, + dsurface, seat, serial, surf_edges); +} + +static void +weston_desktop_xdg_toplevel_ack_configure(struct weston_desktop_xdg_toplevel *toplevel, + struct weston_desktop_xdg_toplevel_configure *configure) +{ + toplevel->next.state = configure->state; + toplevel->next.size = configure->size; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_min_size(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t width, int32_t height) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + toplevel->next.min_size.width = width; + toplevel->next.min_size.height = height; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_max_size(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t width, int32_t height) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + toplevel->next.max_size.width = width; + toplevel->next.max_size.height = height; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_maximized(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_maximized_requested(toplevel->base.desktop, dsurface, true); +} + +static void +weston_desktop_xdg_toplevel_protocol_unset_maximized(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_maximized_requested(toplevel->base.desktop, dsurface, false); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_fullscreen(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *output_resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_output *output = NULL; + + if (output_resource != NULL) + output = weston_head_from_resource(output_resource)->output; + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_fullscreen_requested(toplevel->base.desktop, dsurface, + true, output); +} + +static void +weston_desktop_xdg_toplevel_protocol_unset_fullscreen(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_fullscreen_requested(toplevel->base.desktop, dsurface, + false, NULL); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_minimized(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_minimized_requested(toplevel->base.desktop, dsurface); +} + +static void +weston_desktop_xdg_toplevel_send_configure(struct weston_desktop_xdg_toplevel *toplevel, + struct weston_desktop_xdg_toplevel_configure *configure) +{ + uint32_t *s; + struct wl_array states; + + configure->state = toplevel->pending.state; + configure->size = toplevel->pending.size; + + wl_array_init(&states); + if (toplevel->pending.state.maximized) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED; + } + if (toplevel->pending.state.fullscreen) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN; + } + if (toplevel->pending.state.resizing) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = ZXDG_TOPLEVEL_V6_STATE_RESIZING; + } + if (toplevel->pending.state.activated) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = ZXDG_TOPLEVEL_V6_STATE_ACTIVATED; + } + + zxdg_toplevel_v6_send_configure(toplevel->resource, + toplevel->pending.size.width, + toplevel->pending.size.height, + &states); + + wl_array_release(&states); +}; + +static void +weston_desktop_xdg_toplevel_set_maximized(struct weston_desktop_surface *dsurface, + void *user_data, bool maximized) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.maximized = maximized; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_fullscreen(struct weston_desktop_surface *dsurface, + void *user_data, bool fullscreen) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.fullscreen = fullscreen; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_resizing(struct weston_desktop_surface *dsurface, + void *user_data, bool resizing) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.resizing = resizing; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_activated(struct weston_desktop_surface *dsurface, + void *user_data, bool activated) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.activated = activated; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_size(struct weston_desktop_surface *dsurface, + void *user_data, + int32_t width, int32_t height) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.size.width = width; + toplevel->pending.size.height = height; + + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplevel, + int32_t sx, int32_t sy) +{ + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(toplevel->base.desktop_surface); + + if (!wsurface->buffer_ref.buffer && !toplevel->added) { + weston_desktop_xdg_toplevel_ensure_added(toplevel); + return; + } + if (!wsurface->buffer_ref.buffer) + return; + + struct weston_geometry geometry = + weston_desktop_surface_get_geometry(toplevel->base.desktop_surface); + + if ((toplevel->next.state.maximized || toplevel->next.state.fullscreen) && + (toplevel->next.size.width != geometry.width || + toplevel->next.size.height != geometry.height)) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(toplevel->base.desktop_surface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + ZXDG_SHELL_V6_ERROR_INVALID_SURFACE_STATE, + "xdg_surface buffer does not match the configured state"); + return; + } + + toplevel->current.state = toplevel->next.state; + toplevel->current.min_size = toplevel->next.min_size; + toplevel->current.max_size = toplevel->next.max_size; + + weston_desktop_api_committed(toplevel->base.desktop, + toplevel->base.desktop_surface, + sx, sy); +} + +static void +weston_desktop_xdg_toplevel_close(struct weston_desktop_xdg_toplevel *toplevel) +{ + zxdg_toplevel_v6_send_close(toplevel->resource); +} + +static bool +weston_desktop_xdg_toplevel_get_maximized(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.maximized; +} + +static bool +weston_desktop_xdg_toplevel_get_fullscreen(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.fullscreen; +} + +static bool +weston_desktop_xdg_toplevel_get_resizing(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.resizing; +} + +static bool +weston_desktop_xdg_toplevel_get_activated(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.activated; +} + +static void +weston_desktop_xdg_toplevel_destroy(struct weston_desktop_xdg_toplevel *toplevel) +{ + if (toplevel->added) + weston_desktop_api_surface_removed(toplevel->base.desktop, + toplevel->base.desktop_surface); +} + +static void +weston_desktop_xdg_toplevel_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + + if (dsurface != NULL) + weston_desktop_surface_resource_destroy(resource); +} + +static const struct zxdg_toplevel_v6_interface weston_desktop_xdg_toplevel_implementation = { + .destroy = weston_desktop_destroy_request, + .set_parent = weston_desktop_xdg_toplevel_protocol_set_parent, + .set_title = weston_desktop_xdg_toplevel_protocol_set_title, + .set_app_id = weston_desktop_xdg_toplevel_protocol_set_app_id, + .show_window_menu = weston_desktop_xdg_toplevel_protocol_show_window_menu, + .move = weston_desktop_xdg_toplevel_protocol_move, + .resize = weston_desktop_xdg_toplevel_protocol_resize, + .set_min_size = weston_desktop_xdg_toplevel_protocol_set_min_size, + .set_max_size = weston_desktop_xdg_toplevel_protocol_set_max_size, + .set_maximized = weston_desktop_xdg_toplevel_protocol_set_maximized, + .unset_maximized = weston_desktop_xdg_toplevel_protocol_unset_maximized, + .set_fullscreen = weston_desktop_xdg_toplevel_protocol_set_fullscreen, + .unset_fullscreen = weston_desktop_xdg_toplevel_protocol_unset_fullscreen, + .set_minimized = weston_desktop_xdg_toplevel_protocol_set_minimized, +}; + +static void +weston_desktop_xdg_popup_protocol_grab(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_popup *popup = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_seat *wseat = wl_resource_get_user_data(seat_resource); + struct weston_desktop_seat *seat = weston_desktop_seat_from_seat(wseat); + struct weston_desktop_surface *topmost; + bool parent_is_toplevel = + popup->parent->role == WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL; + + /* Check that if we have a valid wseat we also got a valid desktop seat */ + if (wseat != NULL && seat == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + if (popup->committed) { + wl_resource_post_error(popup->resource, + ZXDG_POPUP_V6_ERROR_INVALID_GRAB, + "xdg_popup already is mapped"); + return; + } + + /* If seat is NULL then get_topmost_surface will return NULL. In + * combination with setting parent_is_toplevel to TRUE here we will + * avoid posting an error, and we will instead gracefully fail the + * grab and dismiss the surface. + * FIXME: this is a hack because currently we cannot check the topmost + * parent with a destroyed weston_seat */ + if (seat == NULL) + parent_is_toplevel = true; + + topmost = weston_desktop_seat_popup_grab_get_topmost_surface(seat); + if ((topmost == NULL && !parent_is_toplevel) || + (topmost != NULL && topmost != popup->parent->desktop_surface)) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + ZXDG_SHELL_V6_ERROR_NOT_THE_TOPMOST_POPUP, + "xdg_popup was not created on the topmost popup"); + return; + } + + popup->seat = seat; + weston_desktop_surface_popup_grab(popup->base.desktop_surface, + popup->seat, serial); +} + +static void +weston_desktop_xdg_popup_send_configure(struct weston_desktop_xdg_popup *popup) +{ + zxdg_popup_v6_send_configure(popup->resource, + popup->geometry.x, + popup->geometry.y, + popup->geometry.width, + popup->geometry.height); +} + +static void +weston_desktop_xdg_popup_update_position(struct weston_desktop_surface *dsurface, + void *user_data); + +static void +weston_desktop_xdg_popup_committed(struct weston_desktop_xdg_popup *popup) +{ + struct weston_surface *wsurface = + weston_desktop_surface_get_surface (popup->base.desktop_surface); + struct weston_view *view; + + wl_list_for_each(view, &wsurface->views, surface_link) + weston_view_update_transform(view); + + if (!popup->committed) + weston_desktop_xdg_surface_schedule_configure(&popup->base); + popup->committed = true; + weston_desktop_xdg_popup_update_position(popup->base.desktop_surface, + popup); +} + +static void +weston_desktop_xdg_popup_update_position(struct weston_desktop_surface *dsurface, + void *user_data) +{ +} + +static void +weston_desktop_xdg_popup_close(struct weston_desktop_xdg_popup *popup) +{ + zxdg_popup_v6_send_popup_done(popup->resource); +} + +static void +weston_desktop_xdg_popup_destroy(struct weston_desktop_xdg_popup *popup) +{ + struct weston_desktop_surface *topmost; + struct weston_desktop_client *client = + weston_desktop_surface_get_client(popup->base.desktop_surface); + + if (!weston_desktop_surface_get_grab(popup->base.desktop_surface)) + return; + + topmost = weston_desktop_seat_popup_grab_get_topmost_surface(popup->seat); + if (topmost != popup->base.desktop_surface) { + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + ZXDG_SHELL_V6_ERROR_NOT_THE_TOPMOST_POPUP, + "xdg_popup was destroyed while it was not the topmost popup."); + } + + weston_desktop_surface_popup_ungrab(popup->base.desktop_surface, + popup->seat); +} + +static void +weston_desktop_xdg_popup_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + + if (dsurface != NULL) + weston_desktop_surface_resource_destroy(resource); +} + +static const struct zxdg_popup_v6_interface weston_desktop_xdg_popup_implementation = { + .destroy = weston_desktop_destroy_request, + .grab = weston_desktop_xdg_popup_protocol_grab, +}; + +static void +weston_desktop_xdg_surface_send_configure(void *user_data) +{ + struct weston_desktop_xdg_surface *surface = user_data; + struct weston_desktop_xdg_surface_configure *configure; + + surface->configure_idle = NULL; + + configure = zalloc(weston_desktop_surface_configure_biggest_size); + if (configure == NULL) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(surface->desktop_surface); + struct wl_client *wl_client = + weston_desktop_client_get_client(client); + wl_client_post_no_memory(wl_client); + return; + } + wl_list_insert(surface->configure_list.prev, &configure->link); + configure->serial = + wl_display_next_serial(weston_desktop_get_display(surface->desktop)); + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_send_configure((struct weston_desktop_xdg_toplevel *) surface, + (struct weston_desktop_xdg_toplevel_configure *) configure); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_send_configure((struct weston_desktop_xdg_popup *) surface); + break; + } + + zxdg_surface_v6_send_configure(surface->resource, configure->serial); +} + +static bool +weston_desktop_xdg_toplevel_state_compare(struct weston_desktop_xdg_toplevel *toplevel) +{ + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + } configured; + + if (!toplevel->base.configured) + return false; + + if (wl_list_empty(&toplevel->base.configure_list)) { + /* Last configure is actually the current state, just use it */ + configured.state = toplevel->current.state; + configured.size.width = toplevel->base.surface->width; + configured.size.height = toplevel->base.surface->height; + } else { + struct weston_desktop_xdg_toplevel_configure *configure = + wl_container_of(toplevel->base.configure_list.prev, + configure, base.link); + + configured.state = configure->state; + configured.size = configure->size; + } + + if (toplevel->pending.state.activated != configured.state.activated) + return false; + if (toplevel->pending.state.fullscreen != configured.state.fullscreen) + return false; + if (toplevel->pending.state.maximized != configured.state.maximized) + return false; + if (toplevel->pending.state.resizing != configured.state.resizing) + return false; + + if (toplevel->pending.size.width == configured.size.width && + toplevel->pending.size.height == configured.size.height) + return true; + + if (toplevel->pending.size.width == 0 && + toplevel->pending.size.height == 0) + return true; + + return false; +} + +static void +weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface) +{ + struct wl_display *display = weston_desktop_get_display(surface->desktop); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + bool pending_same = false; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + pending_same = weston_desktop_xdg_toplevel_state_compare((struct weston_desktop_xdg_toplevel *) surface); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + break; + } + + if (surface->configure_idle != NULL) { + if (!pending_same) + return; + + wl_event_source_remove(surface->configure_idle); + surface->configure_idle = NULL; + } else { + if (pending_same) + return; + + surface->configure_idle = + wl_event_loop_add_idle(loop, + weston_desktop_xdg_surface_send_configure, + surface); + } +} + +static void +weston_desktop_xdg_surface_protocol_get_toplevel(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(dsurface); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + if (weston_surface_set_role(wsurface, weston_desktop_xdg_toplevel_role, + resource, ZXDG_SHELL_V6_ERROR_ROLE) < 0) + return; + + toplevel->resource = + weston_desktop_surface_add_resource(toplevel->base.desktop_surface, + &zxdg_toplevel_v6_interface, + &weston_desktop_xdg_toplevel_implementation, + id, weston_desktop_xdg_toplevel_resource_destroy); + if (toplevel->resource == NULL) + return; + + toplevel->base.role = WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL; +} + +static void +weston_desktop_xdg_surface_protocol_get_popup(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *parent_resource, + struct wl_resource *positioner_resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(dsurface); + struct weston_desktop_xdg_popup *popup = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_surface *parent_surface = + wl_resource_get_user_data(parent_resource); + struct weston_desktop_xdg_surface *parent = + weston_desktop_surface_get_implementation_data(parent_surface); + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(positioner_resource); + + /* Checking whether the size and anchor rect both have a positive size + * is enough to verify both have been correctly set */ + if (positioner->size.width == 0 || positioner->anchor_rect.width == 0) { + wl_resource_post_error(resource, + ZXDG_SHELL_V6_ERROR_INVALID_POSITIONER, + "positioner object is not complete"); + return; + } + + if (weston_surface_set_role(wsurface, weston_desktop_xdg_popup_role, + resource, ZXDG_SHELL_V6_ERROR_ROLE) < 0) + return; + + popup->resource = + weston_desktop_surface_add_resource(popup->base.desktop_surface, + &zxdg_popup_v6_interface, + &weston_desktop_xdg_popup_implementation, + id, weston_desktop_xdg_popup_resource_destroy); + if (popup->resource == NULL) + return; + + popup->base.role = WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP; + popup->parent = parent; + + popup->geometry = + weston_desktop_xdg_positioner_get_geometry(positioner, + dsurface, + parent_surface); + + weston_desktop_surface_set_relative_to(popup->base.desktop_surface, + parent_surface, + popup->geometry.x, + popup->geometry.y, + true); +} + +static bool +weston_desktop_xdg_surface_check_role(struct weston_desktop_xdg_surface *surface) +{ + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(surface->desktop_surface); + const char *role; + + role = weston_surface_get_role(wsurface); + if (role != NULL && + (role == weston_desktop_xdg_toplevel_role || + role == weston_desktop_xdg_popup_role)) + return true; + + wl_resource_post_error(surface->resource, + ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + return false; +} + +static void +weston_desktop_xdg_surface_protocol_set_window_geometry(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t x, int32_t y, + int32_t width, int32_t height) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + + if (!weston_desktop_xdg_surface_check_role(surface)) + return; + + surface->has_next_geometry = true; + surface->next_geometry.x = x; + surface->next_geometry.y = y; + surface->next_geometry.width = width; + surface->next_geometry.height = height; +} + +static void +weston_desktop_xdg_surface_protocol_ack_configure(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t serial) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_xdg_surface_configure *configure, *temp; + bool found = false; + + if (!weston_desktop_xdg_surface_check_role(surface)) + return; + + wl_list_for_each_safe(configure, temp, &surface->configure_list, link) { + if (configure->serial < serial) { + wl_list_remove(&configure->link); + free(configure); + } else if (configure->serial == serial) { + wl_list_remove(&configure->link); + found = true; + break; + } else { + break; + } + } + if (!found) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + wl_resource_post_error(client_resource, + ZXDG_SHELL_V6_ERROR_INVALID_SURFACE_STATE, + "Wrong configure serial: %u", serial); + return; + } + + surface->configured = true; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_ack_configure((struct weston_desktop_xdg_toplevel *) surface, + (struct weston_desktop_xdg_toplevel_configure *) configure); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + break; + } + + free(configure); +} + +static void +weston_desktop_xdg_surface_ping(struct weston_desktop_surface *dsurface, + uint32_t serial, void *user_data) +{ + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + + zxdg_shell_v6_send_ping(weston_desktop_client_get_resource(client), + serial); +} + +static void +weston_desktop_xdg_surface_committed(struct weston_desktop_surface *dsurface, + void *user_data, + int32_t sx, int32_t sy) +{ + struct weston_desktop_xdg_surface *surface = user_data; + struct weston_surface *wsurface = + weston_desktop_surface_get_surface (dsurface); + + if (wsurface->buffer_ref.buffer && !surface->configured) { + wl_resource_post_error(surface->resource, + ZXDG_SURFACE_V6_ERROR_UNCONFIGURED_BUFFER, + "xdg_surface has never been configured"); + return; + } + + if (surface->has_next_geometry) { + surface->has_next_geometry = false; + weston_desktop_surface_set_geometry(surface->desktop_surface, + surface->next_geometry); + } + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + wl_resource_post_error(surface->resource, + ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_committed((struct weston_desktop_xdg_toplevel *) surface, sx, sy); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_committed((struct weston_desktop_xdg_popup *) surface); + break; + } +} + +static void +weston_desktop_xdg_surface_close(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_surface *surface = user_data; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_close((struct weston_desktop_xdg_toplevel *) surface); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_close((struct weston_desktop_xdg_popup *) surface); + break; + } +} + +static void +weston_desktop_xdg_surface_destroy(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_surface *surface = user_data; + struct weston_desktop_xdg_surface_configure *configure, *temp; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_destroy((struct weston_desktop_xdg_toplevel *) surface); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_destroy((struct weston_desktop_xdg_popup *) surface); + break; + } + + if (surface->configure_idle != NULL) + wl_event_source_remove(surface->configure_idle); + + wl_list_for_each_safe(configure, temp, &surface->configure_list, link) + free(configure); + + free(surface); +} + +static const struct zxdg_surface_v6_interface weston_desktop_xdg_surface_implementation = { + .destroy = weston_desktop_destroy_request, + .get_toplevel = weston_desktop_xdg_surface_protocol_get_toplevel, + .get_popup = weston_desktop_xdg_surface_protocol_get_popup, + .set_window_geometry = weston_desktop_xdg_surface_protocol_set_window_geometry, + .ack_configure = weston_desktop_xdg_surface_protocol_ack_configure, +}; + +static const struct weston_desktop_surface_implementation weston_desktop_xdg_surface_internal_implementation = { + /* These are used for toplevel only */ + .set_maximized = weston_desktop_xdg_toplevel_set_maximized, + .set_fullscreen = weston_desktop_xdg_toplevel_set_fullscreen, + .set_resizing = weston_desktop_xdg_toplevel_set_resizing, + .set_activated = weston_desktop_xdg_toplevel_set_activated, + .set_size = weston_desktop_xdg_toplevel_set_size, + + .get_maximized = weston_desktop_xdg_toplevel_get_maximized, + .get_fullscreen = weston_desktop_xdg_toplevel_get_fullscreen, + .get_resizing = weston_desktop_xdg_toplevel_get_resizing, + .get_activated = weston_desktop_xdg_toplevel_get_activated, + + /* These are used for popup only */ + .update_position = weston_desktop_xdg_popup_update_position, + + /* Common API */ + .committed = weston_desktop_xdg_surface_committed, + .ping = weston_desktop_xdg_surface_ping, + .close = weston_desktop_xdg_surface_close, + + .destroy = weston_desktop_xdg_surface_destroy, +}; + +static void +weston_desktop_xdg_shell_protocol_create_positioner(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id) +{ + struct weston_desktop_client *client = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_positioner *positioner; + + positioner = zalloc(sizeof(struct weston_desktop_xdg_positioner)); + if (positioner == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + positioner->client = client; + positioner->desktop = weston_desktop_client_get_desktop(positioner->client); + + positioner->resource = + wl_resource_create(wl_client, + &zxdg_positioner_v6_interface, + wl_resource_get_version(resource), id); + if (positioner->resource == NULL) { + wl_client_post_no_memory(wl_client); + free(positioner); + return; + } + wl_resource_set_implementation(positioner->resource, + &weston_desktop_xdg_positioner_implementation, + positioner, weston_desktop_xdg_positioner_destroy); +} + +static void +weston_desktop_xdg_surface_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + + if (dsurface != NULL) + weston_desktop_surface_resource_destroy(resource); +} + +static void +weston_desktop_xdg_shell_protocol_get_xdg_surface(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface_resource) +{ + struct weston_desktop_client *client = + wl_resource_get_user_data(resource); + struct weston_surface *wsurface = + wl_resource_get_user_data(surface_resource); + struct weston_desktop_xdg_surface *surface; + + surface = zalloc(weston_desktop_surface_role_biggest_size); + if (surface == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + surface->desktop = weston_desktop_client_get_desktop(client); + surface->surface = wsurface; + wl_list_init(&surface->configure_list); + + surface->desktop_surface = + weston_desktop_surface_create(surface->desktop, client, + surface->surface, + &weston_desktop_xdg_surface_internal_implementation, + surface); + if (surface->desktop_surface == NULL) { + free(surface); + return; + } + + surface->resource = + weston_desktop_surface_add_resource(surface->desktop_surface, + &zxdg_surface_v6_interface, + &weston_desktop_xdg_surface_implementation, + id, weston_desktop_xdg_surface_resource_destroy); + if (surface->resource == NULL) + return; + + if (wsurface->buffer_ref.buffer != NULL) { + wl_resource_post_error(surface->resource, + ZXDG_SURFACE_V6_ERROR_UNCONFIGURED_BUFFER, + "xdg_surface must not have a buffer at creation"); + return; + } +} + +static void +weston_desktop_xdg_shell_protocol_pong(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t serial) +{ + struct weston_desktop_client *client = + wl_resource_get_user_data(resource); + + weston_desktop_client_pong(client, serial); +} + +static const struct zxdg_shell_v6_interface weston_desktop_xdg_shell_implementation = { + .destroy = weston_desktop_destroy_request, + .create_positioner = weston_desktop_xdg_shell_protocol_create_positioner, + .get_xdg_surface = weston_desktop_xdg_shell_protocol_get_xdg_surface, + .pong = weston_desktop_xdg_shell_protocol_pong, +}; + +static void +weston_desktop_xdg_shell_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct weston_desktop *desktop = data; + + weston_desktop_client_create(desktop, client, NULL, + &zxdg_shell_v6_interface, + &weston_desktop_xdg_shell_implementation, + version, id); +} + +struct wl_global * +weston_desktop_xdg_shell_v6_create(struct weston_desktop *desktop, struct wl_display *display) +{ + return wl_global_create(display, &zxdg_shell_v6_interface, + WD_XDG_SHELL_PROTOCOL_VERSION, desktop, + weston_desktop_xdg_shell_bind); +} diff --git a/libweston-desktop/xdg-shell.c b/libweston-desktop/xdg-shell.c new file mode 100644 index 0000000..4a9eb97 --- /dev/null +++ b/libweston-desktop/xdg-shell.c @@ -0,0 +1,1498 @@ +/* + * Copyright © 2010-2012 Intel Corporation + * Copyright © 2011-2012 Collabora, Ltd. + * Copyright © 2013 Raspberry Pi Foundation + * Copyright © 2016 Quentin "Sardem FF7" Glidic + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include + +#include +#include +#include "xdg-shell-server-protocol.h" + +#include +#include "internal.h" + +/************************************************************************************ + * WARNING: This file implements the stable xdg shell protocol. + * Any changes to this file may also need to be added to the xdg-shell-v6.c file which + * implements the older unstable xdg shell v6 protocol. + ************************************************************************************/ + +#define WD_XDG_SHELL_PROTOCOL_VERSION 1 + +static const char *weston_desktop_xdg_toplevel_role = "xdg_toplevel"; +static const char *weston_desktop_xdg_popup_role = "xdg_popup"; + +struct weston_desktop_xdg_positioner { + struct weston_desktop *desktop; + struct weston_desktop_client *client; + struct wl_resource *resource; + + struct weston_size size; + struct weston_geometry anchor_rect; + enum xdg_positioner_anchor anchor; + enum xdg_positioner_gravity gravity; + enum xdg_positioner_constraint_adjustment constraint_adjustment; + struct weston_position offset; +}; + +enum weston_desktop_xdg_surface_role { + WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE, + WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL, + WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP, +}; + +struct weston_desktop_xdg_surface { + struct wl_resource *resource; + struct weston_desktop *desktop; + struct weston_surface *surface; + struct weston_desktop_surface *desktop_surface; + bool configured; + struct wl_event_source *configure_idle; + struct wl_list configure_list; /* weston_desktop_xdg_surface_configure::link */ + + bool has_next_geometry; + struct weston_geometry next_geometry; + + enum weston_desktop_xdg_surface_role role; +}; + +struct weston_desktop_xdg_surface_configure { + struct wl_list link; /* weston_desktop_xdg_surface::configure_list */ + uint32_t serial; +}; + +struct weston_desktop_xdg_toplevel_state { + bool maximized; + bool fullscreen; + bool resizing; + bool activated; +}; + +struct weston_desktop_xdg_toplevel_configure { + struct weston_desktop_xdg_surface_configure base; + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; +}; + +struct weston_desktop_xdg_toplevel { + struct weston_desktop_xdg_surface base; + + struct wl_resource *resource; + bool added; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + } pending; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + struct weston_size min_size, max_size; + } next; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size min_size, max_size; + } current; +}; + +struct weston_desktop_xdg_popup { + struct weston_desktop_xdg_surface base; + + struct wl_resource *resource; + bool committed; + struct weston_desktop_xdg_surface *parent; + struct weston_desktop_seat *seat; + struct weston_geometry geometry; +}; + +#define weston_desktop_surface_role_biggest_size (sizeof(struct weston_desktop_xdg_toplevel)) +#define weston_desktop_surface_configure_biggest_size (sizeof(struct weston_desktop_xdg_toplevel)) + + +static struct weston_geometry +weston_desktop_xdg_positioner_get_geometry(struct weston_desktop_xdg_positioner *positioner, + struct weston_desktop_surface *dsurface, + struct weston_desktop_surface *parent) +{ + struct weston_geometry geometry = { + .x = positioner->offset.x, + .y = positioner->offset.y, + .width = positioner->size.width, + .height = positioner->size.height, + }; + + switch (positioner->anchor) { + case XDG_POSITIONER_ANCHOR_TOP: + case XDG_POSITIONER_ANCHOR_TOP_LEFT: + case XDG_POSITIONER_ANCHOR_TOP_RIGHT: + geometry.y += positioner->anchor_rect.y; + break; + case XDG_POSITIONER_ANCHOR_BOTTOM: + case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT: + case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT: + geometry.y += positioner->anchor_rect.y + positioner->anchor_rect.height; + break; + default: + geometry.y += positioner->anchor_rect.y + positioner->anchor_rect.height / 2; + } + + switch (positioner->anchor) { + case XDG_POSITIONER_ANCHOR_LEFT: + case XDG_POSITIONER_ANCHOR_TOP_LEFT: + case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT: + geometry.x += positioner->anchor_rect.x; + break; + case XDG_POSITIONER_ANCHOR_RIGHT: + case XDG_POSITIONER_ANCHOR_TOP_RIGHT: + case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT: + geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width; + break; + default: + geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width / 2; + } + + switch (positioner->gravity) { + case XDG_POSITIONER_GRAVITY_TOP: + case XDG_POSITIONER_GRAVITY_TOP_LEFT: + case XDG_POSITIONER_GRAVITY_TOP_RIGHT: + geometry.y -= geometry.height; + break; + case XDG_POSITIONER_GRAVITY_BOTTOM: + case XDG_POSITIONER_GRAVITY_BOTTOM_LEFT: + case XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT: + geometry.y = geometry.y; + break; + default: + geometry.y -= geometry.height / 2; + } + + switch (positioner->gravity) { + case XDG_POSITIONER_GRAVITY_LEFT: + case XDG_POSITIONER_GRAVITY_TOP_LEFT: + case XDG_POSITIONER_GRAVITY_BOTTOM_LEFT: + geometry.x -= geometry.width; + break; + case XDG_POSITIONER_GRAVITY_RIGHT: + case XDG_POSITIONER_GRAVITY_TOP_RIGHT: + case XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT: + geometry.x = geometry.x; + break; + default: + geometry.x -= geometry.width / 2; + } + + if (positioner->constraint_adjustment == XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE) + return geometry; + + /* TODO: add compositor policy configuration and the code here */ + + return geometry; +} + +static void +weston_desktop_xdg_positioner_protocol_set_size(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t width, int32_t height) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + if (width < 1 || height < 1) { + wl_resource_post_error(resource, + XDG_POSITIONER_ERROR_INVALID_INPUT, + "width and height must be positives and non-zero"); + return; + } + + positioner->size.width = width; + positioner->size.height = height; +} + +static void +weston_desktop_xdg_positioner_protocol_set_anchor_rect(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t x, int32_t y, + int32_t width, int32_t height) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + if (width < 0 || height < 0) { + wl_resource_post_error(resource, + XDG_POSITIONER_ERROR_INVALID_INPUT, + "width and height must be non-negative"); + return; + } + + positioner->anchor_rect.x = x; + positioner->anchor_rect.y = y; + positioner->anchor_rect.width = width; + positioner->anchor_rect.height = height; +} + +static void +weston_desktop_xdg_positioner_protocol_set_anchor(struct wl_client *wl_client, + struct wl_resource *resource, + enum xdg_positioner_anchor anchor) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + positioner->anchor = anchor; +} + +static void +weston_desktop_xdg_positioner_protocol_set_gravity(struct wl_client *wl_client, + struct wl_resource *resource, + enum xdg_positioner_gravity gravity) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + positioner->gravity = gravity; +} + +static void +weston_desktop_xdg_positioner_protocol_set_constraint_adjustment(struct wl_client *wl_client, + struct wl_resource *resource, + enum xdg_positioner_constraint_adjustment constraint_adjustment) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + positioner->constraint_adjustment = constraint_adjustment; +} + +static void +weston_desktop_xdg_positioner_protocol_set_offset(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t x, int32_t y) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + positioner->offset.x = x; + positioner->offset.y = y; +} + +static void +weston_desktop_xdg_positioner_destroy(struct wl_resource *resource) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + free(positioner); +} + +static const struct xdg_positioner_interface weston_desktop_xdg_positioner_implementation = { + .destroy = weston_desktop_destroy_request, + .set_size = weston_desktop_xdg_positioner_protocol_set_size, + .set_anchor_rect = weston_desktop_xdg_positioner_protocol_set_anchor_rect, + .set_anchor = weston_desktop_xdg_positioner_protocol_set_anchor, + .set_gravity = weston_desktop_xdg_positioner_protocol_set_gravity, + .set_constraint_adjustment = weston_desktop_xdg_positioner_protocol_set_constraint_adjustment, + .set_offset = weston_desktop_xdg_positioner_protocol_set_offset, +}; + +static void +weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface); + +static void +weston_desktop_xdg_toplevel_ensure_added(struct weston_desktop_xdg_toplevel *toplevel) +{ + if (toplevel->added) + return; + + weston_desktop_api_surface_added(toplevel->base.desktop, + toplevel->base.desktop_surface); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); + toplevel->added = true; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_parent(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *parent_resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_surface *parent = NULL; + + if (parent_resource != NULL) + parent = wl_resource_get_user_data(parent_resource); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_set_parent(toplevel->base.desktop, dsurface, parent); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_title(struct wl_client *wl_client, + struct wl_resource *resource, + const char *title) +{ + struct weston_desktop_surface *toplevel = + wl_resource_get_user_data(resource); + + weston_desktop_surface_set_title(toplevel, title); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_app_id(struct wl_client *wl_client, + struct wl_resource *resource, + const char *app_id) +{ + struct weston_desktop_surface *toplevel = + wl_resource_get_user_data(resource); + + weston_desktop_surface_set_app_id(toplevel, app_id); +} + +static void +weston_desktop_xdg_toplevel_protocol_show_window_menu(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial, + int32_t x, int32_t y) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_seat *seat = + wl_resource_get_user_data(seat_resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + if (!toplevel->base.configured) { + wl_resource_post_error(toplevel->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "Surface has not been configured yet"); + return; + } + + if (seat == NULL) + return; + + weston_desktop_api_show_window_menu(toplevel->base.desktop, + dsurface, seat, x, y); +} + +static void +weston_desktop_xdg_toplevel_protocol_move(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_seat *seat = + wl_resource_get_user_data(seat_resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + if (!toplevel->base.configured) { + wl_resource_post_error(toplevel->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "Surface has not been configured yet"); + return; + } + + if (seat == NULL) + return; + + weston_desktop_api_move(toplevel->base.desktop, dsurface, seat, serial); +} + +static void +weston_desktop_xdg_toplevel_protocol_resize(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial, + enum xdg_toplevel_resize_edge edges) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_seat *seat = + wl_resource_get_user_data(seat_resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + enum weston_desktop_surface_edge surf_edges = + (enum weston_desktop_surface_edge) edges; + + if (!toplevel->base.configured) { + wl_resource_post_error(toplevel->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "Surface has not been configured yet"); + return; + } + + if (seat == NULL) + return; + + weston_desktop_api_resize(toplevel->base.desktop, + dsurface, seat, serial, surf_edges); +} + +static void +weston_desktop_xdg_toplevel_ack_configure(struct weston_desktop_xdg_toplevel *toplevel, + struct weston_desktop_xdg_toplevel_configure *configure) +{ + toplevel->next.state = configure->state; + toplevel->next.size = configure->size; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_min_size(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t width, int32_t height) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + toplevel->next.min_size.width = width; + toplevel->next.min_size.height = height; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_max_size(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t width, int32_t height) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + toplevel->next.max_size.width = width; + toplevel->next.max_size.height = height; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_maximized(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_maximized_requested(toplevel->base.desktop, dsurface, true); +} + +static void +weston_desktop_xdg_toplevel_protocol_unset_maximized(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_maximized_requested(toplevel->base.desktop, dsurface, false); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_fullscreen(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *output_resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_output *output = NULL; + + if (output_resource != NULL) + output = weston_head_from_resource(output_resource)->output; + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_fullscreen_requested(toplevel->base.desktop, dsurface, + true, output); +} + +static void +weston_desktop_xdg_toplevel_protocol_unset_fullscreen(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_fullscreen_requested(toplevel->base.desktop, dsurface, + false, NULL); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_minimized(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_minimized_requested(toplevel->base.desktop, dsurface); +} + +static void +weston_desktop_xdg_toplevel_send_configure(struct weston_desktop_xdg_toplevel *toplevel, + struct weston_desktop_xdg_toplevel_configure *configure) +{ + uint32_t *s; + struct wl_array states; + + configure->state = toplevel->pending.state; + configure->size = toplevel->pending.size; + + wl_array_init(&states); + if (toplevel->pending.state.maximized) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = XDG_TOPLEVEL_STATE_MAXIMIZED; + } + if (toplevel->pending.state.fullscreen) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = XDG_TOPLEVEL_STATE_FULLSCREEN; + } + if (toplevel->pending.state.resizing) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = XDG_TOPLEVEL_STATE_RESIZING; + } + if (toplevel->pending.state.activated) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = XDG_TOPLEVEL_STATE_ACTIVATED; + } + + xdg_toplevel_send_configure(toplevel->resource, + toplevel->pending.size.width, + toplevel->pending.size.height, + &states); + + wl_array_release(&states); +}; + +static void +weston_desktop_xdg_toplevel_set_maximized(struct weston_desktop_surface *dsurface, + void *user_data, bool maximized) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.maximized = maximized; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_fullscreen(struct weston_desktop_surface *dsurface, + void *user_data, bool fullscreen) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.fullscreen = fullscreen; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_resizing(struct weston_desktop_surface *dsurface, + void *user_data, bool resizing) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.resizing = resizing; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_activated(struct weston_desktop_surface *dsurface, + void *user_data, bool activated) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.activated = activated; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_size(struct weston_desktop_surface *dsurface, + void *user_data, + int32_t width, int32_t height) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.size.width = width; + toplevel->pending.size.height = height; + + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplevel, + int32_t sx, int32_t sy) +{ + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(toplevel->base.desktop_surface); + + if (!wsurface->buffer_ref.buffer && !toplevel->added) { + weston_desktop_xdg_toplevel_ensure_added(toplevel); + return; + } + if (!wsurface->buffer_ref.buffer) + return; + + struct weston_geometry geometry = + weston_desktop_surface_get_geometry(toplevel->base.desktop_surface); + + if (toplevel->next.state.maximized && + (toplevel->next.size.width != geometry.width || + toplevel->next.size.height != geometry.height)) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(toplevel->base.desktop_surface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, + "xdg_surface buffer (%" PRIi32 " x %" PRIi32 ") " + "does not match the configured maximized state (%" PRIi32 " x %" PRIi32 ")", + geometry.width, geometry.height, + toplevel->next.size.width, + toplevel->next.size.height); + return; + } + + if (toplevel->next.state.fullscreen && + (toplevel->next.size.width < geometry.width || + toplevel->next.size.height < geometry.height)) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(toplevel->base.desktop_surface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, + "xdg_surface buffer (%" PRIi32 " x %" PRIi32 ") " + "is larger than the configured fullscreen state (%" PRIi32 " x %" PRIi32 ")", + geometry.width, geometry.height, + toplevel->next.size.width, + toplevel->next.size.height); + return; + } + + toplevel->current.state = toplevel->next.state; + toplevel->current.min_size = toplevel->next.min_size; + toplevel->current.max_size = toplevel->next.max_size; + + weston_desktop_api_committed(toplevel->base.desktop, + toplevel->base.desktop_surface, + sx, sy); +} + +static void +weston_desktop_xdg_toplevel_close(struct weston_desktop_xdg_toplevel *toplevel) +{ + xdg_toplevel_send_close(toplevel->resource); +} + +static bool +weston_desktop_xdg_toplevel_get_maximized(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.maximized; +} + +static bool +weston_desktop_xdg_toplevel_get_fullscreen(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.fullscreen; +} + +static bool +weston_desktop_xdg_toplevel_get_resizing(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.resizing; +} + +static bool +weston_desktop_xdg_toplevel_get_activated(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.activated; +} + +static void +weston_desktop_xdg_toplevel_destroy(struct weston_desktop_xdg_toplevel *toplevel) +{ + if (toplevel->added) + weston_desktop_api_surface_removed(toplevel->base.desktop, + toplevel->base.desktop_surface); +} + +static void +weston_desktop_xdg_toplevel_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + + if (dsurface != NULL) + weston_desktop_surface_resource_destroy(resource); +} + +static const struct xdg_toplevel_interface weston_desktop_xdg_toplevel_implementation = { + .destroy = weston_desktop_destroy_request, + .set_parent = weston_desktop_xdg_toplevel_protocol_set_parent, + .set_title = weston_desktop_xdg_toplevel_protocol_set_title, + .set_app_id = weston_desktop_xdg_toplevel_protocol_set_app_id, + .show_window_menu = weston_desktop_xdg_toplevel_protocol_show_window_menu, + .move = weston_desktop_xdg_toplevel_protocol_move, + .resize = weston_desktop_xdg_toplevel_protocol_resize, + .set_min_size = weston_desktop_xdg_toplevel_protocol_set_min_size, + .set_max_size = weston_desktop_xdg_toplevel_protocol_set_max_size, + .set_maximized = weston_desktop_xdg_toplevel_protocol_set_maximized, + .unset_maximized = weston_desktop_xdg_toplevel_protocol_unset_maximized, + .set_fullscreen = weston_desktop_xdg_toplevel_protocol_set_fullscreen, + .unset_fullscreen = weston_desktop_xdg_toplevel_protocol_unset_fullscreen, + .set_minimized = weston_desktop_xdg_toplevel_protocol_set_minimized, +}; + +static void +weston_desktop_xdg_popup_protocol_grab(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_popup *popup = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_seat *wseat = wl_resource_get_user_data(seat_resource); + struct weston_desktop_seat *seat = weston_desktop_seat_from_seat(wseat); + struct weston_desktop_surface *topmost; + bool parent_is_toplevel = + popup->parent->role == WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL; + + /* Check that if we have a valid wseat we also got a valid desktop seat */ + if (wseat != NULL && seat == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + if (popup->committed) { + wl_resource_post_error(popup->resource, + XDG_POPUP_ERROR_INVALID_GRAB, + "xdg_popup already is mapped"); + return; + } + + /* If seat is NULL then get_topmost_surface will return NULL. In + * combination with setting parent_is_toplevel to TRUE here we will + * avoid posting an error, and we will instead gracefully fail the + * grab and dismiss the surface. + * FIXME: this is a hack because currently we cannot check the topmost + * parent with a destroyed weston_seat */ + if (seat == NULL) + parent_is_toplevel = true; + + topmost = weston_desktop_seat_popup_grab_get_topmost_surface(seat); + if ((topmost == NULL && !parent_is_toplevel) || + (topmost != NULL && topmost != popup->parent->desktop_surface)) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP, + "xdg_popup was not created on the topmost popup"); + return; + } + + popup->seat = seat; + weston_desktop_surface_popup_grab(popup->base.desktop_surface, + popup->seat, serial); +} + +static void +weston_desktop_xdg_popup_send_configure(struct weston_desktop_xdg_popup *popup) +{ + xdg_popup_send_configure(popup->resource, + popup->geometry.x, + popup->geometry.y, + popup->geometry.width, + popup->geometry.height); +} + +static void +weston_desktop_xdg_popup_update_position(struct weston_desktop_surface *dsurface, + void *user_data); + +static void +weston_desktop_xdg_popup_committed(struct weston_desktop_xdg_popup *popup) +{ + struct weston_surface *wsurface = + weston_desktop_surface_get_surface (popup->base.desktop_surface); + struct weston_view *view; + + wl_list_for_each(view, &wsurface->views, surface_link) + weston_view_update_transform(view); + + if (!popup->committed) + weston_desktop_xdg_surface_schedule_configure(&popup->base); + popup->committed = true; + weston_desktop_xdg_popup_update_position(popup->base.desktop_surface, + popup); +} + +static void +weston_desktop_xdg_popup_update_position(struct weston_desktop_surface *dsurface, + void *user_data) +{ +} + +static void +weston_desktop_xdg_popup_close(struct weston_desktop_xdg_popup *popup) +{ + xdg_popup_send_popup_done(popup->resource); +} + +static void +weston_desktop_xdg_popup_destroy(struct weston_desktop_xdg_popup *popup) +{ + struct weston_desktop_surface *topmost; + struct weston_desktop_client *client = + weston_desktop_surface_get_client(popup->base.desktop_surface); + + if (!weston_desktop_surface_get_grab(popup->base.desktop_surface)) + return; + + topmost = weston_desktop_seat_popup_grab_get_topmost_surface(popup->seat); + if (topmost != popup->base.desktop_surface) { + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP, + "xdg_popup was destroyed while it was not the topmost popup."); + } + + weston_desktop_surface_popup_ungrab(popup->base.desktop_surface, + popup->seat); +} + +static void +weston_desktop_xdg_popup_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + + if (dsurface != NULL) + weston_desktop_surface_resource_destroy(resource); +} + +static const struct xdg_popup_interface weston_desktop_xdg_popup_implementation = { + .destroy = weston_desktop_destroy_request, + .grab = weston_desktop_xdg_popup_protocol_grab, +}; + +static void +weston_desktop_xdg_surface_send_configure(void *user_data) +{ + struct weston_desktop_xdg_surface *surface = user_data; + struct weston_desktop_xdg_surface_configure *configure; + + surface->configure_idle = NULL; + + configure = zalloc(weston_desktop_surface_configure_biggest_size); + if (configure == NULL) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(surface->desktop_surface); + struct wl_client *wl_client = + weston_desktop_client_get_client(client); + wl_client_post_no_memory(wl_client); + return; + } + wl_list_insert(surface->configure_list.prev, &configure->link); + configure->serial = + wl_display_next_serial(weston_desktop_get_display(surface->desktop)); + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_send_configure((struct weston_desktop_xdg_toplevel *) surface, + (struct weston_desktop_xdg_toplevel_configure *) configure); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_send_configure((struct weston_desktop_xdg_popup *) surface); + break; + } + + xdg_surface_send_configure(surface->resource, configure->serial); +} + +static bool +weston_desktop_xdg_toplevel_state_compare(struct weston_desktop_xdg_toplevel *toplevel) +{ + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + } configured; + + if (!toplevel->base.configured) + return false; + + if (wl_list_empty(&toplevel->base.configure_list)) { + /* Last configure is actually the current state, just use it */ + configured.state = toplevel->current.state; + configured.size.width = toplevel->base.surface->width; + configured.size.height = toplevel->base.surface->height; + } else { + struct weston_desktop_xdg_toplevel_configure *configure = + wl_container_of(toplevel->base.configure_list.prev, + configure, base.link); + + configured.state = configure->state; + configured.size = configure->size; + } + + if (toplevel->pending.state.activated != configured.state.activated) + return false; + if (toplevel->pending.state.fullscreen != configured.state.fullscreen) + return false; + if (toplevel->pending.state.maximized != configured.state.maximized) + return false; + if (toplevel->pending.state.resizing != configured.state.resizing) + return false; + + if (toplevel->pending.size.width == configured.size.width && + toplevel->pending.size.height == configured.size.height) + return true; + + if (toplevel->pending.size.width == 0 && + toplevel->pending.size.height == 0) + return true; + + return false; +} + +static void +weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface) +{ + struct wl_display *display = weston_desktop_get_display(surface->desktop); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + bool pending_same = false; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + pending_same = weston_desktop_xdg_toplevel_state_compare((struct weston_desktop_xdg_toplevel *) surface); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + break; + } + + if (surface->configure_idle != NULL) { + if (!pending_same) + return; + + wl_event_source_remove(surface->configure_idle); + surface->configure_idle = NULL; + } else { + if (pending_same) + return; + + surface->configure_idle = + wl_event_loop_add_idle(loop, + weston_desktop_xdg_surface_send_configure, + surface); + } +} + +static void +weston_desktop_xdg_surface_protocol_get_toplevel(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(dsurface); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + if (weston_surface_set_role(wsurface, weston_desktop_xdg_toplevel_role, + resource, XDG_WM_BASE_ERROR_ROLE) < 0) + return; + + toplevel->resource = + weston_desktop_surface_add_resource(toplevel->base.desktop_surface, + &xdg_toplevel_interface, + &weston_desktop_xdg_toplevel_implementation, + id, weston_desktop_xdg_toplevel_resource_destroy); + if (toplevel->resource == NULL) + return; + + toplevel->base.role = WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL; +} + +static void +weston_desktop_xdg_surface_protocol_get_popup(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *parent_resource, + struct wl_resource *positioner_resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(dsurface); + struct weston_desktop_xdg_popup *popup = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_surface *parent_surface; + struct weston_desktop_xdg_surface *parent; + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(positioner_resource); + + /* Popup parents are allowed to be non-null, but only if a parent is + * specified 'using some other protocol' before committing. Since we + * don't support such a protocol yet, clients cannot legitimately + * create a popup with a non-null parent. */ + if (!parent_resource) { + wl_resource_post_error(resource, + XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT, + "popup parent must be non-null"); + return; + } + + parent_surface = wl_resource_get_user_data(parent_resource); + parent = weston_desktop_surface_get_implementation_data(parent_surface); + + /* Checking whether the size and anchor rect both have a positive size + * is enough to verify both have been correctly set */ + if (positioner->size.width == 0 || positioner->anchor_rect.width == 0 || + positioner->anchor_rect.height == 0) { + wl_resource_post_error(resource, + XDG_WM_BASE_ERROR_INVALID_POSITIONER, + "positioner object is not complete"); + return; + } + + if (weston_surface_set_role(wsurface, weston_desktop_xdg_popup_role, + resource, XDG_WM_BASE_ERROR_ROLE) < 0) + return; + + popup->resource = + weston_desktop_surface_add_resource(popup->base.desktop_surface, + &xdg_popup_interface, + &weston_desktop_xdg_popup_implementation, + id, weston_desktop_xdg_popup_resource_destroy); + if (popup->resource == NULL) + return; + + popup->base.role = WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP; + popup->parent = parent; + + popup->geometry = + weston_desktop_xdg_positioner_get_geometry(positioner, + dsurface, + parent_surface); + + weston_desktop_surface_set_relative_to(popup->base.desktop_surface, + parent_surface, + popup->geometry.x, + popup->geometry.y, + true); +} + +static bool +weston_desktop_xdg_surface_check_role(struct weston_desktop_xdg_surface *surface) +{ + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(surface->desktop_surface); + const char *role; + + role = weston_surface_get_role(wsurface); + if (role != NULL && + (role == weston_desktop_xdg_toplevel_role || + role == weston_desktop_xdg_popup_role)) + return true; + + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + return false; +} + +static void +weston_desktop_xdg_surface_protocol_set_window_geometry(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t x, int32_t y, + int32_t width, int32_t height) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + + if (!weston_desktop_xdg_surface_check_role(surface)) + return; + + surface->has_next_geometry = true; + surface->next_geometry.x = x; + surface->next_geometry.y = y; + surface->next_geometry.width = width; + surface->next_geometry.height = height; +} + +static void +weston_desktop_xdg_surface_protocol_ack_configure(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t serial) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_xdg_surface_configure *configure, *temp; + bool found = false; + + if (!weston_desktop_xdg_surface_check_role(surface)) + return; + + wl_list_for_each_safe(configure, temp, &surface->configure_list, link) { + if (configure->serial < serial) { + wl_list_remove(&configure->link); + free(configure); + } else if (configure->serial == serial) { + wl_list_remove(&configure->link); + found = true; + break; + } else { + break; + } + } + if (!found) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + wl_resource_post_error(client_resource, + XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, + "Wrong configure serial: %u", serial); + return; + } + + surface->configured = true; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_ack_configure((struct weston_desktop_xdg_toplevel *) surface, + (struct weston_desktop_xdg_toplevel_configure *) configure); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + break; + } + + free(configure); +} + +static void +weston_desktop_xdg_surface_ping(struct weston_desktop_surface *dsurface, + uint32_t serial, void *user_data) +{ + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + + xdg_wm_base_send_ping(weston_desktop_client_get_resource(client), + serial); +} + +static void +weston_desktop_xdg_surface_committed(struct weston_desktop_surface *dsurface, + void *user_data, + int32_t sx, int32_t sy) +{ + struct weston_desktop_xdg_surface *surface = user_data; + struct weston_surface *wsurface = + weston_desktop_surface_get_surface (dsurface); + + if (wsurface->buffer_ref.buffer && !surface->configured) { + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER, + "xdg_surface has never been configured"); + return; + } + + if (surface->has_next_geometry) { + surface->has_next_geometry = false; + weston_desktop_surface_set_geometry(surface->desktop_surface, + surface->next_geometry); + } + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_committed((struct weston_desktop_xdg_toplevel *) surface, sx, sy); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_committed((struct weston_desktop_xdg_popup *) surface); + break; + } +} + +static void +weston_desktop_xdg_surface_close(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_surface *surface = user_data; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_close((struct weston_desktop_xdg_toplevel *) surface); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_close((struct weston_desktop_xdg_popup *) surface); + break; + } +} + +static void +weston_desktop_xdg_surface_destroy(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_surface *surface = user_data; + struct weston_desktop_xdg_surface_configure *configure, *temp; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_destroy((struct weston_desktop_xdg_toplevel *) surface); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_destroy((struct weston_desktop_xdg_popup *) surface); + break; + } + + if (surface->configure_idle != NULL) + wl_event_source_remove(surface->configure_idle); + + wl_list_for_each_safe(configure, temp, &surface->configure_list, link) + free(configure); + + free(surface); +} + +static const struct xdg_surface_interface weston_desktop_xdg_surface_implementation = { + .destroy = weston_desktop_destroy_request, + .get_toplevel = weston_desktop_xdg_surface_protocol_get_toplevel, + .get_popup = weston_desktop_xdg_surface_protocol_get_popup, + .set_window_geometry = weston_desktop_xdg_surface_protocol_set_window_geometry, + .ack_configure = weston_desktop_xdg_surface_protocol_ack_configure, +}; + +static const struct weston_desktop_surface_implementation weston_desktop_xdg_surface_internal_implementation = { + /* These are used for toplevel only */ + .set_maximized = weston_desktop_xdg_toplevel_set_maximized, + .set_fullscreen = weston_desktop_xdg_toplevel_set_fullscreen, + .set_resizing = weston_desktop_xdg_toplevel_set_resizing, + .set_activated = weston_desktop_xdg_toplevel_set_activated, + .set_size = weston_desktop_xdg_toplevel_set_size, + + .get_maximized = weston_desktop_xdg_toplevel_get_maximized, + .get_fullscreen = weston_desktop_xdg_toplevel_get_fullscreen, + .get_resizing = weston_desktop_xdg_toplevel_get_resizing, + .get_activated = weston_desktop_xdg_toplevel_get_activated, + + /* These are used for popup only */ + .update_position = weston_desktop_xdg_popup_update_position, + + /* Common API */ + .committed = weston_desktop_xdg_surface_committed, + .ping = weston_desktop_xdg_surface_ping, + .close = weston_desktop_xdg_surface_close, + + .destroy = weston_desktop_xdg_surface_destroy, +}; + +static void +weston_desktop_xdg_shell_protocol_create_positioner(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id) +{ + struct weston_desktop_client *client = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_positioner *positioner; + + positioner = zalloc(sizeof(struct weston_desktop_xdg_positioner)); + if (positioner == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + positioner->client = client; + positioner->desktop = weston_desktop_client_get_desktop(positioner->client); + + positioner->resource = + wl_resource_create(wl_client, + &xdg_positioner_interface, + wl_resource_get_version(resource), id); + if (positioner->resource == NULL) { + wl_client_post_no_memory(wl_client); + free(positioner); + return; + } + wl_resource_set_implementation(positioner->resource, + &weston_desktop_xdg_positioner_implementation, + positioner, weston_desktop_xdg_positioner_destroy); +} + +static void +weston_desktop_xdg_surface_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + + if (dsurface != NULL) + weston_desktop_surface_resource_destroy(resource); +} + +static void +weston_desktop_xdg_shell_protocol_get_xdg_surface(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface_resource) +{ + struct weston_desktop_client *client = + wl_resource_get_user_data(resource); + struct weston_surface *wsurface = + wl_resource_get_user_data(surface_resource); + struct weston_desktop_xdg_surface *surface; + + surface = zalloc(weston_desktop_surface_role_biggest_size); + if (surface == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + surface->desktop = weston_desktop_client_get_desktop(client); + surface->surface = wsurface; + wl_list_init(&surface->configure_list); + + surface->desktop_surface = + weston_desktop_surface_create(surface->desktop, client, + surface->surface, + &weston_desktop_xdg_surface_internal_implementation, + surface); + if (surface->desktop_surface == NULL) { + free(surface); + return; + } + + surface->resource = + weston_desktop_surface_add_resource(surface->desktop_surface, + &xdg_surface_interface, + &weston_desktop_xdg_surface_implementation, + id, weston_desktop_xdg_surface_resource_destroy); + if (surface->resource == NULL) + return; + + if (wsurface->buffer_ref.buffer != NULL) { + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER, + "xdg_surface must not have a buffer at creation"); + return; + } +} + +static void +weston_desktop_xdg_shell_protocol_pong(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t serial) +{ + struct weston_desktop_client *client = + wl_resource_get_user_data(resource); + + weston_desktop_client_pong(client, serial); +} + +static const struct xdg_wm_base_interface weston_desktop_xdg_shell_implementation = { + .destroy = weston_desktop_destroy_request, + .create_positioner = weston_desktop_xdg_shell_protocol_create_positioner, + .get_xdg_surface = weston_desktop_xdg_shell_protocol_get_xdg_surface, + .pong = weston_desktop_xdg_shell_protocol_pong, +}; + +static void +weston_desktop_xdg_shell_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct weston_desktop *desktop = data; + + weston_desktop_client_create(desktop, client, NULL, + &xdg_wm_base_interface, + &weston_desktop_xdg_shell_implementation, + version, id); +} + +struct wl_global * +weston_desktop_xdg_wm_base_create(struct weston_desktop *desktop, + struct wl_display *display) +{ + return wl_global_create(display, &xdg_wm_base_interface, + WD_XDG_SHELL_PROTOCOL_VERSION, desktop, + weston_desktop_xdg_shell_bind); +} diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c new file mode 100644 index 0000000..711c8a3 --- /dev/null +++ b/libweston-desktop/xwayland.c @@ -0,0 +1,425 @@ +/* + * Copyright © 2010-2012 Intel Corporation + * Copyright © 2011-2012 Collabora, Ltd. + * Copyright © 2013 Raspberry Pi Foundation + * Copyright © 2016 Quentin "Sardem FF7" Glidic + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include + +#include + +#include +#include + +#include +#include "internal.h" +#include "xwayland/xwayland-internal-interface.h" + +enum weston_desktop_xwayland_surface_state { + NONE, + TOPLEVEL, + MAXIMIZED, + FULLSCREEN, + TRANSIENT, + XWAYLAND, +}; + +struct weston_desktop_xwayland { + struct weston_desktop *desktop; + struct weston_desktop_client *client; + struct weston_layer layer; +}; + +struct weston_desktop_xwayland_surface { + struct weston_desktop_xwayland *xwayland; + struct weston_desktop *desktop; + struct weston_desktop_surface *surface; + struct wl_listener resource_destroy_listener; + struct weston_view *view; + const struct weston_xwayland_client_interface *client_interface; + struct weston_geometry next_geometry; + bool has_next_geometry; + bool committed; + bool added; + enum weston_desktop_xwayland_surface_state state; +}; + +static void +weston_desktop_xwayland_surface_change_state(struct weston_desktop_xwayland_surface *surface, + enum weston_desktop_xwayland_surface_state state, + struct weston_desktop_surface *parent, + int32_t x, int32_t y) +{ + struct weston_surface *wsurface; + bool to_add = (parent == NULL && state != XWAYLAND); + + assert(state != NONE); + assert(!parent || state == TRANSIENT); + + if (to_add && surface->added) { + surface->state = state; + return; + } + + wsurface = weston_desktop_surface_get_surface(surface->surface); + + if (surface->state != state) { + if (surface->state == XWAYLAND) { + assert(!surface->added); + + weston_desktop_surface_unlink_view(surface->view); + weston_view_destroy(surface->view); + surface->view = NULL; + weston_surface_unmap(wsurface); + } + + if (to_add) { + weston_desktop_surface_unset_relative_to(surface->surface); + weston_desktop_api_surface_added(surface->desktop, + surface->surface); + surface->added = true; + if (surface->state == NONE && surface->committed) + /* We had a race, and wl_surface.commit() was + * faster, just fake a commit to map the + * surface */ + weston_desktop_api_committed(surface->desktop, + surface->surface, + 0, 0); + + } else if (surface->added) { + weston_desktop_api_surface_removed(surface->desktop, + surface->surface); + surface->added = false; + } + + if (state == XWAYLAND) { + assert(!surface->added); + + surface->view = + weston_desktop_surface_create_view(surface->surface); + weston_layer_entry_insert(&surface->xwayland->layer.view_list, + &surface->view->layer_link); + surface->view->is_mapped = true; + wsurface->is_mapped = true; + } + + surface->state = state; + } + + if (parent != NULL) + weston_desktop_surface_set_relative_to(surface->surface, parent, + x, y, false); +} + +static void +weston_desktop_xwayland_surface_committed(struct weston_desktop_surface *dsurface, + void *user_data, + int32_t sx, int32_t sy) +{ + struct weston_desktop_xwayland_surface *surface = user_data; + struct weston_geometry oldgeom; + + assert(dsurface == surface->surface); + surface->committed = true; + +#ifdef WM_DEBUG + weston_log("%s: xwayland surface %p\n", __func__, surface); +#endif + + if (surface->has_next_geometry) { + oldgeom = weston_desktop_surface_get_geometry(surface->surface); + sx -= surface->next_geometry.x - oldgeom.x; + sy -= surface->next_geometry.y - oldgeom.x; + + surface->has_next_geometry = false; + weston_desktop_surface_set_geometry(surface->surface, + surface->next_geometry); + } + + if (surface->added) + weston_desktop_api_committed(surface->desktop, surface->surface, + sx, sy); +} + +static void +weston_desktop_xwayland_surface_set_size(struct weston_desktop_surface *dsurface, + void *user_data, + int32_t width, int32_t height) +{ + struct weston_desktop_xwayland_surface *surface = user_data; + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(surface->surface); + + surface->client_interface->send_configure(wsurface, width, height); +} + +static void +weston_desktop_xwayland_surface_destroy(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xwayland_surface *surface = user_data; + + wl_list_remove(&surface->resource_destroy_listener.link); + + weston_desktop_surface_unset_relative_to(surface->surface); + if (surface->added) + weston_desktop_api_surface_removed(surface->desktop, + surface->surface); + else if (surface->state == XWAYLAND) + weston_desktop_surface_unlink_view(surface->view); + + free(surface); +} + +static bool +weston_desktop_xwayland_surface_get_maximized(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xwayland_surface *surface = user_data; + + return surface->state == MAXIMIZED; +} + +static bool +weston_desktop_xwayland_surface_get_fullscreen(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xwayland_surface *surface = user_data; + + return surface->state == FULLSCREEN; +} + +static const struct weston_desktop_surface_implementation weston_desktop_xwayland_surface_internal_implementation = { + .committed = weston_desktop_xwayland_surface_committed, + .set_size = weston_desktop_xwayland_surface_set_size, + + .get_maximized = weston_desktop_xwayland_surface_get_maximized, + .get_fullscreen = weston_desktop_xwayland_surface_get_fullscreen, + + .destroy = weston_desktop_xwayland_surface_destroy, +}; + +static void +weston_destop_xwayland_resource_destroyed(struct wl_listener *listener, + void *data) +{ + struct weston_desktop_xwayland_surface *surface = + wl_container_of(listener, surface, resource_destroy_listener); + + weston_desktop_surface_destroy(surface->surface); +} + +static struct weston_desktop_xwayland_surface * +create_surface(struct weston_desktop_xwayland *xwayland, + struct weston_surface *wsurface, + const struct weston_xwayland_client_interface *client_interface) +{ + struct weston_desktop_xwayland_surface *surface; + + surface = zalloc(sizeof(struct weston_desktop_xwayland_surface)); + if (surface == NULL) + return NULL; + + surface->xwayland = xwayland; + surface->desktop = xwayland->desktop; + surface->client_interface = client_interface; + + surface->surface = + weston_desktop_surface_create(surface->desktop, + xwayland->client, wsurface, + &weston_desktop_xwayland_surface_internal_implementation, + surface); + if (surface->surface == NULL) { + free(surface); + return NULL; + } + + surface->resource_destroy_listener.notify = + weston_destop_xwayland_resource_destroyed; + wl_resource_add_destroy_listener(wsurface->resource, + &surface->resource_destroy_listener); + + weston_desktop_surface_set_pid(surface->surface, 0); + + return surface; +} + +static void +set_toplevel(struct weston_desktop_xwayland_surface *surface) +{ + weston_desktop_xwayland_surface_change_state(surface, TOPLEVEL, NULL, + 0, 0); +} + +static void +set_toplevel_with_position(struct weston_desktop_xwayland_surface *surface, + int32_t x, int32_t y) +{ + weston_desktop_xwayland_surface_change_state(surface, TOPLEVEL, NULL, + 0, 0); + weston_desktop_api_set_xwayland_position(surface->desktop, + surface->surface, x, y); +} + +static void +set_parent(struct weston_desktop_xwayland_surface *surface, + struct weston_surface *wparent) +{ + struct weston_desktop_surface *parent; + + if (!weston_surface_is_desktop_surface(wparent)) + return; + + parent = weston_surface_get_desktop_surface(wparent); + weston_desktop_api_set_parent(surface->desktop, surface->surface, parent); +} + +static void +set_transient(struct weston_desktop_xwayland_surface *surface, + struct weston_surface *wparent, int x, int y) +{ + struct weston_desktop_surface *parent; + + if (!weston_surface_is_desktop_surface(wparent)) + return; + + parent = weston_surface_get_desktop_surface(wparent); + weston_desktop_xwayland_surface_change_state(surface, TRANSIENT, parent, + x, y); +} + +static void +set_fullscreen(struct weston_desktop_xwayland_surface *surface, + struct weston_output *output) +{ + weston_desktop_xwayland_surface_change_state(surface, FULLSCREEN, NULL, + 0, 0); + weston_desktop_api_fullscreen_requested(surface->desktop, + surface->surface, true, output); +} + +static void +set_xwayland(struct weston_desktop_xwayland_surface *surface, int x, int y) +{ + weston_desktop_xwayland_surface_change_state(surface, XWAYLAND, NULL, + x, y); + weston_view_set_position(surface->view, x, y); +} + +static int +move(struct weston_desktop_xwayland_surface *surface, + struct weston_pointer *pointer) +{ + if (surface->state == TOPLEVEL || + surface->state == MAXIMIZED || + surface->state == FULLSCREEN) + weston_desktop_api_move(surface->desktop, surface->surface, + pointer->seat, pointer->grab_serial); + return 0; +} + +static int +resize(struct weston_desktop_xwayland_surface *surface, + struct weston_pointer *pointer, uint32_t edges) +{ + if (surface->state == TOPLEVEL || + surface->state == MAXIMIZED || + surface->state == FULLSCREEN) + weston_desktop_api_resize(surface->desktop, surface->surface, + pointer->seat, pointer->grab_serial, + edges); + return 0; +} + +static void +set_title(struct weston_desktop_xwayland_surface *surface, const char *title) +{ + weston_desktop_surface_set_title(surface->surface, title); +} + +static void +set_window_geometry(struct weston_desktop_xwayland_surface *surface, + int32_t x, int32_t y, int32_t width, int32_t height) +{ + surface->has_next_geometry = true; + surface->next_geometry.x = x; + surface->next_geometry.y = y; + surface->next_geometry.width = width; + surface->next_geometry.height = height; +} + +static void +set_maximized(struct weston_desktop_xwayland_surface *surface) +{ + weston_desktop_xwayland_surface_change_state(surface, MAXIMIZED, NULL, + 0, 0); + weston_desktop_api_maximized_requested(surface->desktop, + surface->surface, true); +} + +static void +set_pid(struct weston_desktop_xwayland_surface *surface, pid_t pid) +{ + weston_desktop_surface_set_pid(surface->surface, pid); +} + +static const struct weston_desktop_xwayland_interface weston_desktop_xwayland_interface = { + .create_surface = create_surface, + .set_toplevel = set_toplevel, + .set_toplevel_with_position = set_toplevel_with_position, + .set_parent = set_parent, + .set_transient = set_transient, + .set_fullscreen = set_fullscreen, + .set_xwayland = set_xwayland, + .move = move, + .resize = resize, + .set_title = set_title, + .set_window_geometry = set_window_geometry, + .set_maximized = set_maximized, + .set_pid = set_pid, +}; + +void +weston_desktop_xwayland_init(struct weston_desktop *desktop) +{ + struct weston_compositor *compositor = weston_desktop_get_compositor(desktop); + struct weston_desktop_xwayland *xwayland; + + xwayland = zalloc(sizeof(struct weston_desktop_xwayland)); + if (xwayland == NULL) + return; + + xwayland->desktop = desktop; + xwayland->client = weston_desktop_client_create(desktop, NULL, NULL, NULL, NULL, 0, 0); + + weston_layer_init(&xwayland->layer, compositor); + /* We put this layer on top of regular shell surfaces, but hopefully + * below any UI the shell would add */ + weston_layer_set_position(&xwayland->layer, + WESTON_LAYER_POSITION_NORMAL + 1); + + compositor->xwayland = xwayland; + compositor->xwayland_interface = &weston_desktop_xwayland_interface; +} diff --git a/libweston/animation.c b/libweston/animation.c new file mode 100644 index 0000000..9ca7774 --- /dev/null +++ b/libweston/animation.c @@ -0,0 +1,533 @@ +/* + * Copyright © 2011 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include "libweston-internal.h" +#include "shared/helpers.h" +#include "shared/timespec-util.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, const struct timespec *time) +{ + 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 (timespec_sub_to_msec(time, &spring->timestamp) > 1000) { + weston_log("unexpectedly large timestamp jump " + "(from %" PRId64 " to %" PRId64 ")\n", + timespec_to_msec(&spring->timestamp), + timespec_to_msec(time)); + timespec_add_msec(&spring->timestamp, time, -1000); + } + + step = 0.01; + while (4 < timespec_sub_to_msec(time, &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; + } + + timespec_add_msec(&spring->timestamp, &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_view_animation_frame_func_t)(struct weston_view_animation *animation); + +struct weston_view_animation { + struct weston_view *view; + struct weston_animation animation; + struct weston_spring spring; + struct weston_transform transform; + struct wl_listener listener; + float start, stop; + weston_view_animation_frame_func_t frame; + weston_view_animation_frame_func_t reset; + weston_view_animation_done_func_t done; + void *data; + void *private; +}; + +WL_EXPORT void +weston_view_animation_destroy(struct weston_view_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_view_geometry_dirty(animation->view); + if (animation->done) + animation->done(animation, animation->data); + free(animation); +} + +static void +handle_animation_view_destroy(struct wl_listener *listener, void *data) +{ + struct weston_view_animation *animation = + container_of(listener, + struct weston_view_animation, listener); + + weston_view_animation_destroy(animation); +} + +static void +weston_view_animation_frame(struct weston_animation *base, + struct weston_output *output, + const struct timespec *time) +{ + struct weston_view_animation *animation = + container_of(base, + struct weston_view_animation, animation); + struct weston_compositor *compositor = + animation->view->surface->compositor; + + if (base->frame_counter <= 1) + animation->spring.timestamp = *time; + + weston_spring_update(&animation->spring, time); + + if (weston_spring_done(&animation->spring)) { + weston_view_schedule_repaint(animation->view); + weston_view_animation_destroy(animation); + return; + } + + if (animation->frame) + animation->frame(animation); + + weston_view_geometry_dirty(animation->view); + weston_view_schedule_repaint(animation->view); + + /* The view's output_mask will be zero if its position is + * offscreen. Animations should always run but as they are also + * run off the repaint cycle, if there's nothing to repaint + * the animation stops running. Therefore if we catch this situation + * and schedule a repaint on all outputs it will be avoided. + */ + if (animation->view->output_mask == 0) + weston_compositor_schedule_repaint(compositor); +} + +static void +idle_animation_destroy(void *data) +{ + struct weston_view_animation *animation = data; + + weston_view_animation_destroy(animation); +} + +static struct weston_view_animation * +weston_view_animation_create(struct weston_view *view, + float start, float stop, + weston_view_animation_frame_func_t frame, + weston_view_animation_frame_func_t reset, + weston_view_animation_done_func_t done, + void *data, + void *private) +{ + struct weston_view_animation *animation; + struct weston_compositor *ec = view->surface->compositor; + struct wl_event_loop *loop; + + animation = malloc(sizeof *animation); + if (!animation) + return NULL; + + animation->view = view; + animation->frame = frame; + animation->reset = reset; + animation->done = done; + animation->data = data; + animation->start = start; + animation->stop = stop; + animation->private = private; + + weston_matrix_init(&animation->transform.matrix); + wl_list_insert(&view->geometry.transformation_list, + &animation->transform.link); + + animation->animation.frame = weston_view_animation_frame; + + animation->listener.notify = handle_animation_view_destroy; + wl_signal_add(&view->destroy_signal, &animation->listener); + + if (view->output) { + wl_list_insert(&view->output->animation_list, + &animation->animation.link); + } else { + wl_list_init(&animation->animation.link); + loop = wl_display_get_event_loop(ec->wl_display); + wl_event_loop_add_idle(loop, idle_animation_destroy, animation); + } + + return animation; +} + +static void +weston_view_animation_run(struct weston_view_animation *animation) +{ + struct timespec zero_time = { 0 }; + + animation->animation.frame_counter = 0; + weston_view_animation_frame(&animation->animation, NULL, &zero_time); +} + +static void +reset_alpha(struct weston_view_animation *animation) +{ + struct weston_view *view = animation->view; + + view->alpha = animation->stop; +} + +static void +zoom_frame(struct weston_view_animation *animation) +{ + struct weston_view *es = animation->view; + 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->surface->width, + -0.5f * es->surface->height, 0); + weston_matrix_scale(&animation->transform.matrix, scale, scale, scale); + weston_matrix_translate(&animation->transform.matrix, + 0.5f * es->surface->width, + 0.5f * es->surface->height, 0); + + es->alpha = animation->spring.current; + if (es->alpha > 1.0) + es->alpha = 1.0; +} + +WL_EXPORT struct weston_view_animation * +weston_zoom_run(struct weston_view *view, float start, float stop, + weston_view_animation_done_func_t done, void *data) +{ + struct weston_view_animation *zoom; + + zoom = weston_view_animation_create(view, start, stop, + zoom_frame, reset_alpha, + done, data, NULL); + + if (zoom == NULL) + return NULL; + + weston_spring_init(&zoom->spring, 300.0, start, stop); + zoom->spring.friction = 1400; + zoom->spring.previous = start - (stop - start) * 0.03; + + weston_view_animation_run(zoom); + + return zoom; +} + +static void +fade_frame(struct weston_view_animation *animation) +{ + if (animation->spring.current > 0.999) + animation->view->alpha = 1; + else if (animation->spring.current < 0.001 ) + animation->view->alpha = 0; + else + animation->view->alpha = animation->spring.current; +} + +WL_EXPORT struct weston_view_animation * +weston_fade_run(struct weston_view *view, + float start, float end, float k, + weston_view_animation_done_func_t done, void *data) +{ + struct weston_view_animation *fade; + + fade = weston_view_animation_create(view, start, end, + fade_frame, reset_alpha, + done, data, NULL); + + if (fade == NULL) + return NULL; + + weston_spring_init(&fade->spring, 1000.0, start, end); + fade->spring.friction = 4000; + fade->spring.previous = start - (end - start) * 0.1; + + view->alpha = start; + + weston_view_animation_run(fade); + + return fade; +} + +WL_EXPORT void +weston_fade_update(struct weston_view_animation *fade, float target) +{ + fade->spring.target = target; + fade->stop = target; +} + +static void +stable_fade_frame(struct weston_view_animation *animation) +{ + struct weston_view *back_view; + + if (animation->spring.current > 0.999) + animation->view->alpha = 1; + else if (animation->spring.current < 0.001 ) + animation->view->alpha = 0; + else + animation->view->alpha = animation->spring.current; + + back_view = (struct weston_view *) animation->private; + back_view->alpha = + (animation->spring.target - animation->view->alpha) / + (1.0 - animation->view->alpha); + weston_view_geometry_dirty(back_view); +} + +WL_EXPORT struct weston_view_animation * +weston_stable_fade_run(struct weston_view *front_view, float start, + struct weston_view *back_view, float end, + weston_view_animation_done_func_t done, void *data) +{ + struct weston_view_animation *fade; + + fade = weston_view_animation_create(front_view, 0, 0, + stable_fade_frame, NULL, + done, data, back_view); + + if (fade == NULL) + return NULL; + + weston_spring_init(&fade->spring, 400, start, end); + fade->spring.friction = 1150; + + front_view->alpha = start; + back_view->alpha = end; + + weston_view_animation_run(fade); + + return fade; +} + +static void +slide_frame(struct weston_view_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_view_animation * +weston_slide_run(struct weston_view *view, float start, float stop, + weston_view_animation_done_func_t done, void *data) +{ + struct weston_view_animation *animation; + + animation = weston_view_animation_create(view, start, stop, + slide_frame, NULL, done, + data, NULL); + if (!animation) + return NULL; + + weston_spring_init(&animation->spring, 400.0, 0.0, 1.0); + animation->spring.friction = 600; + animation->spring.clip = WESTON_SPRING_BOUNCE; + + weston_view_animation_run(animation); + + return animation; +} + +struct weston_move_animation { + int dx; + int dy; + bool reverse; + bool scale; + weston_view_animation_done_func_t done; +}; + +static void +move_frame(struct weston_view_animation *animation) +{ + struct weston_move_animation *move = animation->private; + float scale; + float progress = animation->spring.current; + + if (move->reverse) + progress = 1.0 - progress; + + scale = animation->start + + (animation->stop - animation->start) * + progress; + weston_matrix_init(&animation->transform.matrix); + if (move->scale) + weston_matrix_scale(&animation->transform.matrix, scale, scale, + 1.0f); + weston_matrix_translate(&animation->transform.matrix, + move->dx * progress, move->dy * progress, + 0); +} + +static void +move_done(struct weston_view_animation *animation, void *data) +{ + struct weston_move_animation *move = animation->private; + + if (move->done) + move->done(animation, data); + + free(move); +} + +static struct weston_view_animation * +weston_move_scale_run_internal(struct weston_view *view, int dx, int dy, + float start, float end, bool reverse, bool scale, + weston_view_animation_done_func_t done, + void *data) +{ + struct weston_move_animation *move; + struct weston_view_animation *animation; + + move = malloc(sizeof(*move)); + if (!move) + return NULL; + move->dx = dx; + move->dy = dy; + move->reverse = reverse; + move->scale = scale; + move->done = done; + + animation = weston_view_animation_create(view, start, end, move_frame, + NULL, move_done, data, move); + + if (animation == NULL){ + free(move); + return NULL; + } + + weston_spring_init(&animation->spring, 400.0, 0.0, 1.0); + animation->spring.friction = 1150; + + weston_view_animation_run(animation); + + return animation; +} + +WL_EXPORT struct weston_view_animation * +weston_move_scale_run(struct weston_view *view, int dx, int dy, + float start, float end, bool reverse, + weston_view_animation_done_func_t done, void *data) +{ + return weston_move_scale_run_internal(view, dx, dy, start, end, reverse, + true, done, data); +} + +WL_EXPORT struct weston_view_animation * +weston_move_run(struct weston_view *view, int dx, int dy, + float start, float end, bool reverse, + weston_view_animation_done_func_t done, void *data) +{ + return weston_move_scale_run_internal(view, dx, dy, start, end, reverse, + false, done, data); +} diff --git a/libweston/backend-drm/auth/wayland_drm_auth.h b/libweston/backend-drm/auth/wayland_drm_auth.h new file mode 100755 index 0000000..773a2ea --- /dev/null +++ b/libweston/backend-drm/auth/wayland_drm_auth.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef WAYLAND_DRM_AUTH_H +#define WAYLAND_DRM_AUTH_H +#include +#include "libweston/libweston.h" + +#undef LOG_TAG +#undef LOG_DOMAIN +#define LOG_TAG "DRMAUTH" +#define LOG_DOMAIN 0xD001400 + +#define FILENAME (strrchr(__FILE__, '/') ? (strrchr(__FILE__, '/') + 1) : __FILE__) + +#ifndef DRMAUTH_LOGD +#define DRMAUTH_LOGD(format, ...) \ + do { \ + HILOG_DEBUG("[%s@%s:%d] " format "\n", __FUNCTION__, FILENAME, __LINE__, ##__VA_ARGS__); \ + } while (0) +#endif + +#ifndef DRMAUTH_LOGI +#define DRMAUTH_LOGI(format, ...) \ + do { \ + HILOG_INFO("[%s@%s:%d] " format "\n", __FUNCTION__, FILENAME, __LINE__, ##__VA_ARGS__); \ + } while (0) +#endif + +#ifndef DRMAUTH_LOGW +#define DRMAUTH_LOGW(format, ...) \ + do { \ + HILOG_WARN("[%s@%s:%d] " format "\n", __FUNCTION__, FILENAME, __LINE__, ##__VA_ARGS__); \ + } while (0) +#endif + +#ifndef DRMAUTH_LOGE +#define DRMAUTH_LOGE(format, ...) \ + do { \ + HILOG_ERROR("[%s@%s:%d] " format "\n", __FUNCTION__, FILENAME, __LINE__, ##__VA_ARGS__); \ + } while (0) +#endif + + +#ifndef CHK_RETURN +#define CHK_RETURN(val, ret, ...) \ + do { \ + if (val) { \ + __VA_ARGS__; \ + return (ret); \ + } \ + } while (0) +#endif + +#ifndef CHK_RETURN_NO_VALUE +#define CHK_RETURN_NO_VALUE(val, ret, ...) \ + do { \ + if (val) { \ + __VA_ARGS__; \ + return; \ + } \ + } while (0) +#endif + +#endif // WAYLAND_DRM_AUTH_H diff --git a/libweston/backend-drm/auth/wayland_drm_auth_server.c b/libweston/backend-drm/auth/wayland_drm_auth_server.c new file mode 100755 index 0000000..91c9120 --- /dev/null +++ b/libweston/backend-drm/auth/wayland_drm_auth_server.c @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "wayland_drm_auth_server.h" +#include +#include +#include +#include "xf86drm.h" +#include "wayland_drm_auth.h" +#include "drm-auth-server-protocol.h" + +typedef struct { + int maserFd; + struct wl_global *drmAuthGlobal; +} DrmAuthMng; + +DrmAuthMng *g_drmAuthMng = NULL; + +static void DrmAuthenticate(struct wl_client *client, struct wl_resource *resource, uint32_t magic) +{ + int ret; + DrmAuthMng *drmAuthMng = wl_resource_get_user_data(resource); + DRMAUTH_LOGD("DrmAuthenticate magic %x", magic); + CHK_RETURN_NO_VALUE((drmAuthMng == NULL), DRMAUTH_LOGE("can not get user data")); + ret = drmAuthMagic(drmAuthMng->maserFd, magic); + if (ret) { + DRMAUTH_LOGE("drm authenticate failed errno : %d", errno); + wl_resource_post_event(resource, WL_DRM_AUTH_STATUS, WL_DRM_AUTH_STATUS_FAILED); + return; + } + wl_resource_post_event(resource, WL_DRM_AUTH_STATUS, WL_DRM_AUTH_STATUS_SUCCESS); +} + +static const struct wl_drm_auth_interface g_drmAuthInterface = { DrmAuthenticate }; + +static void BindDrmAuth(struct wl_client *client, void *data, uint32_t version, uint32_t id) +{ + struct wl_resource *resource; + DRMAUTH_LOGD("BindDrmAuth"); + resource = wl_resource_create(client, &wl_drm_auth_interface, 1, id); + CHK_RETURN_NO_VALUE((resource == NULL), DRMAUTH_LOGE("create resource failed"); wl_client_post_no_memory(client)); + wl_resource_set_implementation(resource, &g_drmAuthInterface, data, NULL); +} + +void DeInitWaylandDrmAuthService() +{ + DRMAUTH_LOGD("DeInitWaylandDrmAuthService"); + if (g_drmAuthMng->drmAuthGlobal != NULL) { + wl_global_destroy(g_drmAuthMng->drmAuthGlobal); + } + if (g_drmAuthMng != NULL) { + free(g_drmAuthMng); + g_drmAuthMng = NULL; + } +} + +int InitWaylandDrmAuthService(struct wl_display *display, int drmMasterFd) +{ + if (g_drmAuthMng != NULL) { + DRMAUTH_LOGI("drm auth service has inited will do nothing"); + } + g_drmAuthMng = calloc(1, sizeof(DrmAuthMng)); + CHK_RETURN((g_drmAuthMng == NULL), -1, DRMAUTH_LOGE("calloc DrmAuthMng Failed errno : %d", errno)); + g_drmAuthMng->maserFd = drmMasterFd; + g_drmAuthMng->drmAuthGlobal = wl_global_create(display, &wl_drm_auth_interface, 1, g_drmAuthMng, BindDrmAuth); + CHK_RETURN((g_drmAuthMng == NULL), -1, DRMAUTH_LOGE("drm auth global create failed"); + DeInitWaylandDrmAuthService()); + return 0; +} diff --git a/libweston/backend-drm/auth/wayland_drm_auth_server.h b/libweston/backend-drm/auth/wayland_drm_auth_server.h new file mode 100755 index 0000000..01507db --- /dev/null +++ b/libweston/backend-drm/auth/wayland_drm_auth_server.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef WAYLAND_DRM_AUTH_SERVER_H +#define WAYLAND_DRM_AUTH_SERVER_H + +#include "wayland-server.h" + +/* * + * @brief init the wayland drm authenticate service + * + * it will create drm auth global to the wayland server, and handle the client authenticate resquest + * + * @param display Indicates the pointer of wayland display + * + * @param drmMasterFd Indicates the file descriptor of drm mast + * + * @return Returns 0 if the operation is successful; returns an error code defined in {@link DispErrCode} + * otherwise. + * @since 1.0 + * @version 1.0 + */ +extern int InitWaylandDrmAuthService(struct wl_display *display, int drmMasterFd); + +/* * + * @brief deinit the wayland drm authenticate service + * + * it will destroy the drm auth global and free the memory of drm auth service + * + * otherwise. + * @since 1.0 + * @version 1.0 + */ +extern void DeInitWaylandDrmAuthService(); + +#endif // WAYLAND_DRM_AUTH_SERVER_H \ No newline at end of file diff --git a/libweston/backend-drm/drm-gbm.c b/libweston/backend-drm/drm-gbm.c new file mode 100644 index 0000000..30609e3 --- /dev/null +++ b/libweston/backend-drm/drm-gbm.c @@ -0,0 +1,365 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2017, 2018 Collabora, Ltd. + * Copyright © 2017, 2018 General Electric Company + * Copyright (c) 2018 DisplayLink (UK) Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "drm-internal.h" +#include "pixman-renderer.h" +#include "pixel-formats.h" +#include "renderer-gl/gl-renderer.h" +#include "shared/weston-egl-ext.h" +#include "linux-dmabuf.h" +#include "linux-explicit-synchronization.h" + +struct gl_renderer_interface *gl_renderer; + +static struct gbm_device * +create_gbm_device(int fd) +{ + struct gbm_device *gbm; + + gl_renderer = weston_load_module("gl-renderer.so", + "gl_renderer_interface"); + if (!gl_renderer) + return NULL; + + /* GBM will load a dri driver, but even though they need symbols from + * libglapi, in some version of Mesa they are not linked to it. Since + * only the gl-renderer module links to it, the call above won't make + * these symbols globally available, and loading the DRI driver fails. + * Workaround this by dlopen()'ing libglapi with RTLD_GLOBAL. */ + dlopen("libglapi.so.0", RTLD_LAZY | RTLD_GLOBAL); + + gbm = gbm_create_device(fd); + + return gbm; +} + +/* When initializing EGL, if the preferred buffer format isn't available + * we may be able to substitute an ARGB format for an XRGB one. + * + * This returns 0 if substitution isn't possible, but 0 might be a + * legitimate format for other EGL platforms, so the caller is + * responsible for checking for 0 before calling gl_renderer->create(). + * + * This works around https://bugs.freedesktop.org/show_bug.cgi?id=89689 + * but it's entirely possible we'll see this again on other implementations. + */ +static uint32_t +fallback_format_for(uint32_t format) +{ + const struct pixel_format_info *pf; + + pf = pixel_format_get_info_by_opaque_substitute(format); + if (!pf) + return 0; + + return pf->format; +} + +static int +drm_backend_create_gl_renderer(struct drm_backend *b) +{ + uint32_t format[3] = { + b->gbm_format, + fallback_format_for(b->gbm_format), + 0, + }; + struct gl_renderer_display_options options = { + .egl_platform = EGL_PLATFORM_GBM_KHR, + .egl_native_display = b->gbm, + .egl_surface_type = EGL_WINDOW_BIT, + .drm_formats = format, + .drm_formats_count = 2, + }; + + if (format[1]) + options.drm_formats_count = 3; + + if (gl_renderer->display_create(b->compositor, &options) < 0) + return -1; + + return 0; +} + +int +init_egl(struct drm_backend *b) +{ + b->gbm = create_gbm_device(b->drm.fd); + + if (!b->gbm) + return -1; + + if (drm_backend_create_gl_renderer(b) < 0) { + gbm_device_destroy(b->gbm); + return -1; + } + + return 0; +} + +static void drm_output_fini_cursor_egl(struct drm_output *output) +{ + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(output->gbm_cursor_fb); i++) { + drm_fb_unref(output->gbm_cursor_fb[i]); + output->gbm_cursor_fb[i] = NULL; + } +} + +static int +drm_output_init_cursor_egl(struct drm_output *output, struct drm_backend *b) +{ + unsigned int i; + + /* No point creating cursors if we don't have a plane for them. */ + if (!output->cursor_plane) + return 0; + + for (i = 0; i < ARRAY_LENGTH(output->gbm_cursor_fb); i++) { + struct gbm_bo *bo; + + bo = gbm_bo_create(b->gbm, b->cursor_width, b->cursor_height, + GBM_FORMAT_ARGB8888, + GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE); + if (!bo) + goto err; + + output->gbm_cursor_fb[i] = + drm_fb_get_from_bo(bo, b, false, BUFFER_CURSOR); + if (!output->gbm_cursor_fb[i]) { + gbm_bo_destroy(bo); + goto err; + } + output->gbm_cursor_handle[i] = gbm_bo_get_handle(bo).s32; + } + + return 0; + +err: + weston_log("cursor buffers unavailable, using gl cursors\n"); + b->cursors_are_broken = true; + drm_output_fini_cursor_egl(output); + return -1; +} + +/* Init output state that depends on gl or gbm */ +int +drm_output_init_egl(struct drm_output *output, struct drm_backend *b) +{ + uint32_t format[2] = { + output->gbm_format, + fallback_format_for(output->gbm_format), + }; + struct gl_renderer_output_options options = { + .drm_formats = format, + .drm_formats_count = 1, + }; + struct weston_mode *mode = output->base.current_mode; + struct drm_plane *plane = output->scanout_plane; + unsigned int i; + + assert(output->gbm_surface == NULL); + + for (i = 0; i < plane->count_formats; i++) { + if (plane->formats[i].format == output->gbm_format) + break; + } + + if (i == plane->count_formats) { + weston_log("format 0x%x not supported by output %s\n", + output->gbm_format, output->base.name); + return -1; + } + +#ifdef HAVE_GBM_MODIFIERS + if (plane->formats[i].count_modifiers > 0) { + output->gbm_surface = + gbm_surface_create_with_modifiers(b->gbm, + mode->width, + mode->height, + output->gbm_format, + plane->formats[i].modifiers, + plane->formats[i].count_modifiers); + } + + /* If allocating with modifiers fails, try again without. This can + * happen when the KMS display device supports modifiers but the + * GBM driver does not, e.g. the old i915 Mesa driver. */ + if (!output->gbm_surface) +#endif + { + output->gbm_surface = + gbm_surface_create(b->gbm, mode->width, mode->height, + output->gbm_format, + output->gbm_bo_flags); + } + + if (!output->gbm_surface) { + weston_log("failed to create gbm surface\n"); + return -1; + } + + if (options.drm_formats[1]) + options.drm_formats_count = 2; + options.window_for_legacy = (EGLNativeWindowType) output->gbm_surface; + options.window_for_platform = output->gbm_surface; + if (gl_renderer->output_window_create(&output->base, &options) < 0) { + weston_log("failed to create gl renderer output state\n"); + gbm_surface_destroy(output->gbm_surface); + output->gbm_surface = NULL; + return -1; + } + + drm_output_init_cursor_egl(output, b); + + return 0; +} + +void +drm_output_fini_egl(struct drm_output *output) +{ + struct drm_backend *b = to_drm_backend(output->base.compositor); + + /* Destroying the GBM surface will destroy all our GBM buffers, + * regardless of refcount. Ensure we destroy them here. */ + if (!b->shutting_down && + output->scanout_plane->state_cur->fb && + output->scanout_plane->state_cur->fb->type == BUFFER_GBM_SURFACE) { + drm_plane_reset_state(output->scanout_plane); + } + + gl_renderer->output_destroy(&output->base); + gbm_surface_destroy(output->gbm_surface); + output->gbm_surface = NULL; + drm_output_fini_cursor_egl(output); +} + +struct drm_fb * +drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage) +{ + struct drm_output *output = state->output; + struct drm_backend *b = to_drm_backend(output->base.compositor); + struct gbm_bo *bo; + struct drm_fb *ret; + + output->base.compositor->renderer->repaint_output(&output->base, + damage); + + bo = gbm_surface_lock_front_buffer(output->gbm_surface); + if (!bo) { + weston_log("failed to lock front buffer: %s\n", + strerror(errno)); + return NULL; + } + + /* The renderer always produces an opaque image. */ + ret = drm_fb_get_from_bo(bo, b, true, BUFFER_GBM_SURFACE); + if (!ret) { + weston_log("failed to get drm_fb for bo\n"); + gbm_surface_release_buffer(output->gbm_surface, bo); + return NULL; + } + ret->gbm_surface = output->gbm_surface; + + return ret; +} + +static void +switch_to_gl_renderer(struct drm_backend *b) +{ + struct drm_output *output; + bool dmabuf_support_inited; + bool linux_explicit_sync_inited; + + if (!b->use_pixman) + return; + + dmabuf_support_inited = !!b->compositor->renderer->import_dmabuf; + linux_explicit_sync_inited = + b->compositor->capabilities & WESTON_CAP_EXPLICIT_SYNC; + + weston_log("Switching to GL renderer\n"); + + b->gbm = create_gbm_device(b->drm.fd); + if (!b->gbm) { + weston_log("Failed to create gbm device. " + "Aborting renderer switch\n"); + return; + } + + wl_list_for_each(output, &b->compositor->output_list, base.link) + pixman_renderer_output_destroy(&output->base); + + b->compositor->renderer->destroy(b->compositor); + + if (drm_backend_create_gl_renderer(b) < 0) { + gbm_device_destroy(b->gbm); + weston_log("Failed to create GL renderer. Quitting.\n"); + /* FIXME: we need a function to shutdown cleanly */ + assert(0); + } + + wl_list_for_each(output, &b->compositor->output_list, base.link) + drm_output_init_egl(output, b); + + b->use_pixman = 0; + + if (!dmabuf_support_inited && b->compositor->renderer->import_dmabuf) { + if (linux_dmabuf_setup(b->compositor) < 0) + weston_log("Error: initializing dmabuf " + "support failed.\n"); + } + + if (!linux_explicit_sync_inited && + (b->compositor->capabilities & WESTON_CAP_EXPLICIT_SYNC)) { + if (linux_explicit_synchronization_setup(b->compositor) < 0) + weston_log("Error: initializing explicit " + " synchronization support failed.\n"); + } +} + +void +renderer_switch_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) +{ + struct drm_backend *b = + to_drm_backend(keyboard->seat->compositor); + + switch_to_gl_renderer(b); +} + diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h new file mode 100755 index 0000000..dc55605 --- /dev/null +++ b/libweston/backend-drm/drm-internal.h @@ -0,0 +1,831 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2017, 2018 Collabora, Ltd. + * Copyright © 2017, 2018 General Electric Company + * Copyright (c) 2018 DisplayLink (UK) Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include + +#ifdef BUILD_DRM_GBM +#include +#endif +#include + +#include +#include +#include +#include "shared/helpers.h" +#include "libinput-seat.h" +#include "backend.h" +#include "libweston-internal.h" + +#ifndef DRM_CLIENT_CAP_ASPECT_RATIO +#define DRM_CLIENT_CAP_ASPECT_RATIO 4 +#endif + +#ifndef GBM_BO_USE_CURSOR +#define GBM_BO_USE_CURSOR GBM_BO_USE_CURSOR_64X64 +#endif + +#ifndef GBM_BO_USE_LINEAR +#define GBM_BO_USE_LINEAR (1 << 4) +#endif + +#ifndef DRM_PLANE_ZPOS_INVALID_PLANE +#define DRM_PLANE_ZPOS_INVALID_PLANE 0xffffffffffffffffULL +#endif + +/** + * A small wrapper to print information into the 'drm-backend' debug scope. + * + * The following conventions are used to print variables: + * + * - fixed uint32_t values, including Weston object IDs such as weston_output + * IDs, DRM object IDs such as CRTCs or properties, and GBM/DRM formats: + * "%lu (0x%lx)" (unsigned long) value, (unsigned long) value + * + * - fixed uint64_t values, such as DRM property values (including object IDs + * when used as a value): + * "%llu (0x%llx)" (unsigned long long) value, (unsigned long long) value + * + * - non-fixed-width signed int: + * "%d" value + * + * - non-fixed-width unsigned int: + * "%u (0x%x)" value, value + * + * - non-fixed-width unsigned long: + * "%lu (0x%lx)" value, value + * + * Either the integer or hexadecimal forms may be omitted if it is known that + * one representation is not useful (e.g. width/height in hex are rarely what + * you want). + * + * This is to avoid implicit widening or narrowing when we use fixed-size + * types: uint32_t can be resolved by either unsigned int or unsigned long + * on a 32-bit system but only unsigned int on a 64-bit system, with uint64_t + * being unsigned long long on a 32-bit system and unsigned long on a 64-bit + * system. To avoid confusing side effects, we explicitly cast to the widest + * possible type and use a matching format specifier. + */ +// OHOS hilog +//#define drm_debug(b, ...) \ +// weston_log_scope_printf((b)->debug, __VA_ARGS__) +#define drm_debug(b, fmt, ...) (HILOG_INFO(LOG_CORE, fmt, ##__VA_ARGS__)) + +#define MAX_CLONED_CONNECTORS 4 + +#ifndef DRM_MODE_PICTURE_ASPECT_64_27 +#define DRM_MODE_PICTURE_ASPECT_64_27 3 +#define DRM_MODE_FLAG_PIC_AR_64_27 \ + (DRM_MODE_PICTURE_ASPECT_64_27<<19) +#endif +#ifndef DRM_MODE_PICTURE_ASPECT_256_135 +#define DRM_MODE_PICTURE_ASPECT_256_135 4 +#define DRM_MODE_FLAG_PIC_AR_256_135 \ + (DRM_MODE_PICTURE_ASPECT_256_135<<19) +#endif + + +/** + * Represents the values of an enum-type KMS property + */ +struct drm_property_enum_info { + const char *name; /**< name as string (static, not freed) */ + bool valid; /**< true if value is supported; ignore if false */ + uint64_t value; /**< raw value */ +}; + +/** + * Holds information on a DRM property, including its ID and the enum + * values it holds. + * + * DRM properties are allocated dynamically, and maintained as DRM objects + * within the normal object ID space; they thus do not have a stable ID + * to refer to. This includes enum values, which must be referred to by + * integer values, but these are not stable. + * + * drm_property_info allows a cache to be maintained where Weston can use + * enum values internally to refer to properties, with the mapping to DRM + * ID values being maintained internally. + */ +struct drm_property_info { + const char *name; /**< name as string (static, not freed) */ + uint32_t prop_id; /**< KMS property object ID */ + uint32_t flags; + unsigned int num_enum_values; /**< number of enum values */ + struct drm_property_enum_info *enum_values; /**< array of enum values */ + unsigned int num_range_values; + uint64_t range_values[2]; +}; + +/** + * List of properties attached to DRM planes + */ +enum wdrm_plane_property { + WDRM_PLANE_TYPE = 0, + WDRM_PLANE_SRC_X, + WDRM_PLANE_SRC_Y, + WDRM_PLANE_SRC_W, + WDRM_PLANE_SRC_H, + WDRM_PLANE_CRTC_X, + WDRM_PLANE_CRTC_Y, + WDRM_PLANE_CRTC_W, + WDRM_PLANE_CRTC_H, + WDRM_PLANE_FB_ID, + WDRM_PLANE_CRTC_ID, + WDRM_PLANE_IN_FORMATS, + WDRM_PLANE_IN_FENCE_FD, + WDRM_PLANE_FB_DAMAGE_CLIPS, + WDRM_PLANE_ZPOS, + WDRM_PLANE__COUNT +}; + +/** + * Possible values for the WDRM_PLANE_TYPE property. + */ +enum wdrm_plane_type { + WDRM_PLANE_TYPE_PRIMARY = 0, + WDRM_PLANE_TYPE_CURSOR, + WDRM_PLANE_TYPE_OVERLAY, + WDRM_PLANE_TYPE__COUNT +}; + +/** + * List of properties attached to a DRM connector + */ +enum wdrm_connector_property { + WDRM_CONNECTOR_EDID = 0, + WDRM_CONNECTOR_DPMS, + WDRM_CONNECTOR_CRTC_ID, + WDRM_CONNECTOR_NON_DESKTOP, + WDRM_CONNECTOR_CONTENT_PROTECTION, + WDRM_CONNECTOR_HDCP_CONTENT_TYPE, + WDRM_CONNECTOR_PANEL_ORIENTATION, + WDRM_CONNECTOR__COUNT +}; + +enum wdrm_content_protection_state { + WDRM_CONTENT_PROTECTION_UNDESIRED = 0, + WDRM_CONTENT_PROTECTION_DESIRED, + WDRM_CONTENT_PROTECTION_ENABLED, + WDRM_CONTENT_PROTECTION__COUNT +}; + +enum wdrm_hdcp_content_type { + WDRM_HDCP_CONTENT_TYPE0 = 0, + WDRM_HDCP_CONTENT_TYPE1, + WDRM_HDCP_CONTENT_TYPE__COUNT +}; + +enum wdrm_dpms_state { + WDRM_DPMS_STATE_OFF = 0, + WDRM_DPMS_STATE_ON, + WDRM_DPMS_STATE_STANDBY, /* unused */ + WDRM_DPMS_STATE_SUSPEND, /* unused */ + WDRM_DPMS_STATE__COUNT +}; + +enum wdrm_panel_orientation { + WDRM_PANEL_ORIENTATION_NORMAL = 0, + WDRM_PANEL_ORIENTATION_UPSIDE_DOWN, + WDRM_PANEL_ORIENTATION_LEFT_SIDE_UP, + WDRM_PANEL_ORIENTATION_RIGHT_SIDE_UP, + WDRM_PANEL_ORIENTATION__COUNT +}; + +/** + * List of properties attached to DRM CRTCs + */ +enum wdrm_crtc_property { + WDRM_CRTC_MODE_ID = 0, + WDRM_CRTC_ACTIVE, + WDRM_CRTC__COUNT +}; + +struct drm_backend { + struct weston_backend base; + struct weston_compositor *compositor; + + 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; + dev_t devnum; + } drm; + struct gbm_device *gbm; + struct wl_listener session_listener; + uint32_t gbm_format; + + /* we need these parameters in order to not fail drmModeAddFB2() + * due to out of bounds dimensions, and then mistakenly set + * sprites_are_broken: + */ + int min_width, max_width; + int min_height, max_height; + + struct wl_list plane_list; + + void *repaint_data; + + bool state_invalid; + + /* CRTC IDs not used by any enabled output. */ + struct wl_array unused_crtcs; + + bool sprites_are_broken; + bool cursors_are_broken; + + bool universal_planes; + bool atomic_modeset; + + bool use_pixman; + bool use_pixman_shadow; + + struct udev_input input; + + int32_t cursor_width; + int32_t cursor_height; + + uint32_t pageflip_timeout; + + bool shutting_down; + + bool aspect_ratio_supported; + + bool fb_modifiers; + + struct weston_log_scope *debug; +}; + +struct drm_mode { + struct weston_mode base; + drmModeModeInfo mode_info; + uint32_t blob_id; +}; + +enum drm_fb_type { + BUFFER_INVALID = 0, /**< never used */ + BUFFER_CLIENT, /**< directly sourced from client */ + BUFFER_DMABUF, /**< imported from linux_dmabuf client */ + BUFFER_PIXMAN_DUMB, /**< internal Pixman rendering */ + BUFFER_GBM_SURFACE, /**< internal EGL rendering */ + BUFFER_CURSOR, /**< internal cursor buffer */ +}; + +struct drm_fb { + enum drm_fb_type type; + + int refcnt; + + uint32_t fb_id, size; + uint32_t handles[4]; + uint32_t strides[4]; + uint32_t offsets[4]; + int num_planes; + const struct pixel_format_info *format; + uint64_t modifier; + int width, height; + int fd; + struct weston_buffer_reference buffer_ref; + struct weston_buffer_release_reference buffer_release_ref; + + /* Used by gbm fbs */ + struct gbm_bo *bo; + struct gbm_surface *gbm_surface; + + /* 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]; +}; + +/** + * Pending state holds one or more drm_output_state structures, collected from + * performing repaint. This pending state is transient, and only lives between + * beginning a repaint group and flushing the results: after flush, each + * output state will complete and be retired separately. + */ +struct drm_pending_state { + struct drm_backend *backend; + struct wl_list output_list; +}; + +/* + * Output state holds the dynamic state for one Weston output, i.e. a KMS CRTC, + * plus >= 1 each of encoder/connector/plane. Since everything but the planes + * is currently statically assigned per-output, we mainly use this to track + * plane state. + * + * pending_state is set when the output state is owned by a pending_state, + * i.e. when it is being constructed and has not yet been applied. When the + * output state has been applied, the owning pending_state is freed. + */ +struct drm_output_state { + struct drm_pending_state *pending_state; + struct drm_output *output; + struct wl_list link; + enum dpms_enum dpms; + enum weston_hdcp_protection protection; + struct wl_list plane_list; +}; + +/** + * An instance of this class is created each time we believe we have a plane + * suitable to be used by a view as a direct scan-out. The list is initalized + * and populated locally. + */ +struct drm_plane_zpos { + struct drm_plane *plane; + struct wl_list link; /**< :candidate_plane_zpos_list */ +}; + +/** + * Plane state holds the dynamic state for a plane: where it is positioned, + * and which buffer it is currently displaying. + * + * The plane state is owned by an output state, except when setting an initial + * state. See drm_output_state for notes on state object lifetime. + */ +struct drm_plane_state { + struct drm_plane *plane; + struct drm_output *output; + struct drm_output_state *output_state; + + struct drm_fb *fb; + + struct weston_view *ev; /**< maintained for drm_assign_planes only */ + + int32_t src_x, src_y; + uint32_t src_w, src_h; + int32_t dest_x, dest_y; + uint32_t dest_w, dest_h; + + uint64_t zpos; + + bool complete; + + /* We don't own the fd, so we shouldn't close it */ + int in_fence_fd; + + uint32_t damage_blob_id; /* damage to kernel */ + + struct wl_list link; /* drm_output_state::plane_list */ +}; + +/** + * A plane represents one buffer, positioned within a CRTC, and stacked + * relative to other planes on the same CRTC. + * + * Each CRTC has a 'primary plane', which use used to display the classic + * framebuffer contents, as accessed through the legacy drmModeSetCrtc + * call (which combines setting the CRTC's actual physical mode, and the + * properties of the primary plane). + * + * The cursor plane also has its own alternate legacy API. + * + * Other planes are used opportunistically to display content we do not + * wish to blit into the primary plane. These non-primary/cursor planes + * are referred to as 'sprites'. + */ +struct drm_plane { + struct weston_plane base; + + struct drm_backend *backend; + + enum wdrm_plane_type type; + + uint32_t possible_crtcs; + uint32_t plane_id; + uint32_t count_formats; + + struct drm_property_info props[WDRM_PLANE__COUNT]; + + /* The last state submitted to the kernel for this plane. */ + struct drm_plane_state *state_cur; + + uint64_t zpos_min; + uint64_t zpos_max; + + struct wl_list link; + + struct { + uint32_t format; + uint32_t count_modifiers; + uint64_t *modifiers; + } formats[]; +}; + +struct drm_head { + struct weston_head base; + struct drm_backend *backend; + + drmModeConnector *connector; + uint32_t connector_id; + struct drm_edid edid; + + /* Holds the properties for the connector */ + struct drm_property_info props_conn[WDRM_CONNECTOR__COUNT]; + + struct backlight *backlight; + + drmModeModeInfo inherited_mode; /**< Original mode on the connector */ + uint32_t inherited_crtc_id; /**< Original CRTC assignment */ +}; + +struct drm_output { + struct weston_output base; + struct drm_backend *backend; + + uint32_t crtc_id; /* object ID to pass to DRM functions */ + int pipe; /* index of CRTC in resource array / bitmasks */ + + /* Holds the properties for the CRTC */ + struct drm_property_info props_crtc[WDRM_CRTC__COUNT]; + + bool page_flip_pending; + bool atomic_complete_pending; + bool destroy_pending; + bool disable_pending; + bool dpms_off_pending; + + uint32_t gbm_cursor_handle[2]; + struct drm_fb *gbm_cursor_fb[2]; + struct drm_plane *cursor_plane; + struct weston_view *cursor_view; + int current_cursor; + + struct gbm_surface *gbm_surface; + uint32_t gbm_format; + uint32_t gbm_bo_flags; + + /* Plane being displayed directly on the CRTC */ + struct drm_plane *scanout_plane; + + /* The last state submitted to the kernel for this CRTC. */ + struct drm_output_state *state_cur; + /* The previously-submitted state, where the hardware has not + * yet acknowledged completion of state_cur. */ + struct drm_output_state *state_last; + + 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; + + struct wl_event_source *pageflip_timer; + + bool virtual; + + submit_frame_cb virtual_submit_frame; +}; + +static inline struct drm_head * +to_drm_head(struct weston_head *base) +{ + return container_of(base, struct drm_head, base); +} + +static inline struct drm_output * +to_drm_output(struct weston_output *base) +{ + return container_of(base, struct drm_output, base); +} + +static inline struct drm_backend * +to_drm_backend(struct weston_compositor *base) +{ + return container_of(base->backend, struct drm_backend, base); +} + +static inline struct drm_mode * +to_drm_mode(struct weston_mode *base) +{ + return container_of(base, struct drm_mode, base); +} + +static inline const char * +drm_output_get_plane_type_name(struct drm_plane *p) +{ + switch (p->type) { + case WDRM_PLANE_TYPE_PRIMARY: + return "primary"; + case WDRM_PLANE_TYPE_CURSOR: + return "cursor"; + case WDRM_PLANE_TYPE_OVERLAY: + return "overlay"; + default: + assert(0); + // OHOS build + return ""; + break; + } +} + +struct drm_output * +drm_output_find_by_crtc(struct drm_backend *b, uint32_t crtc_id); + +struct drm_head * +drm_head_find_by_connector(struct drm_backend *backend, uint32_t connector_id); + +static inline bool +drm_view_transform_supported(struct weston_view *ev, struct weston_output *output) +{ + struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; + + /* This will incorrectly disallow cases where the combination of + * buffer and view transformations match the output transform. + * Fixing this requires a full analysis of the transformation + * chain. */ + if (ev->transform.enabled && + ev->transform.matrix.type >= WESTON_MATRIX_TRANSFORM_ROTATE) + return false; + + if (viewport->buffer.transform != output->transform) + return false; + + return true; +} + +int +drm_mode_ensure_blob(struct drm_backend *backend, struct drm_mode *mode); + +struct drm_mode * +drm_output_choose_mode(struct drm_output *output, + struct weston_mode *target_mode); +void +update_head_from_connector(struct drm_head *head, + drmModeObjectProperties *props); + +void +drm_mode_list_destroy(struct drm_backend *backend, struct wl_list *mode_list); + +void +drm_output_print_modes(struct drm_output *output); + +int +drm_output_set_mode(struct weston_output *base, + enum weston_drm_backend_output_mode mode, + const char *modeline); + +void +drm_property_info_populate(struct drm_backend *b, + const struct drm_property_info *src, + struct drm_property_info *info, + unsigned int num_infos, + drmModeObjectProperties *props); +uint64_t +drm_property_get_value(struct drm_property_info *info, + const drmModeObjectProperties *props, + uint64_t def); +uint64_t * +drm_property_get_range_values(struct drm_property_info *info, + const drmModeObjectProperties *props); +int +drm_plane_populate_formats(struct drm_plane *plane, const drmModePlane *kplane, + const drmModeObjectProperties *props, + const bool use_modifiers); +void +drm_property_info_free(struct drm_property_info *info, int num_props); + +extern struct drm_property_enum_info plane_type_enums[]; +extern const struct drm_property_info plane_props[]; +extern struct drm_property_enum_info dpms_state_enums[]; +extern struct drm_property_enum_info content_protection_enums[]; +extern struct drm_property_enum_info hdcp_content_type_enums[]; +extern const struct drm_property_info connector_props[]; +extern const struct drm_property_info crtc_props[]; + +int +init_kms_caps(struct drm_backend *b); + +int +drm_pending_state_test(struct drm_pending_state *pending_state); +int +drm_pending_state_apply(struct drm_pending_state *pending_state); +int +drm_pending_state_apply_sync(struct drm_pending_state *pending_state); + +void +drm_output_set_gamma(struct weston_output *output_base, + uint16_t size, uint16_t *r, uint16_t *g, uint16_t *b); + +void +drm_output_update_msc(struct drm_output *output, unsigned int seq); +void +drm_output_update_complete(struct drm_output *output, uint32_t flags, + unsigned int sec, unsigned int usec); +int +on_drm_input(int fd, uint32_t mask, void *data); + +struct drm_fb * +drm_fb_ref(struct drm_fb *fb); +void +drm_fb_unref(struct drm_fb *fb); + +struct drm_fb * +drm_fb_create_dumb(struct drm_backend *b, int width, int height, + uint32_t format); +struct drm_fb * +drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, + bool is_opaque, enum drm_fb_type type); + +#ifdef BUILD_DRM_GBM +extern struct drm_fb * +drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev); +extern bool +drm_can_scanout_dmabuf(struct weston_compositor *ec, + struct linux_dmabuf_buffer *dmabuf); +#else +static inline struct drm_fb * +drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev) +{ + return NULL; +} +static inline bool +drm_can_scanout_dmabuf(struct weston_compositor *ec, + struct linux_dmabuf_buffer *dmabuf) +{ + return false; +} +#endif + +struct drm_pending_state * +drm_pending_state_alloc(struct drm_backend *backend); +void +drm_pending_state_free(struct drm_pending_state *pending_state); +struct drm_output_state * +drm_pending_state_get_output(struct drm_pending_state *pending_state, + struct drm_output *output); + + +/** + * Mode for drm_output_state_duplicate. + */ +enum drm_output_state_duplicate_mode { + DRM_OUTPUT_STATE_CLEAR_PLANES, /**< reset all planes to off */ + DRM_OUTPUT_STATE_PRESERVE_PLANES, /**< preserve plane state */ +}; + +struct drm_output_state * +drm_output_state_alloc(struct drm_output *output, + struct drm_pending_state *pending_state); +struct drm_output_state * +drm_output_state_duplicate(struct drm_output_state *src, + struct drm_pending_state *pending_state, + enum drm_output_state_duplicate_mode plane_mode); +void +drm_output_state_free(struct drm_output_state *state); +struct drm_plane_state * +drm_output_state_get_plane(struct drm_output_state *state_output, + struct drm_plane *plane); +struct drm_plane_state * +drm_output_state_get_existing_plane(struct drm_output_state *state_output, + struct drm_plane *plane); + + + +struct drm_plane_state * +drm_plane_state_alloc(struct drm_output_state *state_output, + struct drm_plane *plane); +struct drm_plane_state * +drm_plane_state_duplicate(struct drm_output_state *state_output, + struct drm_plane_state *src); +void +drm_plane_state_free(struct drm_plane_state *state, bool force); +void +drm_plane_state_put_back(struct drm_plane_state *state); +bool +drm_plane_state_coords_for_view(struct drm_plane_state *state, + struct weston_view *ev, uint64_t zpos); +void +drm_plane_reset_state(struct drm_plane *plane); + +void +drm_assign_planes(struct weston_output *output_base, void *repaint_data); + +bool +drm_plane_is_available(struct drm_plane *plane, struct drm_output *output); + +void +drm_output_render(struct drm_output_state *state, pixman_region32_t *damage); + +int +parse_gbm_format(const char *s, uint32_t default_value, uint32_t *gbm_format); + +extern struct gl_renderer_interface *gl_renderer; + +#ifdef BUILD_DRM_VIRTUAL +extern int +drm_backend_init_virtual_output_api(struct weston_compositor *compositor); +#else +inline static int +drm_backend_init_virtual_output_api(struct weston_compositor *compositor) +{ + return 0; +} +#endif + +#ifdef BUILD_DRM_GBM +int +init_egl(struct drm_backend *b); + +int +drm_output_init_egl(struct drm_output *output, struct drm_backend *b); + +void +drm_output_fini_egl(struct drm_output *output); + +struct drm_fb * +drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage); + +void +renderer_switch_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data); +#else +inline static int +init_egl(struct drm_backend *b) +{ + weston_log("Compiled without GBM/EGL support\n"); + return -1; +} + +inline static int +drm_output_init_egl(struct drm_output *output, struct drm_backend *b) +{ + return -1; +} + +inline static void +drm_output_fini_egl(struct drm_output *output) +{ +} + +inline static struct drm_fb * +drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage) +{ + return NULL; +} + +inline static void +renderer_switch_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) +{ + weston_log("Compiled without GBM/EGL support\n"); +} +#endif diff --git a/libweston/backend-drm/drm-virtual.c b/libweston/backend-drm/drm-virtual.c new file mode 100644 index 0000000..ebebbbd --- /dev/null +++ b/libweston/backend-drm/drm-virtual.c @@ -0,0 +1,362 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2017, 2018 Collabora, Ltd. + * Copyright © 2017, 2018 General Electric Company + * Copyright (c) 2018 DisplayLink (UK) Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "drm-internal.h" +#include "renderer-gl/gl-renderer.h" + +/** + * Create a drm_plane for virtual output + * + * Call drm_virtual_plane_destroy to clean up the plane. + * + * @param b DRM compositor backend + * @param output Output to create internal plane for + */ +static struct drm_plane * +drm_virtual_plane_create(struct drm_backend *b, struct drm_output *output) +{ + struct drm_plane *plane; + + /* num of formats is one */ + plane = zalloc(sizeof(*plane) + sizeof(plane->formats[0])); + if (!plane) { + weston_log("%s: out of memory\n", __func__); + return NULL; + } + + plane->type = WDRM_PLANE_TYPE_PRIMARY; + plane->backend = b; + plane->state_cur = drm_plane_state_alloc(NULL, plane); + plane->state_cur->complete = true; + plane->formats[0].format = output->gbm_format; + plane->count_formats = 1; + if ((output->gbm_bo_flags & GBM_BO_USE_LINEAR) && b->fb_modifiers) { + uint64_t *modifiers = zalloc(sizeof *modifiers); + if (modifiers) { + *modifiers = DRM_FORMAT_MOD_LINEAR; + plane->formats[0].modifiers = modifiers; + plane->formats[0].count_modifiers = 1; + } + } + + weston_plane_init(&plane->base, b->compositor, 0, 0); + wl_list_insert(&b->plane_list, &plane->link); + + return plane; +} + +/** + * Destroy one DRM plane + * + * @param plane Plane to deallocate (will be freed) + */ +static void +drm_virtual_plane_destroy(struct drm_plane *plane) +{ + drm_plane_state_free(plane->state_cur, true); + weston_plane_release(&plane->base); + wl_list_remove(&plane->link); + if (plane->formats[0].modifiers) + free(plane->formats[0].modifiers); + free(plane); +} + +static int +drm_virtual_output_start_repaint_loop(struct weston_output *output_base) +{ + weston_output_finish_frame(output_base, NULL, + WP_PRESENTATION_FEEDBACK_INVALID); + + return 0; +} + +static int +drm_virtual_output_submit_frame(struct drm_output *output, + struct drm_fb *fb) +{ + struct drm_backend *b = to_drm_backend(output->base.compositor); + int fd, ret; + + assert(fb->num_planes == 1); + ret = drmPrimeHandleToFD(b->drm.fd, fb->handles[0], DRM_CLOEXEC, &fd); + if (ret) { + weston_log("drmPrimeHandleFD failed, errno=%d\n", errno); + return -1; + } + + drm_fb_ref(fb); + ret = output->virtual_submit_frame(&output->base, fd, fb->strides[0], + fb); + if (ret < 0) { + drm_fb_unref(fb); + close(fd); + } + return ret; +} + +static int +drm_virtual_output_repaint(struct weston_output *output_base, + pixman_region32_t *damage, + void *repaint_data) +{ + struct drm_pending_state *pending_state = repaint_data; + struct drm_output_state *state = NULL; + struct drm_output *output = to_drm_output(output_base); + struct drm_plane *scanout_plane = output->scanout_plane; + struct drm_plane_state *scanout_state; + + assert(output->virtual); + + if (output->disable_pending || output->destroy_pending) + goto err; + + /* Drop frame if there isn't free buffers */ + if (!gbm_surface_has_free_buffers(output->gbm_surface)) { + weston_log("%s: Drop frame!!\n", __func__); + return -1; + } + + assert(!output->state_last); + + /* If planes have been disabled in the core, we might not have + * hit assign_planes at all, so might not have valid output state + * here. */ + state = drm_pending_state_get_output(pending_state, output); + if (!state) + state = drm_output_state_duplicate(output->state_cur, + pending_state, + DRM_OUTPUT_STATE_CLEAR_PLANES); + + drm_output_render(state, damage); + scanout_state = drm_output_state_get_plane(state, scanout_plane); + if (!scanout_state || !scanout_state->fb) + goto err; + + if (drm_virtual_output_submit_frame(output, scanout_state->fb) < 0) + goto err; + + return 0; + +err: + drm_output_state_free(state); + return -1; +} + +static void +drm_virtual_output_deinit(struct weston_output *base) +{ + struct drm_output *output = to_drm_output(base); + + drm_output_fini_egl(output); + + drm_virtual_plane_destroy(output->scanout_plane); +} + +static void +drm_virtual_output_destroy(struct weston_output *base) +{ + struct drm_output *output = to_drm_output(base); + + assert(output->virtual); + + if (output->base.enabled) + drm_virtual_output_deinit(&output->base); + + weston_output_release(&output->base); + + drm_output_state_free(output->state_cur); + + free(output); +} + +static int +drm_virtual_output_enable(struct weston_output *output_base) +{ + struct drm_output *output = to_drm_output(output_base); + struct drm_backend *b = to_drm_backend(output_base->compositor); + + assert(output->virtual); + + if (b->use_pixman) { + weston_log("Not support pixman renderer on Virtual output\n"); + goto err; + } + + if (!output->virtual_submit_frame) { + weston_log("The virtual_submit_frame hook is not set\n"); + goto err; + } + + output->scanout_plane = drm_virtual_plane_create(b, output); + if (!output->scanout_plane) { + weston_log("Failed to find primary plane for output %s\n", + output->base.name); + return -1; + } + + if (drm_output_init_egl(output, b) < 0) { + weston_log("Failed to init output gl state\n"); + goto err; + } + + output->base.start_repaint_loop = drm_virtual_output_start_repaint_loop; + output->base.repaint = drm_virtual_output_repaint; + output->base.assign_planes = drm_assign_planes; + output->base.set_dpms = NULL; + output->base.switch_mode = NULL; + output->base.gamma_size = 0; + output->base.set_gamma = NULL; + + weston_compositor_stack_plane(b->compositor, + &output->scanout_plane->base, + &b->compositor->primary_plane); + + return 0; +err: + return -1; +} + +static int +drm_virtual_output_disable(struct weston_output *base) +{ + struct drm_output *output = to_drm_output(base); + + assert(output->virtual); + + if (output->base.enabled) + drm_virtual_output_deinit(&output->base); + + return 0; +} + +static struct weston_output * +drm_virtual_output_create(struct weston_compositor *c, char *name) +{ + struct drm_output *output; + + output = zalloc(sizeof *output); + if (!output) + return NULL; + + output->virtual = true; + output->gbm_bo_flags = GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING; + + weston_output_init(&output->base, c, name); + + output->base.enable = drm_virtual_output_enable; + output->base.destroy = drm_virtual_output_destroy; + output->base.disable = drm_virtual_output_disable; + output->base.attach_head = NULL; + + output->state_cur = drm_output_state_alloc(output, NULL); + + weston_compositor_add_pending_output(&output->base, c); + + return &output->base; +} + +static uint32_t +drm_virtual_output_set_gbm_format(struct weston_output *base, + const char *gbm_format) +{ + struct drm_output *output = to_drm_output(base); + struct drm_backend *b = to_drm_backend(base->compositor); + + if (parse_gbm_format(gbm_format, b->gbm_format, &output->gbm_format) == -1) + output->gbm_format = b->gbm_format; + + return output->gbm_format; +} + +static void +drm_virtual_output_set_submit_frame_cb(struct weston_output *output_base, + submit_frame_cb cb) +{ + struct drm_output *output = to_drm_output(output_base); + + output->virtual_submit_frame = cb; +} + +static int +drm_virtual_output_get_fence_fd(struct weston_output *output_base) +{ + return gl_renderer->create_fence_fd(output_base); +} + +static void +drm_virtual_output_buffer_released(struct drm_fb *fb) +{ + drm_fb_unref(fb); +} + +static void +drm_virtual_output_finish_frame(struct weston_output *output_base, + struct timespec *stamp, + uint32_t presented_flags) +{ + struct drm_output *output = to_drm_output(output_base); + struct drm_plane_state *ps; + + wl_list_for_each(ps, &output->state_cur->plane_list, link) + ps->complete = true; + + drm_output_state_free(output->state_last); + output->state_last = NULL; + + weston_output_finish_frame(&output->base, stamp, presented_flags); + + /* 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 const struct weston_drm_virtual_output_api virt_api = { + drm_virtual_output_create, + drm_virtual_output_set_gbm_format, + drm_virtual_output_set_submit_frame_cb, + drm_virtual_output_get_fence_fd, + drm_virtual_output_buffer_released, + drm_virtual_output_finish_frame +}; + +int drm_backend_init_virtual_output_api(struct weston_compositor *compositor) +{ + return weston_plugin_api_register(compositor, + WESTON_DRM_VIRTUAL_OUTPUT_API_NAME, + &virt_api, sizeof(virt_api)); +} diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c new file mode 100755 index 0000000..8e10181 --- /dev/null +++ b/libweston/backend-drm/drm.c @@ -0,0 +1,3086 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2017, 2018 Collabora, Ltd. + * Copyright © 2017, 2018 General Electric Company + * Copyright (c) 2018 DisplayLink (UK) Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include "drm-internal.h" +#include "shared/helpers.h" +#include "shared/timespec-util.h" +#include "shared/string-helpers.h" +#include "pixman-renderer.h" +#include "pixel-formats.h" +#include "libbacklight.h" +#include "libinput-seat.h" +#include "launcher-util.h" +#include "vaapi-recorder.h" +#include "presentation-time-server-protocol.h" +#include "linux-dmabuf.h" +#include "linux-dmabuf-unstable-v1-server-protocol.h" +#include "linux-explicit-synchronization.h" +#include "wayland_drm_auth_server.h" // OHOS drm auth + +static const char default_seat[] = "seat0"; + +static void +drm_backend_create_faked_zpos(struct drm_backend *b) +{ + struct drm_plane *plane; + uint64_t zpos = 0ULL; + uint64_t zpos_min_primary; + uint64_t zpos_min_overlay; + uint64_t zpos_min_cursor; + + zpos_min_primary = zpos; + wl_list_for_each(plane, &b->plane_list, link) { + /* if the property is there, bail out sooner */ + if (plane->props[WDRM_PLANE_ZPOS].prop_id != 0) + return; + + if (plane->type != WDRM_PLANE_TYPE_PRIMARY) + continue; + zpos++; + } + + zpos_min_overlay = zpos; + wl_list_for_each(plane, &b->plane_list, link) { + if (plane->type != WDRM_PLANE_TYPE_OVERLAY) + continue; + zpos++; + } + + zpos_min_cursor = zpos; + wl_list_for_each(plane, &b->plane_list, link) { + if (plane->type != WDRM_PLANE_TYPE_CURSOR) + continue; + zpos++; + } + + drm_debug(b, "[drm-backend] zpos property not found. " + "Using invented immutable zpos values:\n"); + /* assume that invented zpos values are immutable */ + wl_list_for_each(plane, &b->plane_list, link) { + if (plane->type == WDRM_PLANE_TYPE_PRIMARY) { + plane->zpos_min = zpos_min_primary; + plane->zpos_max = zpos_min_primary; + } else if (plane->type == WDRM_PLANE_TYPE_OVERLAY) { + plane->zpos_min = zpos_min_overlay; + plane->zpos_max = zpos_min_overlay; + } else if (plane->type == WDRM_PLANE_TYPE_CURSOR) { + plane->zpos_min = zpos_min_cursor; + plane->zpos_max = zpos_min_cursor; + } + drm_debug(b, "\t[plane] %s plane %d, zpos_min %"PRIu64", " + "zpos_max %"PRIu64"\n", + drm_output_get_plane_type_name(plane), + plane->plane_id, plane->zpos_min, plane->zpos_max); + } +} + +static void +wl_array_remove_uint32(struct wl_array *array, uint32_t elm) +{ + uint32_t *pos, *end; + + end = (uint32_t *) ((char *) array->data + array->size); + + wl_array_for_each(pos, array) { + if (*pos != elm) + continue; + + array->size -= sizeof(*pos); + if (pos + 1 == end) + break; + + memmove(pos, pos + 1, (char *) end - (char *) (pos + 1)); + break; + } +} + +static int +pageflip_timeout(void *data) { + /* + * Our timer just went off, that means we're not receiving drm + * page flip events anymore for that output. Let's gracefully exit + * weston with a return value so devs can debug what's going on. + */ + struct drm_output *output = data; + struct weston_compositor *compositor = output->base.compositor; + + weston_log("Pageflip timeout reached on output %s, your " + "driver is probably buggy! Exiting.\n", + output->base.name); + weston_compositor_exit_with_code(compositor, EXIT_FAILURE); + + return 0; +} + +/* Creates the pageflip timer. Note that it isn't armed by default */ +static int +drm_output_pageflip_timer_create(struct drm_output *output) +{ + struct wl_event_loop *loop = NULL; + struct weston_compositor *ec = output->base.compositor; + + loop = wl_display_get_event_loop(ec->wl_display); + assert(loop); + output->pageflip_timer = wl_event_loop_add_timer(loop, + pageflip_timeout, + output); + + if (output->pageflip_timer == NULL) { + weston_log("creating drm pageflip timer failed: %s\n", + strerror(errno)); + return -1; + } + + return 0; +} + +static void +drm_output_destroy(struct weston_output *output_base); + +/** + * Returns true if the plane can be used on the given output for its current + * repaint cycle. + */ +bool +drm_plane_is_available(struct drm_plane *plane, struct drm_output *output) +{ + assert(plane->state_cur); + + if (output->virtual) + return false; + + /* The plane still has a request not yet completed by the kernel. */ + if (!plane->state_cur->complete) + return false; + + /* The plane is still active on another output. */ + if (plane->state_cur->output && plane->state_cur->output != output) + return false; + + /* Check whether the plane can be used with this CRTC; possible_crtcs + * is a bitmask of CRTC indices (pipe), rather than CRTC object ID. */ + return !!(plane->possible_crtcs & (1 << output->pipe)); +} + +struct drm_output * +drm_output_find_by_crtc(struct drm_backend *b, uint32_t crtc_id) +{ + struct drm_output *output; + + wl_list_for_each(output, &b->compositor->output_list, base.link) { + if (output->crtc_id == crtc_id) + return output; + } + + return NULL; +} + +struct drm_head * +drm_head_find_by_connector(struct drm_backend *backend, uint32_t connector_id) +{ + struct weston_head *base; + struct drm_head *head; + + wl_list_for_each(base, + &backend->compositor->head_list, compositor_link) { + head = to_drm_head(base); + if (head->connector_id == connector_id) + return head; + } + + return NULL; +} + +/** + * Get output state to disable output + * + * Returns a pointer to an output_state object which can be used to disable + * an output (e.g. DPMS off). + * + * @param pending_state The pending state object owning this update + * @param output The output to disable + * @returns A drm_output_state to disable the output + */ +static struct drm_output_state * +drm_output_get_disable_state(struct drm_pending_state *pending_state, + struct drm_output *output) +{ + struct drm_output_state *output_state; + + output_state = drm_output_state_duplicate(output->state_cur, + pending_state, + DRM_OUTPUT_STATE_CLEAR_PLANES); + output_state->dpms = WESTON_DPMS_OFF; + + output_state->protection = WESTON_HDCP_DISABLE; + + return output_state; +} + + +/** + * Mark a drm_output_state (the output's last state) as complete. This handles + * any post-completion actions such as updating the repaint timer, disabling the + * output, and finally freeing the state. + */ +void +drm_output_update_complete(struct drm_output *output, uint32_t flags, + unsigned int sec, unsigned int usec) +{ + struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_plane_state *ps; + struct timespec ts; + + /* Stop the pageflip timer instead of rearming it here */ + if (output->pageflip_timer) + wl_event_source_timer_update(output->pageflip_timer, 0); + + wl_list_for_each(ps, &output->state_cur->plane_list, link) + ps->complete = true; + + drm_output_state_free(output->state_last); + output->state_last = NULL; + + if (output->destroy_pending) { + output->destroy_pending = false; + output->disable_pending = false; + output->dpms_off_pending = false; + drm_output_destroy(&output->base); + return; + } else if (output->disable_pending) { + output->disable_pending = false; + output->dpms_off_pending = false; + weston_output_disable(&output->base); + return; + } else if (output->dpms_off_pending) { + struct drm_pending_state *pending = drm_pending_state_alloc(b); + output->dpms_off_pending = false; + drm_output_get_disable_state(pending, output); + drm_pending_state_apply_sync(pending); + } + if (output->state_cur->dpms == WESTON_DPMS_OFF && + output->base.repaint_status != REPAINT_AWAITING_COMPLETION) { + /* DPMS can happen to us either in the middle of a repaint + * cycle (when we have painted fresh content, only to throw it + * away for DPMS off), or at any other random point. If the + * latter is true, then we cannot go through finish_frame, + * because the repaint machinery does not expect this. */ + return; + } + + ts.tv_sec = sec; + ts.tv_nsec = usec * 1000; + weston_output_finish_frame(&output->base, &ts, flags); + + /* 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 struct drm_fb * +drm_output_render_pixman(struct drm_output_state *state, + pixman_region32_t *damage) +{ + struct drm_output *output = state->output; + struct weston_compositor *ec = output->base.compositor; + + output->current_image ^= 1; + + pixman_renderer_output_set_buffer(&output->base, + output->image[output->current_image]); + pixman_renderer_output_set_hw_extra_damage(&output->base, + &output->previous_damage); + + ec->renderer->repaint_output(&output->base, damage); + + pixman_region32_copy(&output->previous_damage, damage); + + return drm_fb_ref(output->dumb[output->current_image]); +} + +void +drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) +{ + struct drm_output *output = state->output; + struct weston_compositor *c = output->base.compositor; + struct drm_plane_state *scanout_state; + struct drm_plane *scanout_plane = output->scanout_plane; + struct drm_property_info *damage_info = + &scanout_plane->props[WDRM_PLANE_FB_DAMAGE_CLIPS]; + struct drm_backend *b = to_drm_backend(c); + struct drm_fb *fb; + pixman_region32_t scanout_damage; + pixman_box32_t *rects; + int n_rects; + + /* If we already have a client buffer promoted to scanout, then we don't + * want to render. */ + scanout_state = drm_output_state_get_plane(state, + output->scanout_plane); + if (scanout_state->fb) + return; + + /* + * If we don't have any damage on the primary plane, and we already + * have a renderer buffer active, we can reuse it; else we pass + * the damaged region into the renderer to re-render the affected + * area. + */ + if (!pixman_region32_not_empty(damage) && + scanout_plane->state_cur->fb && + (scanout_plane->state_cur->fb->type == BUFFER_GBM_SURFACE || + scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB)) { + fb = drm_fb_ref(scanout_plane->state_cur->fb); + } else if (b->use_pixman) { + fb = drm_output_render_pixman(state, damage); + } else { + fb = drm_output_render_gl(state, damage); + } + + if (!fb) { + drm_plane_state_put_back(scanout_state); + return; + } + + scanout_state->fb = fb; + scanout_state->output = output; + + scanout_state->src_x = 0; + scanout_state->src_y = 0; + scanout_state->src_w = fb->width << 16; + scanout_state->src_h = fb->height << 16; + + scanout_state->dest_x = 0; + scanout_state->dest_y = 0; + scanout_state->dest_w = output->base.current_mode->width; + scanout_state->dest_h = output->base.current_mode->height; + + pixman_region32_subtract(&c->primary_plane.damage, + &c->primary_plane.damage, damage); + + /* Don't bother calculating plane damage if the plane doesn't support it */ + if (damage_info->prop_id == 0) + return; + + pixman_region32_init(&scanout_damage); + pixman_region32_copy(&scanout_damage, damage); + + if (output->base.zoom.active) { + weston_matrix_transform_region(&scanout_damage, + &output->base.matrix, + &scanout_damage); + } else { + pixman_region32_translate(&scanout_damage, + -output->base.x, -output->base.y); + weston_transformed_region(output->base.width, + output->base.height, + output->base.transform, + output->base.current_scale, + &scanout_damage, + &scanout_damage); + } + + assert(scanout_state->damage_blob_id == 0); + + rects = pixman_region32_rectangles(&scanout_damage, &n_rects); + + /* + * If this function fails, the blob id should still be 0. + * This tells the kernel there is no damage information, which means + * that it will consider the whole plane damaged. While this may + * affect efficiency, it should still produce correct results. + */ + drmModeCreatePropertyBlob(b->drm.fd, rects, + sizeof(*rects) * n_rects, + &scanout_state->damage_blob_id); + + pixman_region32_fini(&scanout_damage); +} + +static int +drm_output_repaint(struct weston_output *output_base, + pixman_region32_t *damage, + void *repaint_data) +{ + struct drm_pending_state *pending_state = repaint_data; + struct drm_output *output = to_drm_output(output_base); + struct drm_output_state *state = NULL; + struct drm_plane_state *scanout_state; + + assert(!output->virtual); + + if (output->disable_pending || output->destroy_pending) + goto err; + + assert(!output->state_last); + + /* If planes have been disabled in the core, we might not have + * hit assign_planes at all, so might not have valid output state + * here. */ + state = drm_pending_state_get_output(pending_state, output); + if (!state) + state = drm_output_state_duplicate(output->state_cur, + pending_state, + DRM_OUTPUT_STATE_CLEAR_PLANES); + state->dpms = WESTON_DPMS_ON; + + if (output_base->allow_protection) + state->protection = output_base->desired_protection; + else + state->protection = WESTON_HDCP_DISABLE; + + drm_output_render(state, damage); + scanout_state = drm_output_state_get_plane(state, + output->scanout_plane); + if (!scanout_state || !scanout_state->fb) + goto err; + + return 0; + +err: + drm_output_state_free(state); + return -1; +} + +/* Determine the type of vblank synchronization to use for the output. + * + * The pipe parameter indicates which CRTC is in use. Knowing this, we + * can determine which vblank sequence type to use for it. Traditional + * cards had only two CRTCs, with CRTC 0 using no special flags, and + * CRTC 1 using DRM_VBLANK_SECONDARY. The first bit of the pipe + * parameter indicates this. + * + * Bits 1-5 of the pipe parameter are 5 bit wide pipe number between + * 0-31. If this is non-zero it indicates we're dealing with a + * multi-gpu situation and we need to calculate the vblank sync + * using DRM_BLANK_HIGH_CRTC_MASK. + */ +static unsigned int +drm_waitvblank_pipe(struct drm_output *output) +{ + if (output->pipe > 1) + return (output->pipe << DRM_VBLANK_HIGH_CRTC_SHIFT) & + DRM_VBLANK_HIGH_CRTC_MASK; + else if (output->pipe > 0) + return DRM_VBLANK_SECONDARY; + else + return 0; +} + +static int +drm_output_start_repaint_loop(struct weston_output *output_base) +{ + struct drm_output *output = to_drm_output(output_base); + struct drm_pending_state *pending_state; + struct drm_plane *scanout_plane = output->scanout_plane; + struct drm_backend *backend = + to_drm_backend(output_base->compositor); + struct timespec ts, tnow; + struct timespec vbl2now; + int64_t refresh_nsec; + int ret; + drmVBlank vbl = { + .request.type = DRM_VBLANK_RELATIVE, + .request.sequence = 0, + .request.signal = 0, + }; + + if (output->disable_pending || output->destroy_pending) + return 0; + + if (!output->scanout_plane->state_cur->fb) { + /* We can't page flip if there's no mode set */ + goto finish_frame; + } + + /* Need to smash all state in from scratch; current timings might not + * be what we want, page flip might not work, etc. + */ + if (backend->state_invalid) + goto finish_frame; + + assert(scanout_plane->state_cur->output == output); + + /* Try to get current msc and timestamp via instant query */ + vbl.request.type |= drm_waitvblank_pipe(output); + ret = drmWaitVBlank(backend->drm.fd, &vbl); + + /* Error ret or zero timestamp means failure to get valid timestamp */ + if ((ret == 0) && (vbl.reply.tval_sec > 0 || vbl.reply.tval_usec > 0)) { + ts.tv_sec = vbl.reply.tval_sec; + ts.tv_nsec = vbl.reply.tval_usec * 1000; + + /* Valid timestamp for most recent vblank - not stale? + * Stale ts could happen on Linux 3.17+, so make sure it + * is not older than 1 refresh duration since now. + */ + weston_compositor_read_presentation_clock(backend->compositor, + &tnow); + timespec_sub(&vbl2now, &tnow, &ts); + refresh_nsec = + millihz_to_nsec(output->base.current_mode->refresh); + if (timespec_to_nsec(&vbl2now) < refresh_nsec) { + drm_output_update_msc(output, vbl.reply.sequence); + weston_output_finish_frame(output_base, &ts, + WP_PRESENTATION_FEEDBACK_INVALID); + return 0; + } + } + + /* Immediate query didn't provide valid timestamp. + * Use pageflip fallback. + */ + + assert(!output->page_flip_pending); + assert(!output->state_last); + + pending_state = drm_pending_state_alloc(backend); + drm_output_state_duplicate(output->state_cur, pending_state, + DRM_OUTPUT_STATE_PRESERVE_PLANES); + + ret = drm_pending_state_apply(pending_state); + if (ret != 0) { + weston_log("applying repaint-start state failed: %s\n", + strerror(errno)); + if (ret == -EACCES) + return -1; + goto finish_frame; + } + + return 0; + +finish_frame: + /* if we cannot page-flip, immediately finish frame */ + weston_output_finish_frame(output_base, NULL, + WP_PRESENTATION_FEEDBACK_INVALID); + return 0; +} + +/** + * Begin a new repaint cycle + * + * Called by the core compositor at the beginning of a repaint cycle. Creates + * a new pending_state structure to own any output state created by individual + * output repaint functions until the repaint is flushed or cancelled. + */ +static void * +drm_repaint_begin(struct weston_compositor *compositor) +{ + struct drm_backend *b = to_drm_backend(compositor); + struct drm_pending_state *ret; + + ret = drm_pending_state_alloc(b); + b->repaint_data = ret; + + // OHOS remove logger + // if (weston_log_scope_is_enabled(b->debug)) { + // char *dbg = weston_compositor_print_scene_graph(compositor); + // drm_debug(b, "[repaint] Beginning repaint; pending_state %p\n", + // ret); + // drm_debug(b, "%s", dbg); + // free(dbg); + // } + + return ret; +} + +/** + * Flush a repaint set + * + * Called by the core compositor when a repaint cycle has been completed + * and should be flushed. Frees the pending state, transitioning ownership + * of the output state from the pending state, to the update itself. When + * the update completes (see drm_output_update_complete), the output + * state will be freed. + */ +static int +drm_repaint_flush(struct weston_compositor *compositor, void *repaint_data) +{ + struct drm_backend *b = to_drm_backend(compositor); + struct drm_pending_state *pending_state = repaint_data; + int ret; + + ret = drm_pending_state_apply(pending_state); + if (ret != 0) + weston_log("repaint-flush failed: %s\n", strerror(errno)); + + drm_debug(b, "[repaint] flushed pending_state %p\n", pending_state); + b->repaint_data = NULL; + + return (ret == -EACCES) ? -1 : 0; +} + +/** + * Cancel a repaint set + * + * Called by the core compositor when a repaint has finished, so the data + * held across the repaint cycle should be discarded. + */ +static void +drm_repaint_cancel(struct weston_compositor *compositor, void *repaint_data) +{ + struct drm_backend *b = to_drm_backend(compositor); + struct drm_pending_state *pending_state = repaint_data; + + drm_pending_state_free(pending_state); + drm_debug(b, "[repaint] cancel pending_state %p\n", pending_state); + b->repaint_data = NULL; +} + +static int +drm_output_init_pixman(struct drm_output *output, struct drm_backend *b); +static void +drm_output_fini_pixman(struct drm_output *output); + +static int +drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mode) +{ + struct drm_output *output = to_drm_output(output_base); + struct drm_backend *b = to_drm_backend(output_base->compositor); + struct drm_mode *drm_mode = drm_output_choose_mode(output, mode); + + if (!drm_mode) { + weston_log("%s: invalid resolution %dx%d\n", + output_base->name, 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; + + /* XXX: This drops our current buffer too early, before we've started + * displaying it. Ideally this should be much more atomic and + * integrated with a full repaint cycle, rather than doing a + * sledgehammer modeswitch first, and only later showing new + * content. + */ + b->state_invalid = true; + + if (b->use_pixman) { + drm_output_fini_pixman(output); + if (drm_output_init_pixman(output, b) < 0) { + weston_log("failed to init output pixman state with " + "new mode\n"); + return -1; + } + } else { + drm_output_fini_egl(output); + if (drm_output_init_egl(output, b) < 0) { + weston_log("failed to init output egl state with " + "new mode"); + return -1; + } + } + + return 0; +} + +static int +init_pixman(struct drm_backend *b) +{ + return pixman_renderer_init(b->compositor); +} + +/** + * Create a drm_plane for a hardware plane + * + * Creates one drm_plane structure for a hardware plane, and initialises its + * properties and formats. + * + * In the absence of universal plane support, where KMS does not explicitly + * expose the primary and cursor planes to userspace, this may also create + * an 'internal' plane for internal management. + * + * This function does not add the plane to the list of usable planes in Weston + * itself; the caller is responsible for this. + * + * Call drm_plane_destroy to clean up the plane. + * + * @sa drm_output_find_special_plane + * @param b DRM compositor backend + * @param kplane DRM plane to create, or NULL if creating internal plane + * @param output Output to create internal plane for, or NULL + * @param type Type to use when creating internal plane, or invalid + * @param format Format to use for internal planes, or 0 + */ +static struct drm_plane * +drm_plane_create(struct drm_backend *b, const drmModePlane *kplane, + struct drm_output *output, enum wdrm_plane_type type, + uint32_t format) +{ + struct drm_plane *plane; + drmModeObjectProperties *props; + uint64_t *zpos_range_values; + uint32_t num_formats = (kplane) ? kplane->count_formats : 1; + + plane = zalloc(sizeof(*plane) + + (sizeof(plane->formats[0]) * num_formats)); + if (!plane) { + weston_log("%s: out of memory\n", __func__); + return NULL; + } + + plane->backend = b; + plane->count_formats = num_formats; + plane->state_cur = drm_plane_state_alloc(NULL, plane); + plane->state_cur->complete = true; + + if (kplane) { + plane->possible_crtcs = kplane->possible_crtcs; + plane->plane_id = kplane->plane_id; + + props = drmModeObjectGetProperties(b->drm.fd, kplane->plane_id, + DRM_MODE_OBJECT_PLANE); + if (!props) { + weston_log("couldn't get plane properties\n"); + goto err; + } + drm_property_info_populate(b, plane_props, plane->props, + WDRM_PLANE__COUNT, props); + plane->type = + drm_property_get_value(&plane->props[WDRM_PLANE_TYPE], + props, + WDRM_PLANE_TYPE__COUNT); + + zpos_range_values = + drm_property_get_range_values(&plane->props[WDRM_PLANE_ZPOS], + props); + + if (zpos_range_values) { + plane->zpos_min = zpos_range_values[0]; + plane->zpos_max = zpos_range_values[1]; + } else { + plane->zpos_min = DRM_PLANE_ZPOS_INVALID_PLANE; + plane->zpos_max = DRM_PLANE_ZPOS_INVALID_PLANE; + } + + if (drm_plane_populate_formats(plane, kplane, props, + b->fb_modifiers) < 0) { + drmModeFreeObjectProperties(props); + goto err; + } + + drmModeFreeObjectProperties(props); + } + else { + plane->possible_crtcs = (1 << output->pipe); + plane->plane_id = 0; + plane->count_formats = 1; + plane->formats[0].format = format; + plane->type = type; + plane->zpos_max = DRM_PLANE_ZPOS_INVALID_PLANE; + plane->zpos_min = DRM_PLANE_ZPOS_INVALID_PLANE; + } + + if (plane->type == WDRM_PLANE_TYPE__COUNT) + goto err_props; + + /* With universal planes, everything is a DRM plane; without + * universal planes, the only DRM planes are overlay planes. + * Everything else is a fake plane. */ + if (b->universal_planes) { + assert(kplane); + } else { + if (kplane) + assert(plane->type == WDRM_PLANE_TYPE_OVERLAY); + else + assert(plane->type != WDRM_PLANE_TYPE_OVERLAY && + output); + } + + weston_plane_init(&plane->base, b->compositor, 0, 0); + wl_list_insert(&b->plane_list, &plane->link); + + return plane; + +err_props: + drm_property_info_free(plane->props, WDRM_PLANE__COUNT); +err: + drm_plane_state_free(plane->state_cur, true); + free(plane); + return NULL; +} + +/** + * Find, or create, a special-purpose plane + * + * Primary and cursor planes are a special case, in that before universal + * planes, they are driven by non-plane API calls. Without universal plane + * support, the only way to configure a primary plane is via drmModeSetCrtc, + * and the only way to configure a cursor plane is drmModeSetCursor2. + * + * Although they may actually be regular planes in the hardware, without + * universal plane support, these planes are not actually exposed to + * userspace in the regular plane list. + * + * However, for ease of internal tracking, we want to manage all planes + * through the same drm_plane structures. Therefore, when we are running + * without universal plane support, we create fake drm_plane structures + * to track these planes. + * + * @param b DRM backend + * @param output Output to use for plane + * @param type Type of plane + */ +static struct drm_plane * +drm_output_find_special_plane(struct drm_backend *b, struct drm_output *output, + enum wdrm_plane_type type) +{ + struct drm_plane *plane; + + if (!b->universal_planes) { + uint32_t format; + + switch (type) { + case WDRM_PLANE_TYPE_CURSOR: + format = DRM_FORMAT_ARGB8888; + break; + case WDRM_PLANE_TYPE_PRIMARY: + /* We don't know what formats the primary plane supports + * before universal planes, so we just assume that the + * GBM format works; however, this isn't set until after + * the output is created. */ + format = 0; + break; + default: + assert(!"invalid type in drm_output_find_special_plane"); + break; + } + + return drm_plane_create(b, NULL, output, type, format); + } + + wl_list_for_each(plane, &b->plane_list, link) { + struct drm_output *tmp; + bool found_elsewhere = false; + + if (plane->type != type) + continue; + if (!drm_plane_is_available(plane, output)) + continue; + + /* On some platforms, primary/cursor planes can roam + * between different CRTCs, so make sure we don't claim the + * same plane for two outputs. */ + wl_list_for_each(tmp, &b->compositor->output_list, + base.link) { + if (tmp->cursor_plane == plane || + tmp->scanout_plane == plane) { + found_elsewhere = true; + break; + } + } + + if (found_elsewhere) + continue; + + plane->possible_crtcs = (1 << output->pipe); + return plane; + } + + return NULL; +} + +/** + * Destroy one DRM plane + * + * Destroy a DRM plane, removing it from screen and releasing its retained + * buffers in the process. The counterpart to drm_plane_create. + * + * @param plane Plane to deallocate (will be freed) + */ +static void +drm_plane_destroy(struct drm_plane *plane) +{ + if (plane->type == WDRM_PLANE_TYPE_OVERLAY) + drmModeSetPlane(plane->backend->drm.fd, plane->plane_id, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + drm_plane_state_free(plane->state_cur, true); + drm_property_info_free(plane->props, WDRM_PLANE__COUNT); + weston_plane_release(&plane->base); + wl_list_remove(&plane->link); + free(plane); +} + +/** + * Initialise sprites (overlay planes) + * + * Walk the list of provided DRM planes, and add overlay planes. + * + * Call destroy_sprites to free these planes. + * + * @param b DRM compositor backend + */ +static void +create_sprites(struct drm_backend *b) +{ + drmModePlaneRes *kplane_res; + drmModePlane *kplane; + struct drm_plane *drm_plane; + uint32_t i; + kplane_res = drmModeGetPlaneResources(b->drm.fd); + if (!kplane_res) { + weston_log("failed to get plane resources: %s\n", + strerror(errno)); + return; + } + + for (i = 0; i < kplane_res->count_planes; i++) { + kplane = drmModeGetPlane(b->drm.fd, kplane_res->planes[i]); + if (!kplane) + continue; + + drm_plane = drm_plane_create(b, kplane, NULL, + WDRM_PLANE_TYPE__COUNT, 0); + drmModeFreePlane(kplane); + if (!drm_plane) + continue; + + if (drm_plane->type == WDRM_PLANE_TYPE_OVERLAY) + weston_compositor_stack_plane(b->compositor, + &drm_plane->base, + &b->compositor->primary_plane); + } + + drmModeFreePlaneResources(kplane_res); +} + +/** + * Clean up sprites (overlay planes) + * + * The counterpart to create_sprites. + * + * @param b DRM compositor backend + */ +static void +destroy_sprites(struct drm_backend *b) +{ + struct drm_plane *plane, *next; + + wl_list_for_each_safe(plane, next, &b->plane_list, link) + drm_plane_destroy(plane); +} + +/* returns a value between 0-255 range, where higher is brighter */ +static uint32_t +drm_get_backlight(struct drm_head *head) +{ + long brightness, max_brightness, norm; + + brightness = backlight_get_brightness(head->backlight); + max_brightness = backlight_get_max_brightness(head->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 = to_drm_output(output_base); + struct drm_head *head; + long max_brightness, new_brightness; + + if (value > 255) + return; + + wl_list_for_each(head, &output->base.head_list, base.output_link) { + if (!head->backlight) + return; + + max_brightness = backlight_get_max_brightness(head->backlight); + + /* get denormalized value */ + new_brightness = (value * max_brightness) / 255; + + backlight_set_brightness(head->backlight, new_brightness); + } +} + +static void +drm_output_init_backlight(struct drm_output *output) +{ + struct weston_head *base; + struct drm_head *head; + + output->base.set_backlight = NULL; + + wl_list_for_each(base, &output->base.head_list, output_link) { + head = to_drm_head(base); + + if (head->backlight) { + weston_log("Initialized backlight for head '%s', device %s\n", + head->base.name, head->backlight->path); + + if (!output->base.set_backlight) { + output->base.set_backlight = drm_set_backlight; + output->base.backlight_current = + drm_get_backlight(head); + } + } + } +} + +/** + * Power output on or off + * + * The DPMS/power level of an output is used to switch it on or off. This + * is DRM's hook for doing so, which can called either as part of repaint, + * or independently of the repaint loop. + * + * If we are called as part of repaint, we simply set the relevant bit in + * state and return. + * + * This function is never called on a virtual output. + */ +static void +drm_set_dpms(struct weston_output *output_base, enum dpms_enum level) +{ + struct drm_output *output = to_drm_output(output_base); + struct drm_backend *b = to_drm_backend(output_base->compositor); + struct drm_pending_state *pending_state = b->repaint_data; + struct drm_output_state *state; + int ret; + + assert(!output->virtual); + + if (output->state_cur->dpms == level) + return; + + /* If we're being called during the repaint loop, then this is + * simple: discard any previously-generated state, and create a new + * state where we disable everything. When we come to flush, this + * will be applied. + * + * However, we need to be careful: we can be called whilst another + * output is in its repaint cycle (pending_state exists), but our + * output still has an incomplete state application outstanding. + * In that case, we need to wait until that completes. */ + if (pending_state && !output->state_last) { + /* The repaint loop already sets DPMS on; we don't need to + * explicitly set it on here, as it will already happen + * whilst applying the repaint state. */ + if (level == WESTON_DPMS_ON) + return; + + state = drm_pending_state_get_output(pending_state, output); + if (state) + drm_output_state_free(state); + state = drm_output_get_disable_state(pending_state, output); + return; + } + + /* As we throw everything away when disabling, just send us back through + * a repaint cycle. */ + if (level == WESTON_DPMS_ON) { + if (output->dpms_off_pending) + output->dpms_off_pending = false; + weston_output_schedule_repaint(output_base); + return; + } + + /* If we've already got a request in the pipeline, then we need to + * park our DPMS request until that request has quiesced. */ + if (output->state_last) { + output->dpms_off_pending = true; + return; + } + + pending_state = drm_pending_state_alloc(b); + drm_output_get_disable_state(pending_state, output); + ret = drm_pending_state_apply_sync(pending_state); + if (ret != 0) + weston_log("drm_set_dpms: couldn't disable output?\n"); +} + +static const char * const connector_type_names[] = { + [DRM_MODE_CONNECTOR_Unknown] = "Unknown", + [DRM_MODE_CONNECTOR_VGA] = "VGA", + [DRM_MODE_CONNECTOR_DVII] = "DVI-I", + [DRM_MODE_CONNECTOR_DVID] = "DVI-D", + [DRM_MODE_CONNECTOR_DVIA] = "DVI-A", + [DRM_MODE_CONNECTOR_Composite] = "Composite", + [DRM_MODE_CONNECTOR_SVIDEO] = "SVIDEO", + [DRM_MODE_CONNECTOR_LVDS] = "LVDS", + [DRM_MODE_CONNECTOR_Component] = "Component", + [DRM_MODE_CONNECTOR_9PinDIN] = "DIN", + [DRM_MODE_CONNECTOR_DisplayPort] = "DP", + [DRM_MODE_CONNECTOR_HDMIA] = "HDMI-A", + [DRM_MODE_CONNECTOR_HDMIB] = "HDMI-B", + [DRM_MODE_CONNECTOR_TV] = "TV", + [DRM_MODE_CONNECTOR_eDP] = "eDP", + [DRM_MODE_CONNECTOR_VIRTUAL] = "Virtual", + [DRM_MODE_CONNECTOR_DSI] = "DSI", + [DRM_MODE_CONNECTOR_DPI] = "DPI", +}; + +/** Create a name given a DRM connector + * + * \param con The DRM connector whose type and id form the name. + * \return A newly allocate string, or NULL on error. Must be free()'d + * after use. + * + * The name does not identify the DRM display device. + */ +static char * +make_connector_name(const drmModeConnector *con) +{ + char *name; + const char *type_name = NULL; + int ret; + + if (con->connector_type < ARRAY_LENGTH(connector_type_names)) + type_name = connector_type_names[con->connector_type]; + + if (!type_name) + type_name = "UNNAMED"; + + ret = asprintf(&name, "%s-%d", type_name, con->connector_type_id); + if (ret < 0) + return NULL; + + return name; +} + +static int +drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) +{ + int w = output->base.current_mode->width; + int h = output->base.current_mode->height; + uint32_t format = output->gbm_format; + uint32_t pixman_format; + unsigned int i; + b->use_pixman_shadow = false; // OHOS + const struct pixman_renderer_output_options options = { + .use_shadow = b->use_pixman_shadow, + }; + + switch (format) { + case DRM_FORMAT_XRGB8888: + pixman_format = PIXMAN_x8r8g8b8; + break; + case DRM_FORMAT_RGB565: + pixman_format = PIXMAN_r5g6b5; + break; + default: + weston_log("Unsupported pixman format 0x%x\n", format); + return -1; + } + + /* FIXME error checking */ + for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { + output->dumb[i] = drm_fb_create_dumb(b, w, h, format); + if (!output->dumb[i]) + goto err; + + output->image[i] = + pixman_image_create_bits(pixman_format, w, h, + output->dumb[i]->map, + output->dumb[i]->strides[0]); + if (!output->image[i]) + goto err; + } + + if (pixman_renderer_output_create(&output->base, &options) < 0) + goto err; + + weston_log("DRM: output %s %s shadow framebuffer.\n", output->base.name, + b->use_pixman_shadow ? "uses" : "does not use"); + + 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_unref(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) +{ + struct drm_backend *b = to_drm_backend(output->base.compositor); + unsigned int i; + + /* Destroying the Pixman surface will destroy all our buffers, + * regardless of refcount. Ensure we destroy them here. */ + if (!b->shutting_down && + output->scanout_plane->state_cur->fb && + output->scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB) { + drm_plane_reset_state(output->scanout_plane); + } + + pixman_renderer_output_destroy(&output->base); + pixman_region32_fini(&output->previous_damage); + + for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { + pixman_image_unref(output->image[i]); + drm_fb_unref(output->dumb[i]); + output->dumb[i] = NULL; + output->image[i] = NULL; + } +} + +static void +setup_output_seat_constraint(struct drm_backend *b, + struct weston_output *output, + const char *s) +{ + if (strcmp(s, "") != 0) { + struct weston_pointer *pointer; + struct udev_seat *seat; + + seat = udev_seat_get_named(&b->input, s); + if (!seat) + return; + + seat->base.output = output; + + pointer = weston_seat_get_pointer(&seat->base); + if (pointer) + weston_pointer_clamp(pointer, + &pointer->x, + &pointer->y); + } +} + +static int +drm_output_attach_head(struct weston_output *output_base, + struct weston_head *head_base) +{ + struct drm_backend *b = to_drm_backend(output_base->compositor); + + if (wl_list_length(&output_base->head_list) >= MAX_CLONED_CONNECTORS) + return -1; + + if (!output_base->enabled) + return 0; + + /* XXX: ensure the configuration will work. + * This is actually impossible without major infrastructure + * work. */ + + /* Need to go through modeset to add connectors. */ + /* XXX: Ideally we'd do this per-output, not globally. */ + /* XXX: Doing it globally, what guarantees another output's update + * will not clear the flag before this output is updated? + */ + b->state_invalid = true; + + weston_output_schedule_repaint(output_base); + + return 0; +} + +static void +drm_output_detach_head(struct weston_output *output_base, + struct weston_head *head_base) +{ + struct drm_backend *b = to_drm_backend(output_base->compositor); + + if (!output_base->enabled) + return; + + /* Need to go through modeset to drop connectors that should no longer + * be driven. */ + /* XXX: Ideally we'd do this per-output, not globally. */ + b->state_invalid = true; + + weston_output_schedule_repaint(output_base); +} + +int +parse_gbm_format(const char *s, uint32_t default_value, uint32_t *gbm_format) +{ + const struct pixel_format_info *pinfo; + + if (s == NULL) { + *gbm_format = default_value; + + return 0; + } + + pinfo = pixel_format_get_info_by_drm_name(s); + if (!pinfo) { + weston_log("fatal: unrecognized pixel format: %s\n", s); + + return -1; + } + + /* GBM formats and DRM formats are identical. */ + *gbm_format = pinfo->format; + + return 0; +} + +static int +drm_head_read_current_setup(struct drm_head *head, struct drm_backend *backend) +{ + int drm_fd = backend->drm.fd; + drmModeEncoder *encoder; + drmModeCrtc *crtc; + + /* Get the current mode on the crtc that's currently driving + * this connector. */ + encoder = drmModeGetEncoder(drm_fd, head->connector->encoder_id); + if (encoder != NULL) { + head->inherited_crtc_id = encoder->crtc_id; + + crtc = drmModeGetCrtc(drm_fd, encoder->crtc_id); + drmModeFreeEncoder(encoder); + + if (crtc == NULL) + return -1; + if (crtc->mode_valid) + head->inherited_mode = crtc->mode; + drmModeFreeCrtc(crtc); + } + + return 0; +} + +static void +drm_output_set_gbm_format(struct weston_output *base, + const char *gbm_format) +{ + struct drm_output *output = to_drm_output(base); + struct drm_backend *b = to_drm_backend(base->compositor); + + if (parse_gbm_format(gbm_format, b->gbm_format, &output->gbm_format) == -1) + output->gbm_format = b->gbm_format; +} + +static void +drm_output_set_seat(struct weston_output *base, + const char *seat) +{ + struct drm_output *output = to_drm_output(base); + struct drm_backend *b = to_drm_backend(base->compositor); + + setup_output_seat_constraint(b, &output->base, + seat ? seat : ""); +} + +static int +drm_output_init_gamma_size(struct drm_output *output) +{ + struct drm_backend *backend = to_drm_backend(output->base.compositor); + drmModeCrtc *crtc; + + assert(output->base.compositor); + assert(output->crtc_id != 0); + crtc = drmModeGetCrtc(backend->drm.fd, output->crtc_id); + if (!crtc) + return -1; + + output->base.gamma_size = crtc->gamma_size; + + drmModeFreeCrtc(crtc); + + return 0; +} + +static uint32_t +drm_head_get_possible_crtcs_mask(struct drm_head *head) +{ + uint32_t possible_crtcs = 0; + drmModeEncoder *encoder; + int i; + + for (i = 0; i < head->connector->count_encoders; i++) { + encoder = drmModeGetEncoder(head->backend->drm.fd, + head->connector->encoders[i]); + if (!encoder) + continue; + + possible_crtcs |= encoder->possible_crtcs; + drmModeFreeEncoder(encoder); + } + + return possible_crtcs; +} + +static int +drm_crtc_get_index(drmModeRes *resources, uint32_t crtc_id) +{ + int i; + + for (i = 0; i < resources->count_crtcs; i++) { + if (resources->crtcs[i] == crtc_id) + return i; + } + + assert(0 && "unknown crtc id"); + return -1; +} + +/** Pick a CRTC that might be able to drive all attached connectors + * + * @param output The output whose attached heads to include. + * @param resources The DRM KMS resources. + * @return CRTC index, or -1 on failure or not found. + */ +static int +drm_output_pick_crtc(struct drm_output *output, drmModeRes *resources) +{ + struct drm_backend *backend; + struct weston_head *base; + struct drm_head *head; + uint32_t possible_crtcs = 0xffffffff; + int existing_crtc[32]; + unsigned j, n = 0; + uint32_t crtc_id; + int best_crtc_index = -1; + int fallback_crtc_index = -1; + int i; + bool match; + + backend = to_drm_backend(output->base.compositor); + + /* This algorithm ignores drmModeEncoder::possible_clones restriction, + * because it is more often set wrong than not in the kernel. */ + + /* Accumulate a mask of possible crtcs and find existing routings. */ + wl_list_for_each(base, &output->base.head_list, output_link) { + head = to_drm_head(base); + + possible_crtcs &= drm_head_get_possible_crtcs_mask(head); + + crtc_id = head->inherited_crtc_id; + if (crtc_id > 0 && n < ARRAY_LENGTH(existing_crtc)) + existing_crtc[n++] = drm_crtc_get_index(resources, + crtc_id); + } + + /* Find a crtc that could drive each connector individually at least, + * and prefer existing routings. */ + for (i = 0; i < resources->count_crtcs; i++) { + crtc_id = resources->crtcs[i]; + + /* Could the crtc not drive each connector? */ + if (!(possible_crtcs & (1 << i))) + continue; + + /* Is the crtc already in use? */ + if (drm_output_find_by_crtc(backend, crtc_id)) + continue; + + /* Try to preserve the existing CRTC -> connector routing; + * it makes initialisation faster, and also since we have a + * very dumb picking algorithm, may preserve a better + * choice. */ + for (j = 0; j < n; j++) { + if (existing_crtc[j] == i) + return i; + } + + /* Check if any other head had existing routing to this CRTC. + * If they did, this is not the best CRTC as it might be needed + * for another output we haven't enabled yet. */ + match = false; + wl_list_for_each(base, &backend->compositor->head_list, + compositor_link) { + head = to_drm_head(base); + + if (head->base.output == &output->base) + continue; + + if (weston_head_is_enabled(&head->base)) + continue; + + if (head->inherited_crtc_id == crtc_id) { + match = true; + break; + } + } + if (!match) + best_crtc_index = i; + + fallback_crtc_index = i; + } + + if (best_crtc_index != -1) + return best_crtc_index; + + if (fallback_crtc_index != -1) + return fallback_crtc_index; + + /* Likely possible_crtcs was empty due to asking for clones, + * but since the DRM documentation says the kernel lies, let's + * pick one crtc anyway. Trial and error is the only way to + * be sure if something doesn't work. */ + + /* First pick any existing assignment. */ + for (j = 0; j < n; j++) { + crtc_id = resources->crtcs[existing_crtc[j]]; + if (!drm_output_find_by_crtc(backend, crtc_id)) + return existing_crtc[j]; + } + + /* Otherwise pick any available crtc. */ + for (i = 0; i < resources->count_crtcs; i++) { + crtc_id = resources->crtcs[i]; + + if (!drm_output_find_by_crtc(backend, crtc_id)) + return i; + } + + return -1; +} + +/** Allocate a CRTC for the output + * + * @param output The output with no allocated CRTC. + * @param resources DRM KMS resources. + * @return 0 on success, -1 on failure. + * + * Finds a free CRTC that might drive the attached connectors, reserves the CRTC + * for the output, and loads the CRTC properties. + * + * Populates the cursor and scanout planes. + * + * On failure, the output remains without a CRTC. + */ +static int +drm_output_init_crtc(struct drm_output *output, drmModeRes *resources) +{ + struct drm_backend *b = to_drm_backend(output->base.compositor); + drmModeObjectPropertiesPtr props; + int i; + + assert(output->crtc_id == 0); + + i = drm_output_pick_crtc(output, resources); + if (i < 0) { + weston_log("Output '%s': No available CRTCs.\n", + output->base.name); + return -1; + } + + output->crtc_id = resources->crtcs[i]; + output->pipe = i; + + props = drmModeObjectGetProperties(b->drm.fd, output->crtc_id, + DRM_MODE_OBJECT_CRTC); + if (!props) { + weston_log("failed to get CRTC properties\n"); + goto err_crtc; + } + drm_property_info_populate(b, crtc_props, output->props_crtc, + WDRM_CRTC__COUNT, props); + drmModeFreeObjectProperties(props); + + output->scanout_plane = + drm_output_find_special_plane(b, output, + WDRM_PLANE_TYPE_PRIMARY); + if (!output->scanout_plane) { + weston_log("Failed to find primary plane for output %s\n", + output->base.name); + goto err_crtc; + } + + /* Without universal planes, we can't discover which formats are + * supported by the primary plane; we just hope that the GBM format + * works. */ + if (!b->universal_planes) + output->scanout_plane->formats[0].format = output->gbm_format; + + /* Failing to find a cursor plane is not fatal, as we'll fall back + * to software cursor. */ + output->cursor_plane = + drm_output_find_special_plane(b, output, + WDRM_PLANE_TYPE_CURSOR); + + wl_array_remove_uint32(&b->unused_crtcs, output->crtc_id); + + return 0; + +err_crtc: + output->crtc_id = 0; + output->pipe = 0; + + return -1; +} + +/** Free the CRTC from the output + * + * @param output The output whose CRTC to deallocate. + * + * The CRTC reserved for the given output becomes free to use again. + */ +static void +drm_output_fini_crtc(struct drm_output *output) +{ + struct drm_backend *b = to_drm_backend(output->base.compositor); + uint32_t *unused; + + /* If the compositor is already shutting down, the planes have already + * been destroyed. */ + if (!b->shutting_down) { + if (!b->universal_planes) { + /* Without universal planes, our special planes are + * pseudo-planes allocated at output creation, freed at + * output destruction, and not usable by other outputs. + */ + if (output->cursor_plane) + drm_plane_destroy(output->cursor_plane); + if (output->scanout_plane) + drm_plane_destroy(output->scanout_plane); + } else { + /* With universal planes, the 'special' planes are + * allocated at startup, freed at shutdown, and live on + * the plane list in between. We want the planes to + * continue to exist and be freed up for other outputs. + */ + if (output->cursor_plane) + drm_plane_reset_state(output->cursor_plane); + if (output->scanout_plane) + drm_plane_reset_state(output->scanout_plane); + } + } + + drm_property_info_free(output->props_crtc, WDRM_CRTC__COUNT); + + assert(output->crtc_id != 0); + + unused = wl_array_add(&b->unused_crtcs, sizeof(*unused)); + *unused = output->crtc_id; + + /* Force resetting unused CRTCs */ + b->state_invalid = true; + + output->crtc_id = 0; + output->cursor_plane = NULL; + output->scanout_plane = NULL; +} + +static int +drm_output_enable(struct weston_output *base) +{ + struct drm_output *output = to_drm_output(base); + struct drm_backend *b = to_drm_backend(base->compositor); + drmModeRes *resources; + int ret; + + assert(!output->virtual); + + resources = drmModeGetResources(b->drm.fd); + if (!resources) { + weston_log("drmModeGetResources failed\n"); + return -1; + } + ret = drm_output_init_crtc(output, resources); + drmModeFreeResources(resources); + if (ret < 0) + return -1; + + if (drm_output_init_gamma_size(output) < 0) + goto err; + + if (b->pageflip_timeout) + drm_output_pageflip_timer_create(output); + + if (b->use_pixman) { + if (drm_output_init_pixman(output, b) < 0) { + weston_log("Failed to init output pixman state\n"); + goto err; + } + } else if (drm_output_init_egl(output, b) < 0) { + weston_log("Failed to init output gl state\n"); + goto err; + } + + drm_output_init_backlight(output); + + output->base.start_repaint_loop = drm_output_start_repaint_loop; + output->base.repaint = drm_output_repaint; + output->base.assign_planes = drm_assign_planes; + output->base.set_dpms = drm_set_dpms; + output->base.switch_mode = drm_output_switch_mode; + output->base.set_gamma = drm_output_set_gamma; + + if (output->cursor_plane) + weston_compositor_stack_plane(b->compositor, + &output->cursor_plane->base, + NULL); + else + b->cursors_are_broken = true; + + weston_compositor_stack_plane(b->compositor, + &output->scanout_plane->base, + &b->compositor->primary_plane); + + weston_log("Output %s (crtc %d) video modes:\n", + output->base.name, output->crtc_id); + drm_output_print_modes(output); + + return 0; + +err: + drm_output_fini_crtc(output); + + return -1; +} + +static void +drm_output_deinit(struct weston_output *base) +{ + struct drm_output *output = to_drm_output(base); + struct drm_backend *b = to_drm_backend(base->compositor); + + if (b->use_pixman) + drm_output_fini_pixman(output); + else + drm_output_fini_egl(output); + + /* Since our planes are no longer in use anywhere, remove their base + * weston_plane's link from the plane stacking list, unless we're + * shutting down, in which case the plane has already been + * destroyed. */ + if (!b->shutting_down) { + wl_list_remove(&output->scanout_plane->base.link); + wl_list_init(&output->scanout_plane->base.link); + + if (output->cursor_plane) { + wl_list_remove(&output->cursor_plane->base.link); + wl_list_init(&output->cursor_plane->base.link); + /* Turn off hardware cursor */ + drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); + } + } + + drm_output_fini_crtc(output); +} + +static void +drm_head_destroy(struct drm_head *head); + +static void +drm_output_destroy(struct weston_output *base) +{ + struct drm_output *output = to_drm_output(base); + struct drm_backend *b = to_drm_backend(base->compositor); + + assert(!output->virtual); + + if (output->page_flip_pending || output->atomic_complete_pending) { + output->destroy_pending = true; + weston_log("destroy output while page flip pending\n"); + return; + } + + if (output->base.enabled) + drm_output_deinit(&output->base); + + drm_mode_list_destroy(b, &output->base.mode_list); + + if (output->pageflip_timer) + wl_event_source_remove(output->pageflip_timer); + + weston_output_release(&output->base); + + assert(!output->state_last); + drm_output_state_free(output->state_cur); + + free(output); +} + +static int +drm_output_disable(struct weston_output *base) +{ + struct drm_output *output = to_drm_output(base); + + assert(!output->virtual); + + if (output->page_flip_pending || output->atomic_complete_pending) { + output->disable_pending = true; + return -1; + } + + weston_log("Disabling output %s\n", output->base.name); + + if (output->base.enabled) + drm_output_deinit(&output->base); + + output->disable_pending = false; + + return 0; +} + +/** + * Update the list of unused connectors and CRTCs + * + * This keeps the unused_crtc arrays up to date. + * + * @param b Weston backend structure + * @param resources DRM resources for this device + */ +static void +drm_backend_update_unused_outputs(struct drm_backend *b, drmModeRes *resources) +{ + int i; + + wl_array_release(&b->unused_crtcs); + wl_array_init(&b->unused_crtcs); + + for (i = 0; i < resources->count_crtcs; i++) { + struct drm_output *output; + uint32_t *crtc_id; + + output = drm_output_find_by_crtc(b, resources->crtcs[i]); + if (output && output->base.enabled) + continue; + + crtc_id = wl_array_add(&b->unused_crtcs, sizeof(*crtc_id)); + *crtc_id = resources->crtcs[i]; + } +} + +/* + * This function converts the protection status from drm values to + * weston_hdcp_protection status. The drm values as read from the connector + * properties "Content Protection" and "HDCP Content Type" need to be converted + * to appropriate weston values, that can be sent to a client application. + */ +static int +get_weston_protection_from_drm(enum wdrm_content_protection_state protection, + enum wdrm_hdcp_content_type type, + enum weston_hdcp_protection *weston_protection) + +{ + if (protection >= WDRM_CONTENT_PROTECTION__COUNT) + return -1; + if (protection == WDRM_CONTENT_PROTECTION_DESIRED || + protection == WDRM_CONTENT_PROTECTION_UNDESIRED) { + *weston_protection = WESTON_HDCP_DISABLE; + return 0; + } + if (type >= WDRM_HDCP_CONTENT_TYPE__COUNT) + return -1; + if (type == WDRM_HDCP_CONTENT_TYPE0) { + *weston_protection = WESTON_HDCP_ENABLE_TYPE_0; + return 0; + } + if (type == WDRM_HDCP_CONTENT_TYPE1) { + *weston_protection = WESTON_HDCP_ENABLE_TYPE_1; + return 0; + } + return -1; +} + +/** + * Get current content-protection status for a given head. + * + * @param head drm_head, whose protection is to be retrieved + * @param props drm property object of the connector, related to the head + * @return protection status in case of success, -1 otherwise + */ +static enum weston_hdcp_protection +drm_head_get_current_protection(struct drm_head *head, + drmModeObjectProperties *props) +{ + struct drm_property_info *info; + enum wdrm_content_protection_state protection; + enum wdrm_hdcp_content_type type; + enum weston_hdcp_protection weston_hdcp = WESTON_HDCP_DISABLE; + + info = &head->props_conn[WDRM_CONNECTOR_CONTENT_PROTECTION]; + protection = drm_property_get_value(info, props, + WDRM_CONTENT_PROTECTION__COUNT); + + if (protection == WDRM_CONTENT_PROTECTION__COUNT) + return WESTON_HDCP_DISABLE; + + info = &head->props_conn[WDRM_CONNECTOR_HDCP_CONTENT_TYPE]; + type = drm_property_get_value(info, props, + WDRM_HDCP_CONTENT_TYPE__COUNT); + + /* + * In case of platforms supporting HDCP1.4, only property + * 'Content Protection' is exposed and not the 'HDCP Content Type' + * for such cases HDCP Type 0 should be considered as the content-type. + */ + + if (type == WDRM_HDCP_CONTENT_TYPE__COUNT) + type = WDRM_HDCP_CONTENT_TYPE0; + + if (get_weston_protection_from_drm(protection, type, + &weston_hdcp) == -1) { + weston_log("Invalid drm protection:%d type:%d, for head:%s connector-id:%d\n", + protection, type, head->base.name, + head->connector_id); + return WESTON_HDCP_DISABLE; + } + + return weston_hdcp; +} + +/** Replace connector data and monitor information + * + * @param head The head to update. + * @param connector The connector data to be owned by the head, must match + * the head's connector ID. + * @return 0 on success, -1 on failure. + * + * Takes ownership of @c connector on success, not on failure. + * + * May schedule a heads changed call. + */ +static int +drm_head_assign_connector_info(struct drm_head *head, + drmModeConnector *connector) +{ + drmModeObjectProperties *props; + + assert(connector); + assert(head->connector_id == connector->connector_id); + + props = drmModeObjectGetProperties(head->backend->drm.fd, + head->connector_id, + DRM_MODE_OBJECT_CONNECTOR); + if (!props) { + weston_log("Error: failed to get connector '%s' properties\n", + head->base.name); + return -1; + } + + if (head->connector) + drmModeFreeConnector(head->connector); + head->connector = connector; + + drm_property_info_populate(head->backend, connector_props, + head->props_conn, + WDRM_CONNECTOR__COUNT, props); + update_head_from_connector(head, props); + + weston_head_set_content_protection_status(&head->base, + drm_head_get_current_protection(head, props)); + drmModeFreeObjectProperties(props); + + return 0; +} + +static void +drm_head_log_info(struct drm_head *head, const char *msg) +{ + if (head->base.connected) { + weston_log("DRM: head '%s' %s, connector %d is connected, " + "EDID make '%s', model '%s', serial '%s'\n", + head->base.name, msg, head->connector_id, + head->base.make, head->base.model, + head->base.serial_number ?: ""); + } else { + weston_log("DRM: head '%s' %s, connector %d is disconnected.\n", + head->base.name, msg, head->connector_id); + } +} + +/** Update connector and monitor information + * + * @param head The head to update. + * + * Re-reads the DRM property lists for the connector and updates monitor + * information and connection status. This may schedule a heads changed call + * to the user. + */ +static void +drm_head_update_info(struct drm_head *head) +{ + drmModeConnector *connector; + + connector = drmModeGetConnector(head->backend->drm.fd, + head->connector_id); + if (!connector) { + weston_log("DRM: getting connector info for '%s' failed.\n", + head->base.name); + return; + } + + if (drm_head_assign_connector_info(head, connector) < 0) + drmModeFreeConnector(connector); + + if (head->base.device_changed) + drm_head_log_info(head, "updated"); +} + +/** + * Create a Weston head for a connector + * + * Given a DRM connector, create a matching drm_head structure and add it + * to Weston's head list. + * + * @param backend Weston backend structure + * @param connector_id DRM connector ID for the head + * @param drm_device udev device pointer + * @returns The new head, or NULL on failure. + */ +static struct drm_head * +drm_head_create(struct drm_backend *backend, uint32_t connector_id, + struct udev_device *drm_device) +{ + struct drm_head *head; + drmModeConnector *connector; + char *name; + + head = zalloc(sizeof *head); + if (!head) + return NULL; + + connector = drmModeGetConnector(backend->drm.fd, connector_id); + if (!connector) + goto err_alloc; + + name = make_connector_name(connector); + if (!name) + goto err_alloc; + + weston_head_init(&head->base, name); + free(name); + + head->connector_id = connector_id; + head->backend = backend; + + head->backlight = backlight_init(drm_device, connector->connector_type); + + if (drm_head_assign_connector_info(head, connector) < 0) + goto err_init; + + if (head->connector->connector_type == DRM_MODE_CONNECTOR_LVDS || + head->connector->connector_type == DRM_MODE_CONNECTOR_eDP) + weston_head_set_internal(&head->base); + + if (drm_head_read_current_setup(head, backend) < 0) { + weston_log("Failed to retrieve current mode from connector %d.\n", + head->connector_id); + /* Not fatal. */ + } + + weston_compositor_add_head(backend->compositor, &head->base); + drm_head_log_info(head, "found"); + + return head; + +err_init: + weston_head_release(&head->base); + +err_alloc: + if (connector) + drmModeFreeConnector(connector); + + free(head); + + return NULL; +} + +static void +drm_head_destroy(struct drm_head *head) +{ + weston_head_release(&head->base); + + drm_property_info_free(head->props_conn, WDRM_CONNECTOR__COUNT); + drmModeFreeConnector(head->connector); + + if (head->backlight) + backlight_destroy(head->backlight); + + free(head); +} + +/** + * Create a Weston output structure + * + * Create an "empty" drm_output. This is the implementation of + * weston_backend::create_output. + * + * Creating an output is usually followed by drm_output_attach_head() + * and drm_output_enable() to make use of it. + * + * @param compositor The compositor instance. + * @param name Name for the new output. + * @returns The output, or NULL on failure. + */ +static struct weston_output * +drm_output_create(struct weston_compositor *compositor, const char *name) +{ + struct drm_backend *b = to_drm_backend(compositor); + struct drm_output *output; + + output = zalloc(sizeof *output); + if (output == NULL) + return NULL; + + output->backend = b; +#ifdef BUILD_DRM_GBM + output->gbm_bo_flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING; +#endif + + weston_output_init(&output->base, compositor, name); + + output->base.enable = drm_output_enable; + output->base.destroy = drm_output_destroy; + output->base.disable = drm_output_disable; + output->base.attach_head = drm_output_attach_head; + output->base.detach_head = drm_output_detach_head; + + output->destroy_pending = false; + output->disable_pending = false; + + output->state_cur = drm_output_state_alloc(output, NULL); + + weston_compositor_add_pending_output(&output->base, b->compositor); + + return &output->base; +} + +static int +drm_backend_create_heads(struct drm_backend *b, struct udev_device *drm_device) +{ + struct drm_head *head; + drmModeRes *resources; + int i; + + resources = drmModeGetResources(b->drm.fd); + if (!resources) { + weston_log("drmModeGetResources failed\n"); + return -1; + } + + b->min_width = resources->min_width; + b->max_width = resources->max_width; + b->min_height = resources->min_height; + b->max_height = resources->max_height; + + for (i = 0; i < resources->count_connectors; i++) { + uint32_t connector_id = resources->connectors[i]; + + head = drm_head_create(b, connector_id, drm_device); + if (!head) { + weston_log("DRM: failed to create head for connector %d.\n", + connector_id); + } + } + + drm_backend_update_unused_outputs(b, resources); + + drmModeFreeResources(resources); + + return 0; +} + +static void +drm_backend_update_heads(struct drm_backend *b, struct udev_device *drm_device) +{ + drmModeRes *resources; + struct weston_head *base, *next; + struct drm_head *head; + int i; + + resources = drmModeGetResources(b->drm.fd); + if (!resources) { + weston_log("drmModeGetResources failed\n"); + return; + } + + /* collect new connectors that have appeared, e.g. MST */ + for (i = 0; i < resources->count_connectors; i++) { + uint32_t connector_id = resources->connectors[i]; + + head = drm_head_find_by_connector(b, connector_id); + if (head) { + drm_head_update_info(head); + } else { + head = drm_head_create(b, connector_id, drm_device); + if (!head) + weston_log("DRM: failed to create head for hot-added connector %d.\n", + connector_id); + } + } + + /* Remove connectors that have disappeared. */ + wl_list_for_each_safe(base, next, + &b->compositor->head_list, compositor_link) { + bool removed = true; + + head = to_drm_head(base); + + for (i = 0; i < resources->count_connectors; i++) { + if (resources->connectors[i] == head->connector_id) { + removed = false; + break; + } + } + + if (!removed) + continue; + + weston_log("DRM: head '%s' (connector %d) disappeared.\n", + head->base.name, head->connector_id); + drm_head_destroy(head); + } + + drm_backend_update_unused_outputs(b, resources); + + drmModeFreeResources(resources); +} + +static enum wdrm_connector_property +drm_head_find_property_by_id(struct drm_head *head, uint32_t property_id) +{ + int i; + enum wdrm_connector_property prop = WDRM_CONNECTOR__COUNT; + + if (!head || !property_id) + return WDRM_CONNECTOR__COUNT; + + for (i = 0; i < WDRM_CONNECTOR__COUNT; i++) + if (head->props_conn[i].prop_id == property_id) { + prop = (enum wdrm_connector_property) i; + break; + } + return prop; +} + +static void +drm_backend_update_conn_props(struct drm_backend *b, + uint32_t connector_id, + uint32_t property_id) +{ + struct drm_head *head; + enum wdrm_connector_property conn_prop; + drmModeObjectProperties *props; + + head = drm_head_find_by_connector(b, connector_id); + if (!head) { + weston_log("DRM: failed to find head for connector id: %d.\n", + connector_id); + return; + } + + conn_prop = drm_head_find_property_by_id(head, property_id); + if (conn_prop >= WDRM_CONNECTOR__COUNT) + return; + + props = drmModeObjectGetProperties(b->drm.fd, + connector_id, + DRM_MODE_OBJECT_CONNECTOR); + if (!props) { + weston_log("Error: failed to get connector '%s' properties\n", + head->base.name); + return; + } + if (conn_prop == WDRM_CONNECTOR_CONTENT_PROTECTION) { + weston_head_set_content_protection_status(&head->base, + drm_head_get_current_protection(head, props)); + } + drmModeFreeObjectProperties(props); +} + +static int +udev_event_is_hotplug(struct drm_backend *b, struct udev_device *device) +{ + const char *sysnum; + const char *val; + + sysnum = udev_device_get_sysnum(device); + if (!sysnum || atoi(sysnum) != b->drm.id) + return 0; + + val = udev_device_get_property_value(device, "HOTPLUG"); + if (!val) + return 0; + + return strcmp(val, "1") == 0; +} + +static int +udev_event_is_conn_prop_change(struct drm_backend *b, + struct udev_device *device, + uint32_t *connector_id, + uint32_t *property_id) + +{ + const char *val; + int id; + + val = udev_device_get_property_value(device, "CONNECTOR"); + if (!val || !safe_strtoint(val, &id)) + return 0; + else + *connector_id = id; + + val = udev_device_get_property_value(device, "PROPERTY"); + if (!val || !safe_strtoint(val, &id)) + return 0; + else + *property_id = id; + + return 1; +} + +static int +udev_drm_event(int fd, uint32_t mask, void *data) +{ + struct drm_backend *b = data; + struct udev_device *event; + uint32_t conn_id, prop_id; + + event = udev_monitor_receive_device(b->udev_monitor); + + if (udev_event_is_hotplug(b, event)) { + if (udev_event_is_conn_prop_change(b, event, &conn_id, &prop_id)) + drm_backend_update_conn_props(b, conn_id, prop_id); + else + drm_backend_update_heads(b, event); + } + + udev_device_unref(event); + + return 1; +} + +static void +drm_destroy(struct weston_compositor *ec) +{ + struct drm_backend *b = to_drm_backend(ec); + struct weston_head *base, *next; + + udev_input_destroy(&b->input); + + wl_event_source_remove(b->udev_drm_source); + wl_event_source_remove(b->drm_source); + + b->shutting_down = true; + + destroy_sprites(b); + +// OHOS remove logger +// weston_log_scope_destroy(b->debug); + b->debug = NULL; + weston_compositor_shutdown(ec); + + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) + drm_head_destroy(to_drm_head(base)); + +#ifdef BUILD_DRM_GBM + if (b->gbm) + gbm_device_destroy(b->gbm); +#endif + + udev_monitor_unref(b->udev_monitor); + udev_unref(b->udev); + + weston_launcher_destroy(ec->launcher); + + wl_array_release(&b->unused_crtcs); + + DeInitWaylandDrmAuthService(); // OHOS drm auth + close(b->drm.fd); + free(b->drm.filename); + free(b); +} + +static void +session_notify(struct wl_listener *listener, void *data) +{ + struct weston_compositor *compositor = data; + struct drm_backend *b = to_drm_backend(compositor); + struct drm_plane *plane; + struct drm_output *output; + + if (compositor->session_active) { + weston_log("activating session\n"); + weston_compositor_wake(compositor); + weston_compositor_damage_all(compositor); + b->state_invalid = true; + udev_input_enable(&b->input); + } else { + weston_log("deactivating session\n"); + udev_input_disable(&b->input); + + 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 attempts at repainting. When we switch + * back, we schedule a repaint, which will process + * pending frame callbacks. */ + + wl_list_for_each(output, &compositor->output_list, base.link) { + output->base.repaint_needed = false; + if (output->cursor_plane) + drmModeSetCursor(b->drm.fd, output->crtc_id, + 0, 0, 0); + } + + output = container_of(compositor->output_list.next, + struct drm_output, base.link); + + wl_list_for_each(plane, &b->plane_list, link) { + if (plane->type != WDRM_PLANE_TYPE_OVERLAY) + continue; + + drmModeSetPlane(b->drm.fd, + plane->plane_id, + output->crtc_id, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0); + } + } +} + + +/** + * Handle KMS GPU being added/removed + * + * If the device being added/removed is the KMS device, we activate/deactivate + * the compositor session. + * + * @param compositor The compositor instance. + * @param device The device being added/removed. + * @param added Whether the device is being added (or removed) + */ +static void +drm_device_changed(struct weston_compositor *compositor, + dev_t device, bool added) +{ + struct drm_backend *b = to_drm_backend(compositor); + + if (b->drm.fd < 0 || b->drm.devnum != device || + compositor->session_active == added) + return; + + compositor->session_active = added; + wl_signal_emit(&compositor->session_signal, compositor); +} + +/** + * Determines whether or not a device is capable of modesetting. If successful, + * sets b->drm.fd and b->drm.filename to the opened device. + */ +static bool +drm_device_is_kms(struct drm_backend *b, struct udev_device *device) +{ + const char *filename = udev_device_get_devnode(device); + const char *sysnum = udev_device_get_sysnum(device); + dev_t devnum = udev_device_get_devnum(device); + drmModeRes *res; + int id = -1, fd; + + if (!filename) + return false; + + fd = weston_launcher_open(b->compositor->launcher, filename, O_RDWR | O_CLOEXEC); // OHOS + if (fd < 0) + return false; + + res = drmModeGetResources(fd); + if (!res) + goto out_fd; + + if (res->count_crtcs <= 0 || res->count_connectors <= 0 || + res->count_encoders <= 0) + goto out_res; + + if (sysnum) + id = atoi(sysnum); + if (!sysnum || id < 0) { + weston_log("couldn't get sysnum for device %s\n", filename); + goto out_res; + } + + /* We can be called successfully on multiple devices; if we have, + * clean up old entries. */ + if (b->drm.fd >= 0) + weston_launcher_close(b->compositor->launcher, b->drm.fd); + free(b->drm.filename); + + b->drm.fd = fd; + b->drm.id = id; + b->drm.filename = strdup(filename); + b->drm.devnum = devnum; + + drmModeFreeResources(res); + + return true; + +out_res: + drmModeFreeResources(res); +out_fd: + weston_launcher_close(b->compositor->launcher, fd); + return false; +} + +/* + * 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. + * Devices are also vetted to make sure they are are capable of modesetting, + * rather than pure render nodes (GPU with no display), or pure + * memory-allocation devices (VGEM). + */ +static struct udev_device* +find_primary_gpu(struct drm_backend *b, 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(b->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)) { + bool is_boot_vga = false; + + path = udev_list_entry_get_name(entry); + device = udev_device_new_from_syspath(b->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")) + is_boot_vga = true; + } + + /* If we already have a modesetting-capable device, and this + * device isn't our boot-VGA device, we aren't going to use + * it. */ + if (!is_boot_vga && drm_device) { + udev_device_unref(device); + continue; + } + + /* Make sure this device is actually capable of modesetting; + * if this call succeeds, b->drm.{fd,filename} will be set, + * and any old values freed. */ + if (!drm_device_is_kms(b, device)) { + udev_device_unref(device); + continue; + } + + /* There can only be one boot_vga device, and we try to use it + * at all costs. */ + if (is_boot_vga) { + if (drm_device) + udev_device_unref(drm_device); + drm_device = device; + break; + } + + /* Per the (!is_boot_vga && drm_device) test above, we only + * trump existing saved devices with boot-VGA devices, so if + * we end up here, this must be the first device we've seen. */ + assert(!drm_device); + drm_device = device; + } + + /* If we're returning a device to use, we must have an open FD for + * it. */ + assert(!!drm_device == (b->drm.fd >= 0)); + + udev_enumerate_unref(e); + return drm_device; +} + +static struct udev_device * +open_specific_drm_device(struct drm_backend *b, const char *name) +{ + struct udev_device *device; + + device = udev_device_new_from_subsystem_sysname(b->udev, "drm", name); + if (!device) { + weston_log("ERROR: could not open DRM device '%s'\n", name); + return NULL; + } + + if (!drm_device_is_kms(b, device)) { + udev_device_unref(device); + weston_log("ERROR: DRM device '%s' is not a KMS device.\n", name); + return NULL; + } + + /* If we're returning a device to use, we must have an open FD for + * it. */ + assert(b->drm.fd >= 0); + + return device; +} + +static void +planes_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) +{ + struct drm_backend *b = data; + + switch (key) { + case KEY_C: + b->cursors_are_broken ^= true; + break; + case KEY_V: + /* We don't support overlay-plane usage with legacy KMS. */ + if (b->atomic_modeset) + b->sprites_are_broken ^= true; + break; + default: + break; + } +} + +#ifdef BUILD_VAAPI_RECORDER +static void +recorder_destroy(struct drm_output *output) +{ + vaapi_recorder_destroy(output->recorder); + output->recorder = NULL; + + weston_output_disable_planes_decr(&output->base); + + wl_list_remove(&output->recorder_frame_listener.link); + weston_log("[libva recorder] done\n"); +} + +static void +recorder_frame_notify(struct wl_listener *listener, void *data) +{ + struct drm_output *output; + struct drm_backend *b; + int fd, ret; + + output = container_of(listener, struct drm_output, + recorder_frame_listener); + b = to_drm_backend(output->base.compositor); + + if (!output->recorder) + return; + + ret = drmPrimeHandleToFD(b->drm.fd, + output->scanout_plane->state_cur->fb->handles[0], + DRM_CLOEXEC, &fd); + if (ret) { + weston_log("[libva recorder] " + "failed to create prime fd for front buffer\n"); + return; + } + + ret = vaapi_recorder_frame(output->recorder, fd, + output->scanout_plane->state_cur->fb->strides[0]); + if (ret < 0) { + weston_log("[libva recorder] aborted: %s\n", strerror(errno)); + recorder_destroy(output); + } +} + +static void * +create_recorder(struct drm_backend *b, int width, int height, + const char *filename) +{ + int fd; + drm_magic_t magic; + + fd = open(b->drm.filename, O_RDWR | O_CLOEXEC); + if (fd < 0) + return NULL; + + drmGetMagic(fd, &magic); + drmAuthMagic(b->drm.fd, magic); + + return vaapi_recorder_create(fd, width, height, filename); +} + +static void +recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) +{ + struct drm_backend *b = data; + struct drm_output *output; + int width, height; + + output = container_of(b->compositor->output_list.next, + struct drm_output, base.link); + + if (!output->recorder) { + if (output->gbm_format != DRM_FORMAT_XRGB8888) { + weston_log("failed to start vaapi recorder: " + "output format not supported\n"); + return; + } + + width = output->base.current_mode->width; + height = output->base.current_mode->height; + + output->recorder = + create_recorder(b, width, height, "capture.h264"); + if (!output->recorder) { + weston_log("failed to create vaapi recorder\n"); + return; + } + + weston_output_disable_planes_incr(&output->base); + + 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 { + recorder_destroy(output); + } +} +#else +static void +recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) +{ + weston_log("Compiled without libva support\n"); +} +#endif + + +static const struct weston_drm_output_api api = { + drm_output_set_mode, + drm_output_set_gbm_format, + drm_output_set_seat, +}; + +static struct drm_backend * +drm_backend_create(struct weston_compositor *compositor, + struct weston_drm_backend_config *config) +{ + struct drm_backend *b; + struct udev_device *drm_device; + struct wl_event_loop *loop; + const char *seat_id = default_seat; + const char *session_seat; + int ret; + + session_seat = getenv("XDG_SEAT"); + if (session_seat) + seat_id = session_seat; + + if (config->seat_id) + seat_id = config->seat_id; + + weston_log("initializing drm backend\n"); + + b = zalloc(sizeof *b); + if (b == NULL) + return NULL; + + b->state_invalid = true; + b->drm.fd = -1; + wl_array_init(&b->unused_crtcs); + + b->compositor = compositor; + b->use_pixman = config->use_pixman; + b->pageflip_timeout = config->pageflip_timeout; + b->use_pixman_shadow = config->use_pixman_shadow; +// OHOS remove logger +// b->debug = weston_compositor_add_log_scope(compositor, "drm-backend", +// "Debug messages from DRM/KMS backend\n", +// NULL, NULL, NULL); + + compositor->backend = &b->base; + compositor->require_input = !config->continue_without_input; + + if (parse_gbm_format(config->gbm_format, DRM_FORMAT_XRGB8888, &b->gbm_format) < 0) + goto err_compositor; + + /* Check if we run drm-backend using weston-launch */ + compositor->launcher = weston_launcher_connect(compositor, config->tty, + seat_id, true); + if (compositor->launcher == NULL) { + weston_log("fatal: drm backend should be run using " + "weston-launch binary, or your system should " + "provide the logind D-Bus API.\n"); + goto err_compositor; + } + + b->udev = udev_new(); + if (b->udev == NULL) { + weston_log("failed to initialize udev context\n"); + goto err_launcher; + } + + b->session_listener.notify = session_notify; + wl_signal_add(&compositor->session_signal, &b->session_listener); + + if (config->specific_device) + drm_device = open_specific_drm_device(b, config->specific_device); + else + drm_device = find_primary_gpu(b, seat_id); + if (drm_device == NULL) { + weston_log("no drm device found\n"); + goto err_udev; + } + // OHOS drm auth: init the wayland drm auth service + InitWaylandDrmAuthService(compositor->wl_display, b->drm.fd); + + if (init_kms_caps(b) < 0) { + weston_log("failed to initialize kms\n"); + goto err_udev_dev; + } + + if (b->use_pixman) { + if (init_pixman(b) < 0) { + weston_log("failed to initialize pixman renderer\n"); + goto err_udev_dev; + } + } else { + if (init_egl(b) < 0) { + weston_log("failed to initialize egl\n"); + goto err_udev_dev; + } + } + + b->base.destroy = drm_destroy; + b->base.repaint_begin = drm_repaint_begin; + b->base.repaint_flush = drm_repaint_flush; + b->base.repaint_cancel = drm_repaint_cancel; + b->base.create_output = drm_output_create; + b->base.device_changed = drm_device_changed; + b->base.can_scanout_dmabuf = drm_can_scanout_dmabuf; + + weston_setup_vt_switch_bindings(compositor); + + wl_list_init(&b->plane_list); + create_sprites(b); + + if (udev_input_init(&b->input, + compositor, b->udev, seat_id, + config->configure_device) < 0) { + weston_log("failed to create input devices\n"); +// OHOS build +// goto err_sprite; + } + + if (drm_backend_create_heads(b, drm_device) < 0) { + weston_log("Failed to create heads for %s\n", b->drm.filename); + goto err_udev_input; + } + + /* 'compute' faked zpos values in case HW doesn't expose any */ + drm_backend_create_faked_zpos(b); + + /* A this point we have some idea of whether or not we have a working + * cursor plane. */ + if (!b->cursors_are_broken) + compositor->capabilities |= WESTON_CAP_CURSOR_PLANE; + + loop = wl_display_get_event_loop(compositor->wl_display); + b->drm_source = + wl_event_loop_add_fd(loop, b->drm.fd, + WL_EVENT_READABLE, on_drm_input, b); + + b->udev_monitor = udev_monitor_new_from_netlink(b->udev, "udev"); + if (b->udev_monitor == NULL) { + weston_log("failed to initialize udev monitor\n"); + goto err_drm_source; + } + udev_monitor_filter_add_match_subsystem_devtype(b->udev_monitor, + "drm", NULL); + b->udev_drm_source = + wl_event_loop_add_fd(loop, + udev_monitor_get_fd(b->udev_monitor), + WL_EVENT_READABLE, udev_drm_event, b); + + if (udev_monitor_enable_receiving(b->udev_monitor) < 0) { + weston_log("failed to enable udev-monitor receiving\n"); + goto err_udev_monitor; + } + + udev_device_unref(drm_device); + +// OHOS remove debugger + // weston_compositor_add_debug_binding(compositor, KEY_O, + // planes_binding, b); + // weston_compositor_add_debug_binding(compositor, KEY_C, + // planes_binding, b); + // weston_compositor_add_debug_binding(compositor, KEY_V, + // planes_binding, b); + // weston_compositor_add_debug_binding(compositor, KEY_Q, + // recorder_binding, b); + // weston_compositor_add_debug_binding(compositor, KEY_W, + // renderer_switch_binding, b); + + + if (compositor->renderer->import_dmabuf) { + if (linux_dmabuf_setup(compositor) < 0) + weston_log("Error: initializing dmabuf " + "support failed.\n"); + if (weston_direct_display_setup(compositor) < 0) + weston_log("Error: initializing direct-display " + "support failed.\n"); + } + + if (compositor->capabilities & WESTON_CAP_EXPLICIT_SYNC) { + if (linux_explicit_synchronization_setup(compositor) < 0) + weston_log("Error: initializing explicit " + " synchronization support failed.\n"); + } + + if (b->atomic_modeset) + if (weston_compositor_enable_content_protection(compositor) < 0) + weston_log("Error: initializing content-protection " + "support failed.\n"); + + ret = weston_plugin_api_register(compositor, WESTON_DRM_OUTPUT_API_NAME, + &api, sizeof(api)); + + if (ret < 0) { + weston_log("Failed to register output API.\n"); + goto err_udev_monitor; + } + + ret = drm_backend_init_virtual_output_api(compositor); + if (ret < 0) { + weston_log("Failed to register virtual output API.\n"); + goto err_udev_monitor; + } + + return b; + +err_udev_monitor: + wl_event_source_remove(b->udev_drm_source); + udev_monitor_unref(b->udev_monitor); +err_drm_source: + wl_event_source_remove(b->drm_source); +err_udev_input: + udev_input_destroy(&b->input); +// OHOS build +// err_sprite: +#ifdef BUILD_DRM_GBM + if (b->gbm) + gbm_device_destroy(b->gbm); +#endif + destroy_sprites(b); +err_udev_dev: + udev_device_unref(drm_device); +err_udev: + udev_unref(b->udev); +err_launcher: + weston_launcher_destroy(compositor->launcher); +err_compositor: + weston_compositor_shutdown(compositor); + free(b); + return NULL; +} + +static void +config_init_to_defaults(struct weston_drm_backend_config *config) +{ + // OHOS + // config->use_pixman_shadow = true; + config->use_pixman_shadow = false; +} + +WL_EXPORT int +weston_backend_init(struct weston_compositor *compositor, + struct weston_backend_config *config_base) +{ + struct drm_backend *b; + struct weston_drm_backend_config config = {{ 0, }}; + + if (config_base == NULL || + config_base->struct_version != WESTON_DRM_BACKEND_CONFIG_VERSION || + config_base->struct_size > sizeof(struct weston_drm_backend_config)) { + weston_log("drm backend config structure is invalid\n"); + return -1; + } + + config_init_to_defaults(&config); + memcpy(&config, config_base, config_base->struct_size); + + b = drm_backend_create(compositor, &config); + if (b == NULL) + return -1; + + return 0; +} diff --git a/libweston/backend-drm/fb.c b/libweston/backend-drm/fb.c new file mode 100644 index 0000000..e7349c4 --- /dev/null +++ b/libweston/backend-drm/fb.c @@ -0,0 +1,582 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2017, 2018 Collabora, Ltd. + * Copyright © 2017, 2018 General Electric Company + * Copyright (c) 2018 DisplayLink (UK) Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include "shared/helpers.h" +#include "drm-internal.h" +#include "linux-dmabuf.h" + +static void +drm_fb_destroy(struct drm_fb *fb) +{ + if (fb->fb_id != 0) + drmModeRmFB(fb->fd, fb->fb_id); + weston_buffer_reference(&fb->buffer_ref, NULL); + weston_buffer_release_reference(&fb->buffer_release_ref, NULL); + free(fb); +} + +static void +drm_fb_destroy_dumb(struct drm_fb *fb) +{ + struct drm_mode_destroy_dumb destroy_arg; + + assert(fb->type == BUFFER_PIXMAN_DUMB); + + if (fb->map && fb->size > 0) + munmap(fb->map, fb->size); + + memset(&destroy_arg, 0, sizeof(destroy_arg)); + destroy_arg.handle = fb->handles[0]; + drmIoctl(fb->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg); + + drm_fb_destroy(fb); +} + +static int +drm_fb_addfb(struct drm_backend *b, struct drm_fb *fb) +{ + int ret = -EINVAL; + uint64_t mods[4] = { }; + size_t i; + + /* If we have a modifier set, we must only use the WithModifiers + * entrypoint; we cannot import it through legacy ioctls. */ + if (b->fb_modifiers && fb->modifier != DRM_FORMAT_MOD_INVALID) { + /* KMS demands that if a modifier is set, it must be the same + * for all planes. */ + for (i = 0; i < ARRAY_LENGTH(mods) && fb->handles[i]; i++) + mods[i] = fb->modifier; + ret = drmModeAddFB2WithModifiers(fb->fd, fb->width, fb->height, + fb->format->format, + fb->handles, fb->strides, + fb->offsets, mods, &fb->fb_id, + DRM_MODE_FB_MODIFIERS); + return ret; + } + + ret = drmModeAddFB2(fb->fd, fb->width, fb->height, fb->format->format, + fb->handles, fb->strides, fb->offsets, &fb->fb_id, + 0); + if (ret == 0) + return 0; + + /* Legacy AddFB can't always infer the format from depth/bpp alone, so + * check if our format is one of the lucky ones. */ + if (!fb->format->depth || !fb->format->bpp) + return ret; + + /* Cannot fall back to AddFB for multi-planar formats either. */ + if (fb->handles[1] || fb->handles[2] || fb->handles[3]) + return ret; + + ret = drmModeAddFB(fb->fd, fb->width, fb->height, + fb->format->depth, fb->format->bpp, + fb->strides[0], fb->handles[0], &fb->fb_id); + return ret; +} + +struct drm_fb * +drm_fb_create_dumb(struct drm_backend *b, int width, int height, + uint32_t format) +{ + 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; + fb->refcnt = 1; + + fb->format = pixel_format_get_info(format); + if (!fb->format) { + weston_log("failed to look up format 0x%lx\n", + (unsigned long) format); + goto err_fb; + } + + if (!fb->format->depth || !fb->format->bpp) { + weston_log("format 0x%lx is not compatible with dumb buffers\n", + (unsigned long) format); + goto err_fb; + } + + memset(&create_arg, 0, sizeof create_arg); + create_arg.bpp = fb->format->bpp; + create_arg.width = width; + create_arg.height = height; + + ret = drmIoctl(b->drm.fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_arg); + if (ret) + goto err_fb; + + fb->type = BUFFER_PIXMAN_DUMB; + fb->modifier = DRM_FORMAT_MOD_INVALID; + fb->handles[0] = create_arg.handle; + fb->strides[0] = create_arg.pitch; + fb->num_planes = 1; + fb->size = create_arg.size; + fb->width = width; + fb->height = height; + fb->fd = b->drm.fd; + + if (drm_fb_addfb(b, fb) != 0) { + weston_log("failed to create kms fb: %s\n", strerror(errno)); + goto err_bo; + } + + memset(&map_arg, 0, sizeof map_arg); + map_arg.handle = fb->handles[0]; + ret = drmIoctl(fb->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_arg); + if (ret) + goto err_add_fb; + + fb->map = mmap(NULL, fb->size, PROT_WRITE, + MAP_SHARED, b->drm.fd, map_arg.offset); + if (fb->map == MAP_FAILED) + goto err_add_fb; + + return fb; + +err_add_fb: + drmModeRmFB(b->drm.fd, fb->fb_id); +err_bo: + memset(&destroy_arg, 0, sizeof(destroy_arg)); + destroy_arg.handle = create_arg.handle; + drmIoctl(b->drm.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg); +err_fb: + free(fb); + return NULL; +} + +struct drm_fb * +drm_fb_ref(struct drm_fb *fb) +{ + fb->refcnt++; + return fb; +} + +#ifdef BUILD_DRM_GBM +static void +drm_fb_destroy_gbm(struct gbm_bo *bo, void *data) +{ + struct drm_fb *fb = data; + + assert(fb->type == BUFFER_GBM_SURFACE || fb->type == BUFFER_CLIENT || + fb->type == BUFFER_CURSOR); + drm_fb_destroy(fb); +} + +static void +drm_fb_destroy_dmabuf(struct drm_fb *fb) +{ + /* We deliberately do not close the GEM handles here; GBM manages + * their lifetime through the BO. */ + if (fb->bo) + gbm_bo_destroy(fb->bo); + drm_fb_destroy(fb); +} + +static struct drm_fb * +drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, + struct drm_backend *backend, bool is_opaque) +{ + struct drm_fb *fb; + struct gbm_import_fd_data import_legacy = { + .width = dmabuf->attributes.width, + .height = dmabuf->attributes.height, + .format = dmabuf->attributes.format, + .stride = dmabuf->attributes.stride[0], + .fd = dmabuf->attributes.fd[0], + }; +#ifdef HAVE_GBM_FD_IMPORT + struct gbm_import_fd_modifier_data import_mod = { + .width = dmabuf->attributes.width, + .height = dmabuf->attributes.height, + .format = dmabuf->attributes.format, + .num_fds = dmabuf->attributes.n_planes, + .modifier = dmabuf->attributes.modifier[0], + }; +#endif /* HAVE_GBM_FD_IMPORT */ + + int i; + + /* XXX: TODO: + * + * Currently the buffer is rejected if any dmabuf attribute + * flag is set. This keeps us from passing an inverted / + * interlaced / bottom-first buffer (or any other type that may + * be added in the future) through to an overlay. Ultimately, + * these types of buffers should be handled through buffer + * transforms and not as spot-checks requiring specific + * knowledge. */ + if (dmabuf->attributes.flags) + return NULL; + + fb = zalloc(sizeof *fb); + if (fb == NULL) + return NULL; + + fb->refcnt = 1; + fb->type = BUFFER_DMABUF; + +#ifdef HAVE_GBM_FD_IMPORT + static_assert(ARRAY_LENGTH(import_mod.fds) == + ARRAY_LENGTH(dmabuf->attributes.fd), + "GBM and linux_dmabuf FD size must match"); + static_assert(sizeof(import_mod.fds) == sizeof(dmabuf->attributes.fd), + "GBM and linux_dmabuf FD size must match"); + memcpy(import_mod.fds, dmabuf->attributes.fd, sizeof(import_mod.fds)); + + static_assert(ARRAY_LENGTH(import_mod.strides) == + ARRAY_LENGTH(dmabuf->attributes.stride), + "GBM and linux_dmabuf stride size must match"); + static_assert(sizeof(import_mod.strides) == + sizeof(dmabuf->attributes.stride), + "GBM and linux_dmabuf stride size must match"); + memcpy(import_mod.strides, dmabuf->attributes.stride, + sizeof(import_mod.strides)); + + static_assert(ARRAY_LENGTH(import_mod.offsets) == + ARRAY_LENGTH(dmabuf->attributes.offset), + "GBM and linux_dmabuf offset size must match"); + static_assert(sizeof(import_mod.offsets) == + sizeof(dmabuf->attributes.offset), + "GBM and linux_dmabuf offset size must match"); + memcpy(import_mod.offsets, dmabuf->attributes.offset, + sizeof(import_mod.offsets)); +#endif /* NOT HAVE_GBM_FD_IMPORT */ + + /* The legacy FD-import path does not allow us to supply modifiers, + * multiple planes, or buffer offsets. */ + if (dmabuf->attributes.modifier[0] != DRM_FORMAT_MOD_INVALID || + dmabuf->attributes.n_planes > 1 || + dmabuf->attributes.offset[0] > 0) { +#ifdef HAVE_GBM_FD_IMPORT + fb->bo = gbm_bo_import(backend->gbm, GBM_BO_IMPORT_FD_MODIFIER, + &import_mod, + GBM_BO_USE_SCANOUT); +#else /* NOT HAVE_GBM_FD_IMPORT */ + drm_debug(backend, "\t\t\t[dmabuf] Unsupported use of modifiers.\n"); + goto err_free; +#endif /* NOT HAVE_GBM_FD_IMPORT */ + } else { + fb->bo = gbm_bo_import(backend->gbm, GBM_BO_IMPORT_FD, + &import_legacy, + GBM_BO_USE_SCANOUT); + } + + if (!fb->bo) + goto err_free; + + fb->width = dmabuf->attributes.width; + fb->height = dmabuf->attributes.height; + fb->modifier = dmabuf->attributes.modifier[0]; + fb->size = 0; + fb->fd = backend->drm.fd; + + static_assert(ARRAY_LENGTH(fb->strides) == + ARRAY_LENGTH(dmabuf->attributes.stride), + "drm_fb and dmabuf stride size must match"); + static_assert(sizeof(fb->strides) == sizeof(dmabuf->attributes.stride), + "drm_fb and dmabuf stride size must match"); + memcpy(fb->strides, dmabuf->attributes.stride, sizeof(fb->strides)); + static_assert(ARRAY_LENGTH(fb->offsets) == + ARRAY_LENGTH(dmabuf->attributes.offset), + "drm_fb and dmabuf offset size must match"); + static_assert(sizeof(fb->offsets) == sizeof(dmabuf->attributes.offset), + "drm_fb and dmabuf offset size must match"); + memcpy(fb->offsets, dmabuf->attributes.offset, sizeof(fb->offsets)); + + fb->format = pixel_format_get_info(dmabuf->attributes.format); + if (!fb->format) { + weston_log("couldn't look up format info for 0x%lx\n", + (unsigned long) dmabuf->attributes.format); + goto err_free; + } + + if (is_opaque) + fb->format = pixel_format_get_opaque_substitute(fb->format); + + if (backend->min_width > fb->width || + fb->width > backend->max_width || + backend->min_height > fb->height || + fb->height > backend->max_height) { + weston_log("bo geometry out of bounds\n"); + goto err_free; + } + +#ifdef HAVE_GBM_MODIFIERS + fb->num_planes = dmabuf->attributes.n_planes; + for (i = 0; i < dmabuf->attributes.n_planes; i++) { + union gbm_bo_handle handle; + + handle = gbm_bo_get_handle_for_plane(fb->bo, i); + if (handle.s32 == -1) + goto err_free; + fb->handles[i] = handle.u32; + } +#else /* NOT HAVE_GBM_MODIFIERS */ + { + union gbm_bo_handle handle; + + fb->num_planes = 1; + + handle = gbm_bo_get_handle(fb->bo); + + if (handle.s32 == -1) + goto err_free; + fb->handles[0] = handle.u32; + } +#endif /* NOT HAVE_GBM_MODIFIERS */ + + + if (drm_fb_addfb(backend, fb) != 0) + goto err_free; + + return fb; + +err_free: + drm_fb_destroy_dmabuf(fb); + return NULL; +} + +struct drm_fb * +drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, + bool is_opaque, enum drm_fb_type type) +{ + struct drm_fb *fb = gbm_bo_get_user_data(bo); +#ifdef HAVE_GBM_MODIFIERS + int i; +#endif + + if (fb) { + assert(fb->type == type); + return drm_fb_ref(fb); + } + + fb = zalloc(sizeof *fb); + if (fb == NULL) + return NULL; + + fb->type = type; + fb->refcnt = 1; + fb->bo = bo; + fb->fd = backend->drm.fd; + + fb->width = gbm_bo_get_width(bo); + fb->height = gbm_bo_get_height(bo); + fb->format = pixel_format_get_info(gbm_bo_get_format(bo)); + fb->size = 0; + +#ifdef HAVE_GBM_MODIFIERS + fb->modifier = gbm_bo_get_modifier(bo); + fb->num_planes = gbm_bo_get_plane_count(bo); + for (i = 0; i < fb->num_planes; i++) { + fb->strides[i] = gbm_bo_get_stride_for_plane(bo, i); + fb->handles[i] = gbm_bo_get_handle_for_plane(bo, i).u32; + fb->offsets[i] = gbm_bo_get_offset(bo, i); + } +#else + fb->num_planes = 1; + fb->strides[0] = gbm_bo_get_stride(bo); + fb->handles[0] = gbm_bo_get_handle(bo).u32; + fb->modifier = DRM_FORMAT_MOD_INVALID; +#endif + + if (!fb->format) { + weston_log("couldn't look up format 0x%lx\n", + (unsigned long) gbm_bo_get_format(bo)); + goto err_free; + } + + /* We can scanout an ARGB buffer if the surface's opaque region covers + * the whole output, but we have to use XRGB as the KMS format code. */ + if (is_opaque) + fb->format = pixel_format_get_opaque_substitute(fb->format); + + if (backend->min_width > fb->width || + fb->width > backend->max_width || + backend->min_height > fb->height || + fb->height > backend->max_height) { + weston_log("bo geometry out of bounds\n"); + goto err_free; + } + + if (drm_fb_addfb(backend, fb) != 0) { + if (type == BUFFER_GBM_SURFACE) + weston_log("failed to create kms fb: %s\n", + strerror(errno)); + goto err_free; + } + + gbm_bo_set_user_data(bo, fb, drm_fb_destroy_gbm); + + return fb; + +err_free: + free(fb); + return NULL; +} + +static void +drm_fb_set_buffer(struct drm_fb *fb, struct weston_buffer *buffer, + struct weston_buffer_release *buffer_release) +{ + assert(fb->buffer_ref.buffer == NULL); + assert(fb->type == BUFFER_CLIENT || fb->type == BUFFER_DMABUF); + weston_buffer_reference(&fb->buffer_ref, buffer); + weston_buffer_release_reference(&fb->buffer_release_ref, + buffer_release); +} +#endif + +void +drm_fb_unref(struct drm_fb *fb) +{ + if (!fb) + return; + + assert(fb->refcnt > 0); + if (--fb->refcnt > 0) + return; + + switch (fb->type) { + case BUFFER_PIXMAN_DUMB: + drm_fb_destroy_dumb(fb); + break; +#ifdef BUILD_DRM_GBM + case BUFFER_CURSOR: + case BUFFER_CLIENT: + gbm_bo_destroy(fb->bo); + break; + case BUFFER_GBM_SURFACE: + gbm_surface_release_buffer(fb->gbm_surface, fb->bo); + break; + case BUFFER_DMABUF: + drm_fb_destroy_dmabuf(fb); + break; +#endif + default: + assert(NULL); + break; + } +} + +#ifdef BUILD_DRM_GBM +bool +drm_can_scanout_dmabuf(struct weston_compositor *ec, + struct linux_dmabuf_buffer *dmabuf) +{ + struct drm_fb *fb; + struct drm_backend *b = to_drm_backend(ec); + bool ret = false; + + fb = drm_fb_get_from_dmabuf(dmabuf, b, true); + if (fb) + ret = true; + + drm_fb_unref(fb); + drm_debug(b, "[dmabuf] dmabuf %p, import test %s\n", dmabuf, + ret ? "succeeded" : "failed"); + return ret; +} + +struct drm_fb * +drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev) +{ + struct drm_output *output = state->output; + struct drm_backend *b = to_drm_backend(output->base.compositor); + struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; + bool is_opaque = weston_view_is_opaque(ev, &ev->transform.boundingbox); + struct linux_dmabuf_buffer *dmabuf; + struct drm_fb *fb; + + if (ev->alpha != 1.0f) + return NULL; + + if (!drm_view_transform_supported(ev, &output->base)) + return NULL; + + if (ev->surface->protection_mode == WESTON_SURFACE_PROTECTION_MODE_ENFORCED && + ev->surface->desired_protection > output->base.current_protection) + return NULL; + + if (!buffer) + return NULL; + + if (wl_shm_buffer_get(buffer->resource)) + return NULL; + + /* GBM is used for dmabuf import as well as from client wl_buffer. */ + if (!b->gbm) + return NULL; + + dmabuf = linux_dmabuf_buffer_get(buffer->resource); + if (dmabuf) { + fb = drm_fb_get_from_dmabuf(dmabuf, b, is_opaque); + if (!fb) + return NULL; + } else { + struct gbm_bo *bo; + + bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER, + buffer->resource, GBM_BO_USE_SCANOUT); + if (!bo) + return NULL; + + fb = drm_fb_get_from_bo(bo, b, is_opaque, BUFFER_CLIENT); + if (!fb) { + gbm_bo_destroy(bo); + return NULL; + } + } + + drm_debug(b, "\t\t\t[view] view %p format: %s\n", + ev, fb->format->drm_format_name); + drm_fb_set_buffer(fb, buffer, + ev->surface->buffer_release_ref.buffer_release); + return fb; +} +#endif diff --git a/libweston/backend-drm/kms.c b/libweston/backend-drm/kms.c new file mode 100755 index 0000000..c14b222 --- /dev/null +++ b/libweston/backend-drm/kms.c @@ -0,0 +1,1507 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2017, 2018 Collabora, Ltd. + * Copyright © 2017, 2018 General Electric Company + * Copyright (c) 2018 DisplayLink (UK) Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include +#include +#include "shared/helpers.h" +#include "drm-internal.h" +#include "pixel-formats.h" +#include "presentation-time-server-protocol.h" + +#ifndef DRM_FORMAT_MOD_LINEAR +#define DRM_FORMAT_MOD_LINEAR 0 +#endif + +struct drm_property_enum_info plane_type_enums[] = { + [WDRM_PLANE_TYPE_PRIMARY] = { + .name = "Primary", + }, + [WDRM_PLANE_TYPE_OVERLAY] = { + .name = "Overlay", + }, + [WDRM_PLANE_TYPE_CURSOR] = { + .name = "Cursor", + }, +}; + +const struct drm_property_info plane_props[] = { + [WDRM_PLANE_TYPE] = { + .name = "type", + .enum_values = plane_type_enums, + .num_enum_values = WDRM_PLANE_TYPE__COUNT, + }, + [WDRM_PLANE_SRC_X] = { .name = "SRC_X", }, + [WDRM_PLANE_SRC_Y] = { .name = "SRC_Y", }, + [WDRM_PLANE_SRC_W] = { .name = "SRC_W", }, + [WDRM_PLANE_SRC_H] = { .name = "SRC_H", }, + [WDRM_PLANE_CRTC_X] = { .name = "CRTC_X", }, + [WDRM_PLANE_CRTC_Y] = { .name = "CRTC_Y", }, + [WDRM_PLANE_CRTC_W] = { .name = "CRTC_W", }, + [WDRM_PLANE_CRTC_H] = { .name = "CRTC_H", }, + [WDRM_PLANE_FB_ID] = { .name = "FB_ID", }, + [WDRM_PLANE_CRTC_ID] = { .name = "CRTC_ID", }, + [WDRM_PLANE_IN_FORMATS] = { .name = "IN_FORMATS" }, + [WDRM_PLANE_IN_FENCE_FD] = { .name = "IN_FENCE_FD" }, + [WDRM_PLANE_FB_DAMAGE_CLIPS] = { .name = "FB_DAMAGE_CLIPS" }, + [WDRM_PLANE_ZPOS] = { .name = "zpos" }, +}; + +struct drm_property_enum_info dpms_state_enums[] = { + [WDRM_DPMS_STATE_OFF] = { + .name = "Off", + }, + [WDRM_DPMS_STATE_ON] = { + .name = "On", + }, + [WDRM_DPMS_STATE_STANDBY] = { + .name = "Standby", + }, + [WDRM_DPMS_STATE_SUSPEND] = { + .name = "Suspend", + }, +}; + +struct drm_property_enum_info content_protection_enums[] = { + [WDRM_CONTENT_PROTECTION_UNDESIRED] = { + .name = "Undesired", + }, + [WDRM_CONTENT_PROTECTION_DESIRED] = { + .name = "Desired", + }, + [WDRM_CONTENT_PROTECTION_ENABLED] = { + .name = "Enabled", + }, +}; + +struct drm_property_enum_info hdcp_content_type_enums[] = { + [WDRM_HDCP_CONTENT_TYPE0] = { + .name = "HDCP Type0", + }, + [WDRM_HDCP_CONTENT_TYPE1] = { + .name = "HDCP Type1", + }, +}; + +struct drm_property_enum_info panel_orientation_enums[] = { + [WDRM_PANEL_ORIENTATION_NORMAL] = { .name = "Normal", }, + [WDRM_PANEL_ORIENTATION_UPSIDE_DOWN] = { .name = "Upside Down", }, + [WDRM_PANEL_ORIENTATION_LEFT_SIDE_UP] = { .name = "Left Side Up", }, + [WDRM_PANEL_ORIENTATION_RIGHT_SIDE_UP] = { .name = "Right Side Up", }, +}; + +const struct drm_property_info connector_props[] = { + [WDRM_CONNECTOR_EDID] = { .name = "EDID" }, + [WDRM_CONNECTOR_DPMS] = { + .name = "DPMS", + .enum_values = dpms_state_enums, + .num_enum_values = WDRM_DPMS_STATE__COUNT, + }, + [WDRM_CONNECTOR_CRTC_ID] = { .name = "CRTC_ID", }, + [WDRM_CONNECTOR_NON_DESKTOP] = { .name = "non-desktop", }, + [WDRM_CONNECTOR_CONTENT_PROTECTION] = { + .name = "Content Protection", + .enum_values = content_protection_enums, + .num_enum_values = WDRM_CONTENT_PROTECTION__COUNT, + }, + [WDRM_CONNECTOR_HDCP_CONTENT_TYPE] = { + .name = "HDCP Content Type", + .enum_values = hdcp_content_type_enums, + .num_enum_values = WDRM_HDCP_CONTENT_TYPE__COUNT, + }, + [WDRM_CONNECTOR_PANEL_ORIENTATION] = { + .name = "panel orientation", + .enum_values = panel_orientation_enums, + .num_enum_values = WDRM_PANEL_ORIENTATION__COUNT, + }, +}; + +const struct drm_property_info crtc_props[] = { + [WDRM_CRTC_MODE_ID] = { .name = "MODE_ID", }, + [WDRM_CRTC_ACTIVE] = { .name = "ACTIVE", }, +}; + + +/** + * Mode for drm_pending_state_apply and co. + */ +enum drm_state_apply_mode { + DRM_STATE_APPLY_SYNC, /**< state fully processed */ + DRM_STATE_APPLY_ASYNC, /**< state pending event delivery */ + DRM_STATE_TEST_ONLY, /**< test if the state can be applied */ +}; + +/** + * Get the current value of a KMS property + * + * Given a drmModeObjectGetProperties return, as well as the drm_property_info + * for the target property, return the current value of that property, + * with an optional default. If the property is a KMS enum type, the return + * value will be translated into the appropriate internal enum. + * + * If the property is not present, the default value will be returned. + * + * @param info Internal structure for property to look up + * @param props Raw KMS properties for the target object + * @param def Value to return if property is not found + */ +uint64_t +drm_property_get_value(struct drm_property_info *info, + const drmModeObjectProperties *props, + uint64_t def) +{ + unsigned int i; + + if (info->prop_id == 0) + return def; + + for (i = 0; i < props->count_props; i++) { + unsigned int j; + + if (props->props[i] != info->prop_id) + continue; + + /* Simple (non-enum) types can return the value directly */ + if (info->num_enum_values == 0) + return props->prop_values[i]; + + /* Map from raw value to enum value */ + for (j = 0; j < info->num_enum_values; j++) { + if (!info->enum_values[j].valid) + continue; + if (info->enum_values[j].value != props->prop_values[i]) + continue; + + return j; + } + + /* We don't have a mapping for this enum; return default. */ + break; + } + + return def; +} + +/** + * Get the current range values of a KMS property + * + * Given a drmModeObjectGetProperties return, as well as the drm_property_info + * for the target property, return the current range values of that property, + * + * If the property is not present, or there's no it is not a prop range then + * NULL will be returned. + * + * @param info Internal structure for property to look up + * @param props Raw KMS properties for the target object + */ +uint64_t * +drm_property_get_range_values(struct drm_property_info *info, + const drmModeObjectProperties *props) +{ + unsigned int i; + + if (info->prop_id == 0) + return NULL; + + for (i = 0; i < props->count_props; i++) { + + if (props->props[i] != info->prop_id) + continue; + + if (!(info->flags & DRM_MODE_PROP_RANGE) && + !(info->flags & DRM_MODE_PROP_SIGNED_RANGE)) + continue; + + return info->range_values; + } + + return NULL; +} + +/** + * Cache DRM property values + * + * Update a per-object array of drm_property_info structures, given the + * DRM properties of the object. + * + * Call this every time an object newly appears (note that only connectors + * can be hotplugged), the first time it is seen, or when its status changes + * in a way which invalidates the potential property values (currently, the + * only case for this is connector hotplug). + * + * This updates the property IDs and enum values within the drm_property_info + * array. + * + * DRM property enum values are dynamic at runtime; the user must query the + * property to find out the desired runtime value for a requested string + * name. Using the 'type' field on planes as an example, there is no single + * hardcoded constant for primary plane types; instead, the property must be + * queried at runtime to find the value associated with the string "Primary". + * + * This helper queries and caches the enum values, to allow us to use a set + * of compile-time-constant enums portably across various implementations. + * The values given in enum_names are searched for, and stored in the + * same-indexed field of the map array. + * + * @param b DRM backend object + * @param src DRM property info array to source from + * @param info DRM property info array to copy into + * @param num_infos Number of entries in the source array + * @param props DRM object properties for the object + */ +void +drm_property_info_populate(struct drm_backend *b, + const struct drm_property_info *src, + struct drm_property_info *info, + unsigned int num_infos, + drmModeObjectProperties *props) +{ + drmModePropertyRes *prop; + unsigned i, j; + + for (i = 0; i < num_infos; i++) { + unsigned int j; + + info[i].name = src[i].name; + info[i].prop_id = 0; + info[i].num_enum_values = src[i].num_enum_values; + + if (src[i].num_enum_values == 0) + continue; + + info[i].enum_values = + malloc(src[i].num_enum_values * + sizeof(*info[i].enum_values)); + assert(info[i].enum_values); + for (j = 0; j < info[i].num_enum_values; j++) { + info[i].enum_values[j].name = src[i].enum_values[j].name; + info[i].enum_values[j].valid = false; + } + } + + for (i = 0; i < props->count_props; i++) { + unsigned int k; + + prop = drmModeGetProperty(b->drm.fd, props->props[i]); + if (!prop) + continue; + + for (j = 0; j < num_infos; j++) { + if (!strcmp(prop->name, info[j].name)) + break; + } + + /* We don't know/care about this property. */ + if (j == num_infos) { +#ifdef DEBUG + weston_log("DRM debug: unrecognized property %u '%s'\n", + prop->prop_id, prop->name); +#endif + drmModeFreeProperty(prop); + continue; + } + + if (info[j].num_enum_values == 0 && + (prop->flags & DRM_MODE_PROP_ENUM)) { + weston_log("DRM: expected property %s to not be an" + " enum, but it is; ignoring\n", prop->name); + drmModeFreeProperty(prop); + continue; + } + + info[j].prop_id = props->props[i]; + info[j].flags = prop->flags; + + if (prop->flags & DRM_MODE_PROP_RANGE || + prop->flags & DRM_MODE_PROP_SIGNED_RANGE) { + info[j].num_range_values = prop->count_values; + for (int i = 0; i < prop->count_values; i++) + info[j].range_values[i] = prop->values[i]; + } + + + if (info[j].num_enum_values == 0) { + drmModeFreeProperty(prop); + continue; + } + + if (!(prop->flags & DRM_MODE_PROP_ENUM)) { + weston_log("DRM: expected property %s to be an enum," + " but it is not; ignoring\n", prop->name); + drmModeFreeProperty(prop); + info[j].prop_id = 0; + continue; + } + + for (k = 0; k < info[j].num_enum_values; k++) { + int l; + + for (l = 0; l < prop->count_enums; l++) { + if (!strcmp(prop->enums[l].name, + info[j].enum_values[k].name)) + break; + } + + if (l == prop->count_enums) + continue; + + info[j].enum_values[k].valid = true; + info[j].enum_values[k].value = prop->enums[l].value; + } + + drmModeFreeProperty(prop); + } + +#ifdef DEBUG + for (i = 0; i < num_infos; i++) { + if (info[i].prop_id == 0) + weston_log("DRM warning: property '%s' missing\n", + info[i].name); + } +#endif +} + +/** + * Free DRM property information + * + * Frees all memory associated with a DRM property info array and zeroes + * it out, leaving it usable for a further drm_property_info_update() or + * drm_property_info_free(). + * + * @param info DRM property info array + * @param num_props Number of entries in array to free + */ +void +drm_property_info_free(struct drm_property_info *info, int num_props) +{ + int i; + + for (i = 0; i < num_props; i++) + free(info[i].enum_values); + + memset(info, 0, sizeof(*info) * num_props); +} + +static inline uint32_t * +formats_ptr(struct drm_format_modifier_blob *blob) +{ + return (uint32_t *)(((char *)blob) + blob->formats_offset); +} + +static inline struct drm_format_modifier * +modifiers_ptr(struct drm_format_modifier_blob *blob) +{ + return (struct drm_format_modifier *) + (((char *)blob) + blob->modifiers_offset); +} + +/** + * Populates the plane's formats array, using either the IN_FORMATS blob + * property (if available), or the plane's format list if not. + */ +int +drm_plane_populate_formats(struct drm_plane *plane, const drmModePlane *kplane, + const drmModeObjectProperties *props, + const bool use_modifiers) +{ + unsigned i; + drmModePropertyBlobRes *blob; + struct drm_format_modifier_blob *fmt_mod_blob; + struct drm_format_modifier *blob_modifiers; + uint32_t *blob_formats; + uint32_t blob_id; + + if (!use_modifiers) + goto fallback; + + blob_id = drm_property_get_value(&plane->props[WDRM_PLANE_IN_FORMATS], + props, + 0); + if (blob_id == 0) + goto fallback; + + blob = drmModeGetPropertyBlob(plane->backend->drm.fd, blob_id); + if (!blob) + goto fallback; + + fmt_mod_blob = blob->data; + blob_formats = formats_ptr(fmt_mod_blob); + blob_modifiers = modifiers_ptr(fmt_mod_blob); + + if (plane->count_formats != fmt_mod_blob->count_formats) { + weston_log("DRM backend: format count differs between " + "plane (%d) and IN_FORMATS (%d)\n", + plane->count_formats, + fmt_mod_blob->count_formats); + weston_log("This represents a kernel bug; Weston is " + "unable to continue.\n"); + abort(); + } + + for (i = 0; i < fmt_mod_blob->count_formats; i++) { + uint32_t count_modifiers = 0; + uint64_t *modifiers = NULL; + unsigned j; + + for (j = 0; j < fmt_mod_blob->count_modifiers; j++) { + struct drm_format_modifier *mod = &blob_modifiers[j]; + + if ((i < mod->offset) || (i > mod->offset + 63)) + continue; + if (!(mod->formats & (1 << (i - mod->offset)))) + continue; + + modifiers = realloc(modifiers, + (count_modifiers + 1) * + sizeof(modifiers[0])); + assert(modifiers); + modifiers[count_modifiers++] = mod->modifier; + } + + if (count_modifiers == 0) { + modifiers = malloc(sizeof(*modifiers)); + *modifiers = DRM_FORMAT_MOD_LINEAR; + count_modifiers = 1; + } + + plane->formats[i].format = blob_formats[i]; + plane->formats[i].modifiers = modifiers; + plane->formats[i].count_modifiers = count_modifiers; + } + + drmModeFreePropertyBlob(blob); + + return 0; + +fallback: + /* No IN_FORMATS blob available, so just use the old. */ + assert(plane->count_formats == kplane->count_formats); + for (i = 0; i < kplane->count_formats; i++) { + plane->formats[i].format = kplane->formats[i]; + plane->formats[i].modifiers = malloc(sizeof(uint64_t)); + plane->formats[i].modifiers[0] = DRM_FORMAT_MOD_LINEAR; + plane->formats[i].count_modifiers = 1; + } + + return 0; +} + +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 = to_drm_output(output_base); + struct drm_backend *backend = + to_drm_backend(output->base.compositor); + + /* check */ + if (output_base->gamma_size != size) + return; + + rc = drmModeCrtcSetGamma(backend->drm.fd, + output->crtc_id, + size, r, g, b); + if (rc) + weston_log("set gamma failed: %s\n", strerror(errno)); +} + +/** + * Mark an output state as current on the output, i.e. it has been + * submitted to the kernel. The mode argument determines whether this + * update will be applied synchronously (e.g. when calling drmModeSetCrtc), + * or asynchronously (in which case we wait for events to complete). + */ +static void +drm_output_assign_state(struct drm_output_state *state, + enum drm_state_apply_mode mode) +{ + struct drm_output *output = state->output; + struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_plane_state *plane_state; + struct drm_head *head; + + assert(!output->state_last); + + if (mode == DRM_STATE_APPLY_ASYNC) + output->state_last = output->state_cur; + else + drm_output_state_free(output->state_cur); + + wl_list_remove(&state->link); + wl_list_init(&state->link); + state->pending_state = NULL; + + output->state_cur = state; + + if (b->atomic_modeset && mode == DRM_STATE_APPLY_ASYNC) { + drm_debug(b, "\t[CRTC:%u] setting pending flip\n", output->crtc_id); + output->atomic_complete_pending = true; + } + + if (b->atomic_modeset && + state->protection == WESTON_HDCP_DISABLE) + wl_list_for_each(head, &output->base.head_list, base.output_link) + weston_head_set_content_protection_status(&head->base, + WESTON_HDCP_DISABLE); + + /* Replace state_cur on each affected plane with the new state, being + * careful to dispose of orphaned (but only orphaned) previous state. + * If the previous state is not orphaned (still has an output_state + * attached), it will be disposed of by freeing the output_state. */ + wl_list_for_each(plane_state, &state->plane_list, link) { + struct drm_plane *plane = plane_state->plane; + + if (plane->state_cur && !plane->state_cur->output_state) + drm_plane_state_free(plane->state_cur, true); + plane->state_cur = plane_state; + + if (mode != DRM_STATE_APPLY_ASYNC) { + plane_state->complete = true; + continue; + } + + if (b->atomic_modeset) + continue; + + assert(plane->type != WDRM_PLANE_TYPE_OVERLAY); + if (plane->type == WDRM_PLANE_TYPE_PRIMARY) + output->page_flip_pending = true; + } +} + +static void +drm_output_set_cursor(struct drm_output_state *output_state) +{ + struct drm_output *output = output_state->output; + struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_plane *plane = output->cursor_plane; + struct drm_plane_state *state; + uint32_t handle; + + if (!plane) + return; + + state = drm_output_state_get_existing_plane(output_state, plane); + if (!state) + return; + + if (!state->fb) { + pixman_region32_fini(&plane->base.damage); + pixman_region32_init(&plane->base.damage); + drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); + return; + } + + assert(state->fb == output->gbm_cursor_fb[output->current_cursor]); + assert(!plane->state_cur->output || plane->state_cur->output == output); + + handle = output->gbm_cursor_handle[output->current_cursor]; + if (plane->state_cur->fb != state->fb) { + if (drmModeSetCursor(b->drm.fd, output->crtc_id, handle, + b->cursor_width, b->cursor_height)) { + weston_log("failed to set cursor: %s\n", + strerror(errno)); + goto err; + } + } + + pixman_region32_fini(&plane->base.damage); + pixman_region32_init(&plane->base.damage); + + if (drmModeMoveCursor(b->drm.fd, output->crtc_id, + state->dest_x, state->dest_y)) { + weston_log("failed to move cursor: %s\n", strerror(errno)); + goto err; + } + + return; + +err: + b->cursors_are_broken = true; + drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); +} + +static int +drm_output_apply_state_legacy(struct drm_output_state *state) +{ + struct drm_output *output = state->output; + struct drm_backend *backend = to_drm_backend(output->base.compositor); + struct drm_plane *scanout_plane = output->scanout_plane; + struct drm_property_info *dpms_prop; + struct drm_plane_state *scanout_state; + struct drm_mode *mode; + struct drm_head *head; + const struct pixel_format_info *pinfo = NULL; + uint32_t connectors[MAX_CLONED_CONNECTORS]; + int n_conn = 0; + struct timespec now; + int ret = 0; + + wl_list_for_each(head, &output->base.head_list, base.output_link) { + assert(n_conn < MAX_CLONED_CONNECTORS); + connectors[n_conn++] = head->connector_id; + } + + /* If disable_planes is set then assign_planes() wasn't + * called for this render, so we could still have a stale + * cursor plane set up. + */ + if (output->base.disable_planes) { + output->cursor_view = NULL; + if (output->cursor_plane) { + output->cursor_plane->base.x = INT32_MIN; + output->cursor_plane->base.y = INT32_MIN; + } + } + + if (state->dpms != WESTON_DPMS_ON) { + if (output->cursor_plane) { + ret = drmModeSetCursor(backend->drm.fd, output->crtc_id, + 0, 0, 0); + if (ret) + weston_log("drmModeSetCursor failed disable: %s\n", + strerror(errno)); + } + + ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, 0, 0, 0, + NULL, 0, NULL); + if (ret) + weston_log("drmModeSetCrtc failed disabling: %s\n", + strerror(errno)); + + drm_output_assign_state(state, DRM_STATE_APPLY_SYNC); + weston_compositor_read_presentation_clock(output->base.compositor, &now); + drm_output_update_complete(output, + WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION, + now.tv_sec, now.tv_nsec / 1000); + + return 0; + } + + scanout_state = + drm_output_state_get_existing_plane(state, scanout_plane); + + /* The legacy SetCrtc API doesn't allow us to do scaling, and the + * legacy PageFlip API doesn't allow us to do clipping either. */ + assert(scanout_state->src_x == 0); + assert(scanout_state->src_y == 0); + assert(scanout_state->src_w == + (unsigned) (output->base.current_mode->width << 16)); + assert(scanout_state->src_h == + (unsigned) (output->base.current_mode->height << 16)); + assert(scanout_state->dest_x == 0); + assert(scanout_state->dest_y == 0); + assert(scanout_state->dest_w == scanout_state->src_w >> 16); + assert(scanout_state->dest_h == scanout_state->src_h >> 16); + /* The legacy SetCrtc API doesn't support fences */ + assert(scanout_state->in_fence_fd == -1); + + mode = to_drm_mode(output->base.current_mode); + if (backend->state_invalid || + !scanout_plane->state_cur->fb || + scanout_plane->state_cur->fb->strides[0] != + scanout_state->fb->strides[0]) { + + ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, + scanout_state->fb->fb_id, + 0, 0, + connectors, n_conn, + &mode->mode_info); + if (ret) { + weston_log("set mode failed: %s\n", strerror(errno)); + goto err; + } + } + + pinfo = scanout_state->fb->format; + drm_debug(backend, "\t[CRTC:%u, PLANE:%u] FORMAT: %s\n", + output->crtc_id, scanout_state->plane->plane_id, + pinfo ? pinfo->drm_format_name : "UNKNOWN"); + + if (drmModePageFlip(backend->drm.fd, output->crtc_id, + scanout_state->fb->fb_id, + DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { + weston_log("queueing pageflip failed: %s\n", strerror(errno)); + goto err; + } + + assert(!output->page_flip_pending); + + if (output->pageflip_timer) + wl_event_source_timer_update(output->pageflip_timer, + backend->pageflip_timeout); + + drm_output_set_cursor(state); + + if (state->dpms != output->state_cur->dpms) { + wl_list_for_each(head, &output->base.head_list, base.output_link) { + dpms_prop = &head->props_conn[WDRM_CONNECTOR_DPMS]; + if (dpms_prop->prop_id == 0) + continue; + + ret = drmModeConnectorSetProperty(backend->drm.fd, + head->connector_id, + dpms_prop->prop_id, + state->dpms); + if (ret) { + weston_log("DRM: DPMS: failed property set for %s\n", + head->base.name); + } + } + } + + drm_output_assign_state(state, DRM_STATE_APPLY_ASYNC); + + return 0; + +err: + output->cursor_view = NULL; + drm_output_state_free(state); + return -1; +} + +static int +crtc_add_prop(drmModeAtomicReq *req, struct drm_output *output, + enum wdrm_crtc_property prop, uint64_t val) +{ + struct drm_property_info *info = &output->props_crtc[prop]; + int ret; + + if (info->prop_id == 0) + return -1; + + ret = drmModeAtomicAddProperty(req, output->crtc_id, info->prop_id, + val); + drm_debug(output->backend, "\t\t\t[CRTC:%lu] %lu (%s) -> %llu (0x%llx)\n", + (unsigned long) output->crtc_id, + (unsigned long) info->prop_id, info->name, + (unsigned long long) val, (unsigned long long) val); + return (ret <= 0) ? -1 : 0; +} + +static int +connector_add_prop(drmModeAtomicReq *req, struct drm_head *head, + enum wdrm_connector_property prop, uint64_t val) +{ + struct drm_property_info *info = &head->props_conn[prop]; + int ret; + + if (info->prop_id == 0) + return -1; + + ret = drmModeAtomicAddProperty(req, head->connector_id, + info->prop_id, val); + drm_debug(head->backend, "\t\t\t[CONN:%lu] %lu (%s) -> %llu (0x%llx)\n", + (unsigned long) head->connector_id, + (unsigned long) info->prop_id, info->name, + (unsigned long long) val, (unsigned long long) val); + return (ret <= 0) ? -1 : 0; +} + +static int +plane_add_prop(drmModeAtomicReq *req, struct drm_plane *plane, + enum wdrm_plane_property prop, uint64_t val) +{ + struct drm_property_info *info = &plane->props[prop]; + int ret; + + if (info->prop_id == 0) + return -1; + + ret = drmModeAtomicAddProperty(req, plane->plane_id, info->prop_id, + val); + drm_debug(plane->backend, "\t\t\t[PLANE:%lu] %lu (%s) -> %llu (0x%llx)\n", + (unsigned long) plane->plane_id, + (unsigned long) info->prop_id, info->name, + (unsigned long long) val, (unsigned long long) val); + return (ret <= 0) ? -1 : 0; +} + +static bool +drm_head_has_prop(struct drm_head *head, + enum wdrm_connector_property prop) +{ + if (head && head->props_conn[prop].prop_id != 0) + return true; + + return false; +} + +/* + * This function converts the protection requests from weston_hdcp_protection + * corresponding drm values. These values can be set in "Content Protection" + * & "HDCP Content Type" connector properties. + */ +static void +get_drm_protection_from_weston(enum weston_hdcp_protection weston_protection, + enum wdrm_content_protection_state *drm_protection, + enum wdrm_hdcp_content_type *drm_cp_type) +{ + + switch (weston_protection) { + case WESTON_HDCP_DISABLE: + *drm_protection = WDRM_CONTENT_PROTECTION_UNDESIRED; + *drm_cp_type = WDRM_HDCP_CONTENT_TYPE0; + break; + case WESTON_HDCP_ENABLE_TYPE_0: + *drm_protection = WDRM_CONTENT_PROTECTION_DESIRED; + *drm_cp_type = WDRM_HDCP_CONTENT_TYPE0; + break; + case WESTON_HDCP_ENABLE_TYPE_1: + *drm_protection = WDRM_CONTENT_PROTECTION_DESIRED; + *drm_cp_type = WDRM_HDCP_CONTENT_TYPE1; + break; + default: + assert(0 && "bad weston_hdcp_protection"); + } +} + +static void +drm_head_set_hdcp_property(struct drm_head *head, + enum weston_hdcp_protection protection, + drmModeAtomicReq *req) +{ + int ret; + enum wdrm_content_protection_state drm_protection; + enum wdrm_hdcp_content_type drm_cp_type; + struct drm_property_enum_info *enum_info; + uint64_t prop_val; + + get_drm_protection_from_weston(protection, &drm_protection, + &drm_cp_type); + + if (!drm_head_has_prop(head, WDRM_CONNECTOR_CONTENT_PROTECTION)) + return; + + /* + * Content-type property is not exposed for platforms not supporting + * HDCP2.2, therefore, type-1 cannot be supported. The type-0 content + * still can be supported if the content-protection property is exposed. + */ + if (!drm_head_has_prop(head, WDRM_CONNECTOR_HDCP_CONTENT_TYPE) && + drm_cp_type != WDRM_HDCP_CONTENT_TYPE0) + return; + + enum_info = head->props_conn[WDRM_CONNECTOR_CONTENT_PROTECTION].enum_values; + prop_val = enum_info[drm_protection].value; + ret = connector_add_prop(req, head, WDRM_CONNECTOR_CONTENT_PROTECTION, + prop_val); + assert(ret == 0); + + if (!drm_head_has_prop(head, WDRM_CONNECTOR_HDCP_CONTENT_TYPE)) + return; + + enum_info = head->props_conn[WDRM_CONNECTOR_HDCP_CONTENT_TYPE].enum_values; + prop_val = enum_info[drm_cp_type].value; + ret = connector_add_prop(req, head, WDRM_CONNECTOR_HDCP_CONTENT_TYPE, + prop_val); + assert(ret == 0); +} + +static int +drm_output_apply_state_atomic(struct drm_output_state *state, + drmModeAtomicReq *req, + uint32_t *flags) +{ + struct drm_output *output = state->output; + struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_plane_state *plane_state; + struct drm_mode *current_mode = to_drm_mode(output->base.current_mode); + struct drm_head *head; + int ret = 0; + + drm_debug(b, "\t\t[atomic] %s output %lu (%s) state\n", + (*flags & DRM_MODE_ATOMIC_TEST_ONLY) ? "testing" : "applying", + (unsigned long) output->base.id, output->base.name); + + if (state->dpms != output->state_cur->dpms) { + drm_debug(b, "\t\t\t[atomic] DPMS state differs, modeset OK\n"); + *flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } + + if (state->dpms == WESTON_DPMS_ON) { + ret = drm_mode_ensure_blob(b, current_mode); + if (ret != 0) + return ret; + + ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID, + current_mode->blob_id); + ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 1); + + /* No need for the DPMS property, since it is implicit in + * routing and CRTC activity. */ + wl_list_for_each(head, &output->base.head_list, base.output_link) { + ret |= connector_add_prop(req, head, WDRM_CONNECTOR_CRTC_ID, + output->crtc_id); + } + } else { + ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID, 0); + ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 0); + + /* No need for the DPMS property, since it is implicit in + * routing and CRTC activity. */ + wl_list_for_each(head, &output->base.head_list, base.output_link) + ret |= connector_add_prop(req, head, WDRM_CONNECTOR_CRTC_ID, 0); + } + + wl_list_for_each(head, &output->base.head_list, base.output_link) + drm_head_set_hdcp_property(head, state->protection, req); + + if (ret != 0) { + weston_log("couldn't set atomic CRTC/connector state\n"); + return ret; + } + + wl_list_for_each(plane_state, &state->plane_list, link) { + struct drm_plane *plane = plane_state->plane; + const struct pixel_format_info *pinfo = NULL; + + ret |= plane_add_prop(req, plane, WDRM_PLANE_FB_ID, + plane_state->fb ? plane_state->fb->fb_id : 0); + ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_ID, + plane_state->fb ? output->crtc_id : 0); + ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_X, + plane_state->src_x); + ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_Y, + plane_state->src_y); + ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_W, + plane_state->src_w); + ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_H, + plane_state->src_h); + ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_X, + plane_state->dest_x); + ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_Y, + plane_state->dest_y); + ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_W, + plane_state->dest_w); + ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_H, + plane_state->dest_h); + if (plane->props[WDRM_PLANE_FB_DAMAGE_CLIPS].prop_id != 0) + ret |= plane_add_prop(req, plane, WDRM_PLANE_FB_DAMAGE_CLIPS, + plane_state->damage_blob_id); + + if (plane_state->fb && plane_state->fb->format) + pinfo = plane_state->fb->format; + + drm_debug(plane->backend, "\t\t\t[PLANE:%lu] FORMAT: %s\n", + (unsigned long) plane->plane_id, + pinfo ? pinfo->drm_format_name : "UNKNOWN"); + + if (plane_state->in_fence_fd >= 0) { + ret |= plane_add_prop(req, plane, + WDRM_PLANE_IN_FENCE_FD, + plane_state->in_fence_fd); + } + + /* do note, that 'invented' zpos values are set as immutable */ + if (plane_state->zpos != DRM_PLANE_ZPOS_INVALID_PLANE && + plane_state->plane->zpos_min != plane_state->plane->zpos_max) + ret |= plane_add_prop(req, plane, + WDRM_PLANE_ZPOS, + plane_state->zpos); + + if (ret != 0) { + weston_log("couldn't set plane state\n"); + return ret; + } + } + + return 0; +} + +/** + * Helper function used only by drm_pending_state_apply, with the same + * guarantees and constraints as that function. + */ +static int +drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, + enum drm_state_apply_mode mode) +{ + struct drm_backend *b = pending_state->backend; + struct drm_output_state *output_state, *tmp; + struct drm_plane *plane; + drmModeAtomicReq *req = drmModeAtomicAlloc(); + uint32_t flags; + int ret = 0; + + if (!req) + return -1; + + switch (mode) { + case DRM_STATE_APPLY_SYNC: + flags = 0; + break; + case DRM_STATE_APPLY_ASYNC: + flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; + break; + case DRM_STATE_TEST_ONLY: + flags = DRM_MODE_ATOMIC_TEST_ONLY; + break; + } + + if (b->state_invalid) { + struct weston_head *head_base; + struct drm_head *head; + uint32_t *unused; + int err; + + drm_debug(b, "\t\t[atomic] previous state invalid; " + "starting with fresh state\n"); + + /* If we need to reset all our state (e.g. because we've + * just started, or just been VT-switched in), explicitly + * disable all the CRTCs and connectors we aren't using. */ + wl_list_for_each(head_base, + &b->compositor->head_list, compositor_link) { + struct drm_property_info *info; + + if (weston_head_is_enabled(head_base)) + continue; + + head = to_drm_head(head_base); + + drm_debug(b, "\t\t[atomic] disabling inactive head %s\n", + head_base->name); + + info = &head->props_conn[WDRM_CONNECTOR_CRTC_ID]; + err = drmModeAtomicAddProperty(req, head->connector_id, + info->prop_id, 0); + drm_debug(b, "\t\t\t[CONN:%lu] %lu (%s) -> 0\n", + (unsigned long) head->connector_id, + (unsigned long) info->prop_id, + info->name); + if (err <= 0) + ret = -1; + } + + wl_array_for_each(unused, &b->unused_crtcs) { + struct drm_property_info infos[WDRM_CRTC__COUNT]; + struct drm_property_info *info; + drmModeObjectProperties *props; + uint64_t active; + + memset(infos, 0, sizeof(infos)); + + /* We can't emit a disable on a CRTC that's already + * off, as the kernel will refuse to generate an event + * for an off->off state and fail the commit. + */ + props = drmModeObjectGetProperties(b->drm.fd, + *unused, + DRM_MODE_OBJECT_CRTC); + if (!props) { + ret = -1; + continue; + } + + drm_property_info_populate(b, crtc_props, infos, + WDRM_CRTC__COUNT, + props); + + info = &infos[WDRM_CRTC_ACTIVE]; + active = drm_property_get_value(info, props, 0); + drmModeFreeObjectProperties(props); + if (active == 0) { + drm_property_info_free(infos, WDRM_CRTC__COUNT); + continue; + } + + drm_debug(b, "\t\t[atomic] disabling unused CRTC %lu\n", + (unsigned long) *unused); + + drm_debug(b, "\t\t\t[CRTC:%lu] %lu (%s) -> 0\n", + (unsigned long) *unused, + (unsigned long) info->prop_id, info->name); + err = drmModeAtomicAddProperty(req, *unused, + info->prop_id, 0); + if (err <= 0) + ret = -1; + + info = &infos[WDRM_CRTC_MODE_ID]; + drm_debug(b, "\t\t\t[CRTC:%lu] %lu (%s) -> 0\n", + (unsigned long) *unused, + (unsigned long) info->prop_id, info->name); + err = drmModeAtomicAddProperty(req, *unused, + info->prop_id, 0); + if (err <= 0) + ret = -1; + + drm_property_info_free(infos, WDRM_CRTC__COUNT); + } + + /* Disable all the planes; planes which are being used will + * override this state in the output-state application. */ + // OHOS: now can not disable planes , commit will failed + // wl_list_for_each(plane, &b->plane_list, link) { + // drm_debug(b, "\t\t[atomic] starting with plane %lu disabled\n", + // (unsigned long) plane->plane_id); + // plane_add_prop(req, plane, WDRM_PLANE_CRTC_ID, 0); + // plane_add_prop(req, plane, WDRM_PLANE_FB_ID, 0); + // } + + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } + + wl_list_for_each(output_state, &pending_state->output_list, link) { + if (output_state->output->virtual) + continue; + if (mode == DRM_STATE_APPLY_SYNC) + assert(output_state->dpms == WESTON_DPMS_OFF); + ret |= drm_output_apply_state_atomic(output_state, req, &flags); + } + + if (ret != 0) { + weston_log("atomic: couldn't compile atomic state\n"); + goto out; + } + + ret = drmModeAtomicCommit(b->drm.fd, req, flags, b); + drm_debug(b, "[atomic] drmModeAtomicCommit\n"); + + /* Test commits do not take ownership of the state; return + * without freeing here. */ + if (mode == DRM_STATE_TEST_ONLY) { + drmModeAtomicFree(req); + return ret; + } + + if (ret != 0) { + weston_log("atomic: couldn't commit new state: %s\n", + strerror(errno)); + goto out; + } + + wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, + link) + drm_output_assign_state(output_state, mode); + + b->state_invalid = false; + + assert(wl_list_empty(&pending_state->output_list)); + +out: + drmModeAtomicFree(req); + drm_pending_state_free(pending_state); + return ret; +} + +/** + * Tests a pending state, to see if the kernel will accept the update as + * constructed. + * + * Using atomic modesetting, the kernel performs the same checks as it would + * on a real commit, returning success or failure without actually modifying + * the running state. It does not return -EBUSY if there are pending updates + * in flight, so states may be tested at any point, however this means a + * state which passed testing may fail on a real commit if the timing is not + * respected (e.g. committing before the previous commit has completed). + * + * Without atomic modesetting, we have no way to check, so we optimistically + * claim it will work. + * + * Unlike drm_pending_state_apply() and drm_pending_state_apply_sync(), this + * function does _not_ take ownership of pending_state, nor does it clear + * state_invalid. + */ +int +drm_pending_state_test(struct drm_pending_state *pending_state) +{ + struct drm_backend *b = pending_state->backend; + + if (b->atomic_modeset) + return drm_pending_state_apply_atomic(pending_state, + DRM_STATE_TEST_ONLY); + + /* We have no way to test state before application on the legacy + * modesetting API, so just claim it succeeded. */ + return 0; +} + +/** + * Applies all of a pending_state asynchronously: the primary entry point for + * applying KMS state to a device. Updates the state for all outputs in the + * pending_state, as well as disabling any unclaimed outputs. + * + * Unconditionally takes ownership of pending_state, and clears state_invalid. + */ +int +drm_pending_state_apply(struct drm_pending_state *pending_state) +{ + struct drm_backend *b = pending_state->backend; + struct drm_output_state *output_state, *tmp; + uint32_t *unused; + + if (b->atomic_modeset) + return drm_pending_state_apply_atomic(pending_state, + DRM_STATE_APPLY_ASYNC); + + if (b->state_invalid) { + /* If we need to reset all our state (e.g. because we've + * just started, or just been VT-switched in), explicitly + * disable all the CRTCs we aren't using. This also disables + * all connectors on these CRTCs, so we don't need to do that + * separately with the pre-atomic API. */ + wl_array_for_each(unused, &b->unused_crtcs) + drmModeSetCrtc(b->drm.fd, *unused, 0, 0, 0, NULL, 0, + NULL); + } + + wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, + link) { + struct drm_output *output = output_state->output; + int ret; + + if (output->virtual) { + drm_output_assign_state(output_state, + DRM_STATE_APPLY_ASYNC); + continue; + } + + ret = drm_output_apply_state_legacy(output_state); + if (ret != 0) { + weston_log("Couldn't apply state for output %s\n", + output->base.name); + } + } + + b->state_invalid = false; + + assert(wl_list_empty(&pending_state->output_list)); + + drm_pending_state_free(pending_state); + + return 0; +} + +/** + * The synchronous version of drm_pending_state_apply. May only be used to + * disable outputs. Does so synchronously: the request is guaranteed to have + * completed on return, and the output will not be touched afterwards. + * + * Unconditionally takes ownership of pending_state, and clears state_invalid. + */ +int +drm_pending_state_apply_sync(struct drm_pending_state *pending_state) +{ + struct drm_backend *b = pending_state->backend; + struct drm_output_state *output_state, *tmp; + uint32_t *unused; + + if (b->atomic_modeset) + return drm_pending_state_apply_atomic(pending_state, + DRM_STATE_APPLY_SYNC); + + if (b->state_invalid) { + /* If we need to reset all our state (e.g. because we've + * just started, or just been VT-switched in), explicitly + * disable all the CRTCs we aren't using. This also disables + * all connectors on these CRTCs, so we don't need to do that + * separately with the pre-atomic API. */ + wl_array_for_each(unused, &b->unused_crtcs) + drmModeSetCrtc(b->drm.fd, *unused, 0, 0, 0, NULL, 0, + NULL); + } + + wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, + link) { + int ret; + + assert(output_state->dpms == WESTON_DPMS_OFF); + ret = drm_output_apply_state_legacy(output_state); + if (ret != 0) { + weston_log("Couldn't apply state for output %s\n", + output_state->output->base.name); + } + } + + b->state_invalid = false; + + assert(wl_list_empty(&pending_state->output_list)); + + drm_pending_state_free(pending_state); + + return 0; +} + +void +drm_output_update_msc(struct drm_output *output, unsigned int seq) +{ + uint64_t msc_hi = output->base.msc >> 32; + + if (seq < (output->base.msc & 0xffffffff)) + msc_hi++; + + output->base.msc = (msc_hi << 32) + seq; +} + +static void +page_flip_handler(int fd, unsigned int frame, + unsigned int sec, unsigned int usec, void *data) +{ + struct drm_output *output = data; + struct drm_backend *b = to_drm_backend(output->base.compositor); + uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_VSYNC | + WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | + WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; + + drm_output_update_msc(output, frame); + + assert(!b->atomic_modeset); + assert(output->page_flip_pending); + output->page_flip_pending = false; + + drm_output_update_complete(output, flags, sec, usec); +} + +static void +atomic_flip_handler(int fd, unsigned int frame, unsigned int sec, + unsigned int usec, unsigned int crtc_id, void *data) +{ + struct drm_backend *b = data; + struct drm_output *output = drm_output_find_by_crtc(b, crtc_id); + uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_VSYNC | + WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | + WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; + + /* During the initial modeset, we can disable CRTCs which we don't + * actually handle during normal operation; this will give us events + * for unknown outputs. Ignore them. */ + if (!output || !output->base.enabled) + return; + + drm_output_update_msc(output, frame); + + drm_debug(b, "[atomic][CRTC:%u] flip processing started\n", crtc_id); + assert(b->atomic_modeset); + assert(output->atomic_complete_pending); + output->atomic_complete_pending = false; + + drm_output_update_complete(output, flags, sec, usec); + drm_debug(b, "[atomic][CRTC:%u] flip processing completed\n", crtc_id); +} + +int +on_drm_input(int fd, uint32_t mask, void *data) +{ + struct drm_backend *b = data; + drmEventContext evctx; + + memset(&evctx, 0, sizeof evctx); + evctx.version = 3; + if (b->atomic_modeset) + evctx.page_flip_handler2 = atomic_flip_handler; + else + evctx.page_flip_handler = page_flip_handler; + drmHandleEvent(fd, &evctx); + + return 1; +} + +int +init_kms_caps(struct drm_backend *b) +{ + uint64_t cap; + int ret; + clockid_t clk_id; + + weston_log("using %s\n", b->drm.filename); + + ret = drmGetCap(b->drm.fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap); + if (ret == 0 && cap == 1) + clk_id = CLOCK_MONOTONIC; + else + clk_id = CLOCK_REALTIME; + + if (weston_compositor_set_presentation_clock(b->compositor, clk_id) < 0) { + weston_log("Error: failed to set presentation clock %d.\n", + clk_id); + return -1; + } + + ret = drmGetCap(b->drm.fd, DRM_CAP_CURSOR_WIDTH, &cap); + if (ret == 0) + b->cursor_width = cap; + else + b->cursor_width = 64; + + ret = drmGetCap(b->drm.fd, DRM_CAP_CURSOR_HEIGHT, &cap); + if (ret == 0) + b->cursor_height = cap; + else + b->cursor_height = 64; + + if (!getenv("WESTON_DISABLE_UNIVERSAL_PLANES")) { + ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + b->universal_planes = (ret == 0); + } + + if (b->universal_planes && !getenv("WESTON_DISABLE_ATOMIC")) { + ret = drmGetCap(b->drm.fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap); + if (ret != 0) + cap = 0; + ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_ATOMIC, 1); + b->atomic_modeset = ((ret == 0) && (cap == 1)); + } + weston_log("DRM: %s atomic modesetting\n", + b->atomic_modeset ? "supports" : "does not support"); + + if (!getenv("WESTON_DISABLE_GBM_MODIFIERS")) { + ret = drmGetCap(b->drm.fd, DRM_CAP_ADDFB2_MODIFIERS, &cap); + if (ret == 0) + b->fb_modifiers = cap; + } + weston_log("DRM: %s GBM modifiers\n", + b->fb_modifiers ? "supports" : "does not support"); + + /* + * KMS support for hardware planes cannot properly synchronize + * without nuclear page flip. Without nuclear/atomic, hw plane + * and cursor plane updates would either tear or cause extra + * waits for vblanks which means dropping the compositor framerate + * to a fraction. For cursors, it's not so bad, so they are + * enabled. + */ + if (!b->atomic_modeset || getenv("WESTON_FORCE_RENDERER")) + b->sprites_are_broken = true; + + ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_ASPECT_RATIO, 1); + b->aspect_ratio_supported = (ret == 0); + weston_log("DRM: %s picture aspect ratio\n", + b->aspect_ratio_supported ? "supports" : "does not support"); + + return 0; +} diff --git a/libweston/backend-drm/libbacklight.c b/libweston/backend-drm/libbacklight.c new file mode 100644 index 0000000..4bbc6db --- /dev/null +++ b/libweston/backend-drm/libbacklight.c @@ -0,0 +1,318 @@ +/* + * 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 +#include + +#include "shared/string-helpers.h" + +static long backlight_get(struct backlight *backlight, char *node) +{ + char buffer[100]; + char *path; + int fd, value; + long 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; + } + + if (!safe_strtoint(buffer, &value)) { + ret = -1; + goto out; + } + + 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) { + free(backlight_path); + 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/libweston/backend-drm/libbacklight.h b/libweston/backend-drm/libbacklight.h new file mode 100644 index 0000000..2007824 --- /dev/null +++ b/libweston/backend-drm/libbacklight.h @@ -0,0 +1,79 @@ +/* + * 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 + */ +#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 subsystem 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/libweston/backend-drm/meson.build b/libweston/backend-drm/meson.build new file mode 100644 index 0000000..484c270 --- /dev/null +++ b/libweston/backend-drm/meson.build @@ -0,0 +1,93 @@ +if not get_option('backend-drm') + subdir_done() +endif + +lib_backlight = static_library( + 'backlight', + 'libbacklight.c', + dependencies: [ + dep_libdrm_headers, + dependency('libudev') + ], + include_directories: common_inc, + install: false +) +dep_backlight = declare_dependency( + link_with: lib_backlight, + include_directories: include_directories('.') +) + +config_h.set('BUILD_DRM_COMPOSITOR', '1') + +srcs_drm = [ + 'drm.c', + 'fb.c', + 'modes.c', + 'kms.c', + 'state-helpers.c', + 'state-propose.c', + linux_dmabuf_unstable_v1_protocol_c, + linux_dmabuf_unstable_v1_server_protocol_h, + presentation_time_server_protocol_h, +] + +deps_drm = [ + dep_libdl, + dep_libweston_private, + dep_session_helper, + dep_libdrm, + dep_libinput_backend, + dependency('libudev', version: '>= 136'), + dep_backlight +] + +if get_option('renderer-gl') + dep_gbm = dependency('gbm', required: false) + if not dep_gbm.found() + error('drm-backend with GL renderer requires gbm which was not found. Or, you can use \'-Drenderer-gl=false\'.') + endif + if dep_gbm.version().version_compare('>= 17.1') + config_h.set('HAVE_GBM_MODIFIERS', '1') + endif + if dep_gbm.version().version_compare('>= 17.2') + config_h.set('HAVE_GBM_FD_IMPORT', '1') + endif + deps_drm += dep_gbm + srcs_drm += 'drm-gbm.c' + config_h.set('BUILD_DRM_GBM', '1') +endif + +if get_option('backend-drm-screencast-vaapi') + foreach name : [ 'libva', 'libva-drm' ] + d = dependency(name, version: '>= 0.34.0', required: false) + if not d.found() + error('VA-API recorder requires @0@ >= 0.34.0 which was not found. Or, you can use \'-Dbackend-drm-screencast-vaapi=false\'.'.format(name)) + endif + deps_drm += d + endforeach + + srcs_drm += 'vaapi-recorder.c' + deps_drm += dependency('threads') + config_h.set('BUILD_VAAPI_RECORDER', '1') +endif + +if get_option('remoting') or get_option('pipewire') + if not get_option('renderer-gl') + error('DRM virtual requires renderer-gl.') + endif + srcs_drm += 'drm-virtual.c' + config_h.set('BUILD_DRM_VIRTUAL', '1') +endif + +plugin_drm = shared_library( + 'drm-backend', + srcs_drm, + include_directories: common_inc, + dependencies: deps_drm, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'drm-backend.so=@0@;'.format(plugin_drm.full_path()) + +install_headers(backend_drm_h, subdir: dir_include_libweston_install) diff --git a/libweston/backend-drm/modes.c b/libweston/backend-drm/modes.c new file mode 100755 index 0000000..4f16cf9 --- /dev/null +++ b/libweston/backend-drm/modes.c @@ -0,0 +1,812 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2017, 2018 Collabora, Ltd. + * Copyright © 2017, 2018 General Electric Company + * Copyright (c) 2018 DisplayLink (UK) Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include "drm-internal.h" + +static const char *const aspect_ratio_as_string[] = { + [WESTON_MODE_PIC_AR_NONE] = "", + [WESTON_MODE_PIC_AR_4_3] = " 4:3", + [WESTON_MODE_PIC_AR_16_9] = " 16:9", + [WESTON_MODE_PIC_AR_64_27] = " 64:27", + [WESTON_MODE_PIC_AR_256_135] = " 256:135", +}; + +/* + * Get the aspect-ratio from drmModeModeInfo mode flags. + * + * @param drm_mode_flags- flags from drmModeModeInfo structure. + * @returns aspect-ratio as encoded in enum 'weston_mode_aspect_ratio'. + */ +static enum weston_mode_aspect_ratio +drm_to_weston_mode_aspect_ratio(uint32_t drm_mode_flags) +{ + switch (drm_mode_flags & DRM_MODE_FLAG_PIC_AR_MASK) { + case DRM_MODE_FLAG_PIC_AR_4_3: + return WESTON_MODE_PIC_AR_4_3; + case DRM_MODE_FLAG_PIC_AR_16_9: + return WESTON_MODE_PIC_AR_16_9; + case DRM_MODE_FLAG_PIC_AR_64_27: + return WESTON_MODE_PIC_AR_64_27; + case DRM_MODE_FLAG_PIC_AR_256_135: + return WESTON_MODE_PIC_AR_256_135; + case DRM_MODE_FLAG_PIC_AR_NONE: + default: + return WESTON_MODE_PIC_AR_NONE; + } +} + +static const char * +aspect_ratio_to_string(enum weston_mode_aspect_ratio ratio) +{ + if (ratio < 0 || ratio >= ARRAY_LENGTH(aspect_ratio_as_string) || + !aspect_ratio_as_string[ratio]) + return " (unknown aspect ratio)"; + + return aspect_ratio_as_string[ratio]; +} + +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; + } +} + +int +drm_mode_ensure_blob(struct drm_backend *backend, struct drm_mode *mode) +{ + int ret; + + if (mode->blob_id) + return 0; + + ret = drmModeCreatePropertyBlob(backend->drm.fd, + &mode->mode_info, + sizeof(mode->mode_info), + &mode->blob_id); + if (ret != 0) + weston_log("failed to create mode property blob: %s\n", + strerror(errno)); + + drm_debug(backend, "\t\t\t[atomic] created new mode blob %lu for %s\n", + (unsigned long) mode->blob_id, mode->mode_info.name); + + return ret; +} + +static bool +check_non_desktop(struct drm_head *head, drmModeObjectPropertiesPtr props) +{ + struct drm_property_info *non_desktop_info = + &head->props_conn[WDRM_CONNECTOR_NON_DESKTOP]; + + return drm_property_get_value(non_desktop_info, props, 0); +} + +static uint32_t +get_panel_orientation(struct drm_head *head, drmModeObjectPropertiesPtr props) +{ + struct drm_property_info *orientation = + &head->props_conn[WDRM_CONNECTOR_PANEL_ORIENTATION]; + uint64_t kms_val = + drm_property_get_value(orientation, props, + WDRM_PANEL_ORIENTATION_NORMAL); + + switch (kms_val) { + case WDRM_PANEL_ORIENTATION_NORMAL: + return WL_OUTPUT_TRANSFORM_NORMAL; + case WDRM_PANEL_ORIENTATION_UPSIDE_DOWN: + return WL_OUTPUT_TRANSFORM_180; + case WDRM_PANEL_ORIENTATION_LEFT_SIDE_UP: + return WL_OUTPUT_TRANSFORM_90; + case WDRM_PANEL_ORIENTATION_RIGHT_SIDE_UP: + return WL_OUTPUT_TRANSFORM_270; + default: + assert(!"unknown property value in get_panel_orientation"); + // OHOS build + return WL_OUTPUT_TRANSFORM_NORMAL; + } +} + +static int +parse_modeline(const char *s, drmModeModeInfo *mode) +{ + char hsync[16]; + char vsync[16]; + float fclock; + + memset(mode, 0, sizeof *mode); + + 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 (strcasecmp(hsync, "+hsync") == 0) + mode->flags |= DRM_MODE_FLAG_PHSYNC; + else if (strcasecmp(hsync, "-hsync") == 0) + mode->flags |= DRM_MODE_FLAG_NHSYNC; + else + return -1; + + if (strcasecmp(vsync, "+vsync") == 0) + mode->flags |= DRM_MODE_FLAG_PVSYNC; + else if (strcasecmp(vsync, "-vsync") == 0) + mode->flags |= DRM_MODE_FLAG_NVSYNC; + else + return -1; + + snprintf(mode->name, sizeof mode->name, "%dx%d@%.3f", + mode->hdisplay, mode->vdisplay, fclock); + + return 0; +} + +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); + + /* guarantee our new string is null-terminated */ + text[12] = '\0'; + + /* 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; +} + +/** Parse monitor make, model and serial from EDID + * + * \param head The head whose \c drm_edid to fill in. + * \param props The DRM connector properties to get the EDID from. + * \param[out] make The monitor make (PNP ID). + * \param[out] model The monitor model (name). + * \param[out] serial_number The monitor serial number. + * + * Each of \c *make, \c *model and \c *serial_number are set only if the + * information is found in the EDID. The pointers they are set to must not + * be free()'d explicitly, instead they get implicitly freed when the + * \c drm_head is destroyed. + */ +static void +find_and_parse_output_edid(struct drm_head *head, + drmModeObjectPropertiesPtr props, + const char **make, + const char **model, + const char **serial_number) +{ + drmModePropertyBlobPtr edid_blob = NULL; + uint32_t blob_id; + int rc; + + blob_id = + drm_property_get_value(&head->props_conn[WDRM_CONNECTOR_EDID], + props, 0); + if (!blob_id) + return; + + edid_blob = drmModeGetPropertyBlob(head->backend->drm.fd, blob_id); + if (!edid_blob) + return; + + rc = edid_parse(&head->edid, + edid_blob->data, + edid_blob->length); + if (!rc) { + if (head->edid.pnp_id[0] != '\0') + *make = head->edid.pnp_id; + if (head->edid.monitor_name[0] != '\0') + *model = head->edid.monitor_name; + if (head->edid.serial_number[0] != '\0') + *serial_number = head->edid.serial_number; + } + drmModeFreePropertyBlob(edid_blob); +} + +static uint32_t +drm_refresh_rate_mHz(const drmModeModeInfo *info) +{ + uint64_t refresh; + + /* 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; + + return refresh; +} + +/** + * Add a mode to output's mode list + * + * Copy the supplied DRM mode into a Weston mode structure, and add it to the + * output's mode list. + * + * @param output DRM output to add mode to + * @param info DRM mode structure to add + * @returns Newly-allocated Weston/DRM mode structure + */ +static struct drm_mode * +drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info) +{ + struct drm_mode *mode; + + mode = malloc(sizeof *mode); + if (mode == NULL) + return NULL; + + mode->base.flags = 0; + mode->base.width = info->hdisplay; + mode->base.height = info->vdisplay; + + mode->base.refresh = drm_refresh_rate_mHz(info); + mode->mode_info = *info; + mode->blob_id = 0; + + if (info->type & DRM_MODE_TYPE_PREFERRED) + mode->base.flags |= WL_OUTPUT_MODE_PREFERRED; + + mode->base.aspect_ratio = drm_to_weston_mode_aspect_ratio(info->flags); + + wl_list_insert(output->base.mode_list.prev, &mode->base.link); + + return mode; +} + +/** + * Destroys a mode, and removes it from the list. + */ +static void +drm_output_destroy_mode(struct drm_backend *backend, struct drm_mode *mode) +{ + if (mode->blob_id) + drmModeDestroyPropertyBlob(backend->drm.fd, mode->blob_id); + wl_list_remove(&mode->base.link); + free(mode); +} + +/** Destroy a list of drm_modes + * + * @param backend The backend for releasing mode property blobs. + * @param mode_list The list linked by drm_mode::base.link. + */ +void +drm_mode_list_destroy(struct drm_backend *backend, struct wl_list *mode_list) +{ + struct drm_mode *mode, *next; + + wl_list_for_each_safe(mode, next, mode_list, base.link) + drm_output_destroy_mode(backend, mode); +} + +void +drm_output_print_modes(struct drm_output *output) +{ + struct weston_mode *m; + struct drm_mode *dm; + const char *aspect_ratio; + + wl_list_for_each(m, &output->base.mode_list, link) { + dm = to_drm_mode(m); + + aspect_ratio = aspect_ratio_to_string(m->aspect_ratio); + weston_log_continue(STAMP_SPACE "%dx%d@%.1f%s%s%s, %.1f MHz\n", + m->width, m->height, m->refresh / 1000.0, + aspect_ratio, + m->flags & WL_OUTPUT_MODE_PREFERRED ? + ", preferred" : "", + m->flags & WL_OUTPUT_MODE_CURRENT ? + ", current" : "", + dm->mode_info.clock / 1000.0); + } +} + + +/** + * Find the closest-matching mode for a given target + * + * Given a target mode, find the most suitable mode amongst the output's + * current mode list to use, preferring the current mode if possible, to + * avoid an expensive mode switch. + * + * @param output DRM output + * @param target_mode Mode to attempt to match + * @returns Pointer to a mode from the output's mode list + */ +struct drm_mode * +drm_output_choose_mode(struct drm_output *output, + struct weston_mode *target_mode) +{ + struct drm_mode *tmp_mode = NULL, *mode_fall_back = NULL, *mode; + enum weston_mode_aspect_ratio src_aspect = WESTON_MODE_PIC_AR_NONE; + enum weston_mode_aspect_ratio target_aspect = WESTON_MODE_PIC_AR_NONE; + struct drm_backend *b; + + b = to_drm_backend(output->base.compositor); + target_aspect = target_mode->aspect_ratio; + src_aspect = output->base.current_mode->aspect_ratio; + 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)) { + if (!b->aspect_ratio_supported || src_aspect == target_aspect) + return to_drm_mode(output->base.current_mode); + } + + wl_list_for_each(mode, &output->base.mode_list, base.link) { + + src_aspect = mode->base.aspect_ratio; + if (mode->mode_info.hdisplay == target_mode->width && + mode->mode_info.vdisplay == target_mode->height) { + if (mode->base.refresh == target_mode->refresh || + target_mode->refresh == 0) { + if (!b->aspect_ratio_supported || + src_aspect == target_aspect) + return mode; + else if (!mode_fall_back) + mode_fall_back = mode; + } else if (!tmp_mode) { + tmp_mode = mode; + } + } + } + + if (mode_fall_back) + return mode_fall_back; + + return tmp_mode; +} + +void +update_head_from_connector(struct drm_head *head, + drmModeObjectProperties *props) +{ + const char *make = "unknown"; + const char *model = "unknown"; + const char *serial_number = "unknown"; + + find_and_parse_output_edid(head, props, &make, &model, &serial_number); + weston_head_set_monitor_strings(&head->base, make, model, serial_number); + weston_head_set_non_desktop(&head->base, + check_non_desktop(head, props)); + weston_head_set_subpixel(&head->base, + drm_subpixel_to_wayland(head->connector->subpixel)); + + weston_head_set_physical_size(&head->base, head->connector->mmWidth, + head->connector->mmHeight); + + weston_head_set_transform(&head->base, + get_panel_orientation(head, props)); + + /* Unknown connection status is assumed disconnected. */ + weston_head_set_connection_status(&head->base, + head->connector->connection == DRM_MODE_CONNECTED); +} + +/** + * Choose suitable mode for an output + * + * Find the most suitable mode to use for initial setup (or reconfiguration on + * hotplug etc) for a DRM output. + * + * @param backend the DRM backend + * @param output DRM output to choose mode for + * @param mode Strategy and preference to use when choosing mode + * @param modeline Manually-entered mode (may be NULL) + * @param current_mode Mode currently being displayed on this output + * @returns A mode from the output's mode list, or NULL if none available + */ +static struct drm_mode * +drm_output_choose_initial_mode(struct drm_backend *backend, + struct drm_output *output, + enum weston_drm_backend_output_mode mode, + const char *modeline, + const drmModeModeInfo *current_mode) +{ + struct drm_mode *preferred = NULL; + struct drm_mode *current = NULL; + struct drm_mode *configured = NULL; + struct drm_mode *config_fall_back = NULL; + struct drm_mode *best = NULL; + struct drm_mode *drm_mode; + drmModeModeInfo drm_modeline; + int32_t width = 0; + int32_t height = 0; + uint32_t refresh = 0; + uint32_t aspect_width = 0; + uint32_t aspect_height = 0; + enum weston_mode_aspect_ratio aspect_ratio = WESTON_MODE_PIC_AR_NONE; + int n; + + if (mode == WESTON_DRM_BACKEND_OUTPUT_PREFERRED && modeline) { + n = sscanf(modeline, "%dx%d@%d %u:%u", &width, &height, + &refresh, &aspect_width, &aspect_height); + if (backend->aspect_ratio_supported && n == 5) { + if (aspect_width == 4 && aspect_height == 3) + aspect_ratio = WESTON_MODE_PIC_AR_4_3; + else if (aspect_width == 16 && aspect_height == 9) + aspect_ratio = WESTON_MODE_PIC_AR_16_9; + else if (aspect_width == 64 && aspect_height == 27) + aspect_ratio = WESTON_MODE_PIC_AR_64_27; + else if (aspect_width == 256 && aspect_height == 135) + aspect_ratio = WESTON_MODE_PIC_AR_256_135; + else + weston_log("Invalid modeline \"%s\" for output %s\n", + modeline, output->base.name); + } + if (n != 2 && n != 3 && n != 5) { + width = -1; + + if (parse_modeline(modeline, &drm_modeline) == 0) { + configured = drm_output_add_mode(output, &drm_modeline); + if (!configured) + return NULL; + } else { + weston_log("Invalid modeline \"%s\" for output %s\n", + modeline, output->base.name); + } + } + } + + wl_list_for_each_reverse(drm_mode, &output->base.mode_list, base.link) { + if (width == drm_mode->base.width && + height == drm_mode->base.height && + (refresh == 0 || refresh == drm_mode->mode_info.vrefresh)) { + if (!backend->aspect_ratio_supported || + aspect_ratio == drm_mode->base.aspect_ratio) + configured = drm_mode; + else + config_fall_back = drm_mode; + } + + if (memcmp(current_mode, &drm_mode->mode_info, + sizeof *current_mode) == 0) + current = drm_mode; + + if (drm_mode->base.flags & WL_OUTPUT_MODE_PREFERRED) + preferred = drm_mode; + + best = drm_mode; + } + + if (current == NULL && current_mode->clock != 0) { + current = drm_output_add_mode(output, current_mode); + if (!current) + return NULL; + } + + if (mode == WESTON_DRM_BACKEND_OUTPUT_CURRENT) + configured = current; + + if (configured) + return configured; + + if (config_fall_back) + return config_fall_back; + + if (preferred) + return preferred; + + if (current) + return current; + + if (best) + return best; + + weston_log("no available modes for %s\n", output->base.name); + return NULL; +} + +static uint32_t +u32distance(uint32_t a, uint32_t b) +{ + if (a < b) + return b - a; + else + return a - b; +} + +/** Choose equivalent mode + * + * If the two modes are not equivalent, return NULL. + * Otherwise return the mode that is more likely to work in place of both. + * + * None of the fuzzy matching criteria in this function have any justification. + * + * typedef struct _drmModeModeInfo { + * uint32_t clock; + * uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew; + * uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan; + * + * uint32_t vrefresh; + * + * uint32_t flags; + * uint32_t type; + * char name[DRM_DISPLAY_MODE_LEN]; + * } drmModeModeInfo, *drmModeModeInfoPtr; + */ +static const drmModeModeInfo * +drm_mode_pick_equivalent(const drmModeModeInfo *a, const drmModeModeInfo *b) +{ + uint32_t refresh_a, refresh_b; + + if (a->hdisplay != b->hdisplay || a->vdisplay != b->vdisplay) + return NULL; + + if (a->flags != b->flags) + return NULL; + + /* kHz */ + if (u32distance(a->clock, b->clock) > 500) + return NULL; + + refresh_a = drm_refresh_rate_mHz(a); + refresh_b = drm_refresh_rate_mHz(b); + if (u32distance(refresh_a, refresh_b) > 50) + return NULL; + + if ((a->type ^ b->type) & DRM_MODE_TYPE_PREFERRED) { + if (a->type & DRM_MODE_TYPE_PREFERRED) + return a; + else + return b; + } + + return a; +} + +/* If the given mode info is not already in the list, add it. + * If it is in the list, either keep the existing or replace it, + * depending on which one is "better". + */ +static int +drm_output_try_add_mode(struct drm_output *output, const drmModeModeInfo *info) +{ + struct weston_mode *base; + struct drm_mode *mode = NULL; + struct drm_backend *backend; + const drmModeModeInfo *chosen = NULL; + + assert(info); + + wl_list_for_each(base, &output->base.mode_list, link) { + mode = to_drm_mode(base); + chosen = drm_mode_pick_equivalent(&mode->mode_info, info); + if (chosen) + break; + } + + if (chosen == info) { + assert(mode); + backend = to_drm_backend(output->base.compositor); + drm_output_destroy_mode(backend, mode); + chosen = NULL; + } + + if (!chosen) { + mode = drm_output_add_mode(output, info); + if (!mode) + return -1; + } + /* else { the equivalent mode is already in the list } */ + + return 0; +} + +/** Rewrite the output's mode list + * + * @param output The output. + * @return 0 on success, -1 on failure. + * + * Destroy all existing modes in the list, and reconstruct a new list from + * scratch, based on the currently attached heads. + * + * On failure the output's mode list may contain some modes. + */ +static int +drm_output_update_modelist_from_heads(struct drm_output *output) +{ + struct drm_backend *backend = to_drm_backend(output->base.compositor); + struct weston_head *head_base; + struct drm_head *head; + int i; + int ret; + + assert(!output->base.enabled); + + drm_mode_list_destroy(backend, &output->base.mode_list); + + wl_list_for_each(head_base, &output->base.head_list, output_link) { + head = to_drm_head(head_base); + for (i = 0; i < head->connector->count_modes; i++) { + ret = drm_output_try_add_mode(output, + &head->connector->modes[i]); + if (ret < 0) + return -1; + } + } + + return 0; +} + +int +drm_output_set_mode(struct weston_output *base, + enum weston_drm_backend_output_mode mode, + const char *modeline) +{ + struct drm_output *output = to_drm_output(base); + struct drm_backend *b = to_drm_backend(base->compositor); + struct drm_head *head = to_drm_head(weston_output_get_first_head(base)); + + struct drm_mode *current; + + if (output->virtual) + return -1; + + if (drm_output_update_modelist_from_heads(output) < 0) + return -1; + + current = drm_output_choose_initial_mode(b, output, mode, modeline, + &head->inherited_mode); + if (!current) + return -1; + + output->base.current_mode = ¤t->base; + output->base.current_mode->flags |= WL_OUTPUT_MODE_CURRENT; + + /* Set native_ fields, so weston_output_mode_switch_to_native() works */ + output->base.native_mode = output->base.current_mode; + output->base.native_scale = output->base.current_scale; + + return 0; +} diff --git a/libweston/backend-drm/state-helpers.c b/libweston/backend-drm/state-helpers.c new file mode 100644 index 0000000..0ee663c --- /dev/null +++ b/libweston/backend-drm/state-helpers.c @@ -0,0 +1,473 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2017, 2018 Collabora, Ltd. + * Copyright © 2017, 2018 General Electric Company + * Copyright (c) 2018 DisplayLink (UK) Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include "drm-internal.h" + +/** + * Allocate a new, empty, plane state. + */ +struct drm_plane_state * +drm_plane_state_alloc(struct drm_output_state *state_output, + struct drm_plane *plane) +{ + struct drm_plane_state *state = zalloc(sizeof(*state)); + + assert(state); + state->output_state = state_output; + state->plane = plane; + state->in_fence_fd = -1; + state->zpos = DRM_PLANE_ZPOS_INVALID_PLANE; + + /* Here we only add the plane state to the desired link, and not + * set the member. Having an output pointer set means that the + * plane will be displayed on the output; this won't be the case + * when we go to disable a plane. In this case, it must be part of + * the commit (and thus the output state), but the member must be + * NULL, as it will not be on any output when the state takes + * effect. + */ + if (state_output) + wl_list_insert(&state_output->plane_list, &state->link); + else + wl_list_init(&state->link); + + return state; +} + +/** + * Free an existing plane state. As a special case, the state will not + * normally be freed if it is the current state; see drm_plane_set_state. + */ +void +drm_plane_state_free(struct drm_plane_state *state, bool force) +{ + if (!state) + return; + + wl_list_remove(&state->link); + wl_list_init(&state->link); + state->output_state = NULL; + state->in_fence_fd = -1; + state->zpos = DRM_PLANE_ZPOS_INVALID_PLANE; + + /* Once the damage blob has been submitted, it is refcounted internally + * by the kernel, which means we can safely discard it. + */ + if (state->damage_blob_id != 0) { + drmModeDestroyPropertyBlob(state->plane->backend->drm.fd, + state->damage_blob_id); + state->damage_blob_id = 0; + } + + if (force || state != state->plane->state_cur) { + drm_fb_unref(state->fb); + free(state); + } +} + +/** + * Duplicate an existing plane state into a new plane state, storing it within + * the given output state. If the output state already contains a plane state + * for the drm_plane referenced by 'src', that plane state is freed first. + */ +struct drm_plane_state * +drm_plane_state_duplicate(struct drm_output_state *state_output, + struct drm_plane_state *src) +{ + struct drm_plane_state *dst = zalloc(sizeof(*dst)); + struct drm_plane_state *old, *tmp; + + assert(src); + assert(dst); + *dst = *src; + /* We don't want to copy this, because damage is transient, and only + * lasts for the duration of a single repaint. + */ + dst->damage_blob_id = 0; + wl_list_init(&dst->link); + + wl_list_for_each_safe(old, tmp, &state_output->plane_list, link) { + /* Duplicating a plane state into the same output state, so + * it can replace itself with an identical copy of itself, + * makes no sense. */ + assert(old != src); + if (old->plane == dst->plane) + drm_plane_state_free(old, false); + } + + wl_list_insert(&state_output->plane_list, &dst->link); + if (src->fb) + dst->fb = drm_fb_ref(src->fb); + dst->output_state = state_output; + dst->complete = false; + + return dst; +} + +/** + * Remove a plane state from an output state; if the plane was previously + * enabled, then replace it with a disabling state. This ensures that the + * output state was untouched from it was before the plane state was + * modified by the caller of this function. + * + * This is required as drm_output_state_get_plane may either allocate a + * new plane state, in which case this function will just perform a matching + * drm_plane_state_free, or it may instead repurpose an existing disabling + * state (if the plane was previously active), in which case this function + * will reset it. + */ +void +drm_plane_state_put_back(struct drm_plane_state *state) +{ + struct drm_output_state *state_output; + struct drm_plane *plane; + + if (!state) + return; + + state_output = state->output_state; + plane = state->plane; + drm_plane_state_free(state, false); + + /* Plane was previously disabled; no need to keep this temporary + * state around. */ + if (!plane->state_cur->fb) + return; + + (void) drm_plane_state_alloc(state_output, plane); +} + +/** + * Given a weston_view, fill the drm_plane_state's co-ordinates to display on + * a given plane. + */ +bool +drm_plane_state_coords_for_view(struct drm_plane_state *state, + struct weston_view *ev, uint64_t zpos) +{ + struct drm_output *output = state->output; + struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; + pixman_region32_t dest_rect, src_rect; + pixman_box32_t *box, tbox; + float sxf1, syf1, sxf2, syf2; + + if (!drm_view_transform_supported(ev, &output->base)) + return false; + + /* Update the base weston_plane co-ordinates. */ + box = pixman_region32_extents(&ev->transform.boundingbox); + state->plane->base.x = box->x1; + state->plane->base.y = box->y1; + + /* First calculate the destination co-ordinates by taking the + * area of the view which is visible on this output, performing any + * transforms to account for output rotation and scale as necessary. */ + pixman_region32_init(&dest_rect); + pixman_region32_intersect(&dest_rect, &ev->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); + state->dest_x = tbox.x1; + state->dest_y = tbox.y1; + state->dest_w = tbox.x2 - tbox.x1; + state->dest_h = tbox.y2 - tbox.y1; + pixman_region32_fini(&dest_rect); + + /* Now calculate the source rectangle, by finding the extents of the + * view, and working backwards to source co-ordinates. */ + pixman_region32_init(&src_rect); + pixman_region32_intersect(&src_rect, &ev->transform.boundingbox, + &output->base.region); + box = pixman_region32_extents(&src_rect); + weston_view_from_global_float(ev, box->x1, box->y1, &sxf1, &syf1); + weston_surface_to_buffer_float(ev->surface, sxf1, syf1, &sxf1, &syf1); + weston_view_from_global_float(ev, box->x2, box->y2, &sxf2, &syf2); + weston_surface_to_buffer_float(ev->surface, sxf2, syf2, &sxf2, &syf2); + pixman_region32_fini(&src_rect); + + /* Buffer transforms may mean that x2 is to the left of x1, and/or that + * y2 is above y1. */ + if (sxf2 < sxf1) { + double tmp = sxf1; + sxf1 = sxf2; + sxf2 = tmp; + } + if (syf2 < syf1) { + double tmp = syf1; + syf1 = syf2; + syf2 = tmp; + } + + /* Shift from S23.8 wl_fixed to U16.16 KMS fixed-point encoding. */ + state->src_x = wl_fixed_from_double(sxf1) << 8; + state->src_y = wl_fixed_from_double(syf1) << 8; + state->src_w = wl_fixed_from_double(sxf2 - sxf1) << 8; + state->src_h = wl_fixed_from_double(syf2 - syf1) << 8; + + /* Clamp our source co-ordinates to surface bounds; it's possible + * for intermediate translations to give us slightly incorrect + * co-ordinates if we have, for example, multiple zooming + * transformations. View bounding boxes are also explicitly rounded + * greedily. */ + if (state->src_x < 0) + state->src_x = 0; + if (state->src_y < 0) + state->src_y = 0; + if (state->src_w > (uint32_t) ((buffer->width << 16) - state->src_x)) + state->src_w = (buffer->width << 16) - state->src_x; + if (state->src_h > (uint32_t) ((buffer->height << 16) - state->src_y)) + state->src_h = (buffer->height << 16) - state->src_y; + + /* apply zpos if available */ + state->zpos = zpos; + + return true; +} + +/** + * Reset the current state of a DRM plane + * + * The current state will be freed and replaced by a pristine state. + * + * @param plane The plane to reset the current state of + */ +void +drm_plane_reset_state(struct drm_plane *plane) +{ + drm_plane_state_free(plane->state_cur, true); + plane->state_cur = drm_plane_state_alloc(NULL, plane); + plane->state_cur->complete = true; +} + +/** + * Return a plane state from a drm_output_state. + */ +struct drm_plane_state * +drm_output_state_get_existing_plane(struct drm_output_state *state_output, + struct drm_plane *plane) +{ + struct drm_plane_state *ps; + + wl_list_for_each(ps, &state_output->plane_list, link) { + if (ps->plane == plane) + return ps; + } + + return NULL; +} + +/** + * Return a plane state from a drm_output_state, either existing or + * freshly allocated. + */ +struct drm_plane_state * +drm_output_state_get_plane(struct drm_output_state *state_output, + struct drm_plane *plane) +{ + struct drm_plane_state *ps; + + ps = drm_output_state_get_existing_plane(state_output, plane); + if (ps) + return ps; + + return drm_plane_state_alloc(state_output, plane); +} + +/** + * Allocate a new, empty drm_output_state. This should not generally be used + * in the repaint cycle; see drm_output_state_duplicate. + */ +struct drm_output_state * +drm_output_state_alloc(struct drm_output *output, + struct drm_pending_state *pending_state) +{ + struct drm_output_state *state = zalloc(sizeof(*state)); + + assert(state); + state->output = output; + state->dpms = WESTON_DPMS_OFF; + state->protection = WESTON_HDCP_DISABLE; + state->pending_state = pending_state; + if (pending_state) + wl_list_insert(&pending_state->output_list, &state->link); + else + wl_list_init(&state->link); + + wl_list_init(&state->plane_list); + + return state; +} + +/** + * Duplicate an existing drm_output_state into a new one. This is generally + * used during the repaint cycle, to capture the existing state of an output + * and modify it to create a new state to be used. + * + * The mode determines whether the output will be reset to an a blank state, + * or an exact mirror of the current state. + */ +struct drm_output_state * +drm_output_state_duplicate(struct drm_output_state *src, + struct drm_pending_state *pending_state, + enum drm_output_state_duplicate_mode plane_mode) +{ + struct drm_output_state *dst = malloc(sizeof(*dst)); + struct drm_plane_state *ps; + + assert(dst); + + /* Copy the whole structure, then individually modify the + * pending_state, as well as the list link into our pending + * state. */ + *dst = *src; + + dst->pending_state = pending_state; + if (pending_state) + wl_list_insert(&pending_state->output_list, &dst->link); + else + wl_list_init(&dst->link); + + wl_list_init(&dst->plane_list); + + wl_list_for_each(ps, &src->plane_list, link) { + /* Don't carry planes which are now disabled; these should be + * free for other outputs to reuse. */ + if (!ps->output) + continue; + + if (plane_mode == DRM_OUTPUT_STATE_CLEAR_PLANES) + (void) drm_plane_state_alloc(dst, ps->plane); + else + (void) drm_plane_state_duplicate(dst, ps); + } + + return dst; +} + +/** + * Free an unused drm_output_state. + */ +void +drm_output_state_free(struct drm_output_state *state) +{ + struct drm_plane_state *ps, *next; + + if (!state) + return; + + wl_list_for_each_safe(ps, next, &state->plane_list, link) + drm_plane_state_free(ps, false); + + wl_list_remove(&state->link); + + free(state); +} + +/** + * Allocate a new drm_pending_state + * + * Allocate a new, empty, 'pending state' structure to be used across a + * repaint cycle or similar. + * + * @param backend DRM backend + * @returns Newly-allocated pending state structure + */ +struct drm_pending_state * +drm_pending_state_alloc(struct drm_backend *backend) +{ + struct drm_pending_state *ret; + + ret = calloc(1, sizeof(*ret)); + if (!ret) + return NULL; + + ret->backend = backend; + wl_list_init(&ret->output_list); + + return ret; +} + +/** + * Free a drm_pending_state structure + * + * Frees a pending_state structure, as well as any output_states connected + * to this pending state. + * + * @param pending_state Pending state structure to free + */ +void +drm_pending_state_free(struct drm_pending_state *pending_state) +{ + struct drm_output_state *output_state, *tmp; + + if (!pending_state) + return; + + wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, + link) { + drm_output_state_free(output_state); + } + + free(pending_state); +} + +/** + * Find an output state in a pending state + * + * Given a pending_state structure, find the output_state for a particular + * output. + * + * @param pending_state Pending state structure to search + * @param output Output to find state for + * @returns Output state if present, or NULL if not + */ +struct drm_output_state * +drm_pending_state_get_output(struct drm_pending_state *pending_state, + struct drm_output *output) +{ + struct drm_output_state *output_state; + + wl_list_for_each(output_state, &pending_state->output_list, link) { + if (output_state->output == output) + return output_state; + } + + return NULL; +} diff --git a/libweston/backend-drm/state-propose.c b/libweston/backend-drm/state-propose.c new file mode 100644 index 0000000..b403e30 --- /dev/null +++ b/libweston/backend-drm/state-propose.c @@ -0,0 +1,1149 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2017, 2018 Collabora, Ltd. + * Copyright © 2017, 2018 General Electric Company + * Copyright (c) 2018 DisplayLink (UK) Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include +#include +#include + +#include "drm-internal.h" + +#include "linux-dmabuf.h" +#include "presentation-time-server-protocol.h" + +enum drm_output_propose_state_mode { + DRM_OUTPUT_PROPOSE_STATE_MIXED, /**< mix renderer & planes */ + DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY, /**< only assign to renderer & cursor */ + DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY, /**< no renderer use, only planes */ +}; + +static const char *const drm_output_propose_state_mode_as_string[] = { + [DRM_OUTPUT_PROPOSE_STATE_MIXED] = "mixed state", + [DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY] = "render-only state", + [DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY] = "plane-only state" +}; + +static const char * +drm_propose_state_mode_to_string(enum drm_output_propose_state_mode mode) +{ + if (mode < 0 || mode >= ARRAY_LENGTH(drm_output_propose_state_mode_as_string)) + return " unknown compositing mode"; + + return drm_output_propose_state_mode_as_string[mode]; +} + +static void +drm_output_add_zpos_plane(struct drm_plane *plane, struct wl_list *planes) +{ + struct drm_backend *b = plane->backend; + struct drm_plane_zpos *h_plane; + struct drm_plane_zpos *plane_zpos; + + plane_zpos = zalloc(sizeof(*plane_zpos)); + if (!plane_zpos) + return; + + plane_zpos->plane = plane; + + drm_debug(b, "\t\t\t\t[plane] plane %d added to candidate list\n", + plane->plane_id); + + if (wl_list_empty(planes)) { + wl_list_insert(planes, &plane_zpos->link); + return; + } + + h_plane = wl_container_of(planes->next, h_plane, link); + if (h_plane->plane->zpos_max >= plane->zpos_max) { + wl_list_insert(planes->prev, &plane_zpos->link); + } else { + struct drm_plane_zpos *p_zpos = NULL; + + if (wl_list_length(planes) == 1) { + wl_list_insert(planes->prev, &plane_zpos->link); + return; + } + + wl_list_for_each(p_zpos, planes, link) { + if (p_zpos->plane->zpos_max > + plane_zpos->plane->zpos_max) + break; + } + + wl_list_insert(p_zpos->link.prev, &plane_zpos->link); + } +} + +static void +drm_output_destroy_zpos_plane(struct drm_plane_zpos *plane_zpos) +{ + wl_list_remove(&plane_zpos->link); + free(plane_zpos); +} + +static bool +drm_output_check_plane_has_view_assigned(struct drm_plane *plane, + struct drm_output_state *output_state) +{ + struct drm_plane_state *ps; + wl_list_for_each(ps, &output_state->plane_list, link) { + if (ps->plane == plane && ps->fb) + return true; + } + return false; +} + +static bool +drm_output_plane_has_valid_format(struct drm_plane *plane, + struct drm_output_state *state, + struct drm_fb *fb) +{ + struct drm_backend *b = plane->backend; + unsigned int i; + + if (!fb) + return false; + + /* Check whether the format is supported */ + for (i = 0; i < plane->count_formats; i++) { + unsigned int j; + + if (plane->formats[i].format != fb->format->format) + continue; + + if (fb->modifier == DRM_FORMAT_MOD_INVALID) + return true; + + for (j = 0; j < plane->formats[i].count_modifiers; j++) { + if (plane->formats[i].modifiers[j] == fb->modifier) + return true; + } + } + + drm_debug(b, "\t\t\t\t[%s] not placing view on %s: " + "no free %s planes matching format %s (0x%lx) " + "modifier 0x%llx\n", + drm_output_get_plane_type_name(plane), + drm_output_get_plane_type_name(plane), + drm_output_get_plane_type_name(plane), + fb->format->drm_format_name, + (unsigned long) fb->format, + (unsigned long long) fb->modifier); + + return false; +} + +static bool +drm_output_plane_cursor_has_valid_format(struct weston_view *ev) +{ + struct wl_shm_buffer *shmbuf = + wl_shm_buffer_get(ev->surface->buffer_ref.buffer->resource); + + if (shmbuf && wl_shm_buffer_get_format(shmbuf) == WL_SHM_FORMAT_ARGB8888) + return true; + + return false; +} + +static struct drm_plane_state * +drm_output_prepare_overlay_view(struct drm_plane *plane, + struct drm_output_state *output_state, + struct weston_view *ev, + enum drm_output_propose_state_mode mode, + struct drm_fb *fb, uint64_t zpos) +{ + struct drm_output *output = output_state->output; + struct weston_compositor *ec = output->base.compositor; + struct drm_backend *b = to_drm_backend(ec); + struct drm_plane_state *state = NULL; + int ret; + + assert(!b->sprites_are_broken); + assert(b->atomic_modeset); + + if (!fb) { + drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " + " couldn't get fb\n", ev); + return NULL; + } + + state = drm_output_state_get_plane(output_state, plane); + /* we can't have a 'pending' framebuffer as never set one before reaching here */ + assert(!state->fb); + + state->ev = ev; + state->output = output; + + if (!drm_plane_state_coords_for_view(state, ev, zpos)) { + drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " + "unsuitable transform\n", ev); + drm_plane_state_put_back(state); + state = NULL; + goto out; + } + + /* If the surface buffer has an in-fence fd, but the plane + * doesn't support fences, we can't place the buffer on this + * plane. */ + if (ev->surface->acquire_fence_fd >= 0 && + plane->props[WDRM_PLANE_IN_FENCE_FD].prop_id == 0) { + drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " + "no in-fence support\n", ev); + drm_plane_state_put_back(state); + state = NULL; + goto out; + } + + /* We hold one reference for the lifetime of this function; from + * calling drm_fb_get_from_view() in drm_output_prepare_plane_view(), + * so, we take another reference here to live within the state. */ + state->fb = drm_fb_ref(fb); + + state->in_fence_fd = ev->surface->acquire_fence_fd; + + /* In planes-only mode, we don't have an incremental state to + * test against, so we just hope it'll work. */ + if (mode == DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY) { + drm_debug(b, "\t\t\t[overlay] provisionally placing " + "view %p on overlay %lu in planes-only mode\n", + ev, (unsigned long) plane->plane_id); + goto out; + } + + ret = drm_pending_state_test(output_state->pending_state); + if (ret == 0) { + drm_debug(b, "\t\t\t[overlay] provisionally placing " + "view %p on overlay %d in mixed mode\n", + ev, plane->plane_id); + goto out; + } + + drm_debug(b, "\t\t\t[overlay] not placing view %p on overlay %lu " + "in mixed mode: kernel test failed\n", + ev, (unsigned long) plane->plane_id); + + drm_plane_state_put_back(state); + state = NULL; + +out: + return state; +} + +#ifdef BUILD_DRM_GBM +/** + * Update the image for the current cursor surface + * + * @param plane_state DRM cursor plane state + * @param ev Source view for cursor + */ +static void +cursor_bo_update(struct drm_plane_state *plane_state, struct weston_view *ev) +{ + struct drm_backend *b = plane_state->plane->backend; + struct gbm_bo *bo = plane_state->fb->bo; + struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; + uint32_t buf[b->cursor_width * b->cursor_height]; + int32_t stride; + uint8_t *s; + int i; + + assert(buffer && buffer->shm_buffer); + assert(buffer->shm_buffer == wl_shm_buffer_get(buffer->resource)); + assert(buffer->width <= b->cursor_width); + assert(buffer->height <= b->cursor_height); + + memset(buf, 0, sizeof buf); + stride = wl_shm_buffer_get_stride(buffer->shm_buffer); + s = wl_shm_buffer_get_data(buffer->shm_buffer); + + wl_shm_buffer_begin_access(buffer->shm_buffer); + for (i = 0; i < buffer->height; i++) + memcpy(buf + i * b->cursor_width, + s + i * stride, + buffer->width * 4); + wl_shm_buffer_end_access(buffer->shm_buffer); + + if (gbm_bo_write(bo, buf, sizeof buf) < 0) + weston_log("failed update cursor: %s\n", strerror(errno)); +} + +static struct drm_plane_state * +drm_output_prepare_cursor_view(struct drm_output_state *output_state, + struct weston_view *ev, uint64_t zpos) +{ + struct drm_output *output = output_state->output; + struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_plane *plane = output->cursor_plane; + struct drm_plane_state *plane_state; + bool needs_update = false; + const char *p_name = drm_output_get_plane_type_name(plane); + + assert(!b->cursors_are_broken); + + if (!plane) + return NULL; + + if (!plane->state_cur->complete) + return NULL; + + if (plane->state_cur->output && plane->state_cur->output != output) + return NULL; + + /* We use GBM to import SHM buffers. */ + if (b->gbm == NULL) + return NULL; + + plane_state = + drm_output_state_get_plane(output_state, output->cursor_plane); + + if (plane_state && plane_state->fb) + return NULL; + + /* We can't scale with the legacy API, and we don't try to account for + * simple cropping/translation in cursor_bo_update. */ + plane_state->output = output; + if (!drm_plane_state_coords_for_view(plane_state, ev, zpos)) { + drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " + "unsuitable transform\n", p_name, ev, p_name); + goto err; + } + + if (plane_state->src_x != 0 || plane_state->src_y != 0 || + plane_state->src_w > (unsigned) b->cursor_width << 16 || + plane_state->src_h > (unsigned) b->cursor_height << 16 || + plane_state->src_w != plane_state->dest_w << 16 || + plane_state->src_h != plane_state->dest_h << 16) { + drm_debug(b, "\t\t\t\t[%s] not assigning view %p to %s plane " + "(positioning requires cropping or scaling)\n", + p_name, ev, p_name); + goto err; + } + + /* Since we're setting plane state up front, we need to work out + * whether or not we need to upload a new cursor. We can't use the + * plane damage, since the planes haven't actually been calculated + * yet: instead try to figure it out directly. KMS cursor planes are + * pretty unique here, in that they lie partway between a Weston plane + * (direct scanout) and a renderer. */ + if (ev != output->cursor_view || + pixman_region32_not_empty(&ev->surface->damage)) { + output->current_cursor++; + output->current_cursor = + output->current_cursor % + ARRAY_LENGTH(output->gbm_cursor_fb); + needs_update = true; + } + + output->cursor_view = ev; + plane_state->ev = ev; + + plane_state->fb = + drm_fb_ref(output->gbm_cursor_fb[output->current_cursor]); + + if (needs_update) { + drm_debug(b, "\t\t\t\t[%s] copying new content to cursor BO\n", p_name); + cursor_bo_update(plane_state, ev); + } + + /* The cursor API is somewhat special: in cursor_bo_update(), we upload + * a buffer which is always cursor_width x cursor_height, even if the + * surface we want to promote is actually smaller than this. Manually + * mangle the plane state to deal with this. */ + plane_state->src_w = b->cursor_width << 16; + plane_state->src_h = b->cursor_height << 16; + plane_state->dest_w = b->cursor_width; + plane_state->dest_h = b->cursor_height; + + drm_debug(b, "\t\t\t\t[%s] provisionally assigned view %p to cursor\n", + p_name, ev); + + return plane_state; + +err: + drm_plane_state_put_back(plane_state); + return NULL; +} +#else +static struct drm_plane_state * +drm_output_prepare_cursor_view(struct drm_output_state *output_state, + struct weston_view *ev, uint64_t zpos) +{ + return NULL; +} +#endif + +static struct drm_plane_state * +drm_output_prepare_scanout_view(struct drm_output_state *output_state, + struct weston_view *ev, + enum drm_output_propose_state_mode mode, + struct drm_fb *fb, uint64_t zpos) +{ + struct drm_output *output = output_state->output; + struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_plane *scanout_plane = output->scanout_plane; + struct drm_plane_state *state; + const char *p_name = drm_output_get_plane_type_name(scanout_plane); + + assert(!b->sprites_are_broken); + assert(b->atomic_modeset); + assert(mode == DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY); + + /* Check the view spans exactly the output size, calculated in the + * logical co-ordinate space. */ + if (!weston_view_matches_output_entirely(ev, &output->base)) { + drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " + " view does not match output entirely\n", + p_name, ev, p_name); + return NULL; + } + + /* If the surface buffer has an in-fence fd, but the plane doesn't + * support fences, we can't place the buffer on this plane. */ + if (ev->surface->acquire_fence_fd >= 0 && + scanout_plane->props[WDRM_PLANE_IN_FENCE_FD].prop_id == 0) { + drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " + "no in-fence support\n", p_name, ev, p_name); + return NULL; + } + + if (!fb) { + drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " + " couldn't get fb\n", p_name, ev, p_name); + return NULL; + } + + state = drm_output_state_get_plane(output_state, scanout_plane); + + /* The only way we can already have a buffer in the scanout plane is + * if we are in mixed mode, or if a client buffer has already been + * placed into scanout. The former case will never call into here, + * and in the latter case, the view must have been marked as occluded, + * meaning we should never have ended up here. */ + assert(!state->fb); + + /* take another reference here to live within the state */ + state->fb = drm_fb_ref(fb); + state->ev = ev; + state->output = output; + if (!drm_plane_state_coords_for_view(state, ev, zpos)) { + drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " + "unsuitable transform\n", p_name, ev, p_name); + goto err; + } + + if (state->dest_x != 0 || state->dest_y != 0 || + state->dest_w != (unsigned) output->base.current_mode->width || + state->dest_h != (unsigned) output->base.current_mode->height) { + drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " + " invalid plane state\n", p_name, ev, p_name); + goto err; + } + + state->in_fence_fd = ev->surface->acquire_fence_fd; + + /* In plane-only mode, we don't need to test the state now, as we + * will only test it once at the end. */ + return state; + +err: + drm_plane_state_put_back(state); + return NULL; +} + +static bool +drm_output_plane_view_has_valid_format(struct drm_plane *plane, + struct drm_output_state *state, + struct weston_view *ev, + struct drm_fb *fb) +{ + /* depending on the type of the plane we have different requirements */ + switch (plane->type) { + case WDRM_PLANE_TYPE_CURSOR: + return drm_output_plane_cursor_has_valid_format(ev); + case WDRM_PLANE_TYPE_OVERLAY: + return drm_output_plane_has_valid_format(plane, state, fb); + case WDRM_PLANE_TYPE_PRIMARY: + return drm_output_plane_has_valid_format(plane, state, fb); + default: + assert(0); + return false; + } + + return false; +} + +static struct drm_plane_state * +drm_output_try_view_on_plane(struct drm_plane *plane, + struct drm_output_state *state, + struct weston_view *ev, + enum drm_output_propose_state_mode mode, + struct drm_fb *fb, uint64_t zpos) +{ + struct drm_backend *b = state->pending_state->backend; + struct weston_output *wet_output = &state->output->base; + bool view_matches_entire_output, scanout_has_view_assigned; + struct drm_plane *scanout_plane = state->output->scanout_plane; + struct drm_plane_state *ps = NULL; + const char *p_name = drm_output_get_plane_type_name(plane); + enum { + NO_PLANES, /* generic err-handle */ + NO_PLANES_ACCEPTED, + PLACED_ON_PLANE, + } availability = NO_PLANES; + + /* sanity checks in case we over/underflow zpos or pass incorrect + * values */ + assert(zpos <= plane->zpos_max || + zpos != DRM_PLANE_ZPOS_INVALID_PLANE); + + switch (plane->type) { + case WDRM_PLANE_TYPE_CURSOR: + if (b->cursors_are_broken) { + availability = NO_PLANES_ACCEPTED; + goto out; + } + + ps = drm_output_prepare_cursor_view(state, ev, zpos); + if (ps) + availability = PLACED_ON_PLANE; + break; + case WDRM_PLANE_TYPE_OVERLAY: + /* do not attempt to place it in the overlay if we don't have + * anything in the scanout/primary and the view doesn't cover + * the entire output */ + view_matches_entire_output = + weston_view_matches_output_entirely(ev, wet_output); + scanout_has_view_assigned = + drm_output_check_plane_has_view_assigned(scanout_plane, + state); + + if (view_matches_entire_output && !scanout_has_view_assigned) { + availability = NO_PLANES_ACCEPTED; + goto out; + } + + ps = drm_output_prepare_overlay_view(plane, state, ev, mode, + fb, zpos); + if (ps) + availability = PLACED_ON_PLANE; + break; + case WDRM_PLANE_TYPE_PRIMARY: + if (mode != DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY) { + availability = NO_PLANES_ACCEPTED; + goto out; + } + + ps = drm_output_prepare_scanout_view(state, ev, mode, + fb, zpos); + if (ps) + availability = PLACED_ON_PLANE; + break; + default: + assert(0); + break; + } + +out: + switch (availability) { + case NO_PLANES: + /* set initial to this catch-all case, such that + * prepare_cursor/overlay/scanout() should have/contain the + * reason for failling */ + break; + case NO_PLANES_ACCEPTED: + drm_debug(b, "\t\t\t\t[plane] plane %d refusing to " + "place view %p in %s\n", + plane->plane_id, ev, p_name); + break; + case PLACED_ON_PLANE: + break; + } + + + return ps; +} + +static void +drm_output_check_zpos_plane_states(struct drm_output_state *state) +{ + struct drm_plane_state *ps; + + wl_list_for_each(ps, &state->plane_list, link) { + struct wl_list *next_node = ps->link.next; + bool found_dup = false; + + /* skip any plane that is not enabled */ + if (!ps->fb) + continue; + + assert(ps->zpos != DRM_PLANE_ZPOS_INVALID_PLANE); + + /* find another plane with the same zpos value */ + if (next_node == &state->plane_list) + break; + + while (next_node && next_node != &state->plane_list) { + struct drm_plane_state *ps_next; + + ps_next = container_of(next_node, + struct drm_plane_state, + link); + + if (ps->zpos == ps_next->zpos) { + found_dup = true; + break; + } + next_node = next_node->next; + } + + /* this should never happen so exit hard in case + * we screwed up that bad */ + assert(!found_dup); + } +} + +static struct drm_plane_state * +drm_output_prepare_plane_view(struct drm_output_state *state, + struct weston_view *ev, + enum drm_output_propose_state_mode mode, + struct drm_plane_state *scanout_state, + uint64_t current_lowest_zpos) +{ + struct drm_output *output = state->output; + struct drm_backend *b = to_drm_backend(output->base.compositor); + + struct drm_plane_state *ps = NULL; + struct drm_plane *plane; + struct drm_plane_zpos *p_zpos, *p_zpos_next; + struct wl_list zpos_candidate_list; + + struct drm_fb *fb; + + wl_list_init(&zpos_candidate_list); + + /* check view for valid buffer, doesn't make sense to even try */ + if (!weston_view_has_valid_buffer(ev)) + return ps; + + fb = drm_fb_get_from_view(state, ev); + + /* assemble a list with possible candidates */ + wl_list_for_each(plane, &b->plane_list, link) { + if (!drm_plane_is_available(plane, output)) + continue; + + if (drm_output_check_plane_has_view_assigned(plane, state)) { + drm_debug(b, "\t\t\t\t[plane] not adding plane %d to" + " candidate list: view already assigned " + "to a plane\n", plane->plane_id); + continue; + } + + if (plane->zpos_min >= current_lowest_zpos) { + drm_debug(b, "\t\t\t\t[plane] not adding plane %d to " + "candidate list: minium zpos (%"PRIu64") " + "plane's above current lowest zpos " + "(%"PRIu64")\n", plane->plane_id, + plane->zpos_min, current_lowest_zpos); + continue; + } + + if (mode == DRM_OUTPUT_PROPOSE_STATE_MIXED) { + assert(scanout_state != NULL); + if (scanout_state->zpos >= plane->zpos_max) { + drm_debug(b, "\t\t\t\t[plane] not adding plane %d to " + "candidate list: primary's zpos " + "value (%"PRIu64") higher than " + "plane's maximum value (%"PRIu64")\n", + plane->plane_id, scanout_state->zpos, + plane->zpos_max); + continue; + } + } + + if (mode == DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY && + (plane->type == WDRM_PLANE_TYPE_OVERLAY || + plane->type == WDRM_PLANE_TYPE_PRIMARY)) { + drm_debug(b, "\t\t\t\t[plane] not adding plane %d to " + "candidate list: renderer-only mode\n", + plane->plane_id); + continue; + } + + if (plane->type != WDRM_PLANE_TYPE_CURSOR && + b->sprites_are_broken) { + drm_debug(b, "\t\t\t\t[plane] not adding plane %d, type %s to " + "candidate list: sprites are broken!\n", + plane->plane_id, + drm_output_get_plane_type_name(plane)); + continue; + } + + if (!drm_output_plane_view_has_valid_format(plane, state, ev, fb)) { + drm_debug(b, "\t\t\t\t[plane] not adding plane %d to " + "candidate list: invalid pixel format\n", + plane->plane_id); + continue; + } + + drm_output_add_zpos_plane(plane, &zpos_candidate_list); + } + + /* go over the potential candidate list and try to find a possible + * plane suitable for \c ev; start with the highest zpos value of a + * plane to maximize our chances, but do note we pass the zpos value + * based on current tracked value by \c current_lowest_zpos_in_use */ + while (!wl_list_empty(&zpos_candidate_list)) { + struct drm_plane_zpos *head_p_zpos = + wl_container_of(zpos_candidate_list.next, + head_p_zpos, link); + struct drm_plane *plane = head_p_zpos->plane; + const char *p_name = drm_output_get_plane_type_name(plane); + uint64_t zpos; + + if (current_lowest_zpos == DRM_PLANE_ZPOS_INVALID_PLANE) + zpos = plane->zpos_max; + else + zpos = MIN(current_lowest_zpos - 1, plane->zpos_max); + + drm_debug(b, "\t\t\t\t[plane] plane %d picked " + "from candidate list, type: %s\n", + plane->plane_id, p_name); + + ps = drm_output_try_view_on_plane(plane, state, ev, + mode, fb, zpos); + drm_output_destroy_zpos_plane(head_p_zpos); + if (ps) { + drm_debug(b, "\t\t\t\t[view] view %p has been placed to " + "%s plane with computed zpos %"PRIu64"\n", + ev, p_name, zpos); + break; + } + } + + wl_list_for_each_safe(p_zpos, p_zpos_next, &zpos_candidate_list, link) + drm_output_destroy_zpos_plane(p_zpos); + + drm_fb_unref(fb); + return ps; +} + +static struct drm_output_state * +drm_output_propose_state(struct weston_output *output_base, + struct drm_pending_state *pending_state, + enum drm_output_propose_state_mode mode) +{ + struct drm_output *output = to_drm_output(output_base); + struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_output_state *state; + struct drm_plane_state *scanout_state = NULL; + struct weston_view *ev; + + pixman_region32_t surface_overlap, renderer_region, planes_region; + pixman_region32_t occluded_region; + + bool renderer_ok = (mode != DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY); + int ret; + uint64_t current_lowest_zpos = DRM_PLANE_ZPOS_INVALID_PLANE; + + assert(!output->state_last); + state = drm_output_state_duplicate(output->state_cur, + pending_state, + DRM_OUTPUT_STATE_CLEAR_PLANES); + + /* We implement mixed mode by progressively creating and testing + * incremental states, of scanout + overlay + cursor. Since we + * walk our views top to bottom, the scanout plane is last, however + * we always need it in our scene for the test modeset to be + * meaningful. To do this, we steal a reference to the last + * renderer framebuffer we have, if we think it's basically + * compatible. If we don't have that, then we conservatively fall + * back to only using the renderer for this repaint. */ + if (mode == DRM_OUTPUT_PROPOSE_STATE_MIXED) { + struct drm_plane *plane = output->scanout_plane; + struct drm_fb *scanout_fb = plane->state_cur->fb; + + if (!scanout_fb || + (scanout_fb->type != BUFFER_GBM_SURFACE && + scanout_fb->type != BUFFER_PIXMAN_DUMB)) { + drm_debug(b, "\t\t[state] cannot propose mixed mode: " + "for output %s (%lu): no previous renderer " + "fb\n", + output->base.name, + (unsigned long) output->base.id); + drm_output_state_free(state); + return NULL; + } + + if (scanout_fb->width != output_base->current_mode->width || + scanout_fb->height != output_base->current_mode->height) { + drm_debug(b, "\t\t[state] cannot propose mixed mode " + "for output %s (%lu): previous fb has " + "different size\n", + output->base.name, + (unsigned long) output->base.id); + drm_output_state_free(state); + return NULL; + } + + scanout_state = drm_plane_state_duplicate(state, + plane->state_cur); + /* assign the primary primary the lowest zpos value */ + scanout_state->zpos = plane->zpos_min; + drm_debug(b, "\t\t[state] using renderer FB ID %lu for mixed " + "mode for output %s (%lu)\n", + (unsigned long) scanout_fb->fb_id, output->base.name, + (unsigned long) output->base.id); + drm_debug(b, "\t\t[state] scanout will use for zpos %"PRIu64"\n", + scanout_state->zpos); + } + + /* - renderer_region contains the total region which which will be + * covered by the renderer + * - planes_region contains the total region which has been covered by + * hardware planes + * - occluded_region contains the total region which which will be + * covered by the renderer and hardware planes, where the view's + * visible-and-opaque region is added in both cases (the view's + * opaque region accumulates there for each view); it is being used + * to skip the view, if it is completely occluded; includes the + * situation where occluded_region covers entire output's region. + */ + pixman_region32_init(&renderer_region); + pixman_region32_init(&planes_region); + pixman_region32_init(&occluded_region); + + wl_list_for_each(ev, &output_base->compositor->view_list, link) { + struct drm_plane_state *ps = NULL; + bool force_renderer = false; + pixman_region32_t clipped_view; + bool totally_occluded = false; + + drm_debug(b, "\t\t\t[view] evaluating view %p for " + "output %s (%lu)\n", + ev, output->base.name, + (unsigned long) output->base.id); + + /* If this view doesn't touch our output at all, there's no + * reason to do anything with it. */ + if (!(ev->output_mask & (1u << output->base.id))) { + drm_debug(b, "\t\t\t\t[view] ignoring view %p " + "(not on our output)\n", ev); + continue; + } + + /* Ignore views we know to be totally occluded. */ + pixman_region32_init(&clipped_view); + pixman_region32_intersect(&clipped_view, + &ev->transform.boundingbox, + &output->base.region); + + pixman_region32_init(&surface_overlap); + pixman_region32_subtract(&surface_overlap, &clipped_view, + &occluded_region); + /* if the view is completely occluded then ignore that + * view; includes the case where occluded_region covers + * the entire output */ + totally_occluded = !pixman_region32_not_empty(&surface_overlap); + if (totally_occluded) { + drm_debug(b, "\t\t\t\t[view] ignoring view %p " + "(occluded on our output)\n", ev); + pixman_region32_fini(&surface_overlap); + pixman_region32_fini(&clipped_view); + continue; + } + + /* We only assign planes to views which are exclusively present + * on our output. */ + if (ev->output_mask != (1u << output->base.id)) { + drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " + "(on multiple outputs)\n", ev); + force_renderer = true; + } + + if (!weston_view_has_valid_buffer(ev)) { + drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " + "(no buffer available)\n", ev); + force_renderer = true; + } + + /* Since we process views from top to bottom, we know that if + * the view intersects the calculated renderer region, it must + * be part of, or occluded by, it, and cannot go on a plane. */ + pixman_region32_intersect(&surface_overlap, &renderer_region, + &clipped_view); + if (pixman_region32_not_empty(&surface_overlap)) { + drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " + "(occluded by renderer views)\n", ev); + force_renderer = true; + } + pixman_region32_fini(&surface_overlap); + + /* In case of enforced mode of content-protection do not + * assign planes for a protected surface on an unsecured output. + */ + if (ev->surface->protection_mode == WESTON_SURFACE_PROTECTION_MODE_ENFORCED && + ev->surface->desired_protection > output_base->current_protection) { + drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " + "(enforced protection mode on unsecured output)\n", ev); + force_renderer = true; + } + + if (!force_renderer) { + drm_debug(b, "\t\t\t[plane] started with zpos %"PRIu64"\n", + current_lowest_zpos); + ps = drm_output_prepare_plane_view(state, ev, mode, + scanout_state, + current_lowest_zpos); + } + + if (ps) { + current_lowest_zpos = ps->zpos; + drm_debug(b, "\t\t\t[plane] next zpos to use %"PRIu64"\n", + current_lowest_zpos); + + /* If we have been assigned to an overlay or scanout + * plane, add this area to the occluded region, so + * other views are known to be behind it. The cursor + * plane, however, is special, in that it blends with + * the content underneath it: the area should neither + * be added to the renderer region nor the occluded + * region. */ + if (ps->plane->type != WDRM_PLANE_TYPE_CURSOR) { + pixman_region32_union(&planes_region, + &planes_region, + &clipped_view); + + if (!weston_view_is_opaque(ev, &clipped_view)) + pixman_region32_intersect(&clipped_view, + &clipped_view, + &ev->transform.opaque); + /* the visible-and-opaque region of this view + * will occlude views underneath it */ + pixman_region32_union(&occluded_region, + &occluded_region, + &clipped_view); + + pixman_region32_fini(&clipped_view); + + } + continue; + } + + /* We have been assigned to the primary (renderer) plane: + * check if this is OK, and add ourselves to the renderer + * region if so. */ + if (!renderer_ok) { + drm_debug(b, "\t\t[view] failing state generation: " + "placing view %p to renderer not allowed\n", + ev); + pixman_region32_fini(&clipped_view); + goto err_region; + } + + pixman_region32_union(&renderer_region, + &renderer_region, + &clipped_view); + + if (!weston_view_is_opaque(ev, &clipped_view)) + pixman_region32_intersect(&clipped_view, + &clipped_view, + &ev->transform.opaque); + + pixman_region32_union(&occluded_region, + &occluded_region, + &clipped_view); + + pixman_region32_fini(&clipped_view); + + drm_debug(b, "\t\t\t\t[view] view %p will be placed " + "on the renderer\n", ev); + } + + pixman_region32_fini(&renderer_region); + pixman_region32_fini(&planes_region); + pixman_region32_fini(&occluded_region); + + /* In renderer-only mode, we can't test the state as we don't have a + * renderer buffer yet. */ + if (mode == DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY) + return state; + + /* check if we have invalid zpos values, like duplicate(s) */ + drm_output_check_zpos_plane_states(state); + + /* Check to see if this state will actually work. */ + ret = drm_pending_state_test(state->pending_state); + if (ret != 0) { + drm_debug(b, "\t\t[view] failing state generation: " + "atomic test not OK\n"); + goto err; + } + + /* Counterpart to duplicating scanout state at the top of this + * function: if we have taken a renderer framebuffer and placed it in + * the pending state in order to incrementally test overlay planes, + * remove it now. */ + if (mode == DRM_OUTPUT_PROPOSE_STATE_MIXED) { + assert(scanout_state->fb->type == BUFFER_GBM_SURFACE || + scanout_state->fb->type == BUFFER_PIXMAN_DUMB); + drm_plane_state_put_back(scanout_state); + } + return state; + +err_region: + pixman_region32_fini(&renderer_region); + pixman_region32_fini(&occluded_region); +err: + drm_output_state_free(state); + return NULL; +} + +void +drm_assign_planes(struct weston_output *output_base, void *repaint_data) +{ + struct drm_backend *b = to_drm_backend(output_base->compositor); + struct drm_pending_state *pending_state = repaint_data; + struct drm_output *output = to_drm_output(output_base); + struct drm_output_state *state = NULL; + struct drm_plane_state *plane_state; + struct weston_view *ev; + struct weston_plane *primary = &output_base->compositor->primary_plane; + enum drm_output_propose_state_mode mode = DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY; + + drm_debug(b, "\t[repaint] preparing state for output %s (%lu)\n", + output_base->name, (unsigned long) output_base->id); + + if (!b->sprites_are_broken && !output->virtual) { + drm_debug(b, "\t[repaint] trying planes-only build state\n"); + state = drm_output_propose_state(output_base, pending_state, mode); + if (!state) { + drm_debug(b, "\t[repaint] could not build planes-only " + "state, trying mixed\n"); + mode = DRM_OUTPUT_PROPOSE_STATE_MIXED; + state = drm_output_propose_state(output_base, + pending_state, + mode); + } + if (!state) { + drm_debug(b, "\t[repaint] could not build mixed-mode " + "state, trying renderer-only\n"); + } + } else { + drm_debug(b, "\t[state] no overlay plane support\n"); + } + + if (!state) { + mode = DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY; + state = drm_output_propose_state(output_base, pending_state, + mode); + } + + assert(state); + drm_debug(b, "\t[repaint] Using %s composition\n", + drm_propose_state_mode_to_string(mode)); + + wl_list_for_each(ev, &output_base->compositor->view_list, link) { + struct drm_plane *target_plane = NULL; + + /* If this view doesn't touch our output at all, there's no + * reason to do anything with it. */ + if (!(ev->output_mask & (1u << output->base.id))) + continue; + + /* Test whether this buffer can ever go into a plane: + * non-shm, or small enough to be a cursor. + * + * Also, keep a reference when using the pixman renderer. + * That makes it possible to do a seamless switch to the GL + * renderer and since the pixman renderer keeps a reference + * to the buffer anyway, there is no side effects. + */ + if (b->use_pixman || + (weston_view_has_valid_buffer(ev) && + (!wl_shm_buffer_get(ev->surface->buffer_ref.buffer->resource) || + (ev->surface->width <= b->cursor_width && + ev->surface->height <= b->cursor_height)))) + ev->surface->keep_buffer = true; + else + ev->surface->keep_buffer = false; + + /* This is a bit unpleasant, but lacking a temporary place to + * hang a plane off the view, we have to do a nested walk. + * Our first-order iteration has to be planes rather than + * views, because otherwise we won't reset views which were + * previously on planes to being on the primary plane. */ + wl_list_for_each(plane_state, &state->plane_list, link) { + if (plane_state->ev == ev) { + plane_state->ev = NULL; + target_plane = plane_state->plane; + break; + } + } + + if (target_plane) { + drm_debug(b, "\t[repaint] view %p on %s plane %lu\n", + ev, plane_type_enums[target_plane->type].name, + (unsigned long) target_plane->plane_id); + weston_view_move_to_plane(ev, &target_plane->base); + } else { + drm_debug(b, "\t[repaint] view %p using renderer " + "composition\n", ev); + weston_view_move_to_plane(ev, primary); + } + + if (!target_plane || + target_plane->type == WDRM_PLANE_TYPE_CURSOR) { + /* cursor plane & renderer involve a copy */ + ev->psf_flags = 0; + } else { + /* All other planes are a direct scanout of a + * single client buffer. + */ + ev->psf_flags = WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY; + } + } + + /* We rely on output->cursor_view being both an accurate reflection of + * the cursor plane's state, but also being maintained across repaints + * to avoid unnecessary damage uploads, per the comment in + * drm_output_prepare_cursor_view. In the event that we go from having + * a cursor view to not having a cursor view, we need to clear it. */ + if (output->cursor_view) { + plane_state = + drm_output_state_get_existing_plane(state, + output->cursor_plane); + if (!plane_state || !plane_state->fb) + output->cursor_view = NULL; + } +} diff --git a/libweston/backend-drm/vaapi-recorder.c b/libweston/backend-drm/vaapi-recorder.c new file mode 100644 index 0000000..0a74357 --- /dev/null +++ b/libweston/backend-drm/vaapi-recorder.c @@ -0,0 +1,1161 @@ +/* + * Copyright (c) 2012 Intel Corporation. All Rights Reserved. + * Copyright © 2013 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#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 error; + 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 *); + +/* bitstream 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(unsigned 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; +} + +enum output_write_status { + OUTPUT_WRITE_SUCCESS, + OUTPUT_WRITE_OVERFLOW, + OUTPUT_WRITE_FATAL +}; + +static enum output_write_status +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 OUTPUT_WRITE_FATAL; + + if (segment->status & VA_CODED_BUF_STATUS_SLICE_OVERFLOW_MASK) { + r->encoder.output_size *= 2; + vaUnmapBuffer(r->va_dpy, output_buf); + return OUTPUT_WRITE_OVERFLOW; + } + + count = write(r->output_fd, segment->buf, segment->size); + + vaUnmapBuffer(r->va_dpy, output_buf); + + if (count < 0) + return OUTPUT_WRITE_FATAL; + + return OUTPUT_WRITE_SUCCESS; +} + +static void +encoder_encode(struct vaapi_recorder *r, VASurfaceID input) +{ + VABufferID output_buf = VA_INVALID_ID; + + VABufferID buffers[8]; + int count = 0; + int i, slice_type; + enum output_write_status ret; + + 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 == OUTPUT_WRITE_OVERFLOW); + + if (ret == OUTPUT_WRITE_FATAL) + r->error = errno; + + 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 = zalloc(sizeof *r); + if (r == NULL) + 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; +} + +int +vaapi_recorder_frame(struct vaapi_recorder *r, int prime_fd, int stride) +{ + int ret = 0; + + pthread_mutex_lock(&r->mutex); + + if (r->error) { + errno = r->error; + ret = -1; + goto unlock; + } + + /* 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); + +unlock: + pthread_mutex_unlock(&r->mutex); + + return ret; +} diff --git a/libweston/backend-drm/vaapi-recorder.h b/libweston/backend-drm/vaapi-recorder.h new file mode 100644 index 0000000..6b194aa --- /dev/null +++ b/libweston/backend-drm/vaapi-recorder.h @@ -0,0 +1,38 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#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); +int +vaapi_recorder_frame(struct vaapi_recorder *r, int fd, int stride); + +#endif /* _VAAPI_RECORDER_H_ */ diff --git a/libweston/backend-fbdev/fbdev.c b/libweston/backend-fbdev/fbdev.c new file mode 100755 index 0000000..2034877 --- /dev/null +++ b/libweston/backend-fbdev/fbdev.c @@ -0,0 +1,999 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2012 Raspberry Pi Foundation + * Copyright © 2013 Philip Withnall + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "shared/helpers.h" +#include +#include +#include "launcher-util.h" +#include "pixman-renderer.h" +#include "libinput-seat.h" +#include "presentation-time-server-protocol.h" + +struct fbdev_backend { + struct weston_backend base; + struct weston_compositor *compositor; + uint32_t prev_state; + + struct udev *udev; + struct udev_input input; + uint32_t output_transform; + 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_head { + struct weston_head base; + + /* Frame buffer details. */ + char *device; + struct fbdev_screeninfo fb_info; +}; + +struct fbdev_output { + struct fbdev_backend *backend; + struct weston_output base; + + struct weston_mode mode; + struct wl_event_source *finish_frame_timer; + + /* framebuffer mmap details */ + size_t buffer_length; + void *fb; + + /* pixman details. */ + pixman_image_t *hw_surface; +}; + +static const char default_seat[] = "seat0"; + +static inline struct fbdev_head * +to_fbdev_head(struct weston_head *base) +{ + return container_of(base, struct fbdev_head, base); +} + +static inline struct fbdev_output * +to_fbdev_output(struct weston_output *base) +{ + return container_of(base, struct fbdev_output, base); +} + +static inline struct fbdev_backend * +to_fbdev_backend(struct weston_compositor *base) +{ + return container_of(base->backend, struct fbdev_backend, base); +} + +static struct fbdev_head * +fbdev_output_get_head(struct fbdev_output *output) +{ + if (wl_list_length(&output->base.head_list) != 1) + return NULL; + + return container_of(output->base.head_list.next, + struct fbdev_head, base.output_link); +} + +static int +fbdev_output_start_repaint_loop(struct weston_output *output) +{ + struct timespec ts; + + weston_compositor_read_presentation_clock(output->compositor, &ts); + weston_output_finish_frame(output, &ts, WP_PRESENTATION_FEEDBACK_INVALID); + + return 0; +} + +static int +fbdev_output_repaint(struct weston_output *base, pixman_region32_t *damage, + void *repaint_data) +{ + struct fbdev_output *output = to_fbdev_output(base); + struct weston_compositor *ec = output->base.compositor; + + /* Repaint the damaged region onto the back buffer. */ + pixman_renderer_output_set_buffer(base, output->hw_surface); + ec->renderer->repaint_output(base, damage); + + /* 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); + + return 0; +} + +static int +finish_frame_handler(void *data) +{ + struct fbdev_output *output = data; + struct timespec ts; + + weston_compositor_read_presentation_clock(output->base.compositor, &ts); + weston_output_finish_frame(&output->base, &ts, 0); + + 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, ARGB + * and ABGR 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; + else if (vinfo->transp.offset >= vinfo->blue.offset && + vinfo->blue.offset >= vinfo->green.offset && + vinfo->green.offset >= vinfo->red.offset) + type = PIXMAN_TYPE_ABGR; + + 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 */ + + if (refresh_rate >= 1000) /* at least 1 Hz */ + return refresh_rate; + } + + return 60 * 1000; /* default to 60 Hz */ +} + +static int +fbdev_query_screen_info(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)); + info->id[sizeof(info->id)-1] = '\0'; + + 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(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 int +fbdev_wakeup_screen(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; + } + + /* force the framebuffer to wake up */ + varinfo.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; + + /* Set the device's screen information. */ + if (ioctl(fd, FBIOPUT_VSCREENINFO, &varinfo) < 0) { + return -1; + } + + return 1; +} + +/* Returns an FD for the frame buffer device. */ +static int +fbdev_frame_buffer_open(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(fd, screen_info) < 0) { + weston_log("Failed to get frame buffer info: %s\n", + strerror(errno)); + + close(fd); + return -1; + } + + /* Attempt to wake up the framebuffer device, needed for secondary + * framebuffer devices */ + if (fbdev_wakeup_screen(fd, screen_info) < 0) { + weston_log("Failed to activate framebuffer display. " + "Attempting to open output anyway.\n"); + } + + + return fd; +} + +/* Closes the FD on success or failure. */ +static int +fbdev_frame_buffer_map(struct fbdev_output *output, int fd) +{ + struct fbdev_head *head; + int retval = -1; + + head = fbdev_output_get_head(output); + + 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->buffer_length = head->fb_info.buffer_length; + output->fb = mmap(NULL, output->buffer_length, + PROT_WRITE, MAP_SHARED, fd, 0); + if (output->fb == MAP_FAILED) { + weston_log("Failed to mmap frame buffer: %s\n", + strerror(errno)); + output->fb = NULL; + goto out_close; + } + + /* Create a pixman image to wrap the memory mapped frame buffer. */ + output->hw_surface = + pixman_image_create_bits(head->fb_info.pixel_format, + head->fb_info.x_resolution, + head->fb_info.y_resolution, + output->fb, + head->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) { + munmap(output->fb, output->buffer_length); + output->fb = NULL; + } + +out_close: + if (fd >= 0) + close(fd); + + return retval; +} + +static void +fbdev_frame_buffer_unmap(struct fbdev_output *output) +{ + if (!output->fb) { + assert(!output->hw_surface); + return; + } + + weston_log("Unmapping fbdev frame buffer.\n"); + + if (output->hw_surface) + pixman_image_unref(output->hw_surface); + output->hw_surface = NULL; + + if (munmap(output->fb, output->buffer_length) < 0) + weston_log("Failed to munmap frame buffer: %s\n", + strerror(errno)); + + output->fb = NULL; +} + + +static int +fbdev_output_attach_head(struct weston_output *output_base, + struct weston_head *head_base) +{ + struct fbdev_output *output = to_fbdev_output(output_base); + struct fbdev_head *head = to_fbdev_head(head_base); + + /* Clones not supported. */ + if (!wl_list_empty(&output->base.head_list)) + return -1; + + /* only one static mode in list */ + output->mode.flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; + output->mode.width = head->fb_info.x_resolution; + output->mode.height = head->fb_info.y_resolution; + output->mode.refresh = head->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; + + return 0; +} + +static void fbdev_output_destroy(struct weston_output *base); + +static int +fbdev_output_enable(struct weston_output *base) +{ + struct fbdev_output *output = to_fbdev_output(base); + struct fbdev_backend *backend = to_fbdev_backend(base->compositor); + struct fbdev_head *head; + int fb_fd; + struct wl_event_loop *loop; + const struct pixman_renderer_output_options options = { + .use_shadow = true, + }; + + head = fbdev_output_get_head(output); + + /* Create the frame buffer. */ + fb_fd = fbdev_frame_buffer_open(head->device, &head->fb_info); + if (fb_fd < 0) { + weston_log("Creating frame buffer failed.\n"); + return -1; + } + + if (fbdev_frame_buffer_map(output, fb_fd) < 0) { + weston_log("Mapping frame buffer failed.\n"); + return -1; + } + + output->base.start_repaint_loop = fbdev_output_start_repaint_loop; + output->base.repaint = fbdev_output_repaint; + + if (pixman_renderer_output_create(&output->base, &options) < 0) + goto out_hw_surface; + + loop = wl_display_get_event_loop(backend->compositor->wl_display); + output->finish_frame_timer = + wl_event_loop_add_timer(loop, finish_frame_handler, output); + + 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_hw_surface: + fbdev_frame_buffer_unmap(output); + + return -1; +} + +static int +fbdev_output_disable(struct weston_output *base) +{ + struct fbdev_output *output = to_fbdev_output(base); + + if (!base->enabled) + return 0; + + wl_event_source_remove(output->finish_frame_timer); + output->finish_frame_timer = NULL; + + pixman_renderer_output_destroy(&output->base); + fbdev_frame_buffer_unmap(output); + + return 0; +} + +static struct fbdev_head * +fbdev_head_create(struct fbdev_backend *backend, const char *device) +{ + struct fbdev_head *head; + int fb_fd; + + head = zalloc(sizeof *head); + if (!head) + return NULL; + + head->device = strdup(device); + + /* Create the frame buffer. */ + fb_fd = fbdev_frame_buffer_open(head->device, &head->fb_info); + if (fb_fd < 0) { + weston_log("Creating frame buffer head failed.\n"); + goto out_free; + } + close(fb_fd); + + weston_head_init(&head->base, "fbdev"); + weston_head_set_connection_status(&head->base, true); + weston_head_set_monitor_strings(&head->base, "unknown", + head->fb_info.id, NULL); + weston_head_set_subpixel(&head->base, WL_OUTPUT_SUBPIXEL_UNKNOWN); + weston_head_set_physical_size(&head->base, head->fb_info.width_mm, + head->fb_info.height_mm); + + weston_compositor_add_head(backend->compositor, &head->base); + + weston_log("Created head '%s' for device %s (%s)\n", + head->base.name, head->device, head->base.model); + + return head; + +out_free: + free(head->device); + free(head); + + return NULL; +} + +static void +fbdev_head_destroy(struct fbdev_head *head) +{ + weston_head_release(&head->base); + free(head->device); + free(head); +} + +static struct weston_output * +fbdev_output_create(struct weston_compositor *compositor, + const char *name) +{ + struct fbdev_output *output; + + weston_log("Creating fbdev output.\n"); + + output = zalloc(sizeof *output); + if (output == NULL) + return NULL; + + output->backend = to_fbdev_backend(compositor); + + weston_output_init(&output->base, compositor, name); + + output->base.destroy = fbdev_output_destroy; + output->base.disable = fbdev_output_disable; + output->base.enable = fbdev_output_enable; + output->base.attach_head = fbdev_output_attach_head; + + weston_compositor_add_pending_output(&output->base, compositor); + + return &output->base; +} + +static void +fbdev_output_destroy(struct weston_output *base) +{ + struct fbdev_output *output = to_fbdev_output(base); + + weston_log("Destroying fbdev output.\n"); + + fbdev_output_disable(base); + + /* Remove the output. */ + weston_output_release(&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_backend *backend, + struct weston_output *base) +{ + struct fbdev_output *output = to_fbdev_output(base); + struct fbdev_head *head; + struct fbdev_screeninfo new_screen_info; + int fb_fd; + + head = fbdev_output_get_head(output); + + weston_log("Re-enabling fbdev output.\n"); + assert(output->base.enabled); + + /* Create the frame buffer. */ + fb_fd = fbdev_frame_buffer_open(head->device, &new_screen_info); + if (fb_fd < 0) { + weston_log("Creating frame buffer failed.\n"); + return -1; + } + + /* Check whether the frame buffer details have changed since we were + * disabled. */ + if (compare_screen_info(&head->fb_info, &new_screen_info) != 0) { + /* Perform a mode-set to restore the old mode. */ + if (fbdev_set_screen_info(fb_fd, &head->fb_info) < 0) { + weston_log("Failed to restore mode settings. " + "Attempting to re-open output anyway.\n"); + } + + close(fb_fd); + + /* Disable and enable the output so that resources depending on + * the frame buffer X/Y resolution (such as the shadow buffer) + * are re-initialised. */ + fbdev_output_disable(&output->base); + return fbdev_output_enable(&output->base); + } + + /* Map the device if it has the same details as before. */ + if (fbdev_frame_buffer_map(output, fb_fd) < 0) { + weston_log("Mapping frame buffer failed.\n"); + return -1; + } + + return 0; +} + +static void +fbdev_backend_destroy(struct weston_compositor *base) +{ + struct fbdev_backend *backend = to_fbdev_backend(base); + struct weston_head *head, *next; + + udev_input_destroy(&backend->input); + + /* Destroy the output. */ + weston_compositor_shutdown(base); + + wl_list_for_each_safe(head, next, &base->head_list, compositor_link) + fbdev_head_destroy(to_fbdev_head(head)); + + /* Chain up. */ + weston_launcher_destroy(base->launcher); + + udev_unref(backend->udev); + + free(backend); +} + +static void +session_notify(struct wl_listener *listener, void *data) +{ + struct weston_compositor *compositor = data; + struct fbdev_backend *backend = to_fbdev_backend(compositor); + struct weston_output *output; + + if (compositor->session_active) { + weston_log("entering VT\n"); + compositor->state = backend->prev_state; + + wl_list_for_each(output, &compositor->output_list, link) { + fbdev_output_reenable(backend, output); + } + + weston_compositor_damage_all(compositor); + + udev_input_enable(&backend->input); + } else { + weston_log("leaving VT\n"); + udev_input_disable(&backend->input); + + wl_list_for_each(output, &compositor->output_list, link) { + fbdev_frame_buffer_unmap(to_fbdev_output(output)); + } + + backend->prev_state = compositor->state; + weston_compositor_offscreen(compositor); + + /* 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 attempts at repainting. When we switch + * back, we schedule a repaint, which will process + * pending frame callbacks. */ + + wl_list_for_each(output, + &compositor->output_list, link) { + output->repaint_needed = false; + } + } +} + +static char * +find_framebuffer_device(struct fbdev_backend *b, const char *seat) +{ + struct udev_enumerate *e; + struct udev_list_entry *entry; + const char *path, *device_seat, *id; + char *fb_device_path = NULL; + struct udev_device *device, *fb_device, *pci; + + e = udev_enumerate_new(b->udev); + udev_enumerate_add_match_subsystem(e, "graphics"); + udev_enumerate_add_match_sysname(e, "fb[0-9]*"); + + udev_enumerate_scan_devices(e); + fb_device = NULL; + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { + bool is_boot_vga = false; + + path = udev_list_entry_get_name(entry); + device = udev_device_new_from_syspath(b->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")) + is_boot_vga = true; + } + + /* If a framebuffer device was found, and this device isn't + * the boot-VGA device, don't use it. */ + if (!is_boot_vga && fb_device) { + udev_device_unref(device); + continue; + } + + /* There can only be one boot_vga device. Try to use it + * at all costs. */ + if (is_boot_vga) { + if (fb_device) + udev_device_unref(fb_device); + fb_device = device; + break; + } + + /* Per the (!is_boot_vga && fb_device) test above, only + * trump existing saved devices with boot-VGA devices, so if + * the test ends up here, this must be the first device seen. */ + assert(!fb_device); + fb_device = device; + } + + udev_enumerate_unref(e); + + if (fb_device) { + fb_device_path = strdup(udev_device_get_devnode(fb_device)); + udev_device_unref(fb_device); + } + +// OHOS +// return fb_device_path; + weston_log("find_framebuffer_device %s", fb_device_path); + return strdup("/dev/graphics/fb0"); +} + +static struct fbdev_backend * +fbdev_backend_create(struct weston_compositor *compositor, + struct weston_fbdev_backend_config *param) +{ + struct fbdev_backend *backend; + const char *seat_id = default_seat; + const char *session_seat; + + session_seat = getenv("XDG_SEAT"); + if (session_seat) + seat_id = session_seat; + if (param->seat_id) + seat_id = param->seat_id; + + weston_log("initializing fbdev backend\n"); + + backend = zalloc(sizeof *backend); + if (backend == NULL) + return NULL; + + backend->compositor = compositor; + compositor->backend = &backend->base; + if (weston_compositor_set_presentation_clock_software( + compositor) < 0) + goto out_compositor; + + backend->udev = udev_new(); + if (backend->udev == NULL) { + weston_log("Failed to initialize udev context.\n"); + goto out_compositor; + } + + if (!param->device) + param->device = find_framebuffer_device(backend, seat_id); + if (!param->device) { + weston_log("fatal: no framebuffer devices detected.\n"); + goto out_udev; + } + + /* Set up the TTY. */ + backend->session_listener.notify = session_notify; + wl_signal_add(&compositor->session_signal, + &backend->session_listener); + compositor->launcher = + weston_launcher_connect(compositor, param->tty, seat_id, false); + if (!compositor->launcher) { + weston_log("fatal: fbdev backend should be run using " + "weston-launch binary, or your system should " + "provide the logind D-Bus API.\n"); + goto out_udev; + } + + backend->base.destroy = fbdev_backend_destroy; + backend->base.create_output = fbdev_output_create; + + backend->prev_state = WESTON_COMPOSITOR_ACTIVE; + + weston_setup_vt_switch_bindings(compositor); + + if (pixman_renderer_init(compositor) < 0) + goto out_launcher; + + if (!fbdev_head_create(backend, param->device)) + goto out_launcher; + + free(param->device); + + udev_input_init(&backend->input, compositor, backend->udev, + seat_id, param->configure_device); + + return backend; + +out_launcher: + free(param->device); + weston_launcher_destroy(compositor->launcher); + +out_udev: + udev_unref(backend->udev); + +out_compositor: + weston_compositor_shutdown(compositor); + free(backend); + + return NULL; +} + +static void +config_init_to_defaults(struct weston_fbdev_backend_config *config) +{ + config->tty = 0; /* default to current tty */ + config->device = NULL; + config->seat_id = NULL; +} + +WL_EXPORT int +weston_backend_init(struct weston_compositor *compositor, + struct weston_backend_config *config_base) +{ + struct fbdev_backend *b; + struct weston_fbdev_backend_config config = {{ 0, }}; + + if (config_base == NULL || + config_base->struct_version != WESTON_FBDEV_BACKEND_CONFIG_VERSION || + config_base->struct_size > sizeof(struct weston_fbdev_backend_config)) { + weston_log("fbdev backend config structure is invalid\n"); + return -1; + } + + config_init_to_defaults(&config); + memcpy(&config, config_base, config_base->struct_size); + + b = fbdev_backend_create(compositor, &config); + if (b == NULL) + return -1; + return 0; +} diff --git a/libweston/backend-fbdev/meson.build b/libweston/backend-fbdev/meson.build new file mode 100644 index 0000000..e7b1544 --- /dev/null +++ b/libweston/backend-fbdev/meson.build @@ -0,0 +1,30 @@ +if not get_option('backend-fbdev') + subdir_done() +endif + +config_h.set('BUILD_FBDEV_COMPOSITOR', '1') + +srcs_fbdev = [ + 'fbdev.c', + presentation_time_server_protocol_h, +] + +deps_fbdev = [ + dep_libweston_private, + dep_session_helper, + dep_libinput_backend, + dependency('libudev', version: '>= 136'), +] + +plugin_fbdev = shared_library( + 'fbdev-backend', + srcs_fbdev, + include_directories: common_inc, + dependencies: deps_fbdev, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'fbdev-backend.so=@0@;'.format(plugin_fbdev.full_path()) + +install_headers(backend_fbdev_h, subdir: dir_include_libweston_install) diff --git a/libweston/backend-headless/headless.c b/libweston/backend-headless/headless.c new file mode 100644 index 0000000..c312a0f --- /dev/null +++ b/libweston/backend-headless/headless.c @@ -0,0 +1,521 @@ +/* + * Copyright © 2010-2011 Benjamin Franzke + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "shared/helpers.h" +#include "linux-explicit-synchronization.h" +#include "pixman-renderer.h" +#include "renderer-gl/gl-renderer.h" +#include "shared/weston-egl-ext.h" +#include "linux-dmabuf.h" +#include "presentation-time-server-protocol.h" +#include + +enum headless_renderer_type { + HEADLESS_NOOP, + HEADLESS_PIXMAN, + HEADLESS_GL, +}; + +struct headless_backend { + struct weston_backend base; + struct weston_compositor *compositor; + + struct weston_seat fake_seat; + enum headless_renderer_type renderer_type; + + struct gl_renderer_interface *glri; +}; + +struct headless_head { + struct weston_head base; +}; + +struct headless_output { + struct weston_output base; + + struct weston_mode mode; + struct wl_event_source *finish_frame_timer; + uint32_t *image_buf; + pixman_image_t *image; +}; + +static const uint32_t headless_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, +}; + +static inline struct headless_head * +to_headless_head(struct weston_head *base) +{ + return container_of(base, struct headless_head, base); +} + +static inline struct headless_output * +to_headless_output(struct weston_output *base) +{ + return container_of(base, struct headless_output, base); +} + +static inline struct headless_backend * +to_headless_backend(struct weston_compositor *base) +{ + return container_of(base->backend, struct headless_backend, base); +} + +static int +headless_output_start_repaint_loop(struct weston_output *output) +{ + struct timespec ts; + + weston_compositor_read_presentation_clock(output->compositor, &ts); + weston_output_finish_frame(output, &ts, WP_PRESENTATION_FEEDBACK_INVALID); + + return 0; +} + +static int +finish_frame_handler(void *data) +{ + struct headless_output *output = data; + struct timespec ts; + + weston_compositor_read_presentation_clock(output->base.compositor, &ts); + weston_output_finish_frame(&output->base, &ts, 0); + + return 1; +} + +static int +headless_output_repaint(struct weston_output *output_base, + pixman_region32_t *damage, + void *repaint_data) +{ + struct headless_output *output = to_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_disable_gl(struct headless_output *output) +{ + struct weston_compositor *compositor = output->base.compositor; + struct headless_backend *b = to_headless_backend(compositor); + + b->glri->output_destroy(&output->base); +} + +static void +headless_output_disable_pixman(struct headless_output *output) +{ + pixman_renderer_output_destroy(&output->base); + pixman_image_unref(output->image); + free(output->image_buf); +} + +static int +headless_output_disable(struct weston_output *base) +{ + struct headless_output *output = to_headless_output(base); + struct headless_backend *b = to_headless_backend(base->compositor); + + if (!output->base.enabled) + return 0; + + wl_event_source_remove(output->finish_frame_timer); + + switch (b->renderer_type) { + case HEADLESS_GL: + headless_output_disable_gl(output); + break; + case HEADLESS_PIXMAN: + headless_output_disable_pixman(output); + break; + case HEADLESS_NOOP: + break; + } + + return 0; +} + +static void +headless_output_destroy(struct weston_output *base) +{ + struct headless_output *output = to_headless_output(base); + + headless_output_disable(&output->base); + weston_output_release(&output->base); + + free(output); +} + +static int +headless_output_enable_gl(struct headless_output *output) +{ + struct weston_compositor *compositor = output->base.compositor; + struct headless_backend *b = to_headless_backend(compositor); + const struct gl_renderer_pbuffer_options options = { + .width = output->base.current_mode->width, + .height = output->base.current_mode->height, + .drm_formats = headless_formats, + .drm_formats_count = ARRAY_LENGTH(headless_formats), + }; + + if (b->glri->output_pbuffer_create(&output->base, &options) < 0) { + weston_log("failed to create gl renderer output state\n"); + return -1; + } + + return 0; +} + +static int +headless_output_enable_pixman(struct headless_output *output) +{ + const struct pixman_renderer_output_options options = { + .use_shadow = true, + }; + + output->image_buf = malloc(output->base.current_mode->width * + output->base.current_mode->height * 4); + if (!output->image_buf) + return -1; + + output->image = pixman_image_create_bits(PIXMAN_x8r8g8b8, + output->base.current_mode->width, + output->base.current_mode->height, + output->image_buf, + output->base.current_mode->width * 4); + + if (pixman_renderer_output_create(&output->base, &options) < 0) + goto err_renderer; + + pixman_renderer_output_set_buffer(&output->base, output->image); + + return 0; + +err_renderer: + pixman_image_unref(output->image); + free(output->image_buf); + + return -1; +} + +static int +headless_output_enable(struct weston_output *base) +{ + struct headless_output *output = to_headless_output(base); + struct headless_backend *b = to_headless_backend(base->compositor); + struct wl_event_loop *loop; + int ret = 0; + + loop = wl_display_get_event_loop(b->compositor->wl_display); + output->finish_frame_timer = + wl_event_loop_add_timer(loop, finish_frame_handler, output); + + switch (b->renderer_type) { + case HEADLESS_GL: + ret = headless_output_enable_gl(output); + break; + case HEADLESS_PIXMAN: + ret = headless_output_enable_pixman(output); + break; + case HEADLESS_NOOP: + break; + } + + if (ret < 0) { + wl_event_source_remove(output->finish_frame_timer); + return -1; + } + + return 0; +} + +static int +headless_output_set_size(struct weston_output *base, + int width, int height) +{ + struct headless_output *output = to_headless_output(base); + struct weston_head *head; + int output_width, output_height; + + /* We can only be called once. */ + assert(!output->base.current_mode); + + /* Make sure we have scale set. */ + assert(output->base.scale); + + wl_list_for_each(head, &output->base.head_list, output_link) { + weston_head_set_monitor_strings(head, "weston", "headless", + NULL); + + /* XXX: Calculate proper size. */ + weston_head_set_physical_size(head, width, height); + } + + output_width = width * output->base.scale; + output_height = height * output->base.scale; + + 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; + wl_list_insert(&output->base.mode_list, &output->mode.link); + + output->base.current_mode = &output->mode; + + output->base.start_repaint_loop = headless_output_start_repaint_loop; + output->base.repaint = headless_output_repaint; + output->base.assign_planes = NULL; + output->base.set_backlight = NULL; + output->base.set_dpms = NULL; + output->base.switch_mode = NULL; + + return 0; +} + +static struct weston_output * +headless_output_create(struct weston_compositor *compositor, const char *name) +{ + struct headless_output *output; + + /* name can't be NULL. */ + assert(name); + + output = zalloc(sizeof *output); + if (!output) + return NULL; + + weston_output_init(&output->base, compositor, name); + + output->base.destroy = headless_output_destroy; + output->base.disable = headless_output_disable; + output->base.enable = headless_output_enable; + output->base.attach_head = NULL; + + weston_compositor_add_pending_output(&output->base, compositor); + + return &output->base; +} + +static int +headless_head_create(struct weston_compositor *compositor, + const char *name) +{ + struct headless_head *head; + + /* name can't be NULL. */ + assert(name); + + head = zalloc(sizeof *head); + if (head == NULL) + return -1; + + weston_head_init(&head->base, name); + weston_head_set_connection_status(&head->base, true); + + /* Ideally all attributes of the head would be set here, so that the + * user has all the information when deciding to create outputs. + * We do not have those until set_size() time through. + */ + + weston_compositor_add_head(compositor, &head->base); + + return 0; +} + +static void +headless_head_destroy(struct headless_head *head) +{ + weston_head_release(&head->base); + free(head); +} + +static void +headless_destroy(struct weston_compositor *ec) +{ + struct headless_backend *b = to_headless_backend(ec); + struct weston_head *base, *next; + + weston_compositor_shutdown(ec); + + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) + headless_head_destroy(to_headless_head(base)); + + free(b); +} + +static int +headless_gl_renderer_init(struct headless_backend *b) +{ + const struct gl_renderer_display_options options = { + .egl_platform = EGL_PLATFORM_SURFACELESS_MESA, + .egl_native_display = EGL_DEFAULT_DISPLAY, + .egl_surface_type = EGL_PBUFFER_BIT, + .drm_formats = headless_formats, + .drm_formats_count = ARRAY_LENGTH(headless_formats), + }; + + b->glri = weston_load_module("gl-renderer.so", "gl_renderer_interface"); + if (!b->glri) + return -1; + + return b->glri->display_create(b->compositor, &options); +} + +static const struct weston_windowed_output_api api = { + headless_output_set_size, + headless_head_create, +}; + +static struct headless_backend * +headless_backend_create(struct weston_compositor *compositor, + struct weston_headless_backend_config *config) +{ + struct headless_backend *b; + int ret; + + b = zalloc(sizeof *b); + if (b == NULL) + return NULL; + + b->compositor = compositor; + compositor->backend = &b->base; + + if (weston_compositor_set_presentation_clock_software(compositor) < 0) + goto err_free; + + b->base.destroy = headless_destroy; + b->base.create_output = headless_output_create; + + if (config->use_pixman && config->use_gl) { + weston_log("Error: cannot use both Pixman *and* GL renderers.\n"); + goto err_free; + } + + if (config->use_gl) + b->renderer_type = HEADLESS_GL; + else if (config->use_pixman) + b->renderer_type = HEADLESS_PIXMAN; + else + b->renderer_type = HEADLESS_NOOP; + + switch (b->renderer_type) { + case HEADLESS_GL: + ret = headless_gl_renderer_init(b); + break; + case HEADLESS_PIXMAN: + ret = pixman_renderer_init(compositor); + break; + case HEADLESS_NOOP: + ret = noop_renderer_init(compositor); + break; + default: + assert(0 && "invalid renderer type"); + ret = -1; + } + + if (ret < 0) + goto err_input; + + if (compositor->renderer->import_dmabuf) { + if (linux_dmabuf_setup(compositor) < 0) { + weston_log("Error: dmabuf protocol setup failed.\n"); + goto err_input; + } + } + + /* Support zwp_linux_explicit_synchronization_unstable_v1 to enable + * testing. */ + if (linux_explicit_synchronization_setup(compositor) < 0) + goto err_input; + + ret = weston_plugin_api_register(compositor, WESTON_WINDOWED_OUTPUT_API_NAME, + &api, sizeof(api)); + + if (ret < 0) { + weston_log("Failed to register output API.\n"); + goto err_input; + } + + return b; + +err_input: + weston_compositor_shutdown(compositor); +err_free: + free(b); + return NULL; +} + +static void +config_init_to_defaults(struct weston_headless_backend_config *config) +{ +} + +WL_EXPORT int +weston_backend_init(struct weston_compositor *compositor, + struct weston_backend_config *config_base) +{ + struct headless_backend *b; + struct weston_headless_backend_config config = {{ 0, }}; + + if (config_base == NULL || + config_base->struct_version != WESTON_HEADLESS_BACKEND_CONFIG_VERSION || + config_base->struct_size > sizeof(struct weston_headless_backend_config)) { + weston_log("headless backend config structure is invalid\n"); + return -1; + } + + config_init_to_defaults(&config); + memcpy(&config, config_base, config_base->struct_size); + + b = headless_backend_create(compositor, &config); + if (b == NULL) + return -1; + + return 0; +} diff --git a/libweston/backend-headless/meson.build b/libweston/backend-headless/meson.build new file mode 100644 index 0000000..c603bb0 --- /dev/null +++ b/libweston/backend-headless/meson.build @@ -0,0 +1,21 @@ +if not get_option('backend-headless') + subdir_done() +endif + +config_h.set('BUILD_HEADLESS_COMPOSITOR', '1') + +srcs_headless = [ + 'headless.c', + presentation_time_server_protocol_h, +] +plugin_headless = shared_library( + 'headless-backend', + srcs_headless, + include_directories: common_inc, + dependencies: [ dep_libweston_private, dep_libdrm_headers ], + name_prefix: '', + install: true, + install_dir: dir_module_libweston, +) +env_modmap += 'headless-backend.so=@0@;'.format(plugin_headless.full_path()) +install_headers(backend_headless_h, subdir: dir_include_libweston_install) diff --git a/libweston/backend-rdp/meson.build b/libweston/backend-rdp/meson.build new file mode 100644 index 0000000..e3b6025 --- /dev/null +++ b/libweston/backend-rdp/meson.build @@ -0,0 +1,60 @@ +if not get_option('backend-rdp') + subdir_done() +endif + +config_h.set('BUILD_RDP_COMPOSITOR', '1') + +dep_frdp = dependency('freerdp2', version: '>= 2.0.0', required: false) +if not dep_frdp.found() + error('RDP-backend requires freerdp2 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') +endif + +dep_wpr = dependency('winpr2', version: '>= 2.0.0', required: false) +if not dep_wpr.found() + error('RDP-backend requires winpr2 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') +endif + +if cc.has_header('freerdp/version.h', dependencies: dep_frdp) + config_h.set('HAVE_FREERDP_VERSION_H', '1') +endif + +if cc.has_member( + 'SURFACE_BITS_COMMAND', 'bmp', + dependencies : dep_frdp, + prefix : '#include ' +) + config_h.set('HAVE_SURFACE_BITS_BMP', '1') +endif + +if cc.has_type( + 'enum SURFCMD_CMDTYPE', + dependencies : dep_frdp, + prefix : '#include ' +) + config_h.set('HAVE_SURFCMD_CMDTYPE', '1') +endif + +if cc.has_function( + 'nsc_context_set_parameters', + dependencies : dep_frdp, + prefix: '#include ' +) + config_h.set('HAVE_NSC_CONTEXT_SET_PARAMETERS', '1') +endif + +deps_rdp = [ + dep_libweston_private, + dep_frdp, + dep_wpr, +] +plugin_rdp = shared_library( + 'rdp-backend', + 'rdp.c', + include_directories: common_inc, + dependencies: deps_rdp, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'rdp-backend.so=@0@;'.format(plugin_rdp.full_path()) +install_headers(backend_rdp_h, subdir: dir_include_libweston_install) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c new file mode 100644 index 0000000..9e414aa --- /dev/null +++ b/libweston/backend-rdp/rdp.c @@ -0,0 +1,1515 @@ +/* + * Copyright © 2013 Hardening + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#if HAVE_FREERDP_VERSION_H +#include +#else +/* assume it's a early 1.1 version */ +#define FREERDP_VERSION_MAJOR 1 +#define FREERDP_VERSION_MINOR 1 +#define FREERDP_VERSION_REVISION 0 +#endif + +#define FREERDP_VERSION_NUMBER ((FREERDP_VERSION_MAJOR * 0x10000) + \ + (FREERDP_VERSION_MINOR * 0x100) + FREERDP_VERSION_REVISION) + + +#if FREERDP_VERSION_NUMBER >= 0x10201 +#define HAVE_SKIP_COMPRESSION +#endif + +#if FREERDP_VERSION_NUMBER < 0x10202 +# define FREERDP_CB_RET_TYPE void +# define FREERDP_CB_RETURN(V) return +# define NSC_RESET(C, W, H) +# define RFX_RESET(C, W, H) do { rfx_context_reset(C); C->width = W; C->height = H; } while(0) +#else +#if FREERDP_VERSION_MAJOR >= 2 +# define NSC_RESET(C, W, H) nsc_context_reset(C, W, H) +# define RFX_RESET(C, W, H) rfx_context_reset(C, W, H) +#else +# define NSC_RESET(C, W, H) do { nsc_context_reset(C); C->width = W; C->height = H; } while(0) +# define RFX_RESET(C, W, H) do { rfx_context_reset(C); C->width = W; C->height = H; } while(0) +#endif +#define FREERDP_CB_RET_TYPE BOOL +#define FREERDP_CB_RETURN(V) return TRUE +#endif + +#ifdef HAVE_SURFACE_BITS_BMP +#define SURFACE_BPP(cmd) cmd.bmp.bpp +#define SURFACE_CODECID(cmd) cmd.bmp.codecID +#define SURFACE_WIDTH(cmd) cmd.bmp.width +#define SURFACE_HEIGHT(cmd) cmd.bmp.height +#define SURFACE_BITMAP_DATA(cmd) cmd.bmp.bitmapData +#define SURFACE_BITMAP_DATA_LEN(cmd) cmd.bmp.bitmapDataLength +#else +#define SURFACE_BPP(cmd) cmd.bpp +#define SURFACE_CODECID(cmd) cmd.codecID +#define SURFACE_WIDTH(cmd) cmd.width +#define SURFACE_HEIGHT(cmd) cmd.height +#define SURFACE_BITMAP_DATA(cmd) cmd.bitmapData +#define SURFACE_BITMAP_DATA_LEN(cmd) cmd.bitmapDataLength +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if FREERDP_VERSION_MAJOR >= 2 +#include +#endif + +#include "shared/helpers.h" +#include "shared/timespec-util.h" +#include +#include +#include "pixman-renderer.h" + +#define MAX_FREERDP_FDS 32 +#define DEFAULT_AXIS_STEP_DISTANCE 10 +#define RDP_MODE_FREQ 60 * 1000 + +#if FREERDP_VERSION_MAJOR >= 2 && defined(PIXEL_FORMAT_BGRA32) && !defined(PIXEL_FORMAT_B8G8R8A8) + /* The RDP API is truly wonderful: the pixel format definition changed + * from BGRA32 to B8G8R8A8, but some versions ship with a definition of + * PIXEL_FORMAT_BGRA32 which doesn't actually build. Try really, really, + * hard to find one which does. */ +# define DEFAULT_PIXEL_FORMAT PIXEL_FORMAT_BGRA32 +#else +# define DEFAULT_PIXEL_FORMAT RDP_PIXEL_FORMAT_B8G8R8A8 +#endif + +struct rdp_output; + +struct rdp_backend { + struct weston_backend base; + struct weston_compositor *compositor; + + 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; + int no_clients_resize; + int force_no_compression; +}; + +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_head { + struct weston_head base; +}; + +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_backend *rdpBackend; + 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 inline struct rdp_head * +to_rdp_head(struct weston_head *base) +{ + return container_of(base, struct rdp_head, base); +} + +static inline struct rdp_output * +to_rdp_output(struct weston_output *base) +{ + return container_of(base, struct rdp_output, base); +} + +static inline struct rdp_backend * +to_rdp_backend(struct weston_compositor *base) +{ + return container_of(base->backend, struct rdp_backend, base); +} + +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; + 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); + +#ifdef HAVE_SKIP_COMPRESSION + cmd.skipCompression = TRUE; +#else + memset(&cmd, 0, sizeof(*cmd)); +#endif +#ifdef HAVE_SURFCMD_CMDTYPE + cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS; +#endif + cmd.destLeft = damage->extents.x1; + cmd.destTop = damage->extents.y1; + cmd.destRight = damage->extents.x2; + cmd.destBottom = damage->extents.y2; + SURFACE_BPP(cmd) = 32; + SURFACE_CODECID(cmd) = peer->settings->RemoteFxCodecId; + SURFACE_WIDTH(cmd) = width; + SURFACE_HEIGHT(cmd) = 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) + ); + + SURFACE_BITMAP_DATA_LEN(cmd) = Stream_GetPosition(context->encode_stream); + SURFACE_BITMAP_DATA(cmd) = 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; + 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); + +#ifdef HAVE_SKIP_COMPRESSION + cmd.skipCompression = TRUE; +#else + memset(cmd, 0, sizeof(*cmd)); +#endif +#ifdef HAVE_SURFCMD_CMDTYPE + cmd.cmdType = CMDTYPE_SET_SURFACE_BITS; +#endif + cmd.destLeft = damage->extents.x1; + cmd.destTop = damage->extents.y1; + cmd.destRight = damage->extents.x2; + cmd.destBottom = damage->extents.y2; + SURFACE_BPP(cmd) = 32; + SURFACE_CODECID(cmd) = peer->settings->NSCodecId; + SURFACE_WIDTH(cmd) = width; + SURFACE_HEIGHT(cmd) = 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, + width, height, + pixman_image_get_stride(image)); + + SURFACE_BITMAP_DATA_LEN(cmd) = Stream_GetPosition(context->encode_stream); + SURFACE_BITMAP_DATA(cmd) = 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; + SURFACE_FRAME_MARKER 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); + + memset(&cmd, 0, sizeof(cmd)); +#ifdef HAVE_SURFCMD_CMDTYPE + cmd.cmdType = CMDTYPE_SET_SURFACE_BITS; +#endif + SURFACE_BPP(cmd) = 32; + SURFACE_CODECID(cmd) = 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; + SURFACE_WIDTH(cmd) = rect->x2 - rect->x1; + + heightIncrement = peer->settings->MultifragMaxRequestSize / (16 + SURFACE_WIDTH(cmd) * 4); + remainingHeight = rect->y2 - rect->y1; + top = rect->y1; + + subrect.x1 = rect->x1; + subrect.x2 = rect->x2; + + while (remainingHeight) { + SURFACE_HEIGHT(cmd) = (remainingHeight > heightIncrement) ? heightIncrement : remainingHeight; + cmd.destTop = top; + cmd.destBottom = top + SURFACE_HEIGHT(cmd); + SURFACE_BITMAP_DATA_LEN(cmd) = SURFACE_WIDTH(cmd) * SURFACE_HEIGHT(cmd) * 4; + SURFACE_BITMAP_DATA(cmd) = (BYTE *)realloc(SURFACE_BITMAP_DATA(cmd), SURFACE_BITMAP_DATA_LEN(cmd)); + + subrect.y1 = top; + subrect.y2 = top + SURFACE_HEIGHT(cmd); + pixman_image_flipped_subrect(&subrect, image, SURFACE_BITMAP_DATA(cmd)); + + /*weston_log("* sending (%d,%d, %d,%d)\n", subrect.x1, subrect.y1, subrect.x2, subrect.y2); */ + update->SurfaceBits(peer->context, &cmd); + + remainingHeight -= SURFACE_HEIGHT(cmd); + top += SURFACE_HEIGHT(cmd); + } + } + + free(SURFACE_BITMAP_DATA(cmd)); + + 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->rdpBackend->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 int +rdp_output_start_repaint_loop(struct weston_output *output) +{ + struct timespec ts; + + weston_compositor_read_presentation_clock(output->compositor, &ts); + weston_output_finish_frame(output, &ts, WP_PRESENTATION_FEEDBACK_INVALID); + + return 0; +} + +static int +rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage, + void *repaint_data) +{ + 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); + + if (pixman_region32_not_empty(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 int +finish_frame_handler(void *data) +{ + struct rdp_output *output = data; + struct timespec ts; + + weston_compositor_read_presentation_clock(output->base.compositor, &ts); + weston_output_finish_frame(&output->base, &ts, 0); + + return 1; +} + +static struct weston_mode * +rdp_insert_new_mode(struct weston_output *output, int width, int height, int rate) +{ + struct weston_mode *ret; + ret = zalloc(sizeof *ret); + if (!ret) + return NULL; + ret->width = width; + ret->height = height; + ret->refresh = rate; + wl_list_insert(&output->mode_list, &ret->link); + return ret; +} + +static struct weston_mode * +ensure_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 rdp_insert_new_mode(output, target->width, target->height, RDP_MODE_FREQ); +} + +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; + const struct pixman_renderer_output_options options = { }; + + local_mode = ensure_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, &options); + + 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->DesktopWidth == (UINT32)target_mode->width && + settings->DesktopHeight == (UINT32)target_mode->height) + continue; + + 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 +rdp_output_set_size(struct weston_output *base, + int width, int height) +{ + struct rdp_output *output = to_rdp_output(base); + struct weston_head *head; + struct weston_mode *currentMode; + struct weston_mode initMode; + + /* We can only be called once. */ + assert(!output->base.current_mode); + + wl_list_for_each(head, &output->base.head_list, output_link) { + weston_head_set_monitor_strings(head, "weston", "rdp", NULL); + + /* This is a virtual output, so report a zero physical size. + * It's better to let frontends/clients use their defaults. */ + weston_head_set_physical_size(head, 0, 0); + } + + wl_list_init(&output->peers); + + initMode.flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; + initMode.width = width; + initMode.height = height; + initMode.refresh = RDP_MODE_FREQ; + + currentMode = ensure_matching_mode(&output->base, &initMode); + if (!currentMode) + return -1; + + output->base.current_mode = output->base.native_mode = currentMode; + + output->base.start_repaint_loop = rdp_output_start_repaint_loop; + output->base.repaint = rdp_output_repaint; + output->base.assign_planes = NULL; + output->base.set_backlight = NULL; + output->base.set_dpms = NULL; + output->base.switch_mode = rdp_switch_mode; + + return 0; +} + +static int +rdp_output_enable(struct weston_output *base) +{ + struct rdp_output *output = to_rdp_output(base); + struct rdp_backend *b = to_rdp_backend(base->compositor); + struct wl_event_loop *loop; + const struct pixman_renderer_output_options options = { + .use_shadow = true, + }; + + output->shadow_surface = pixman_image_create_bits(PIXMAN_x8r8g8b8, + output->base.current_mode->width, + output->base.current_mode->height, + NULL, + output->base.current_mode->width * 4); + if (output->shadow_surface == NULL) { + weston_log("Failed to create surface for frame buffer.\n"); + return -1; + } + + if (pixman_renderer_output_create(&output->base, &options) < 0) { + pixman_image_unref(output->shadow_surface); + return -1; + } + + loop = wl_display_get_event_loop(b->compositor->wl_display); + output->finish_frame_timer = wl_event_loop_add_timer(loop, finish_frame_handler, output); + + b->output = output; + + return 0; +} + +static int +rdp_output_disable(struct weston_output *base) +{ + struct rdp_output *output = to_rdp_output(base); + struct rdp_backend *b = to_rdp_backend(base->compositor); + + if (!output->base.enabled) + return 0; + + pixman_image_unref(output->shadow_surface); + pixman_renderer_output_destroy(&output->base); + + wl_event_source_remove(output->finish_frame_timer); + b->output = NULL; + + return 0; +} + +static void +rdp_output_destroy(struct weston_output *base) +{ + struct rdp_output *output = to_rdp_output(base); + + rdp_output_disable(&output->base); + weston_output_release(&output->base); + + free(output); +} + +static struct weston_output * +rdp_output_create(struct weston_compositor *compositor, const char *name) +{ + struct rdp_output *output; + + output = zalloc(sizeof *output); + if (output == NULL) + return NULL; + + weston_output_init(&output->base, compositor, name); + + output->base.destroy = rdp_output_destroy; + output->base.disable = rdp_output_disable; + output->base.enable = rdp_output_enable; + output->base.attach_head = NULL; + + weston_compositor_add_pending_output(&output->base, compositor); + + return &output->base; +} + +static int +rdp_head_create(struct weston_compositor *compositor, const char *name) +{ + struct rdp_head *head; + + head = zalloc(sizeof *head); + if (!head) + return -1; + + weston_head_init(&head->base, name); + weston_head_set_connection_status(&head->base, true); + weston_compositor_add_head(compositor, &head->base); + + return 0; +} + +static void +rdp_head_destroy(struct rdp_head *head) +{ + weston_head_release(&head->base); + free(head); +} + +static void +rdp_destroy(struct weston_compositor *ec) +{ + struct rdp_backend *b = to_rdp_backend(ec); + struct weston_head *base, *next; + struct rdp_peers_item *rdp_peer, *tmp; + int i; + + wl_list_for_each_safe(rdp_peer, tmp, &b->output->peers, link) { + freerdp_peer* client = rdp_peer->peer; + + client->Disconnect(client); + freerdp_peer_context_free(client); + freerdp_peer_free(client); + } + + for (i = 0; i < MAX_FREERDP_FDS; i++) + if (b->listener_events[i]) + wl_event_source_remove(b->listener_events[i]); + + weston_compositor_shutdown(ec); + + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) + rdp_head_destroy(to_rdp_head(base)); + + freerdp_listener_free(b->listener); + + free(b->server_cert); + free(b->server_key); + free(b->rdp_key); + free(b); +} + +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_backend *b, 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(b->compositor->wl_display); + for (i = 0; i < rcount; i++) { + fd = (int)(long)(rfds[i]); + b->listener_events[i] = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, + rdp_listener_activity, instance); + } + + for ( ; i < MAX_FREERDP_FDS; i++) + b->listener_events[i] = 0; + return 0; +} + + +static FREERDP_CB_RET_TYPE +rdp_peer_context_new(freerdp_peer* client, RdpPeerContext* context) +{ + context->item.peer = client; + context->item.flags = RDP_PEER_OUTPUT_ENABLED; + +#if FREERDP_VERSION_MAJOR == 1 && FREERDP_VERSION_MINOR == 1 + context->rfx_context = rfx_context_new(); +#else + context->rfx_context = rfx_context_new(TRUE); +#endif + if (!context->rfx_context) { + FREERDP_CB_RETURN(FALSE); + } + + 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, DEFAULT_PIXEL_FORMAT); + + context->nsc_context = nsc_context_new(); + if (!context->nsc_context) + goto out_error_nsc; + +#ifdef HAVE_NSC_CONTEXT_SET_PARAMETERS + nsc_context_set_parameters(context->nsc_context, NSC_COLOR_FORMAT, DEFAULT_PIXEL_FORMAT); +#else + nsc_context_set_pixel_format(context->nsc_context, DEFAULT_PIXEL_FORMAT); +#endif + context->encode_stream = Stream_New(NULL, 65536); + if (!context->encode_stream) + goto out_error_stream; + + FREERDP_CB_RETURN(TRUE); + +out_error_nsc: + rfx_context_free(context->rfx_context); +out_error_stream: + nsc_context_free(context->nsc_context); + FREERDP_CB_RETURN(FALSE); +} + +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_keyboard(context->item.seat); + weston_seat_release_pointer(context->item.seat); + /* XXX we should weston_seat_release(context->item.seat); here + * but it would crash on reconnect */ + } + + 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; + const char *xkbLayout; + const char *xkbVariant; +}; + +/* table reversed from + https://github.com/awakecoding/FreeRDP/blob/master/libfreerdp/locale/xkb_layout_ids.c#L811 */ +static const +struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { + {KBD_ARABIC_101, "ara", 0}, + {KBD_BULGARIAN, 0, 0}, + {KBD_CHINESE_TRADITIONAL_US, 0}, + {KBD_CZECH, "cz", 0}, + {KBD_CZECH_PROGRAMMERS, "cz", "bksl"}, + {KBD_CZECH_QWERTY, "cz", "qwerty"}, + {KBD_DANISH, "dk", 0}, + {KBD_GERMAN, "de", 0}, + {KBD_GERMAN_NEO, "de", "neo"}, + {KBD_GERMAN_IBM, "de", "qwerty"}, + {KBD_GREEK, "gr", 0}, + {KBD_GREEK_220, "gr", "simple"}, + {KBD_GREEK_319, "gr", "extended"}, + {KBD_GREEK_POLYTONIC, "gr", "polytonic"}, + {KBD_US, "us", 0}, + {KBD_US_ENGLISH_TABLE_FOR_IBM_ARABIC_238_L, "ara", "buckwalter"}, + {KBD_SPANISH, "es", 0}, + {KBD_SPANISH_VARIATION, "es", "nodeadkeys"}, + {KBD_FINNISH, "fi", 0}, + {KBD_FRENCH, "fr", 0}, + {KBD_HEBREW, "il", 0}, + {KBD_HUNGARIAN, "hu", 0}, + {KBD_HUNGARIAN_101_KEY, "hu", "standard"}, + {KBD_ICELANDIC, "is", 0}, + {KBD_ITALIAN, "it", 0}, + {KBD_ITALIAN_142, "it", "nodeadkeys"}, + {KBD_JAPANESE, "jp", 0}, + {KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002, "jp", "kana"}, + {KBD_KOREAN, "kr", 0}, + {KBD_KOREAN_INPUT_SYSTEM_IME_2000, "kr", "kr104"}, + {KBD_DUTCH, "nl", 0}, + {KBD_NORWEGIAN, "no", 0}, + {KBD_POLISH_PROGRAMMERS, "pl", 0}, + {KBD_POLISH_214, "pl", "qwertz"}, + {KBD_ROMANIAN, "ro", 0}, + {KBD_RUSSIAN, "ru", 0}, + {KBD_RUSSIAN_TYPEWRITER, "ru", "typewriter"}, + {KBD_CROATIAN, "hr", 0}, + {KBD_SLOVAK, "sk", 0}, + {KBD_SLOVAK_QWERTY, "sk", "qwerty"}, + {KBD_ALBANIAN, 0, 0}, + {KBD_SWEDISH, "se", 0}, + {KBD_THAI_KEDMANEE, "th", 0}, + {KBD_THAI_KEDMANEE_NON_SHIFTLOCK, "th", "tis"}, + {KBD_TURKISH_Q, "tr", 0}, + {KBD_TURKISH_F, "tr", "f"}, + {KBD_URDU, "in", "urd-phonetic3"}, + {KBD_UKRAINIAN, "ua", 0}, + {KBD_BELARUSIAN, "by", 0}, + {KBD_SLOVENIAN, "si", 0}, + {KBD_ESTONIAN, "ee", 0}, + {KBD_LATVIAN, "lv", 0}, + {KBD_LITHUANIAN_IBM, "lt", "ibm"}, + {KBD_FARSI, "af", 0}, + {KBD_VIETNAMESE, "vn", 0}, + {KBD_ARMENIAN_EASTERN, "am", 0}, + {KBD_AZERI_LATIN, 0, 0}, + {KBD_FYRO_MACEDONIAN, "mk", 0}, + {KBD_GEORGIAN, "ge", 0}, + {KBD_FAEROESE, 0, 0}, + {KBD_DEVANAGARI_INSCRIPT, 0, 0}, + {KBD_MALTESE_47_KEY, 0, 0}, + {KBD_NORWEGIAN_WITH_SAMI, "no", "smi"}, + {KBD_KAZAKH, "kz", 0}, + {KBD_KYRGYZ_CYRILLIC, "kg", "phonetic"}, + {KBD_TATAR, "ru", "tt"}, + {KBD_BENGALI, "bd", 0}, + {KBD_BENGALI_INSCRIPT, "bd", "probhat"}, + {KBD_PUNJABI, 0, 0}, + {KBD_GUJARATI, "in", "guj"}, + {KBD_TAMIL, "in", "tam"}, + {KBD_TELUGU, "in", "tel"}, + {KBD_KANNADA, "in", "kan"}, + {KBD_MALAYALAM, "in", "mal"}, + {KBD_HINDI_TRADITIONAL, "in", 0}, + {KBD_MARATHI, 0, 0}, + {KBD_MONGOLIAN_CYRILLIC, "mn", 0}, + {KBD_UNITED_KINGDOM_EXTENDED, "gb", "intl"}, + {KBD_SYRIAC, "syc", 0}, + {KBD_SYRIAC_PHONETIC, "syc", "syc_phonetic"}, + {KBD_NEPALI, "np", 0}, + {KBD_PASHTO, "af", "ps"}, + {KBD_DIVEHI_PHONETIC, 0, 0}, + {KBD_LUXEMBOURGISH, 0, 0}, + {KBD_MAORI, "mao", 0}, + {KBD_CHINESE_SIMPLIFIED_US, 0, 0}, + {KBD_SWISS_GERMAN, "ch", "de_nodeadkeys"}, + {KBD_UNITED_KINGDOM, "gb", 0}, + {KBD_LATIN_AMERICAN, "latam", 0}, + {KBD_BELGIAN_FRENCH, "be", 0}, + {KBD_BELGIAN_PERIOD, "be", "oss_sundeadkeys"}, + {KBD_PORTUGUESE, "pt", 0}, + {KBD_SERBIAN_LATIN, "rs", 0}, + {KBD_AZERI_CYRILLIC, "az", "cyrillic"}, + {KBD_SWEDISH_WITH_SAMI, "se", "smi"}, + {KBD_UZBEK_CYRILLIC, "af", "uz"}, + {KBD_INUKTITUT_LATIN, "ca", "ike"}, + {KBD_CANADIAN_FRENCH_LEGACY, "ca", "fr-legacy"}, + {KBD_SERBIAN_CYRILLIC, "rs", 0}, + {KBD_CANADIAN_FRENCH, "ca", "fr-legacy"}, + {KBD_SWISS_FRENCH, "ch", "fr"}, + {KBD_BOSNIAN, "ba", 0}, + {KBD_IRISH, 0, 0}, + {KBD_BOSNIAN_CYRILLIC, "ba", "us"}, + {KBD_UNITED_STATES_DVORAK, "us", "dvorak"}, + {KBD_PORTUGUESE_BRAZILIAN_ABNT2, "br", "nativo"}, + {KBD_CANADIAN_MULTILINGUAL_STANDARD, "ca", "multix"}, + {KBD_GAELIC, "ie", "CloGaelach"}, + + {0x00000000, 0, 0}, +}; + +/* taken from 2.2.7.1.6 Input Capability Set (TS_INPUT_CAPABILITYSET) */ +static const 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_activate(freerdp_peer* client) +{ + RdpPeerContext *peerCtx; + struct rdp_backend *b; + struct rdp_output *output; + rdpSettings *settings; + rdpPointerUpdate *pointer; + struct rdp_peers_item *peersItem; + struct xkb_rule_names xkbRuleNames; + struct xkb_keymap *keymap; + struct weston_output *weston_output; + int i; + pixman_box32_t box; + pixman_region32_t damage; + char seat_name[50]; + POINTER_SYSTEM_UPDATE pointer_system; + + peerCtx = (RdpPeerContext *)client->context; + b = peerCtx->rdpBackend; + peersItem = &peerCtx->item; + output = b->output; + settings = client->settings; + + if (!settings->SurfaceCommandsEnabled) { + weston_log("client doesn't support required SurfaceCommands\n"); + return FALSE; + } + + if (b->force_no_compression && settings->CompressionEnabled) { + weston_log("Forcing compression off\n"); + settings->CompressionEnabled = FALSE; + } + + if (output->base.width != (int)settings->DesktopWidth || + output->base.height != (int)settings->DesktopHeight) + { + if (b->no_clients_resize) { + /* RDP peers don't dictate their resolution to weston */ + if (!settings->DesktopResize) { + /* peer does not support desktop resize */ + weston_log("%s: client doesn't support resizing, closing connection\n", __FUNCTION__); + return FALSE; + } else { + settings->DesktopWidth = output->base.width; + settings->DesktopHeight = output->base.height; + client->update->DesktopResize(client->context); + } + } else { + /* ask weston to adjust size */ + struct weston_mode new_mode; + struct weston_mode *target_mode; + new_mode.width = (int)settings->DesktopWidth; + new_mode.height = (int)settings->DesktopHeight; + target_mode = ensure_matching_mode(&output->base, &new_mode); + if (!target_mode) { + weston_log("client mode not found\n"); + return FALSE; + } + weston_output_mode_set_native(&output->base, target_mode, 1); + output->base.width = new_mode.width; + output->base.height = new_mode.height; + } + } + + weston_output = &output->base; + RFX_RESET(peerCtx->rfx_context, weston_output->width, weston_output->height); + NSC_RESET(peerCtx->nsc_context, weston_output->width, weston_output->height); + + if (peersItem->flags & RDP_PEER_ACTIVATED) + return TRUE; + + /* when here it's the first reactivation, we need to setup a little more */ + weston_log("kbd_layout:0x%x kbd_type:0x%x kbd_subType:0x%x kbd_functionKeys:0x%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].rdpLayoutCode; i++) { + if (rdp_keyboards[i].rdpLayoutCode == settings->KeyboardLayout) { + xkbRuleNames.layout = rdp_keyboards[i].xkbLayout; + xkbRuleNames.variant = rdp_keyboards[i].xkbVariant; + weston_log("%s: matching layout=%s variant=%s\n", __FUNCTION__, + xkbRuleNames.layout, xkbRuleNames.variant); + break; + } + } + + keymap = NULL; + if (xkbRuleNames.layout) { + keymap = xkb_keymap_new_from_names(b->compositor->xkb_context, + &xkbRuleNames, 0); + } + + if (settings->ClientHostname) + snprintf(seat_name, sizeof(seat_name), "RDP %s", settings->ClientHostname); + else + snprintf(seat_name, sizeof(seat_name), "RDP peer @%s", settings->ClientAddress); + + peersItem->seat = zalloc(sizeof(*peersItem->seat)); + if (!peersItem->seat) { + xkb_keymap_unref(keymap); + weston_log("unable to create a weston_seat\n"); + return FALSE; + } + + weston_seat_init(peersItem->seat, b->compositor, seat_name); + weston_seat_init_keyboard(peersItem->seat, keymap); + xkb_keymap_unref(keymap); + weston_seat_init_pointer(peersItem->seat); + + peersItem->flags |= RDP_PEER_ACTIVATED; + + /* disable pointer on the client side */ + pointer = client->update->pointer; + pointer_system.type = SYSPTR_NULL; + pointer->PointerSystem(client->context, &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_post_connect(freerdp_peer *client) +{ + return TRUE; +} + +static FREERDP_CB_RET_TYPE +xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) +{ + RdpPeerContext *peerContext = (RdpPeerContext *)input->context; + struct rdp_output *output; + uint32_t button = 0; + bool need_frame = false; + struct timespec time; + + if (flags & PTR_FLAGS_MOVE) { + output = peerContext->rdpBackend->output; + if (x < output->base.width && y < output->base.height) { + weston_compositor_get_time(&time); + notify_motion_absolute(peerContext->item.seat, &time, + x, y); + need_frame = true; + } + } + + 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) { + weston_compositor_get_time(&time); + notify_button(peerContext->item.seat, &time, button, + (flags & PTR_FLAGS_DOWN) ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED + ); + need_frame = true; + } + + if (flags & PTR_FLAGS_WHEEL) { + struct weston_pointer_axis_event weston_event; + double value; + + /* 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". + * + * https://blogs.msdn.microsoft.com/oldnewthing/20130123-00/?p=5473 explains the 120 value + */ + value = -(flags & 0xff) / 120.0; + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + value = -value; + + weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; + weston_event.value = DEFAULT_AXIS_STEP_DISTANCE * value; + weston_event.discrete = (int)value; + weston_event.has_discrete = true; + + weston_compositor_get_time(&time); + + notify_axis(peerContext->item.seat, &time, &weston_event); + need_frame = true; + } + + if (need_frame) + notify_pointer_frame(peerContext->item.seat); + + FREERDP_CB_RETURN(TRUE); +} + +static FREERDP_CB_RET_TYPE +xf_extendedMouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) +{ + RdpPeerContext *peerContext = (RdpPeerContext *)input->context; + struct rdp_output *output; + struct timespec time; + + output = peerContext->rdpBackend->output; + if (x < output->base.width && y < output->base.height) { + weston_compositor_get_time(&time); + notify_motion_absolute(peerContext->item.seat, &time, x, y); + } + + FREERDP_CB_RETURN(TRUE); +} + + +static FREERDP_CB_RET_TYPE +xf_input_synchronize_event(rdpInput *input, UINT32 flags) +{ + freerdp_peer *client = input->context->peer; + RdpPeerContext *peerCtx = (RdpPeerContext *)input->context; + struct rdp_output *output = peerCtx->rdpBackend->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); + FREERDP_CB_RETURN(TRUE); +} + + +static FREERDP_CB_RET_TYPE +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; + struct timespec time; + + if (!(peerContext->item.flags & RDP_PEER_ACTIVATED)) + FREERDP_CB_RETURN(TRUE); + + 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 (flags & KBD_FLAGS_EXTENDED) + vk_code |= KBDEXT; + + scan_code = GetKeycodeFromVirtualKeyCode(vk_code, KEYCODE_TYPE_EVDEV); + + /*weston_log("code=%x ext=%d vk_code=%x scan_code=%x\n", code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, + vk_code, scan_code);*/ + weston_compositor_get_time(&time); + notify_key(peerContext->item.seat, &time, + scan_code - 8, keyState, STATE_UPDATE_AUTOMATIC); + } + + FREERDP_CB_RETURN(TRUE); +} + +static FREERDP_CB_RET_TYPE +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); + FREERDP_CB_RETURN(TRUE); +} + + +static FREERDP_CB_RET_TYPE +xf_suppress_output(rdpContext *context, BYTE allow, const RECTANGLE_16 *area) +{ + RdpPeerContext *peerContext = (RdpPeerContext *)context; + + if (allow) + peerContext->item.flags |= RDP_PEER_OUTPUT_ENABLED; + else + peerContext->item.flags &= (~RDP_PEER_OUTPUT_ENABLED); + + FREERDP_CB_RETURN(TRUE); +} + +static int +rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) +{ + int rcount = 0; + void *rfds[MAX_FREERDP_FDS]; + int i, fd; + struct wl_event_loop *loop; + rdpSettings *settings; + rdpInput *input; + RdpPeerContext *peerCtx; + + 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->rdpBackend = b; + + settings = client->settings; + /* configure security settings */ + if (b->rdp_key) + settings->RdpKeyFile = strdup(b->rdp_key); + if (b->tls_enabled) { + settings->CertificateFile = strdup(b->server_cert); + settings->PrivateKeyFile = strdup(b->server_key); + } else { + settings->TlsSecurity = FALSE; + } + settings->NlaSecurity = FALSE; + + if (!client->Initialize(client)) { + weston_log("peer initialization failed\n"); + goto error_initialize; + } + + settings->OsMajorType = OSMAJORTYPE_UNIX; + settings->OsMinorType = OSMINORTYPE_PSEUDO_XSERVER; + settings->ColorDepth = 32; + settings->RefreshRect = TRUE; + settings->RemoteFxCodec = TRUE; + settings->NSCodec = TRUE; + settings->FrameMarkerCommandEnabled = TRUE; + settings->SurfaceFrameMarkerEnabled = TRUE; + + client->Capabilities = xf_peer_capabilities; + client->PostConnect = xf_peer_post_connect; + client->Activate = xf_peer_activate; + + client->update->SuppressOutput = (pSuppressOutput)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 (!client->GetFileDescriptor(client, rfds, &rcount)) { + weston_log("unable to retrieve client fds\n"); + goto error_initialize; + } + + loop = wl_display_get_event_loop(b->compositor->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(&b->output->peers, &peerCtx->item.link); + return 0; + +error_initialize: + client->Close(client); + return -1; +} + + +static FREERDP_CB_RET_TYPE +rdp_incoming_peer(freerdp_listener *instance, freerdp_peer *client) +{ + struct rdp_backend *b = (struct rdp_backend *)instance->param4; + if (rdp_peer_init(client, b) < 0) { + weston_log("error when treating incoming peer\n"); + FREERDP_CB_RETURN(FALSE); + } + + FREERDP_CB_RETURN(TRUE); +} + +static const struct weston_rdp_output_api api = { + rdp_output_set_size, +}; + +static struct rdp_backend * +rdp_backend_create(struct weston_compositor *compositor, + struct weston_rdp_backend_config *config) +{ + struct rdp_backend *b; + char *fd_str; + char *fd_tail; + int fd, ret; + + b = zalloc(sizeof *b); + if (b == NULL) + return NULL; + + b->compositor = compositor; + b->base.destroy = rdp_destroy; + b->base.create_output = rdp_output_create; + b->rdp_key = config->rdp_key ? strdup(config->rdp_key) : NULL; + b->no_clients_resize = config->no_clients_resize; + b->force_no_compression = config->force_no_compression; + + compositor->backend = &b->base; + + /* activate TLS only if certificate/key are available */ + if (config->server_cert && config->server_key) { + weston_log("TLS support activated\n"); + b->server_cert = strdup(config->server_cert); + b->server_key = strdup(config->server_key); + if (!b->server_cert || !b->server_key) + goto err_free_strings; + b->tls_enabled = 1; + } + + if (weston_compositor_set_presentation_clock_software(compositor) < 0) + goto err_compositor; + + if (pixman_renderer_init(compositor) < 0) + goto err_compositor; + + if (rdp_head_create(compositor, "rdp") < 0) + goto err_compositor; + + compositor->capabilities |= WESTON_CAP_ARBITRARY_MODES; + + if (!config->env_socket) { + b->listener = freerdp_listener_new(); + b->listener->PeerAccepted = rdp_incoming_peer; + b->listener->param4 = b; + if (!b->listener->Open(b->listener, config->bind_address, config->port)) { + weston_log("unable to bind rdp socket\n"); + goto err_listener; + } + + if (rdp_implant_listener(b, b->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\n"); + goto err_output; + } + + fd = strtoul(fd_str, &fd_tail, 10); + if (errno != 0 || fd_tail == fd_str || *fd_tail != '\0' + || rdp_peer_init(freerdp_peer_new(fd), b)) + goto err_output; + } + + ret = weston_plugin_api_register(compositor, WESTON_RDP_OUTPUT_API_NAME, + &api, sizeof(api)); + + if (ret < 0) { + weston_log("Failed to register output API.\n"); + goto err_output; + } + + return b; + +err_listener: + freerdp_listener_free(b->listener); +err_output: + weston_output_release(&b->output->base); +err_compositor: + weston_compositor_shutdown(compositor); +err_free_strings: + free(b->rdp_key); + free(b->server_cert); + free(b->server_key); + free(b); + return NULL; +} + +static void +config_init_to_defaults(struct weston_rdp_backend_config *config) +{ + config->bind_address = NULL; + config->port = 3389; + config->rdp_key = NULL; + config->server_cert = NULL; + config->server_key = NULL; + config->env_socket = 0; + config->no_clients_resize = 0; + config->force_no_compression = 0; +} + +WL_EXPORT int +weston_backend_init(struct weston_compositor *compositor, + struct weston_backend_config *config_base) +{ + struct rdp_backend *b; + struct weston_rdp_backend_config config = {{ 0, }}; + int major, minor, revision; + +#if FREERDP_VERSION_MAJOR >= 2 + winpr_InitializeSSL(0); +#endif + freerdp_get_version(&major, &minor, &revision); + weston_log("using FreeRDP version %d.%d.%d\n", major, minor, revision); + + if (config_base == NULL || + config_base->struct_version != WESTON_RDP_BACKEND_CONFIG_VERSION || + config_base->struct_size > sizeof(struct weston_rdp_backend_config)) { + weston_log("RDP backend config structure is invalid\n"); + return -1; + } + + config_init_to_defaults(&config); + memcpy(&config, config_base, config_base->struct_size); + + if (!config.rdp_key && (!config.server_cert || !config.server_key)) { + weston_log("the RDP compositor requires keys and an optional certificate for RDP or TLS security (" + "--rdp4-key or --rdp-tls-cert/--rdp-tls-key)\n"); + return -1; + } + + b = rdp_backend_create(compositor, &config); + if (b == NULL) + return -1; + return 0; +} diff --git a/libweston/backend-wayland/meson.build b/libweston/backend-wayland/meson.build new file mode 100644 index 0000000..7e82513 --- /dev/null +++ b/libweston/backend-wayland/meson.build @@ -0,0 +1,44 @@ +if not get_option('backend-wayland') + subdir_done() +endif + +config_h.set('BUILD_WAYLAND_COMPOSITOR', '1') + +srcs_wlwl = [ + 'wayland.c', + fullscreen_shell_unstable_v1_client_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + presentation_time_protocol_c, + presentation_time_server_protocol_h, + xdg_shell_server_protocol_h, + xdg_shell_protocol_c, +] + +deps_wlwl = [ + dependency('wayland-client'), + dependency('wayland-cursor'), + dep_pixman, + dep_libweston_private, + dep_libdrm_headers, + dep_lib_cairo_shared, +] + +if get_option('renderer-gl') + d = dependency('wayland-egl', required: false) + if not d.found() + error('wayland-backend + gl-renderer requires wayland-egl which was not found. Or, you can use \'-Dbackend-wayland=false\' or \'-Drenderer-gl=false\'.') + endif + deps_wlwl += d +endif + +plugin_wlwl = shared_library( + 'wayland-backend', + srcs_wlwl, + include_directories: common_inc, + dependencies: deps_wlwl, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'wayland-backend.so=@0@;'.format(plugin_wlwl.full_path()) +install_headers(backend_wayland_h, subdir: dir_include_libweston_install) diff --git a/libweston/backend-wayland/wayland.c b/libweston/backend-wayland/wayland.c new file mode 100644 index 0000000..60d42bf --- /dev/null +++ b/libweston/backend-wayland/wayland.c @@ -0,0 +1,2915 @@ +/* + * Copyright © 2010-2011 Benjamin Franzke + * Copyright © 2013 Jason Ekstrand + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef ENABLE_EGL +#include +#endif + +#include +#include +#include "renderer-gl/gl-renderer.h" +#include "shared/weston-egl-ext.h" +#include "pixman-renderer.h" +#include "shared/helpers.h" +#include "shared/image-loader.h" +#include "shared/os-compatibility.h" +#include "shared/cairo-util.h" +#include "shared/timespec-util.h" +#include "fullscreen-shell-unstable-v1-client-protocol.h" +#include "xdg-shell-client-protocol.h" +#include "presentation-time-server-protocol.h" +#include "linux-dmabuf.h" +#include + +#define WINDOW_TITLE "Weston Compositor" + +static const uint32_t wayland_formats[] = { + DRM_FORMAT_ARGB8888, +}; + +struct wayland_backend { + struct weston_backend base; + struct weston_compositor *compositor; + + struct { + struct wl_display *wl_display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_shell *shell; + struct xdg_wm_base *xdg_wm_base; + struct zwp_fullscreen_shell_v1 *fshell; + struct wl_shm *shm; + + struct wl_list output_list; + + struct wl_event_source *wl_source; + uint32_t event_mask; + } parent; + + bool use_pixman; + bool sprawl_across_outputs; + bool fullscreen; + + struct theme *theme; + cairo_device_t *frame_device; + struct wl_cursor_theme *cursor_theme; + struct wl_cursor *cursor; + + struct wl_list input_list; +}; + +struct wayland_output { + struct weston_output base; + + struct { + bool draw_initial_frame; + struct wl_surface *surface; + + struct wl_output *output; + uint32_t global_id; + + struct wl_shell_surface *shell_surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + int configure_width, configure_height; + bool wait_for_configure; + } parent; + + int keyboard_count; + + char *title; + struct frame *frame; + + struct { + struct wl_egl_window *egl_window; + struct { + cairo_surface_t *top; + cairo_surface_t *left; + cairo_surface_t *right; + cairo_surface_t *bottom; + } border; + } gl; + + struct { + struct wl_list buffers; + struct wl_list free_buffers; + } shm; + + struct weston_mode mode; + + struct wl_callback *frame_cb; +}; + +struct wayland_parent_output { + struct wayland_backend *backend; /**< convenience */ + struct wayland_head *head; + struct wl_list link; + + struct wl_output *global; + uint32_t id; + + struct { + char *make; + char *model; + int32_t width, height; + uint32_t subpixel; + } physical; + + int32_t x, y; + uint32_t transform; + uint32_t scale; + + struct wl_callback *sync_cb; /**< wl_output < 2 done replacement */ + + struct wl_list mode_list; + struct weston_mode *preferred_mode; + struct weston_mode *current_mode; +}; + +struct wayland_head { + struct weston_head base; + struct wayland_parent_output *parent_output; +}; + +struct wayland_shm_buffer { + struct wayland_output *output; + struct wl_list link; + struct wl_list free_link; + + struct wl_buffer *buffer; + void *data; + size_t size; + int width; + int height; + pixman_region32_t damage; /**< in global coords */ + int frame_damaged; + + pixman_image_t *pm_image; + cairo_surface_t *c_surface; +}; + +struct wayland_input { + struct weston_seat base; + struct wayland_backend *backend; + struct wl_list link; + + struct { + struct wl_seat *seat; + struct wl_pointer *pointer; + struct wl_keyboard *keyboard; + struct wl_touch *touch; + + struct { + struct wl_surface *surface; + int32_t hx, hy; + } cursor; + } parent; + + struct weston_touch_device *touch_device; + + enum weston_key_state_update keyboard_state_update; + uint32_t key_serial; + uint32_t enter_serial; + uint32_t touch_points; + bool touch_active; + bool has_focus; + int seat_version; + + struct wayland_output *output; + struct wayland_output *touch_focus; + struct wayland_output *keyboard_focus; + + struct weston_pointer_axis_event vert, horiz; +}; + +struct gl_renderer_interface *gl_renderer; + +static inline struct wayland_head * +to_wayland_head(struct weston_head *base) +{ + return container_of(base, struct wayland_head, base); +} + +static inline struct wayland_output * +to_wayland_output(struct weston_output *base) +{ + return container_of(base, struct wayland_output, base); +} + +static inline struct wayland_backend * +to_wayland_backend(struct weston_compositor *base) +{ + return container_of(base->backend, struct wayland_backend, base); +} + +static void +wayland_shm_buffer_destroy(struct wayland_shm_buffer *buffer) +{ + cairo_surface_destroy(buffer->c_surface); + pixman_image_unref(buffer->pm_image); + + wl_buffer_destroy(buffer->buffer); + munmap(buffer->data, buffer->size); + + pixman_region32_fini(&buffer->damage); + + wl_list_remove(&buffer->link); + wl_list_remove(&buffer->free_link); + free(buffer); +} + +static void +buffer_release(void *data, struct wl_buffer *buffer) +{ + struct wayland_shm_buffer *sb = data; + + if (sb->output) { + wl_list_insert(&sb->output->shm.free_buffers, &sb->free_link); + } else { + wayland_shm_buffer_destroy(sb); + } +} + +static const struct wl_buffer_listener buffer_listener = { + buffer_release +}; + +static struct wayland_shm_buffer * +wayland_output_get_shm_buffer(struct wayland_output *output) +{ + struct wayland_backend *b = + to_wayland_backend(output->base.compositor); + struct wl_shm *shm = b->parent.shm; + struct wayland_shm_buffer *sb; + + struct wl_shm_pool *pool; + int width, height, stride; + int32_t fx, fy; + int fd; + unsigned char *data; + + if (!wl_list_empty(&output->shm.free_buffers)) { + sb = container_of(output->shm.free_buffers.next, + struct wayland_shm_buffer, free_link); + wl_list_remove(&sb->free_link); + wl_list_init(&sb->free_link); + + return sb; + } + + if (output->frame) { + width = frame_width(output->frame); + height = frame_height(output->frame); + } else { + width = output->base.current_mode->width; + height = output->base.current_mode->height; + } + + stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); + + fd = os_create_anonymous_file(height * stride); + if (fd < 0) { + weston_log("could not create an anonymous file buffer: %s\n", + strerror(errno)); + return NULL; + } + + data = mmap(NULL, height * stride, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + weston_log("could not mmap %d memory for data: %s\n", height * stride, + strerror(errno)); + close(fd); + return NULL; + } + + sb = zalloc(sizeof *sb); + if (sb == NULL) { + weston_log("could not zalloc %zu memory for sb: %s\n", sizeof *sb, + strerror(errno)); + close(fd); + munmap(data, height * stride); + return NULL; + } + + sb->output = output; + wl_list_init(&sb->free_link); + wl_list_insert(&output->shm.buffers, &sb->link); + + pixman_region32_init(&sb->damage); + pixman_region32_copy(&sb->damage, &output->base.region); + sb->frame_damaged = 1; + + sb->data = data; + sb->width = width; + sb->height = height; + sb->size = height * stride; + + pool = wl_shm_create_pool(shm, fd, sb->size); + + sb->buffer = wl_shm_pool_create_buffer(pool, 0, + width, height, + stride, + WL_SHM_FORMAT_ARGB8888); + wl_buffer_add_listener(sb->buffer, &buffer_listener, sb); + wl_shm_pool_destroy(pool); + close(fd); + + memset(data, 0, sb->size); + + sb->c_surface = + cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, + width, height, stride); + + fx = 0; + fy = 0; + if (output->frame) + frame_interior(output->frame, &fx, &fy, 0, 0); + sb->pm_image = + pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, + (uint32_t *)(data + fy * stride) + fx, + stride); + + return sb; +} + +static void +frame_done(void *data, struct wl_callback *callback, uint32_t time) +{ + struct wayland_output *output = data; + struct timespec ts; + + assert(callback == output->frame_cb); + wl_callback_destroy(callback); + output->frame_cb = NULL; + + /* XXX: use the presentation extension for proper timings */ + + /* + * This is the fallback case, where Presentation extension is not + * available from the parent compositor. We do not know the base for + * 'time', so we cannot feed it to finish_frame(). Do the only thing + * we can, and pretend finish_frame time is when we process this + * event. + */ + weston_compositor_read_presentation_clock(output->base.compositor, &ts); + weston_output_finish_frame(&output->base, &ts, 0); +} + +static const struct wl_callback_listener frame_listener = { + frame_done +}; + +static void +draw_initial_frame(struct wayland_output *output) +{ + struct wayland_shm_buffer *sb; + + sb = wayland_output_get_shm_buffer(output); + + /* If we are rendering with GL, then orphan it so that it gets + * destroyed immediately */ + if (output->gl.egl_window) + sb->output = NULL; + + wl_surface_attach(output->parent.surface, sb->buffer, 0, 0); + wl_surface_damage(output->parent.surface, 0, 0, + sb->width, sb->height); +} + +#ifdef ENABLE_EGL +static void +wayland_output_update_gl_border(struct wayland_output *output) +{ + int32_t ix, iy, iwidth, iheight, fwidth, fheight; + cairo_t *cr; + + if (!output->frame) + return; + if (!(frame_status(output->frame) & FRAME_STATUS_REPAINT)) + return; + + fwidth = frame_width(output->frame); + fheight = frame_height(output->frame); + frame_interior(output->frame, &ix, &iy, &iwidth, &iheight); + + if (!output->gl.border.top) + output->gl.border.top = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + fwidth, iy); + cr = cairo_create(output->gl.border.top); + frame_repaint(output->frame, cr); + cairo_destroy(cr); + gl_renderer->output_set_border(&output->base, GL_RENDERER_BORDER_TOP, + fwidth, iy, + cairo_image_surface_get_stride(output->gl.border.top) / 4, + cairo_image_surface_get_data(output->gl.border.top)); + + + if (!output->gl.border.left) + output->gl.border.left = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + ix, 1); + cr = cairo_create(output->gl.border.left); + cairo_translate(cr, 0, -iy); + frame_repaint(output->frame, cr); + cairo_destroy(cr); + gl_renderer->output_set_border(&output->base, GL_RENDERER_BORDER_LEFT, + ix, 1, + cairo_image_surface_get_stride(output->gl.border.left) / 4, + cairo_image_surface_get_data(output->gl.border.left)); + + + if (!output->gl.border.right) + output->gl.border.right = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + fwidth - (ix + iwidth), 1); + cr = cairo_create(output->gl.border.right); + cairo_translate(cr, -(iwidth + ix), -iy); + frame_repaint(output->frame, cr); + cairo_destroy(cr); + gl_renderer->output_set_border(&output->base, GL_RENDERER_BORDER_RIGHT, + fwidth - (ix + iwidth), 1, + cairo_image_surface_get_stride(output->gl.border.right) / 4, + cairo_image_surface_get_data(output->gl.border.right)); + + + if (!output->gl.border.bottom) + output->gl.border.bottom = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + fwidth, fheight - (iy + iheight)); + cr = cairo_create(output->gl.border.bottom); + cairo_translate(cr, 0, -(iy + iheight)); + frame_repaint(output->frame, cr); + cairo_destroy(cr); + gl_renderer->output_set_border(&output->base, GL_RENDERER_BORDER_BOTTOM, + fwidth, fheight - (iy + iheight), + cairo_image_surface_get_stride(output->gl.border.bottom) / 4, + cairo_image_surface_get_data(output->gl.border.bottom)); +} +#endif + +static int +wayland_output_start_repaint_loop(struct weston_output *output_base) +{ + struct wayland_output *output = to_wayland_output(output_base); + struct wayland_backend *wb = + to_wayland_backend(output->base.compositor); + + /* 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 = false; + + draw_initial_frame(output); + } + + output->frame_cb = wl_surface_frame(output->parent.surface); + wl_callback_add_listener(output->frame_cb, &frame_listener, output); + wl_surface_commit(output->parent.surface); + wl_display_flush(wb->parent.wl_display); + + return 0; +} + +#ifdef ENABLE_EGL +static int +wayland_output_repaint_gl(struct weston_output *output_base, + pixman_region32_t *damage, + void *repaint_data) +{ + struct wayland_output *output = to_wayland_output(output_base); + struct weston_compositor *ec = output->base.compositor; + + output->frame_cb = wl_surface_frame(output->parent.surface); + wl_callback_add_listener(output->frame_cb, &frame_listener, output); + + wayland_output_update_gl_border(output); + + ec->renderer->repaint_output(&output->base, damage); + + pixman_region32_subtract(&ec->primary_plane.damage, + &ec->primary_plane.damage, damage); + return 0; +} +#endif + +static void +wayland_output_update_shm_border(struct wayland_shm_buffer *buffer) +{ + int32_t ix, iy, iwidth, iheight, fwidth, fheight; + cairo_t *cr; + + if (!buffer->output->frame || !buffer->frame_damaged) + return; + + cr = cairo_create(buffer->c_surface); + + frame_interior(buffer->output->frame, &ix, &iy, &iwidth, &iheight); + fwidth = frame_width(buffer->output->frame); + fheight = frame_height(buffer->output->frame); + + /* Set the clip so we don't unnecisaraly damage the surface */ + cairo_move_to(cr, ix, iy); + cairo_rel_line_to(cr, iwidth, 0); + cairo_rel_line_to(cr, 0, iheight); + cairo_rel_line_to(cr, -iwidth, 0); + cairo_line_to(cr, ix, iy); + cairo_line_to(cr, 0, iy); + cairo_line_to(cr, 0, fheight); + cairo_line_to(cr, fwidth, fheight); + cairo_line_to(cr, fwidth, 0); + cairo_line_to(cr, 0, 0); + cairo_line_to(cr, 0, iy); + cairo_close_path(cr); + cairo_clip(cr); + + /* Draw using a pattern so that the final result gets clipped */ + cairo_push_group(cr); + frame_repaint(buffer->output->frame, cr); + cairo_pop_group_to_source(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_paint(cr); + + cairo_destroy(cr); +} + +static void +wayland_shm_buffer_attach(struct wayland_shm_buffer *sb) +{ + pixman_region32_t damage; + pixman_box32_t *rects; + int32_t ix, iy, iwidth, iheight, fwidth, fheight; + int i, n; + + pixman_region32_init(&damage); + pixman_region32_copy(&damage, &sb->damage); + pixman_region32_translate(&damage, -sb->output->base.x, + -sb->output->base.y); + + weston_transformed_region(sb->output->base.width, + sb->output->base.height, + sb->output->base.transform, + sb->output->base.current_scale, + &damage, &damage); + + if (sb->output->frame) { + frame_interior(sb->output->frame, &ix, &iy, &iwidth, &iheight); + fwidth = frame_width(sb->output->frame); + fheight = frame_height(sb->output->frame); + + pixman_region32_translate(&damage, ix, iy); + + if (sb->frame_damaged) { + pixman_region32_union_rect(&damage, &damage, + 0, 0, fwidth, iy); + pixman_region32_union_rect(&damage, &damage, + 0, iy, ix, iheight); + pixman_region32_union_rect(&damage, &damage, + ix + iwidth, iy, + fwidth - (ix + iwidth), iheight); + pixman_region32_union_rect(&damage, &damage, + 0, iy + iheight, + fwidth, fheight - (iy + iheight)); + } + } + + rects = pixman_region32_rectangles(&damage, &n); + wl_surface_attach(sb->output->parent.surface, sb->buffer, 0, 0); + for (i = 0; i < n; ++i) + wl_surface_damage(sb->output->parent.surface, rects[i].x1, + rects[i].y1, rects[i].x2 - rects[i].x1, + rects[i].y2 - rects[i].y1); + + if (sb->output->frame) + pixman_region32_fini(&damage); +} + +static int +wayland_output_repaint_pixman(struct weston_output *output_base, + pixman_region32_t *damage, + void *repaint_data) +{ + struct wayland_output *output = to_wayland_output(output_base); + struct wayland_backend *b = + to_wayland_backend(output->base.compositor); + struct wayland_shm_buffer *sb; + + if (output->frame) { + if (frame_status(output->frame) & FRAME_STATUS_REPAINT) + wl_list_for_each(sb, &output->shm.buffers, link) + sb->frame_damaged = 1; + } + + wl_list_for_each(sb, &output->shm.buffers, link) + pixman_region32_union(&sb->damage, &sb->damage, damage); + + sb = wayland_output_get_shm_buffer(output); + + wayland_output_update_shm_border(sb); + pixman_renderer_output_set_buffer(output_base, sb->pm_image); + b->compositor->renderer->repaint_output(output_base, &sb->damage); + + wayland_shm_buffer_attach(sb); + + output->frame_cb = wl_surface_frame(output->parent.surface); + wl_callback_add_listener(output->frame_cb, &frame_listener, output); + wl_surface_commit(output->parent.surface); + wl_display_flush(b->parent.wl_display); + + pixman_region32_fini(&sb->damage); + pixman_region32_init(&sb->damage); + sb->frame_damaged = 0; + + pixman_region32_subtract(&b->compositor->primary_plane.damage, + &b->compositor->primary_plane.damage, damage); + return 0; +} + +static void +wayland_backend_destroy_output_surface(struct wayland_output *output) +{ + assert(output->parent.surface); + + if (output->parent.xdg_toplevel) { + xdg_toplevel_destroy(output->parent.xdg_toplevel); + output->parent.xdg_toplevel = NULL; + } + + if (output->parent.xdg_surface) { + xdg_surface_destroy(output->parent.xdg_surface); + output->parent.xdg_surface = NULL; + } + + if (output->parent.shell_surface) { + wl_shell_surface_destroy(output->parent.shell_surface); + output->parent.shell_surface = NULL; + } + + wl_surface_destroy(output->parent.surface); + output->parent.surface = NULL; +} + +static void +wayland_output_destroy_shm_buffers(struct wayland_output *output) +{ + struct wayland_shm_buffer *buffer, *next; + + /* Throw away any remaining SHM buffers */ + wl_list_for_each_safe(buffer, next, &output->shm.free_buffers, free_link) + wayland_shm_buffer_destroy(buffer); + /* These will get thrown away when they get released */ + wl_list_for_each(buffer, &output->shm.buffers, link) + buffer->output = NULL; +} + +static int +wayland_output_disable(struct weston_output *base) +{ + struct wayland_output *output = to_wayland_output(base); + struct wayland_backend *b = to_wayland_backend(base->compositor); + + if (!output->base.enabled) + return 0; + + if (b->use_pixman) { + pixman_renderer_output_destroy(&output->base); +#ifdef ENABLE_EGL + } else { + gl_renderer->output_destroy(&output->base); + wl_egl_window_destroy(output->gl.egl_window); +#endif + } + + wayland_output_destroy_shm_buffers(output); + + wayland_backend_destroy_output_surface(output); + + if (output->frame) + frame_destroy(output->frame); + + cairo_surface_destroy(output->gl.border.top); + cairo_surface_destroy(output->gl.border.left); + cairo_surface_destroy(output->gl.border.right); + cairo_surface_destroy(output->gl.border.bottom); + + return 0; +} + +static void +wayland_output_destroy(struct weston_output *base) +{ + struct wayland_output *output = to_wayland_output(base); + + wayland_output_disable(&output->base); + + weston_output_release(&output->base); + + if (output->frame_cb) + wl_callback_destroy(output->frame_cb); + + free(output->title); + free(output); +} + +static const struct wl_shell_surface_listener shell_surface_listener; + +#ifdef ENABLE_EGL +static int +wayland_output_init_gl_renderer(struct wayland_output *output) +{ + int32_t fwidth = 0, fheight = 0; + struct gl_renderer_output_options options = { + .drm_formats = wayland_formats, + .drm_formats_count = ARRAY_LENGTH(wayland_formats), + }; + + if (output->frame) { + fwidth = frame_width(output->frame); + fheight = frame_height(output->frame); + } else { + fwidth = output->base.current_mode->width; + fheight = output->base.current_mode->height; + } + + output->gl.egl_window = + wl_egl_window_create(output->parent.surface, + fwidth, fheight); + if (!output->gl.egl_window) { + weston_log("failure to create wl_egl_window\n"); + return -1; + } + options.window_for_legacy = output->gl.egl_window; + options.window_for_platform = output->gl.egl_window; + + if (gl_renderer->output_window_create(&output->base, &options) < 0) + goto cleanup_window; + + return 0; + +cleanup_window: + wl_egl_window_destroy(output->gl.egl_window); + return -1; +} +#endif + +static int +wayland_output_init_pixman_renderer(struct wayland_output *output) +{ + const struct pixman_renderer_output_options options = { + .use_shadow = true, + }; + return pixman_renderer_output_create(&output->base, &options); +} + +static void +wayland_output_resize_surface(struct wayland_output *output) +{ + struct wayland_backend *b = + to_wayland_backend(output->base.compositor); + int32_t ix, iy, iwidth, iheight; + int32_t width, height; + struct wl_region *region; + + width = output->base.current_mode->width; + height = output->base.current_mode->height; + + if (output->frame) { + frame_resize_inside(output->frame, width, height); + + frame_input_rect(output->frame, &ix, &iy, &iwidth, &iheight); + region = wl_compositor_create_region(b->parent.compositor); + wl_region_add(region, ix, iy, iwidth, iheight); + wl_surface_set_input_region(output->parent.surface, region); + wl_region_destroy(region); + + if (output->parent.xdg_surface) { + xdg_surface_set_window_geometry(output->parent.xdg_surface, + ix, + iy, + iwidth, + iheight); + } + + frame_opaque_rect(output->frame, &ix, &iy, &iwidth, &iheight); + region = wl_compositor_create_region(b->parent.compositor); + wl_region_add(region, ix, iy, iwidth, iheight); + wl_surface_set_opaque_region(output->parent.surface, region); + wl_region_destroy(region); + + width = frame_width(output->frame); + height = frame_height(output->frame); + } else { + region = wl_compositor_create_region(b->parent.compositor); + wl_region_add(region, 0, 0, width, height); + wl_surface_set_input_region(output->parent.surface, region); + wl_region_destroy(region); + + region = wl_compositor_create_region(b->parent.compositor); + wl_region_add(region, 0, 0, width, height); + wl_surface_set_opaque_region(output->parent.surface, region); + wl_region_destroy(region); + + if (output->parent.xdg_surface) { + xdg_surface_set_window_geometry(output->parent.xdg_surface, + 0, + 0, + width, + height); + } + } + +#ifdef ENABLE_EGL + if (output->gl.egl_window) { + wl_egl_window_resize(output->gl.egl_window, + width, height, 0, 0); + + /* These will need to be re-created due to the resize */ + gl_renderer->output_set_border(&output->base, + GL_RENDERER_BORDER_TOP, + 0, 0, 0, NULL); + cairo_surface_destroy(output->gl.border.top); + output->gl.border.top = NULL; + gl_renderer->output_set_border(&output->base, + GL_RENDERER_BORDER_LEFT, + 0, 0, 0, NULL); + cairo_surface_destroy(output->gl.border.left); + output->gl.border.left = NULL; + gl_renderer->output_set_border(&output->base, + GL_RENDERER_BORDER_RIGHT, + 0, 0, 0, NULL); + cairo_surface_destroy(output->gl.border.right); + output->gl.border.right = NULL; + gl_renderer->output_set_border(&output->base, + GL_RENDERER_BORDER_BOTTOM, + 0, 0, 0, NULL); + cairo_surface_destroy(output->gl.border.bottom); + output->gl.border.bottom = NULL; + } +#endif + + wayland_output_destroy_shm_buffers(output); +} + +static int +wayland_output_set_windowed(struct wayland_output *output) +{ + struct wayland_backend *b = + to_wayland_backend(output->base.compositor); + + if (output->frame) + return 0; + + if (!b->theme) { + b->theme = theme_create(); + if (!b->theme) + return -1; + } + output->frame = frame_create(b->theme, 100, 100, + FRAME_BUTTON_CLOSE, output->title, NULL); + if (!output->frame) + return -1; + + if (output->keyboard_count) + frame_set_flag(output->frame, FRAME_FLAG_ACTIVE); + + wayland_output_resize_surface(output); + + if (output->parent.xdg_toplevel) { + xdg_toplevel_unset_fullscreen(output->parent.xdg_toplevel); + } else if (output->parent.shell_surface) { + wl_shell_surface_set_toplevel(output->parent.shell_surface); + } else { + abort(); + } + + return 0; +} + +static void +wayland_output_set_fullscreen(struct wayland_output *output, + enum wl_shell_surface_fullscreen_method method, + uint32_t framerate, struct wl_output *target) +{ + if (output->frame) { + frame_destroy(output->frame); + output->frame = NULL; + } + + wayland_output_resize_surface(output); + + if (output->parent.xdg_toplevel) { + xdg_toplevel_set_fullscreen(output->parent.xdg_toplevel, target); + } else if (output->parent.shell_surface) { + wl_shell_surface_set_fullscreen(output->parent.shell_surface, + method, framerate, target); + } else { + abort(); + } +} + +static struct weston_mode * +wayland_output_choose_mode(struct wayland_output *output, + struct weston_mode *ref_mode) +{ + struct weston_mode *mode; + + /* First look for an exact match */ + wl_list_for_each(mode, &output->base.mode_list, link) + if (mode->width == ref_mode->width && + mode->height == ref_mode->height && + mode->refresh == ref_mode->refresh) + return mode; + + /* If we can't find an exact match, ignore refresh and try again */ + wl_list_for_each(mode, &output->base.mode_list, link) + if (mode->width == ref_mode->width && + mode->height == ref_mode->height) + return mode; + + /* Yeah, we failed */ + return NULL; +} + +enum mode_status { + MODE_STATUS_UNKNOWN, + MODE_STATUS_SUCCESS, + MODE_STATUS_FAIL, + MODE_STATUS_CANCEL, +}; + +static void +mode_feedback_successful(void *data, + struct zwp_fullscreen_shell_mode_feedback_v1 *fb) +{ + enum mode_status *value = data; + + printf("Mode switch successful\n"); + + *value = MODE_STATUS_SUCCESS; +} + +static void +mode_feedback_failed(void *data, struct zwp_fullscreen_shell_mode_feedback_v1 *fb) +{ + enum mode_status *value = data; + + printf("Mode switch failed\n"); + + *value = MODE_STATUS_FAIL; +} + +static void +mode_feedback_cancelled(void *data, struct zwp_fullscreen_shell_mode_feedback_v1 *fb) +{ + enum mode_status *value = data; + + printf("Mode switch cancelled\n"); + + *value = MODE_STATUS_CANCEL; +} + +struct zwp_fullscreen_shell_mode_feedback_v1_listener mode_feedback_listener = { + mode_feedback_successful, + mode_feedback_failed, + mode_feedback_cancelled, +}; + +static enum mode_status +wayland_output_fullscreen_shell_mode_feedback(struct wayland_output *output, + struct weston_mode *mode) +{ + struct wayland_backend *b = to_wayland_backend(output->base.compositor); + struct zwp_fullscreen_shell_mode_feedback_v1 *mode_feedback; + enum mode_status mode_status; + int ret = 0; + + mode_feedback = + zwp_fullscreen_shell_v1_present_surface_for_mode(b->parent.fshell, + output->parent.surface, + output->parent.output, + mode->refresh); + + zwp_fullscreen_shell_mode_feedback_v1_add_listener(mode_feedback, + &mode_feedback_listener, + &mode_status); + + output->parent.draw_initial_frame = false; + draw_initial_frame(output); + wl_surface_commit(output->parent.surface); + + mode_status = MODE_STATUS_UNKNOWN; + while (mode_status == MODE_STATUS_UNKNOWN && ret >= 0) + ret = wl_display_dispatch(b->parent.wl_display); + + zwp_fullscreen_shell_mode_feedback_v1_destroy(mode_feedback); + + return mode_status; +} + +static int +wayland_output_switch_mode(struct weston_output *output_base, + struct weston_mode *mode) +{ + struct wayland_output *output = to_wayland_output(output_base); + struct wayland_backend *b; + struct wl_surface *old_surface; + struct weston_mode *old_mode; + enum mode_status mode_status; + + if (output_base == NULL) { + weston_log("output is NULL.\n"); + return -1; + } + + if (mode == NULL) { + weston_log("mode is NULL.\n"); + return -1; + } + + b = to_wayland_backend(output_base->compositor); + + if (output->parent.xdg_surface || output->parent.shell_surface || !b->parent.fshell) + return -1; + + mode = wayland_output_choose_mode(output, mode); + if (mode == NULL) + return -1; + + if (output->base.current_mode == mode) + return 0; + + old_mode = output->base.current_mode; + old_surface = output->parent.surface; + output->base.current_mode = mode; + output->parent.surface = + wl_compositor_create_surface(b->parent.compositor); + wl_surface_set_user_data(output->parent.surface, output); + + /* Blow the old buffers because we changed size/surfaces */ + wayland_output_resize_surface(output); + + mode_status = wayland_output_fullscreen_shell_mode_feedback(output, mode); + + /* This should kick-start things again */ + wayland_output_start_repaint_loop(&output->base); + + if (mode_status == MODE_STATUS_FAIL) { + output->base.current_mode = old_mode; + wl_surface_destroy(output->parent.surface); + output->parent.surface = old_surface; + wayland_output_resize_surface(output); + + return -1; + } + + old_mode->flags &= ~WL_OUTPUT_MODE_CURRENT; + output->base.current_mode->flags |= WL_OUTPUT_MODE_CURRENT; + + if (b->use_pixman) { + pixman_renderer_output_destroy(output_base); + if (wayland_output_init_pixman_renderer(output) < 0) + goto err_output; +#ifdef ENABLE_EGL + } else { + gl_renderer->output_destroy(output_base); + wl_egl_window_destroy(output->gl.egl_window); + if (wayland_output_init_gl_renderer(output) < 0) + goto err_output; +#endif + } + wl_surface_destroy(old_surface); + + weston_output_schedule_repaint(&output->base); + + return 0; + +err_output: + /* XXX */ + return -1; +} + +static void +handle_xdg_surface_configure(void *data, struct xdg_surface *surface, + uint32_t serial) +{ + xdg_surface_ack_configure(surface, serial); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + handle_xdg_surface_configure +}; + +static void +handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *toplevel, + int32_t width, int32_t height, + struct wl_array *states) +{ + struct wayland_output *output = data; + + output->parent.configure_width = width; + output->parent.configure_height = height; + + output->parent.wait_for_configure = false; + /* FIXME: implement resizing */ +} + +static void +handle_xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) +{ + struct wayland_output *output = data; + struct weston_compositor *compositor = output->base.compositor; + + wayland_output_destroy(&output->base); + + if (wl_list_empty(&compositor->output_list)) + weston_compositor_exit(compositor); +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + handle_xdg_toplevel_configure, + handle_xdg_toplevel_close, +}; + +static int +wayland_backend_create_output_surface(struct wayland_output *output) +{ + struct wayland_backend *b = to_wayland_backend(output->base.compositor); + + assert(!output->parent.surface); + + output->parent.surface = + wl_compositor_create_surface(b->parent.compositor); + if (!output->parent.surface) + return -1; + + wl_surface_set_user_data(output->parent.surface, output); + + output->parent.draw_initial_frame = true; + + if (b->parent.xdg_wm_base) { + output->parent.xdg_surface = + xdg_wm_base_get_xdg_surface(b->parent.xdg_wm_base, + output->parent.surface); + xdg_surface_add_listener(output->parent.xdg_surface, + &xdg_surface_listener, output); + + output->parent.xdg_toplevel = + xdg_surface_get_toplevel(output->parent.xdg_surface); + xdg_toplevel_add_listener(output->parent.xdg_toplevel, + &xdg_toplevel_listener, output); + + xdg_toplevel_set_title(output->parent.xdg_toplevel, output->title); + + wl_surface_commit(output->parent.surface); + + output->parent.wait_for_configure = true; + + while (output->parent.wait_for_configure) + wl_display_dispatch(b->parent.wl_display); + + weston_log("wayland-backend: Using xdg_wm_base\n"); + } + else if (b->parent.shell) { + output->parent.shell_surface = + wl_shell_get_shell_surface(b->parent.shell, + output->parent.surface); + if (!output->parent.shell_surface) { + wl_surface_destroy(output->parent.surface); + return -1; + } + + wl_shell_surface_add_listener(output->parent.shell_surface, + &shell_surface_listener, output); + + weston_log("wayland-backend: Using wl_shell\n"); + } + + return 0; +} + +static int +wayland_output_enable(struct weston_output *base) +{ + struct wayland_output *output = to_wayland_output(base); + struct wayland_backend *b = to_wayland_backend(base->compositor); + enum mode_status mode_status; + int ret = 0; + + weston_log("Creating %dx%d wayland output at (%d, %d)\n", + output->base.current_mode->width, + output->base.current_mode->height, + output->base.x, output->base.y); + + if (!output->parent.surface) + ret = wayland_backend_create_output_surface(output); + + if (ret < 0) + return -1; + + wl_list_init(&output->shm.buffers); + wl_list_init(&output->shm.free_buffers); + + if (b->use_pixman) { + if (wayland_output_init_pixman_renderer(output) < 0) + goto err_output; + + output->base.repaint = wayland_output_repaint_pixman; +#ifdef ENABLE_EGL + } else { + if (wayland_output_init_gl_renderer(output) < 0) + goto err_output; + + output->base.repaint = wayland_output_repaint_gl; +#endif + } + + output->base.start_repaint_loop = wayland_output_start_repaint_loop; + output->base.assign_planes = NULL; + output->base.set_backlight = NULL; + output->base.set_dpms = NULL; + output->base.switch_mode = wayland_output_switch_mode; + + if (b->sprawl_across_outputs) { + if (b->parent.fshell) { + wayland_output_resize_surface(output); + + mode_status = wayland_output_fullscreen_shell_mode_feedback(output, &output->mode); + + if (mode_status == MODE_STATUS_FAIL) { + zwp_fullscreen_shell_v1_present_surface(b->parent.fshell, + output->parent.surface, + ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_CENTER, + output->parent.output); + + output->parent.draw_initial_frame = true; + } + } else { + wayland_output_set_fullscreen(output, + WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER, + output->mode.refresh, output->parent.output); + } + } else if (b->fullscreen) { + wayland_output_set_fullscreen(output, 0, 0, NULL); + } else { + wayland_output_set_windowed(output); + } + + return 0; + +err_output: + wayland_backend_destroy_output_surface(output); + + return -1; +} + +static int +wayland_output_setup_for_parent_output(struct wayland_output *output, + struct wayland_parent_output *poutput); + +static int +wayland_output_setup_fullscreen(struct wayland_output *output, + struct wayland_head *head); + +static int +wayland_output_attach_head(struct weston_output *output_base, + struct weston_head *head_base) +{ + struct wayland_backend *b = to_wayland_backend(output_base->compositor); + struct wayland_output *output = to_wayland_output(output_base); + struct wayland_head *head = to_wayland_head(head_base); + + if (!wl_list_empty(&output->base.head_list)) + return -1; + + if (head->parent_output) { + if (wayland_output_setup_for_parent_output(output, + head->parent_output) < 0) + return -1; + } else if (b->fullscreen) { + if (wayland_output_setup_fullscreen(output, head) < 0) + return -1; + } else { + /* A floating window, nothing to do. */ + } + + return 0; +} + +static void +wayland_output_detach_head(struct weston_output *output_base, + struct weston_head *head) +{ + struct wayland_output *output = to_wayland_output(output_base); + + /* Rely on the disable hook if the output was enabled. We do not + * support cloned heads, so detaching is guaranteed to disable the + * output. + */ + if (output->base.enabled) + return; + + /* undo setup fullscreen */ + if (output->parent.surface) + wayland_backend_destroy_output_surface(output); +} + +static struct weston_output * +wayland_output_create(struct weston_compositor *compositor, const char *name) +{ + struct wayland_output *output; + char *title; + + /* name can't be NULL. */ + assert(name); + + output = zalloc(sizeof *output); + if (output == NULL) { + perror("zalloc"); + return NULL; + } + + if (asprintf(&title, "%s - %s", WINDOW_TITLE, name) < 0) { + free(output); + return NULL; + } + output->title = title; + + weston_output_init(&output->base, compositor, name); + + output->base.destroy = wayland_output_destroy; + output->base.disable = wayland_output_disable; + output->base.enable = wayland_output_enable; + output->base.attach_head = wayland_output_attach_head; + output->base.detach_head = wayland_output_detach_head; + + weston_compositor_add_pending_output(&output->base, compositor); + + return &output->base; +} + +static struct wayland_head * +wayland_head_create(struct weston_compositor *compositor, const char *name) +{ + struct wayland_head *head; + + assert(name); + + head = zalloc(sizeof *head); + if (!head) + return NULL; + + weston_head_init(&head->base, name); + weston_head_set_connection_status(&head->base, true); + weston_compositor_add_head(compositor, &head->base); + + return head; +} + +static int +wayland_head_create_windowed(struct weston_compositor *compositor, + const char *name) +{ + if (!wayland_head_create(compositor, name)) + return -1; + + return 0; +} + +static int +wayland_head_create_for_parent_output(struct weston_compositor *compositor, + struct wayland_parent_output *poutput) +{ + struct wayland_head *head; + char name[100]; + int ret; + + ret = snprintf(name, sizeof(name), "wlparent-%d", poutput->id); + if (ret < 1 || (unsigned)ret >= sizeof(name)) + return -1; + + head = wayland_head_create(compositor, name); + if (!head) + return -1; + + assert(!poutput->head); + head->parent_output = poutput; + poutput->head = head; + + weston_head_set_monitor_strings(&head->base, + poutput->physical.make, + poutput->physical.model, NULL); + weston_head_set_physical_size(&head->base, + poutput->physical.width, + poutput->physical.height); + + return 0; +} + +static void +wayland_head_destroy(struct wayland_head *head) +{ + if (head->parent_output) + head->parent_output->head = NULL; + + weston_head_release(&head->base); + free(head); +} + +static int +wayland_output_set_size(struct weston_output *base, int width, int height) +{ + struct wayland_output *output = to_wayland_output(base); + struct weston_head *head; + int output_width, output_height; + + /* We can only be called once. */ + assert(!output->base.current_mode); + + /* Make sure we have scale set. */ + assert(output->base.scale); + + if (width < 1) { + weston_log("Invalid width \"%d\" for output %s\n", + width, output->base.name); + return -1; + } + + if (height < 1) { + weston_log("Invalid height \"%d\" for output %s\n", + height, output->base.name); + return -1; + } + + wl_list_for_each(head, &output->base.head_list, output_link) { + weston_head_set_monitor_strings(head, "wayland", "none", NULL); + + /* XXX: Calculate proper size. */ + weston_head_set_physical_size(head, width, height); + } + + output_width = width * output->base.scale; + output_height = height * output->base.scale; + + 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; + wl_list_insert(&output->base.mode_list, &output->mode.link); + + output->base.current_mode = &output->mode; + + return 0; +} + +static int +wayland_output_setup_for_parent_output(struct wayland_output *output, + struct wayland_parent_output *poutput) +{ + struct weston_mode *mode; + + if (poutput->current_mode) { + mode = poutput->current_mode; + } else if (poutput->preferred_mode) { + mode = poutput->preferred_mode; + } else if (!wl_list_empty(&poutput->mode_list)) { + mode = container_of(poutput->mode_list.next, + struct weston_mode, link); + } else { + weston_log("No valid modes found. Skipping output.\n"); + return -1; + } + + output->base.scale = 1; + output->base.transform = WL_OUTPUT_TRANSFORM_NORMAL; + + output->parent.output = poutput->global; + + wl_list_insert_list(&output->base.mode_list, &poutput->mode_list); + wl_list_init(&poutput->mode_list); + + /* No other mode should have CURRENT already. */ + mode->flags |= WL_OUTPUT_MODE_CURRENT; + output->base.current_mode = mode; + + /* output->mode is unused in this path. */ + + return 0; +} + +static int +wayland_output_setup_fullscreen(struct wayland_output *output, + struct wayland_head *head) +{ + struct wayland_backend *b = to_wayland_backend(output->base.compositor); + int width = 0, height = 0; + + output->base.scale = 1; + output->base.transform = WL_OUTPUT_TRANSFORM_NORMAL; + + if (wayland_backend_create_output_surface(output) < 0) + return -1; + + /* What should size be set if conditional is false? */ + if (b->parent.xdg_wm_base || b->parent.shell) { + if (output->parent.xdg_toplevel) + xdg_toplevel_set_fullscreen(output->parent.xdg_toplevel, + output->parent.output); + else if (output->parent.shell_surface) + wl_shell_surface_set_fullscreen(output->parent.shell_surface, + 0, 0, NULL); + + wl_display_roundtrip(b->parent.wl_display); + + width = output->parent.configure_width; + height = output->parent.configure_height; + } + + if (wayland_output_set_size(&output->base, width, height) < 0) + goto err_set_size; + + /* The head is not attached yet, so set_size did not set these. */ + weston_head_set_monitor_strings(&head->base, "wayland", "none", NULL); + /* XXX: Calculate proper size. */ + weston_head_set_physical_size(&head->base, width, height); + + return 0; + +err_set_size: + wayland_backend_destroy_output_surface(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) +{ + struct wayland_output *output = data; + + output->parent.configure_width = width; + output->parent.configure_height = 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 input interface */ +static void +input_set_cursor(struct wayland_input *input) +{ + + struct wl_buffer *buffer; + struct wl_cursor_image *image; + + if (!input->backend->cursor) + return; /* Couldn't load the cursor. Can't set it */ + + image = input->backend->cursor->images[0]; + buffer = wl_cursor_image_get_buffer(image); + if (!buffer) + return; + + wl_pointer_set_cursor(input->parent.pointer, input->enter_serial, + input->parent.cursor.surface, + image->hotspot_x, image->hotspot_y); + + wl_surface_attach(input->parent.cursor.surface, buffer, 0, 0); + wl_surface_damage(input->parent.cursor.surface, 0, 0, + image->width, image->height); + wl_surface_commit(input->parent.cursor.surface); +} + +static void +input_handle_pointer_enter(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t fixed_x, wl_fixed_t fixed_y) +{ + struct wayland_input *input = data; + int32_t fx, fy; + enum theme_location location; + double x, y; + + if (!surface) { + input->output = NULL; + input->has_focus = false; + notify_pointer_focus(&input->base, NULL, 0, 0); + return; + } + + x = wl_fixed_to_double(fixed_x); + y = wl_fixed_to_double(fixed_y); + + /* 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); + + if (input->output->frame) { + location = frame_pointer_enter(input->output->frame, input, + x, y); + frame_interior(input->output->frame, &fx, &fy, NULL, NULL); + x -= fx; + y -= fy; + + if (frame_status(input->output->frame) & FRAME_STATUS_REPAINT) + weston_output_schedule_repaint(&input->output->base); + } else { + location = THEME_LOCATION_CLIENT_AREA; + } + + weston_output_transform_coordinate(&input->output->base, x, y, &x, &y); + + if (location == THEME_LOCATION_CLIENT_AREA) { + input->has_focus = true; + notify_pointer_focus(&input->base, &input->output->base, x, y); + wl_pointer_set_cursor(input->parent.pointer, + input->enter_serial, NULL, 0, 0); + } else { + input->has_focus = false; + notify_pointer_focus(&input->base, NULL, 0, 0); + input_set_cursor(input); + } +} + +static void +input_handle_pointer_leave(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface) +{ + struct wayland_input *input = data; + + if (!input->output) + return; + + if (input->output->frame) { + frame_pointer_leave(input->output->frame, input); + + if (frame_status(input->output->frame) & FRAME_STATUS_REPAINT) + weston_output_schedule_repaint(&input->output->base); + } + + notify_pointer_focus(&input->base, NULL, 0, 0); + input->output = NULL; + input->has_focus = false; +} + +static void +input_handle_motion(void *data, struct wl_pointer *pointer, + uint32_t time, wl_fixed_t fixed_x, wl_fixed_t fixed_y) +{ + struct wayland_input *input = data; + int32_t fx, fy; + enum theme_location location; + bool want_frame = false; + double x, y; + struct timespec ts; + + if (!input->output) + return; + + x = wl_fixed_to_double(fixed_x); + y = wl_fixed_to_double(fixed_y); + + if (input->output->frame) { + location = frame_pointer_motion(input->output->frame, input, + x, y); + frame_interior(input->output->frame, &fx, &fy, NULL, NULL); + x -= fx; + y -= fy; + + if (frame_status(input->output->frame) & FRAME_STATUS_REPAINT) + weston_output_schedule_repaint(&input->output->base); + } else { + location = THEME_LOCATION_CLIENT_AREA; + } + + weston_output_transform_coordinate(&input->output->base, x, y, &x, &y); + + if (input->has_focus && location != THEME_LOCATION_CLIENT_AREA) { + input_set_cursor(input); + notify_pointer_focus(&input->base, NULL, 0, 0); + input->has_focus = false; + want_frame = true; + } else if (!input->has_focus && + location == THEME_LOCATION_CLIENT_AREA) { + wl_pointer_set_cursor(input->parent.pointer, + input->enter_serial, NULL, 0, 0); + notify_pointer_focus(&input->base, &input->output->base, x, y); + input->has_focus = true; + want_frame = true; + } + + if (location == THEME_LOCATION_CLIENT_AREA) { + timespec_from_msec(&ts, time); + notify_motion_absolute(&input->base, &ts, x, y); + want_frame = true; + } + + if (want_frame && input->seat_version < WL_POINTER_FRAME_SINCE_VERSION) + notify_pointer_frame(&input->base); +} + +static void +input_handle_button(void *data, struct wl_pointer *pointer, + uint32_t serial, uint32_t time, uint32_t button, + enum wl_pointer_button_state state) +{ + struct wayland_input *input = data; + enum theme_location location; + struct timespec ts; + + if (!input->output) + return; + + if (input->output->frame) { + location = frame_pointer_button(input->output->frame, input, + button, state); + + if (frame_status(input->output->frame) & FRAME_STATUS_MOVE) { + if (input->output->parent.xdg_toplevel) + xdg_toplevel_move(input->output->parent.xdg_toplevel, + input->parent.seat, serial); + else if (input->output->parent.shell_surface) + wl_shell_surface_move(input->output->parent.shell_surface, + input->parent.seat, serial); + frame_status_clear(input->output->frame, + FRAME_STATUS_MOVE); + return; + } + + if (frame_status(input->output->frame) & FRAME_STATUS_CLOSE) { + wayland_output_destroy(&input->output->base); + input->output = NULL; + input->keyboard_focus = NULL; + + if (wl_list_empty(&input->backend->compositor->output_list)) + weston_compositor_exit(input->backend->compositor); + + return; + } + + if (frame_status(input->output->frame) & FRAME_STATUS_REPAINT) + weston_output_schedule_repaint(&input->output->base); + } else { + location = THEME_LOCATION_CLIENT_AREA; + } + + if (location == THEME_LOCATION_CLIENT_AREA) { + timespec_from_msec(&ts, time); + notify_button(&input->base, &ts, button, state); + if (input->seat_version < WL_POINTER_FRAME_SINCE_VERSION) + notify_pointer_frame(&input->base); + } +} + +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; + struct weston_pointer_axis_event weston_event; + struct timespec ts; + + weston_event.axis = axis; + weston_event.value = wl_fixed_to_double(value); + weston_event.has_discrete = false; + + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL && + input->vert.has_discrete) { + weston_event.has_discrete = true; + weston_event.discrete = input->vert.discrete; + input->vert.has_discrete = false; + } else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL && + input->horiz.has_discrete) { + weston_event.has_discrete = true; + weston_event.discrete = input->horiz.discrete; + input->horiz.has_discrete = false; + } + + timespec_from_msec(&ts, time); + + notify_axis(&input->base, &ts, &weston_event); + + if (input->seat_version < WL_POINTER_FRAME_SINCE_VERSION) + notify_pointer_frame(&input->base); +} + +static void +input_handle_frame(void *data, struct wl_pointer *pointer) +{ + struct wayland_input *input = data; + + notify_pointer_frame(&input->base); +} + +static void +input_handle_axis_source(void *data, struct wl_pointer *pointer, + uint32_t source) +{ + struct wayland_input *input = data; + + notify_axis_source(&input->base, source); +} + +static void +input_handle_axis_stop(void *data, struct wl_pointer *pointer, + uint32_t time, uint32_t axis) +{ + struct wayland_input *input = data; + struct weston_pointer_axis_event weston_event; + struct timespec ts; + + weston_event.axis = axis; + weston_event.value = 0; + + timespec_from_msec(&ts, time); + + notify_axis(&input->base, &ts, &weston_event); +} + +static void +input_handle_axis_discrete(void *data, struct wl_pointer *pointer, + uint32_t axis, int32_t discrete) +{ + struct wayland_input *input = data; + + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + input->vert.has_discrete = true; + input->vert.discrete = discrete; + } else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { + input->horiz.has_discrete = true; + input->horiz.discrete = discrete; + } +} + +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, + input_handle_frame, + input_handle_axis_source, + input_handle_axis_stop, + input_handle_axis_discrete, +}; + +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) { + map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (map_str == MAP_FAILED) { + weston_log("mmap failed: %s\n", strerror(errno)); + goto error; + } + + keymap = xkb_keymap_new_from_string(input->backend->compositor->xkb_context, + map_str, + XKB_KEYMAP_FORMAT_TEXT_V1, + 0); + munmap(map_str, size); + + if (!keymap) { + weston_log("failed to compile keymap\n"); + goto error; + } + + input->keyboard_state_update = STATE_UPDATE_NONE; + } else if (format == WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP) { + weston_log("No keymap provided; falling back to default\n"); + keymap = NULL; + input->keyboard_state_update = STATE_UPDATE_AUTOMATIC; + } else { + weston_log("Invalid keymap\n"); + goto error; + } + + close(fd); + + if (weston_seat_get_keyboard(&input->base)) + weston_seat_update_keymap(&input->base, keymap); + else + weston_seat_init_keyboard(&input->base, keymap); + + xkb_keymap_unref(keymap); + + return; + +error: + wl_keyboard_release(input->parent.keyboard); + close(fd); +} + +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; + struct wayland_output *focus; + + focus = input->keyboard_focus; + if (focus) { + /* This shouldn't happen */ + focus->keyboard_count--; + if (!focus->keyboard_count && focus->frame) + frame_unset_flag(focus->frame, FRAME_FLAG_ACTIVE); + if (frame_status(focus->frame) & FRAME_STATUS_REPAINT) + weston_output_schedule_repaint(&focus->base); + } + + if (!surface) { + input->keyboard_focus = NULL; + return; + } + + input->keyboard_focus = wl_surface_get_user_data(surface); + input->keyboard_focus->keyboard_count++; + + focus = input->keyboard_focus; + if (focus->frame) { + frame_set_flag(focus->frame, FRAME_FLAG_ACTIVE); + if (frame_status(focus->frame) & FRAME_STATUS_REPAINT) + weston_output_schedule_repaint(&focus->base); + } + + + /* 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; + struct wayland_output *focus; + + notify_keyboard_focus_out(&input->base); + + focus = input->keyboard_focus; + if (!focus) + return; + + focus->keyboard_count--; + if (!focus->keyboard_count && focus->frame) { + frame_unset_flag(focus->frame, FRAME_FLAG_ACTIVE); + if (frame_status(focus->frame) & FRAME_STATUS_REPAINT) + weston_output_schedule_repaint(&focus->base); + } + + input->keyboard_focus = NULL; +} + +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; + struct timespec ts; + + if (!input->keyboard_focus) + return; + + timespec_from_msec(&ts, time); + + input->key_serial = serial; + notify_key(&input->base, &ts, key, + state ? WL_KEYBOARD_KEY_STATE_PRESSED : + WL_KEYBOARD_KEY_STATE_RELEASED, + input->keyboard_state_update); +} + +static void +input_handle_modifiers(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial_in, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) +{ + struct weston_keyboard *keyboard; + struct wayland_input *input = data; + struct wayland_backend *b = input->backend; + 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(b->compositor->wl_display); + else + serial_out = wl_display_next_serial(b->compositor->wl_display); + + keyboard = weston_seat_get_keyboard(&input->base); + xkb_state_update_mask(keyboard->xkb_state.state, + mods_depressed, mods_latched, + mods_locked, 0, 0, group); + notify_modifiers(&input->base, serial_out); +} + +static void +input_handle_repeat_info(void *data, struct wl_keyboard *keyboard, + int32_t rate, int32_t delay) +{ + struct wayland_input *input = data; + struct wayland_backend *b = input->backend; + + b->compositor->kb_repeat_rate = rate; + b->compositor->kb_repeat_delay = delay; +} + +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, + input_handle_repeat_info, +}; + +static void +input_handle_touch_down(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, + struct wl_surface *surface, int32_t id, + wl_fixed_t fixed_x, wl_fixed_t fixed_y) +{ + struct wayland_input *input = data; + struct wayland_output *output; + enum theme_location location; + bool first_touch; + int32_t fx, fy; + double x, y; + struct timespec ts; + + x = wl_fixed_to_double(fixed_x); + y = wl_fixed_to_double(fixed_y); + + timespec_from_msec(&ts, time); + + first_touch = (input->touch_points == 0); + input->touch_points++; + + input->touch_focus = wl_surface_get_user_data(surface); + output = input->touch_focus; + if (!first_touch && !input->touch_active) + return; + + if (output->frame) { + location = frame_touch_down(output->frame, input, id, x, y); + + frame_interior(output->frame, &fx, &fy, NULL, NULL); + x -= fx; + y -= fy; + + if (frame_status(output->frame) & FRAME_STATUS_REPAINT) + weston_output_schedule_repaint(&output->base); + + if (first_touch && (frame_status(output->frame) & FRAME_STATUS_MOVE)) { + input->touch_points--; + if (output->parent.xdg_toplevel) + xdg_toplevel_move(output->parent.xdg_toplevel, + input->parent.seat, serial); + else if (output->parent.shell_surface) + wl_shell_surface_move(output->parent.shell_surface, + input->parent.seat, serial); + frame_status_clear(output->frame, + FRAME_STATUS_MOVE); + return; + } + + if (first_touch && location != THEME_LOCATION_CLIENT_AREA) + return; + } + + weston_output_transform_coordinate(&output->base, x, y, &x, &y); + + notify_touch(input->touch_device, &ts, id, x, y, WL_TOUCH_DOWN); + input->touch_active = true; +} + +static void +input_handle_touch_up(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, int32_t id) +{ + struct wayland_input *input = data; + struct wayland_output *output = input->touch_focus; + bool active = input->touch_active; + struct timespec ts; + + timespec_from_msec(&ts, time); + + input->touch_points--; + if (input->touch_points == 0) { + input->touch_focus = NULL; + input->touch_active = false; + } + + if (!output) + return; + + if (output->frame) { + frame_touch_up(output->frame, input, id); + + if (frame_status(output->frame) & FRAME_STATUS_CLOSE) { + wayland_output_destroy(&output->base); + input->touch_focus = NULL; + input->keyboard_focus = NULL; + if (wl_list_empty(&input->backend->compositor->output_list)) + weston_compositor_exit(input->backend->compositor); + + return; + } + if (frame_status(output->frame) & FRAME_STATUS_REPAINT) + weston_output_schedule_repaint(&output->base); + } + + if (active) + notify_touch(input->touch_device, &ts, id, 0, 0, WL_TOUCH_UP); +} + +static void +input_handle_touch_motion(void *data, struct wl_touch *wl_touch, + uint32_t time, int32_t id, + wl_fixed_t fixed_x, wl_fixed_t fixed_y) +{ + struct wayland_input *input = data; + struct wayland_output *output = input->touch_focus; + int32_t fx, fy; + double x, y; + struct timespec ts; + + x = wl_fixed_to_double(fixed_x); + y = wl_fixed_to_double(fixed_y); + timespec_from_msec(&ts, time); + + if (!output || !input->touch_active) + return; + + if (output->frame) { + frame_interior(output->frame, &fx, &fy, NULL, NULL); + x -= fx; + y -= fy; + } + + weston_output_transform_coordinate(&output->base, x, y, &x, &y); + + notify_touch(input->touch_device, &ts, id, x, y, WL_TOUCH_MOTION); +} + +static void +input_handle_touch_frame(void *data, struct wl_touch *wl_touch) +{ + struct wayland_input *input = data; + + if (!input->touch_focus || !input->touch_active) + return; + + notify_touch_frame(input->touch_device); +} + +static void +input_handle_touch_cancel(void *data, struct wl_touch *wl_touch) +{ + struct wayland_input *input = data; + + if (!input->touch_focus || !input->touch_active) + return; + + notify_touch_cancel(input->touch_device); +} + +static const struct wl_touch_listener touch_listener = { + input_handle_touch_down, + input_handle_touch_up, + input_handle_touch_motion, + input_handle_touch_frame, + input_handle_touch_cancel, +}; + + +static struct weston_touch_device * +create_touch_device(struct wayland_input *input) +{ + struct weston_touch_device *touch_device; + char str[128]; + + /* manufacture a unique'ish name */ + snprintf(str, sizeof str, "wayland-touch[%u]", + wl_proxy_get_id((struct wl_proxy *)input->parent.seat)); + + touch_device = weston_touch_create_touch_device(input->base.touch_state, + str, NULL, NULL); + + return touch_device; +} + +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->parent.pointer) { + input->parent.pointer = wl_seat_get_pointer(seat); + wl_pointer_set_user_data(input->parent.pointer, input); + wl_pointer_add_listener(input->parent.pointer, + &pointer_listener, input); + weston_seat_init_pointer(&input->base); + } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && input->parent.pointer) { + if (input->seat_version >= WL_POINTER_RELEASE_SINCE_VERSION) + wl_pointer_release(input->parent.pointer); + else + wl_pointer_destroy(input->parent.pointer); + input->parent.pointer = NULL; + weston_seat_release_pointer(&input->base); + } + + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !input->parent.keyboard) { + input->parent.keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_set_user_data(input->parent.keyboard, input); + wl_keyboard_add_listener(input->parent.keyboard, + &keyboard_listener, input); + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && input->parent.keyboard) { + if (input->seat_version >= WL_KEYBOARD_RELEASE_SINCE_VERSION) + wl_keyboard_release(input->parent.keyboard); + else + wl_keyboard_destroy(input->parent.keyboard); + input->parent.keyboard = NULL; + weston_seat_release_keyboard(&input->base); + } + + if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !input->parent.touch) { + input->parent.touch = wl_seat_get_touch(seat); + wl_touch_set_user_data(input->parent.touch, input); + wl_touch_add_listener(input->parent.touch, + &touch_listener, input); + weston_seat_init_touch(&input->base); + input->touch_device = create_touch_device(input); + } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && input->parent.touch) { + weston_touch_device_destroy(input->touch_device); + input->touch_device = NULL; + if (input->seat_version >= WL_TOUCH_RELEASE_SINCE_VERSION) + wl_touch_release(input->parent.touch); + else + wl_touch_destroy(input->parent.touch); + input->parent.touch = NULL; + weston_seat_release_touch(&input->base); + } +} + +static void +input_handle_name(void *data, struct wl_seat *seat, + const char *name) +{ +} + +static const struct wl_seat_listener seat_listener = { + input_handle_capabilities, + input_handle_name, +}; + +static void +display_add_seat(struct wayland_backend *b, uint32_t id, uint32_t available_version) +{ + struct wayland_input *input; + uint32_t version = MIN(available_version, 4); + + input = zalloc(sizeof *input); + if (input == NULL) + return; + + weston_seat_init(&input->base, b->compositor, "default"); + input->backend = b; + input->parent.seat = wl_registry_bind(b->parent.registry, id, + &wl_seat_interface, version); + input->seat_version = version; + wl_list_insert(b->input_list.prev, &input->link); + + wl_seat_add_listener(input->parent.seat, &seat_listener, input); + wl_seat_set_user_data(input->parent.seat, input); + + input->parent.cursor.surface = + wl_compositor_create_surface(b->parent.compositor); + + input->vert.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; + input->horiz.axis = WL_POINTER_AXIS_HORIZONTAL_SCROLL; +} + +static void +wayland_parent_output_geometry(void *data, struct wl_output *output_proxy, + int32_t x, int32_t y, + int32_t physical_width, int32_t physical_height, + int32_t subpixel, const char *make, + const char *model, int32_t transform) +{ + struct wayland_parent_output *output = data; + + output->x = x; + output->y = y; + output->physical.width = physical_width; + output->physical.height = physical_height; + output->physical.subpixel = subpixel; + + free(output->physical.make); + output->physical.make = strdup(make); + free(output->physical.model); + output->physical.model = strdup(model); + + output->transform = transform; +} + +static struct weston_mode * +find_mode(struct wl_list *list, int32_t width, int32_t height, uint32_t refresh) +{ + struct weston_mode *mode; + + wl_list_for_each(mode, list, link) { + if (mode->width == width && mode->height == height && + mode->refresh == refresh) + return mode; + } + + mode = zalloc(sizeof *mode); + if (!mode) + return NULL; + + mode->width = width; + mode->height = height; + mode->refresh = refresh; + wl_list_insert(list, &mode->link); + + return mode; +} + +static struct weston_output * +wayland_parent_output_get_enabled_output(struct wayland_parent_output *poutput) +{ + struct wayland_head *head = poutput->head; + + if (!head) + return NULL; + + if (!weston_head_is_enabled(&head->base)) + return NULL; + + return weston_head_get_output(&head->base); +} + +static void +wayland_parent_output_mode(void *data, struct wl_output *wl_output_proxy, + uint32_t flags, int32_t width, int32_t height, + int32_t refresh) +{ + struct wayland_parent_output *output = data; + struct weston_output *enabled_output; + struct weston_mode *mode; + + enabled_output = wayland_parent_output_get_enabled_output(output); + if (enabled_output) { + mode = find_mode(&enabled_output->mode_list, + width, height, refresh); + if (!mode) + return; + mode->flags = flags; + /* Do a mode-switch on current mode change? */ + } else { + mode = find_mode(&output->mode_list, width, height, refresh); + if (!mode) + return; + mode->flags = flags; + if (flags & WL_OUTPUT_MODE_CURRENT) + output->current_mode = mode; + if (flags & WL_OUTPUT_MODE_PREFERRED) + output->preferred_mode = mode; + } +} + +static const struct wl_output_listener output_listener = { + wayland_parent_output_geometry, + wayland_parent_output_mode +}; + +static void +output_sync_callback(void *data, struct wl_callback *callback, uint32_t unused) +{ + struct wayland_parent_output *output = data; + + assert(output->sync_cb == callback); + wl_callback_destroy(callback); + output->sync_cb = NULL; + + assert(output->backend->sprawl_across_outputs); + + wayland_head_create_for_parent_output(output->backend->compositor, output); +} + +static const struct wl_callback_listener output_sync_listener = { + output_sync_callback +}; + +static void +wayland_backend_register_output(struct wayland_backend *b, uint32_t id) +{ + struct wayland_parent_output *output; + + output = zalloc(sizeof *output); + if (!output) + return; + + output->backend = b; + output->id = id; + output->global = wl_registry_bind(b->parent.registry, id, + &wl_output_interface, 1); + if (!output->global) { + free(output); + return; + } + + wl_output_add_listener(output->global, &output_listener, output); + + output->scale = 0; + output->transform = WL_OUTPUT_TRANSFORM_NORMAL; + output->physical.subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; + wl_list_init(&output->mode_list); + wl_list_insert(&b->parent.output_list, &output->link); + + if (b->sprawl_across_outputs) { + output->sync_cb = wl_display_sync(b->parent.wl_display); + wl_callback_add_listener(output->sync_cb, + &output_sync_listener, output); + } +} + +static void +wayland_parent_output_destroy(struct wayland_parent_output *output) +{ + struct weston_mode *mode, *next; + + if (output->sync_cb) + wl_callback_destroy(output->sync_cb); + + if (output->head) + wayland_head_destroy(output->head); + + wl_output_destroy(output->global); + free(output->physical.make); + free(output->physical.model); + + wl_list_for_each_safe(mode, next, &output->mode_list, link) { + wl_list_remove(&mode->link); + free(mode); + } + + wl_list_remove(&output->link); + free(output); +} + +static void +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) +{ + xdg_wm_base_pong(shell, serial); +} + +static const struct xdg_wm_base_listener wm_base_listener = { + xdg_wm_base_ping, +}; + +static void +registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, + const char *interface, uint32_t version) +{ + struct wayland_backend *b = data; + + if (strcmp(interface, "wl_compositor") == 0) { + b->parent.compositor = + wl_registry_bind(registry, name, + &wl_compositor_interface, + MIN(version, 4)); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + b->parent.xdg_wm_base = + wl_registry_bind(registry, name, + &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(b->parent.xdg_wm_base, + &wm_base_listener, b); + } else if (strcmp(interface, "wl_shell") == 0) { + b->parent.shell = + wl_registry_bind(registry, name, + &wl_shell_interface, 1); + } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { + b->parent.fshell = + wl_registry_bind(registry, name, + &zwp_fullscreen_shell_v1_interface, 1); + } else if (strcmp(interface, "wl_seat") == 0) { + display_add_seat(b, name, version); + } else if (strcmp(interface, "wl_output") == 0) { + wayland_backend_register_output(b, name); + } else if (strcmp(interface, "wl_shm") == 0) { + b->parent.shm = + wl_registry_bind(registry, name, &wl_shm_interface, 1); + } +} + +static void +registry_handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ + struct wayland_backend *b = data; + struct wayland_parent_output *output, *next; + + wl_list_for_each_safe(output, next, &b->parent.output_list, link) + if (output->id == name) + wayland_parent_output_destroy(output); +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static int +wayland_backend_handle_event(int fd, uint32_t mask, void *data) +{ + struct wayland_backend *b = data; + int count = 0; + + if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { + weston_compositor_exit(b->compositor); + return 0; + } + + if (mask & WL_EVENT_READABLE) + count = wl_display_dispatch(b->parent.wl_display); + if (mask & WL_EVENT_WRITABLE) + wl_display_flush(b->parent.wl_display); + + if (mask == 0) { + count = wl_display_dispatch_pending(b->parent.wl_display); + wl_display_flush(b->parent.wl_display); + } + + return count; +} + +static void +wayland_destroy(struct weston_compositor *ec) +{ + struct wayland_backend *b = to_wayland_backend(ec); + struct weston_head *base, *next; + + wl_event_source_remove(b->parent.wl_source); + + weston_compositor_shutdown(ec); + + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) + wayland_head_destroy(to_wayland_head(base)); + + if (b->parent.shm) + wl_shm_destroy(b->parent.shm); + + if (b->parent.xdg_wm_base) + xdg_wm_base_destroy(b->parent.xdg_wm_base); + + if (b->parent.shell) + wl_shell_destroy(b->parent.shell); + + if (b->parent.fshell) + zwp_fullscreen_shell_v1_release(b->parent.fshell); + + if (b->parent.compositor) + wl_compositor_destroy(b->parent.compositor); + + if (b->theme) + theme_destroy(b->theme); + + if (b->frame_device) + cairo_device_destroy(b->frame_device); + + wl_cursor_theme_destroy(b->cursor_theme); + + wl_registry_destroy(b->parent.registry); + wl_display_flush(b->parent.wl_display); + wl_display_disconnect(b->parent.wl_display); + + free(b); +} + +static const char *left_ptrs[] = { + "left_ptr", + "default", + "top_left_arrow", + "left-arrow" +}; + +static void +create_cursor(struct wayland_backend *b, + struct weston_wayland_backend_config *config) +{ + unsigned int i; + + b->cursor_theme = wl_cursor_theme_load(config->cursor_theme, + config->cursor_size, + b->parent.shm); + if (!b->cursor_theme) { + fprintf(stderr, "could not load cursor theme\n"); + return; + } + + b->cursor = NULL; + for (i = 0; !b->cursor && i < ARRAY_LENGTH(left_ptrs); ++i) + b->cursor = wl_cursor_theme_get_cursor(b->cursor_theme, + left_ptrs[i]); + if (!b->cursor) { + fprintf(stderr, "could not load left cursor\n"); + return; + } +} + +static void +fullscreen_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) +{ + struct wayland_backend *b = data; + struct wayland_input *input = NULL; + + wl_list_for_each(input, &b->input_list, link) + if (&input->base == keyboard->seat) + break; + + if (!input || !input->output) + return; + + if (input->output->frame) + wayland_output_set_fullscreen(input->output, 0, 0, NULL); + else + wayland_output_set_windowed(input->output); + + weston_output_schedule_repaint(&input->output->base); +} + +static struct wayland_backend * +wayland_backend_create(struct weston_compositor *compositor, + struct weston_wayland_backend_config *new_config) +{ + struct wayland_backend *b; + struct wl_event_loop *loop; + int fd; + + b = zalloc(sizeof *b); + if (b == NULL) + return NULL; + + b->compositor = compositor; + compositor->backend = &b->base; + + if (weston_compositor_set_presentation_clock_software(compositor) < 0) + goto err_compositor; + + b->parent.wl_display = wl_display_connect(new_config->display_name); + if (b->parent.wl_display == NULL) { + weston_log("Error: Failed to connect to parent Wayland compositor: %s\n", + strerror(errno)); + weston_log_continue(STAMP_SPACE "display option: %s, WAYLAND_DISPLAY=%s\n", + new_config->display_name ?: "(none)", + getenv("WAYLAND_DISPLAY") ?: "(not set)"); + goto err_compositor; + } + + wl_list_init(&b->parent.output_list); + wl_list_init(&b->input_list); + b->parent.registry = wl_display_get_registry(b->parent.wl_display); + wl_registry_add_listener(b->parent.registry, ®istry_listener, b); + wl_display_roundtrip(b->parent.wl_display); + + create_cursor(b, new_config); + +#ifdef ENABLE_EGL + b->use_pixman = new_config->use_pixman; +#else + b->use_pixman = true; +#endif + b->fullscreen = new_config->fullscreen; + + if (!b->use_pixman) { + gl_renderer = weston_load_module("gl-renderer.so", + "gl_renderer_interface"); + if (!gl_renderer) + b->use_pixman = true; + } + + if (!b->use_pixman) { + const struct gl_renderer_display_options options = { + .egl_platform = EGL_PLATFORM_WAYLAND_KHR, + .egl_native_display = b->parent.wl_display, + .egl_surface_type = EGL_WINDOW_BIT, + .drm_formats = wayland_formats, + .drm_formats_count = ARRAY_LENGTH(wayland_formats), + }; + if (gl_renderer->display_create(compositor, &options) < 0) { + weston_log("Failed to initialize the GL renderer; " + "falling back to pixman.\n"); + b->use_pixman = true; + } + } + + if (b->use_pixman) { + if (pixman_renderer_init(compositor) < 0) { + weston_log("Failed to initialize pixman renderer\n"); + goto err_display; + } + } + + b->base.destroy = wayland_destroy; + b->base.create_output = wayland_output_create; + + loop = wl_display_get_event_loop(compositor->wl_display); + + fd = wl_display_get_fd(b->parent.wl_display); + b->parent.wl_source = + wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, + wayland_backend_handle_event, b); + if (b->parent.wl_source == NULL) + goto err_display; + + wl_event_source_check(b->parent.wl_source); + + if (compositor->renderer->import_dmabuf) { + if (linux_dmabuf_setup(compositor) < 0) + weston_log("Error: initializing dmabuf " + "support failed.\n"); + } + + return b; +err_display: + wl_display_disconnect(b->parent.wl_display); +err_compositor: + weston_compositor_shutdown(compositor); + free(b); + return NULL; +} + +static void +wayland_backend_destroy(struct wayland_backend *b) +{ + wl_display_disconnect(b->parent.wl_display); + + if (b->theme) + theme_destroy(b->theme); + if (b->frame_device) + cairo_device_destroy(b->frame_device); + wl_cursor_theme_destroy(b->cursor_theme); + + weston_compositor_shutdown(b->compositor); + free(b); +} + +static const struct weston_windowed_output_api windowed_api = { + wayland_output_set_size, + wayland_head_create_windowed, +}; + +static void +config_init_to_defaults(struct weston_wayland_backend_config *config) +{ +} + +WL_EXPORT int +weston_backend_init(struct weston_compositor *compositor, + struct weston_backend_config *config_base) +{ + struct wayland_backend *b; + struct wayland_parent_output *poutput; + struct weston_wayland_backend_config new_config; + int ret; + + if (config_base == NULL || + config_base->struct_version != WESTON_WAYLAND_BACKEND_CONFIG_VERSION || + config_base->struct_size > sizeof(struct weston_wayland_backend_config)) { + weston_log("wayland backend config structure is invalid\n"); + return -1; + } + + config_init_to_defaults(&new_config); + memcpy(&new_config, config_base, config_base->struct_size); + + b = wayland_backend_create(compositor, &new_config); + + if (!b) + return -1; + + if (new_config.sprawl || b->parent.fshell) { + b->sprawl_across_outputs = true; + wl_display_roundtrip(b->parent.wl_display); + + wl_list_for_each(poutput, &b->parent.output_list, link) + wayland_head_create_for_parent_output(compositor, poutput); + + return 0; + } + + if (new_config.fullscreen) { + if (!wayland_head_create(compositor, "wayland-fullscreen")) { + weston_log("Unable to create a fullscreen head.\n"); + goto err_outputs; + } + + return 0; + } + + ret = weston_plugin_api_register(compositor, WESTON_WINDOWED_OUTPUT_API_NAME, + &windowed_api, sizeof(windowed_api)); + + if (ret < 0) { + weston_log("Failed to register output API.\n"); + wayland_backend_destroy(b); + return -1; + } + + weston_compositor_add_key_binding(compositor, KEY_F, + MODIFIER_CTRL | MODIFIER_ALT, + fullscreen_binding, b); + return 0; + +err_outputs: + wayland_backend_destroy(b); + return -1; +} diff --git a/libweston/backend-x11/meson.build b/libweston/backend-x11/meson.build new file mode 100644 index 0000000..3e6ca54 --- /dev/null +++ b/libweston/backend-x11/meson.build @@ -0,0 +1,58 @@ + +if not get_option('backend-x11') + subdir_done() +endif + +config_h.set('BUILD_X11_COMPOSITOR', '1') + +srcs_x11 = [ + 'x11.c', + presentation_time_server_protocol_h, +] + +dep_x11_xcb = dependency('xcb', version: '>= 1.8', required: false) +if not dep_x11_xcb.found() + error('x11-backend requires xcb >= 1.8 which was not found. Or, you can use \'-Dbackend-x11=false\'.') +endif + +deps_x11 = [ + dep_libweston_private, + dep_libdrm_headers, + dep_x11_xcb, + dep_lib_cairo_shared, + dep_pixman, +] + +foreach name : [ 'xcb-shm', 'x11', 'x11-xcb' ] + d = dependency(name, required: false) + if not d.found() + error('x11-backend requires @0@ which was not found. Or, you can use \'-Dbackend-x11=false\'.'.format(name)) + endif + deps_x11 += d +endforeach + +dep_xcb_xkb = dependency('xcb-xkb', version: '>= 1.9', required: false) +if dep_xcb_xkb.found() + deps_x11 += dep_xcb_xkb + config_h.set('HAVE_XCB_XKB', '1') +endif + +if get_option('renderer-gl') + if not dep_egl.found() + error('x11-backend + gl-renderer requires egl which was not found. Or, you can use \'-Dbackend-x11=false\' or \'-Drenderer-gl=false\'.') + endif + deps_x11 += dep_egl +endif + +plugin_x11 = shared_library( + 'x11-backend', + srcs_x11, + include_directories: common_inc, + dependencies: deps_x11, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'x11-backend.so=@0@;'.format(plugin_x11.full_path()) + +install_headers(backend_x11_h, subdir: dir_include_libweston_install) diff --git a/libweston/backend-x11/x11.c b/libweston/backend-x11/x11.c new file mode 100644 index 0000000..387e97a --- /dev/null +++ b/libweston/backend-x11/x11.c @@ -0,0 +1,1957 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2010-2011 Intel Corporation + * Copyright © 2013 Vasily Khoruzhick + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef HAVE_XCB_XKB +#include +#endif + +#include +#include + +#include + +#include +#include +#include "shared/helpers.h" +#include "shared/image-loader.h" +#include "shared/timespec-util.h" +#include "shared/file-util.h" +#include "renderer-gl/gl-renderer.h" +#include "shared/weston-egl-ext.h" +#include "pixman-renderer.h" +#include "presentation-time-server-protocol.h" +#include "linux-dmabuf.h" +#include "linux-explicit-synchronization.h" +#include + +#define DEFAULT_AXIS_STEP_DISTANCE 10 + +#define WINDOW_MIN_WIDTH 128 +#define WINDOW_MIN_HEIGHT 128 + +#define WINDOW_MAX_WIDTH 8192 +#define WINDOW_MAX_HEIGHT 8192 + +static const uint32_t x11_formats[] = { + DRM_FORMAT_XRGB8888, +}; + +struct x11_backend { + struct weston_backend base; + struct weston_compositor *compositor; + + 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 fullscreen; + int no_input; + 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; + double prev_x; + double 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_head { + struct weston_head base; +}; + +struct x11_output { + struct weston_output base; + + xcb_window_t window; + struct weston_mode mode; + struct weston_mode native; + 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; + bool resize_pending; + bool window_resized; +}; + +struct window_delete_data { + struct x11_backend *backend; + xcb_window_t window; +}; + +struct gl_renderer_interface *gl_renderer; + +static inline struct x11_head * +to_x11_head(struct weston_head *base) +{ + return container_of(base, struct x11_head, base); +} + +static inline struct x11_output * +to_x11_output(struct weston_output *base) +{ + return container_of(base, struct x11_output, base); +} + +static inline struct x11_backend * +to_x11_backend(struct weston_compositor *base) +{ + return container_of(base->backend, struct x11_backend, base); +} + +static xcb_screen_t * +x11_compositor_get_default_screen(struct x11_backend *b) +{ + xcb_screen_iterator_t iter; + int i, screen_nbr = XDefaultScreen(b->dpy); + + iter = xcb_setup_roots_iterator(xcb_get_setup(b->conn)); + for (i = 0; iter.rem; xcb_screen_next(&iter), i++) + if (i == screen_nbr) + return iter.data; + + return xcb_setup_roots_iterator(xcb_get_setup(b->conn)).data; +} + +static struct xkb_keymap * +x11_backend_get_keymap(struct x11_backend *b) +{ + 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(b->conn, 0, b->screen->root, + b->atom.xkb_names, b->atom.string, 0, 1024); + reply = xcb_get_property_reply(b->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_keymap_new_from_names(b->compositor->xkb_context, &names, 0); + + free(reply); + return ret; +} + +static uint32_t +get_xkb_mod_mask(struct x11_backend *b, uint32_t in) +{ + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(&b->core_seat); + struct weston_xkb_info *info = keyboard->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_backend_setup_xkb(struct x11_backend *b) +{ +#ifndef HAVE_XCB_XKB + weston_log("XCB-XKB not available during build\n"); + b->has_xkb = 0; + b->xkb_event_base = 0; + return; +#else + struct weston_keyboard *keyboard; + 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; + uint32_t values[1] = { XCB_EVENT_MASK_PROPERTY_CHANGE }; + + b->has_xkb = 0; + b->xkb_event_base = 0; + + ext = xcb_get_extension_data(b->conn, &xcb_xkb_id); + if (!ext) { + weston_log("XKB extension not available on host X11 server\n"); + return; + } + b->xkb_event_base = ext->first_event; + + select = xcb_xkb_select_events_checked(b->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(b->conn, select); + if (error) { + weston_log("error: failed to select for XKB state events\n"); + free(error); + return; + } + + use_ext = xcb_xkb_use_extension(b->conn, + XCB_XKB_MAJOR_VERSION, + XCB_XKB_MINOR_VERSION); + use_ext_reply = xcb_xkb_use_extension_reply(b->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(b->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(b->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(b->conn, XCB_XKB_ID_USE_CORE_KBD); + state_reply = xcb_xkb_get_state_reply(b->conn, state, NULL); + if (!state_reply) { + weston_log("failed to get initial XKB state\n"); + return; + } + + keyboard = weston_seat_get_keyboard(&b->core_seat); + xkb_state_update_mask(keyboard->xkb_state.state, + get_xkb_mod_mask(b, state_reply->baseMods), + get_xkb_mod_mask(b, state_reply->latchedMods), + get_xkb_mod_mask(b, state_reply->lockedMods), + 0, + 0, + state_reply->group); + + free(state_reply); + + xcb_change_window_attributes(b->conn, b->screen->root, + XCB_CW_EVENT_MASK, values); + + b->has_xkb = 1; +#endif +} + +#ifdef HAVE_XCB_XKB +static void +update_xkb_keymap(struct x11_backend *b) +{ + struct xkb_keymap *keymap; + + keymap = x11_backend_get_keymap(b); + if (!keymap) { + weston_log("failed to get XKB keymap\n"); + return; + } + weston_seat_update_keymap(&b->core_seat, keymap); + xkb_keymap_unref(keymap); +} +#endif + +static int +x11_input_create(struct x11_backend *b, int no_input) +{ + struct xkb_keymap *keymap; + + weston_seat_init(&b->core_seat, b->compositor, "default"); + + if (no_input) + return 0; + + weston_seat_init_pointer(&b->core_seat); + + keymap = x11_backend_get_keymap(b); + if (weston_seat_init_keyboard(&b->core_seat, keymap) < 0) + return -1; + xkb_keymap_unref(keymap); + + x11_backend_setup_xkb(b); + + return 0; +} + +static void +x11_input_destroy(struct x11_backend *b) +{ + weston_seat_release(&b->core_seat); +} + +static int +x11_output_start_repaint_loop(struct weston_output *output) +{ + struct timespec ts; + + weston_compositor_read_presentation_clock(output->compositor, &ts); + weston_output_finish_frame(output, &ts, WP_PRESENTATION_FEEDBACK_INVALID); + + return 0; +} + +static int +x11_output_repaint_gl(struct weston_output *output_base, + pixman_region32_t *damage, + void *repaint_data) +{ + struct x11_output *output = to_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 = to_x11_output(output_base); + struct weston_compositor *ec = output->base.compositor; + struct x11_backend *b = to_x11_backend(ec); + pixman_region32_t transformed_region; + pixman_box32_t *rects; + xcb_rectangle_t *output_rects; + xcb_void_cookie_t cookie; + int nrects, i; + xcb_generic_error_t *err; + + pixman_region32_init(&transformed_region); + pixman_region32_copy(&transformed_region, region); + pixman_region32_translate(&transformed_region, + -output_base->x, -output_base->y); + weston_transformed_region(output_base->width, output_base->height, + output_base->transform, + output_base->current_scale, + &transformed_region, &transformed_region); + + rects = pixman_region32_rectangles(&transformed_region, &nrects); + output_rects = calloc(nrects, sizeof(xcb_rectangle_t)); + + if (output_rects == NULL) { + pixman_region32_fini(&transformed_region); + return; + } + + for (i = 0; i < nrects; i++) { + output_rects[i].x = rects[i].x1; + output_rects[i].y = rects[i].y1; + output_rects[i].width = rects[i].x2 - rects[i].x1; + output_rects[i].height = rects[i].y2 - rects[i].y1; + } + + pixman_region32_fini(&transformed_region); + + cookie = xcb_set_clip_rectangles_checked(b->conn, XCB_CLIP_ORDERING_UNSORTED, + output->gc, + 0, 0, nrects, + output_rects); + err = xcb_request_check(b->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, + void *repaint_data) +{ + struct x11_output *output = to_x11_output(output_base); + struct weston_compositor *ec = output->base.compositor; + struct x11_backend *b = to_x11_backend(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(b->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(b->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; + struct timespec ts; + + weston_compositor_read_presentation_clock(output->base.compositor, &ts); + weston_output_finish_frame(&output->base, &ts, 0); + + return 1; +} + +static void +x11_output_deinit_shm(struct x11_backend *b, struct x11_output *output) +{ + xcb_void_cookie_t cookie; + xcb_generic_error_t *err; + xcb_free_gc(b->conn, output->gc); + + pixman_image_unref(output->hw_surface); + output->hw_surface = NULL; + cookie = xcb_shm_detach_checked(b->conn, output->segment); + err = xcb_request_check(b->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_set_wm_protocols(struct x11_backend *b, + struct x11_output *output) +{ + xcb_atom_t list[1]; + + list[0] = b->atom.wm_delete_window; + xcb_change_property (b->conn, + XCB_PROP_MODE_REPLACE, + output->window, + b->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_backend *b, + 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(b->conn, XCB_PROP_MODE_REPLACE, output->window, + b->atom.net_wm_icon, b->atom.cardinal, 32, + width * height + 2, icon); + free(icon); + pixman_image_unref(image); +} + +static void +x11_output_wait_for_map(struct x11_backend *b, 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(b->conn); + + while (!mapped || !configured) { + event = xcb_wait_for_event(b->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->mode.height = configure_notify->height; + 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_backend *b, struct x11_output *output, + int width, int height) +{ + xcb_visualtype_t *visual_type; + xcb_screen_t *screen; + 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(b->conn, &xcb_shm_id); + if (ext == NULL || !ext->present) { + /* SHM is missing */ + weston_log("SHM extension is not available\n"); + errno = ENOENT; + return -1; + } + + screen = x11_compositor_get_default_screen(b); + visual_type = find_visual_by_id(screen, screen->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(screen, screen->root_visual); + weston_log("Visual depth is %d\n", output->depth); + + for (fmt = xcb_setup_pixmap_formats_iterator(xcb_get_setup(b->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 if (bitsperpixel == 16 && + visual_type->red_mask == 0x00f800 && + visual_type->green_mask == 0x0007e0 && + visual_type->blue_mask == 0x00001f) { + weston_log("Will use r5g6b5 format for SHM surfaces\n"); + pixman_format = PIXMAN_r5g6b5; + } 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(b->conn); + cookie = xcb_shm_attach_checked(b->conn, output->segment, output->shm_id, 1); + err = xcb_request_check(b->conn, cookie); + if (err) { + weston_log("x11shm: xcb_shm_attach error %d, op code %d, resource id %d\n", + err->error_code, err->major_code, err->minor_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(b->conn); + xcb_create_gc(b->conn, output->gc, output->window, 0, NULL); + + return 0; +} + +static int +x11_output_switch_mode(struct weston_output *base, struct weston_mode *mode) +{ + struct x11_backend *b; + struct x11_output *output; + static uint32_t values[2]; + int ret; + + b = to_x11_backend(base->compositor); + output = to_x11_output(base); + + if (mode->width == output->mode.width && + mode->height == output->mode.height) + return 0; + + if (mode->width < WINDOW_MIN_WIDTH || mode->width > WINDOW_MAX_WIDTH) + return -1; + + if (mode->height < WINDOW_MIN_HEIGHT || mode->height > WINDOW_MAX_HEIGHT) + return -1; + + /* xcb_configure_window will create an event, and we could end up + being called twice */ + output->resize_pending = true; + + /* window could've been resized by the user, so don't do it twice */ + if (!output->window_resized) { + values[0] = mode->width; + values[1] = mode->height; + xcb_configure_window(b->conn, output->window, XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT, values); + } + + output->mode.width = mode->width; + output->mode.height = mode->height; + + if (b->use_pixman) { + const struct pixman_renderer_output_options options = { + .use_shadow = true, + }; + pixman_renderer_output_destroy(&output->base); + x11_output_deinit_shm(b, output); + + if (x11_output_init_shm(b, output, + output->base.current_mode->width, + output->base.current_mode->height) < 0) { + weston_log("Failed to initialize SHM for the X11 output\n"); + return -1; + } + + if (pixman_renderer_output_create(&output->base, &options) < 0) { + weston_log("Failed to create pixman renderer for output\n"); + x11_output_deinit_shm(b, output); + return -1; + } + } else { + Window xid = (Window) output->window; + const struct gl_renderer_output_options options = { + .window_for_legacy = (EGLNativeWindowType) output->window, + .window_for_platform = &xid, + .drm_formats = x11_formats, + .drm_formats_count = ARRAY_LENGTH(x11_formats), + }; + + gl_renderer->output_destroy(&output->base); + + ret = gl_renderer->output_window_create(&output->base, &options); + if (ret < 0) + return -1; + } + + output->resize_pending = false; + output->window_resized = false; + + return 0; +} + +static int +x11_output_disable(struct weston_output *base) +{ + struct x11_output *output = to_x11_output(base); + struct x11_backend *backend = to_x11_backend(base->compositor); + + if (!output->base.enabled) + return 0; + + wl_event_source_remove(output->finish_frame_timer); + + if (backend->use_pixman) { + pixman_renderer_output_destroy(&output->base); + x11_output_deinit_shm(backend, output); + } else { + gl_renderer->output_destroy(&output->base); + } + + xcb_destroy_window(backend->conn, output->window); + xcb_flush(backend->conn); + + return 0; +} + +static void +x11_output_destroy(struct weston_output *base) +{ + struct x11_output *output = to_x11_output(base); + + x11_output_disable(&output->base); + weston_output_release(&output->base); + + free(output); +} + +static int +x11_output_enable(struct weston_output *base) +{ + struct x11_output *output = to_x11_output(base); + struct x11_backend *b = to_x11_backend(base->compositor); + + static const char name[] = "Weston Compositor"; + static const char class[] = "weston-1\0Weston Compositor"; + char *title = NULL; + xcb_screen_t *screen; + struct wm_normal_hints normal_hints; + struct wl_event_loop *loop; + char *icon_filename; + + int ret; + 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 + }; + + if (!b->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; + + values[1] = b->null_cursor; + output->window = xcb_generate_id(b->conn); + screen = x11_compositor_get_default_screen(b); + xcb_create_window(b->conn, + XCB_COPY_FROM_PARENT, + output->window, + screen->root, + 0, 0, + output->base.current_mode->width, + output->base.current_mode->height, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + screen->root_visual, + mask, values); + + if (b->fullscreen) { + atom_list[0] = b->atom.net_wm_state_fullscreen; + xcb_change_property(b->conn, XCB_PROP_MODE_REPLACE, + output->window, + b->atom.net_wm_state, + XCB_ATOM_ATOM, 32, + ARRAY_LENGTH(atom_list), atom_list); + } else { + memset(&normal_hints, 0, sizeof normal_hints); + normal_hints.flags = + WM_NORMAL_HINTS_MAX_SIZE | WM_NORMAL_HINTS_MIN_SIZE; + normal_hints.min_width = WINDOW_MIN_WIDTH; + normal_hints.min_height = WINDOW_MIN_HEIGHT; + normal_hints.max_width = WINDOW_MAX_WIDTH; + normal_hints.max_height = WINDOW_MAX_HEIGHT; + xcb_change_property(b->conn, XCB_PROP_MODE_REPLACE, output->window, + b->atom.wm_normal_hints, + b->atom.wm_size_hints, 32, + sizeof normal_hints / 4, + (uint8_t *) &normal_hints); + } + + /* Set window name. Don't bother with non-EWMH WMs. */ + if (output->base.name) { + if (asprintf(&title, "%s - %s", name, output->base.name) < 0) + title = NULL; + } else { + title = strdup(name); + } + + if (title) { + xcb_change_property(b->conn, XCB_PROP_MODE_REPLACE, output->window, + b->atom.net_wm_name, b->atom.utf8_string, 8, + strlen(title), title); + free(title); + } else { + goto err; + } + + xcb_change_property(b->conn, XCB_PROP_MODE_REPLACE, output->window, + b->atom.wm_class, b->atom.string, 8, + sizeof class, class); + + icon_filename = file_name_with_datadir("wayland.png"); + x11_output_set_icon(b, output, icon_filename); + free(icon_filename); + + x11_output_set_wm_protocols(b, output); + + xcb_map_window(b->conn, output->window); + + if (b->fullscreen) + x11_output_wait_for_map(b, output); + + if (b->use_pixman) { + const struct pixman_renderer_output_options options = { + .use_shadow = true, + }; + if (x11_output_init_shm(b, output, + output->base.current_mode->width, + output->base.current_mode->height) < 0) { + weston_log("Failed to initialize SHM for the X11 output\n"); + goto err; + } + if (pixman_renderer_output_create(&output->base, &options) < 0) { + weston_log("Failed to create pixman renderer for output\n"); + x11_output_deinit_shm(b, output); + goto err; + } + + output->base.repaint = x11_output_repaint_shm; + } else { + /* eglCreatePlatformWindowSurfaceEXT takes a Window* + * but eglCreateWindowSurface takes a Window. */ + Window xid = (Window) output->window; + const struct gl_renderer_output_options options = { + .window_for_legacy = (EGLNativeWindowType) output->window, + .window_for_platform = &xid, + .drm_formats = x11_formats, + .drm_formats_count = ARRAY_LENGTH(x11_formats), + }; + + ret = gl_renderer->output_window_create(&output->base, + &options); + if (ret < 0) + goto err; + + output->base.repaint = x11_output_repaint_gl; + } + + output->base.start_repaint_loop = x11_output_start_repaint_loop; + output->base.assign_planes = NULL; + output->base.set_backlight = NULL; + output->base.set_dpms = NULL; + output->base.switch_mode = x11_output_switch_mode; + + loop = wl_display_get_event_loop(b->compositor->wl_display); + output->finish_frame_timer = + wl_event_loop_add_timer(loop, finish_frame_handler, output); + + weston_log("x11 output %dx%d, window id %d\n", + output->base.current_mode->width, + output->base.current_mode->height, + output->window); + + return 0; + +err: + xcb_destroy_window(b->conn, output->window); + xcb_flush(b->conn); + + return -1; +} + +static int +x11_output_set_size(struct weston_output *base, int width, int height) +{ + struct x11_output *output = to_x11_output(base); + struct x11_backend *b = to_x11_backend(base->compositor); + struct weston_head *head; + xcb_screen_t *scrn = b->screen; + int output_width, output_height; + + /* We can only be called once. */ + assert(!output->base.current_mode); + + /* Make sure we have scale set. */ + assert(output->base.scale); + + if (width < WINDOW_MIN_WIDTH) { + weston_log("Invalid width \"%d\" for output %s\n", + width, output->base.name); + return -1; + } + + if (height < WINDOW_MIN_HEIGHT) { + weston_log("Invalid height \"%d\" for output %s\n", + height, output->base.name); + return -1; + } + + wl_list_for_each(head, &output->base.head_list, output_link) { + weston_head_set_monitor_strings(head, "weston-X11", "none", NULL); + weston_head_set_physical_size(head, + width * scrn->width_in_millimeters / scrn->width_in_pixels, + height * scrn->height_in_millimeters / scrn->height_in_pixels); + } + + output_width = width * output->base.scale; + output_height = height * output->base.scale; + + 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->native = output->mode; + output->scale = output->base.scale; + wl_list_insert(&output->base.mode_list, &output->mode.link); + + output->base.current_mode = &output->mode; + output->base.native_mode = &output->native; + output->base.native_scale = output->base.scale; + + return 0; +} + +static struct weston_output * +x11_output_create(struct weston_compositor *compositor, const char *name) +{ + struct x11_output *output; + + /* name can't be NULL. */ + assert(name); + + output = zalloc(sizeof *output); + if (!output) + return NULL; + + weston_output_init(&output->base, compositor, name); + + output->base.destroy = x11_output_destroy; + output->base.disable = x11_output_disable; + output->base.enable = x11_output_enable; + output->base.attach_head = NULL; + + weston_compositor_add_pending_output(&output->base, compositor); + + return &output->base; +} + +static int +x11_head_create(struct weston_compositor *compositor, const char *name) +{ + struct x11_head *head; + + assert(name); + + head = zalloc(sizeof *head); + if (!head) + return -1; + + weston_head_init(&head->base, name); + weston_head_set_connection_status(&head->base, true); + weston_compositor_add_head(compositor, &head->base); + + return 0; +} + +static void +x11_head_destroy(struct x11_head *head) +{ + weston_head_release(&head->base); + free(head); +} + +static struct x11_output * +x11_backend_find_output(struct x11_backend *b, xcb_window_t window) +{ + struct x11_output *output; + + wl_list_for_each(output, &b->compositor->output_list, base.link) { + if (output->window == window) + return output; + } + + return NULL; +} + +static void +x11_backend_delete_window(struct x11_backend *b, xcb_window_t window) +{ + struct x11_output *output; + + output = x11_backend_find_output(b, window); + if (output) + x11_output_destroy(&output->base); + + if (wl_list_empty(&b->compositor->output_list)) + weston_compositor_exit(b->compositor); +} + +static void delete_cb(void *data) +{ + struct window_delete_data *wd = data; + + x11_backend_delete_window(wd->backend, wd->window); + free(wd); +} + +#ifdef HAVE_XCB_XKB +static void +update_xkb_state(struct x11_backend *b, xcb_xkb_state_notify_event_t *state) +{ + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(&b->core_seat); + + xkb_state_update_mask(keyboard->xkb_state.state, + get_xkb_mod_mask(b, state->baseMods), + get_xkb_mod_mask(b, state->latchedMods), + get_xkb_mod_mask(b, state->lockedMods), + 0, + 0, + state->group); + + notify_modifiers(&b->core_seat, + wl_display_next_serial(b->compositor->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_backend *b, uint16_t x11_mask) +{ + uint32_t mask = get_xkb_mod_mask(b, x11_mask); + struct weston_keyboard *keyboard + = weston_seat_get_keyboard(&b->core_seat); + + xkb_state_update_mask(keyboard->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(&b->core_seat, + wl_display_next_serial(b->compositor->wl_display)); +} + +static void +x11_backend_deliver_button_event(struct x11_backend *b, + xcb_generic_event_t *event) +{ + xcb_button_press_event_t *button_event = + (xcb_button_press_event_t *) event; + uint32_t button; + struct x11_output *output; + struct weston_pointer_axis_event weston_event; + bool is_button_pressed = event->response_type == XCB_BUTTON_PRESS; + struct timespec time = { 0 }; + + assert(event->response_type == XCB_BUTTON_PRESS || + event->response_type == XCB_BUTTON_RELEASE); + + output = x11_backend_find_output(b, button_event->event); + if (!output) + return; + + if (is_button_pressed) + xcb_grab_pointer(b->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(b->conn, button_event->time); + + if (!b->has_xkb) + update_xkb_state_from_core(b, button_event->state); + + switch (button_event->detail) { + case 1: + button = BTN_LEFT; + 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 (is_button_pressed) { + weston_event.value = -DEFAULT_AXIS_STEP_DISTANCE; + weston_event.discrete = -1; + weston_event.has_discrete = true; + weston_event.axis = + WL_POINTER_AXIS_VERTICAL_SCROLL; + weston_compositor_get_time(&time); + notify_axis(&b->core_seat, &time, &weston_event); + notify_pointer_frame(&b->core_seat); + } + return; + case 5: + if (is_button_pressed) { + weston_event.value = DEFAULT_AXIS_STEP_DISTANCE; + weston_event.discrete = 1; + weston_event.has_discrete = true; + weston_event.axis = + WL_POINTER_AXIS_VERTICAL_SCROLL; + weston_compositor_get_time(&time); + notify_axis(&b->core_seat, &time, &weston_event); + notify_pointer_frame(&b->core_seat); + } + return; + case 6: + if (is_button_pressed) { + weston_event.value = -DEFAULT_AXIS_STEP_DISTANCE; + weston_event.discrete = -1; + weston_event.has_discrete = true; + weston_event.axis = + WL_POINTER_AXIS_HORIZONTAL_SCROLL; + weston_compositor_get_time(&time); + notify_axis(&b->core_seat, &time, &weston_event); + notify_pointer_frame(&b->core_seat); + } + return; + case 7: + if (is_button_pressed) { + weston_event.value = DEFAULT_AXIS_STEP_DISTANCE; + weston_event.discrete = 1; + weston_event.has_discrete = true; + weston_event.axis = + WL_POINTER_AXIS_HORIZONTAL_SCROLL; + weston_compositor_get_time(&time); + notify_axis(&b->core_seat, &time, &weston_event); + notify_pointer_frame(&b->core_seat); + } + return; + default: + button = button_event->detail + BTN_SIDE - 8; + break; + } + + weston_compositor_get_time(&time); + + notify_button(&b->core_seat, &time, button, + is_button_pressed ? WL_POINTER_BUTTON_STATE_PRESSED : + WL_POINTER_BUTTON_STATE_RELEASED); + notify_pointer_frame(&b->core_seat); +} + +static void +x11_backend_deliver_motion_event(struct x11_backend *b, + xcb_generic_event_t *event) +{ + struct x11_output *output; + double x, y; + struct weston_pointer_motion_event motion_event = { 0 }; + xcb_motion_notify_event_t *motion_notify = + (xcb_motion_notify_event_t *) event; + struct timespec time; + + if (!b->has_xkb) + update_xkb_state_from_core(b, motion_notify->state); + output = x11_backend_find_output(b, motion_notify->event); + if (!output) + return; + + weston_output_transform_coordinate(&output->base, + motion_notify->event_x, + motion_notify->event_y, + &x, &y); + + motion_event = (struct weston_pointer_motion_event) { + .mask = WESTON_POINTER_MOTION_REL, + .dx = x - b->prev_x, + .dy = y - b->prev_y + }; + + weston_compositor_get_time(&time); + notify_motion(&b->core_seat, &time, &motion_event); + notify_pointer_frame(&b->core_seat); + + b->prev_x = x; + b->prev_y = y; +} + +static void +x11_backend_deliver_enter_event(struct x11_backend *b, + xcb_generic_event_t *event) +{ + struct x11_output *output; + double x, y; + + xcb_enter_notify_event_t *enter_notify = + (xcb_enter_notify_event_t *) event; + if (enter_notify->state >= Button1Mask) + return; + if (!b->has_xkb) + update_xkb_state_from_core(b, enter_notify->state); + output = x11_backend_find_output(b, enter_notify->event); + if (!output) + return; + + weston_output_transform_coordinate(&output->base, + enter_notify->event_x, + enter_notify->event_y, &x, &y); + + notify_pointer_focus(&b->core_seat, &output->base, x, y); + + b->prev_x = x; + b->prev_y = y; +} + +static int +x11_backend_next_event(struct x11_backend *b, + xcb_generic_event_t **event, uint32_t mask) +{ + if (mask & WL_EVENT_READABLE) + *event = xcb_poll_for_event(b->conn); + else + *event = xcb_poll_for_queued_event(b->conn); + + return *event != NULL; +} + +static int +x11_backend_handle_event(int fd, uint32_t mask, void *data) +{ + struct x11_backend *b = 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_configure_notify_event_t *configure; + xcb_atom_t atom; + xcb_window_t window; + uint32_t *k; + uint32_t i, set; + uint8_t response_type; + int count; + struct timespec time; + + prev = NULL; + count = 0; + while (x11_backend_next_event(b, &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(b, key_release->state); + weston_compositor_get_time(&time); + notify_key(&b->core_seat, + &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; + b->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(&b->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(&b->core_seat, &b->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 (!b->has_xkb) + update_xkb_state_from_core(b, key_press->state); + weston_compositor_get_time(&time); + notify_key(&b->core_seat, + &time, + key_press->detail - 8, + WL_KEYBOARD_KEY_STATE_PRESSED, + b->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 (!b->has_xkb) { + prev = event; + break; + } + key_release = (xcb_key_press_event_t *) event; + weston_compositor_get_time(&time); + notify_key(&b->core_seat, + &time, + key_release->detail - 8, + WL_KEYBOARD_KEY_STATE_RELEASED, + STATE_UPDATE_NONE); + break; + case XCB_BUTTON_PRESS: + case XCB_BUTTON_RELEASE: + x11_backend_deliver_button_event(b, event); + break; + case XCB_MOTION_NOTIFY: + x11_backend_deliver_motion_event(b, event); + break; + + case XCB_EXPOSE: + expose = (xcb_expose_event_t *) event; + output = x11_backend_find_output(b, expose->window); + if (!output) + break; + + weston_output_damage(&output->base); + weston_output_schedule_repaint(&output->base); + break; + + case XCB_ENTER_NOTIFY: + x11_backend_deliver_enter_event(b, event); + break; + + case XCB_LEAVE_NOTIFY: + enter_notify = (xcb_enter_notify_event_t *) event; + if (enter_notify->state >= Button1Mask) + break; + if (!b->has_xkb) + update_xkb_state_from_core(b, enter_notify->state); + notify_pointer_focus(&b->core_seat, NULL, 0, 0); + break; + + case XCB_CLIENT_MESSAGE: + client_message = (xcb_client_message_event_t *) event; + atom = client_message->data.data32[0]; + window = client_message->window; + if (atom == b->atom.wm_delete_window) { + struct wl_event_loop *loop; + struct window_delete_data *data = malloc(sizeof *data); + + /* if malloc failed we should at least try to + * delete the window, even if it may result in + * a crash. + */ + if (!data) { + x11_backend_delete_window(b, window); + break; + } + data->backend = b; + data->window = window; + loop = wl_display_get_event_loop(b->compositor->wl_display); + wl_event_loop_add_idle(loop, delete_cb, data); + } + break; + + case XCB_CONFIGURE_NOTIFY: + configure = (struct xcb_configure_notify_event_t *) event; + struct x11_output *output = + x11_backend_find_output(b, configure->window); + + if (!output || output->resize_pending) + break; + + struct weston_mode mode = output->mode; + + if (mode.width == configure->width && + mode.height == configure->height) + break; + + output->window_resized = true; + + mode.width = configure->width; + mode.height = configure->height; + + if (weston_output_mode_set_native(&output->base, + &mode, output->scale) < 0) + weston_log("Mode switch failed\n"); + + 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(&b->core_seat); + break; + + default: + break; + } + +#ifdef HAVE_XCB_XKB + if (b->has_xkb) { + if (response_type == b->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(b, state); + } else if (response_type == XCB_PROPERTY_NOTIFY) { + xcb_property_notify_event_t *prop_notify = + (xcb_property_notify_event_t *) event; + if (prop_notify->window == b->screen->root && + prop_notify->atom == b->atom.xkb_names && + prop_notify->state == XCB_PROPERTY_NEW_VALUE) + update_xkb_keymap(b); + } + } +#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(b, key_release->state); + weston_compositor_get_time(&time); + notify_key(&b->core_seat, + &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_backend, field) + +static void +x11_backend_get_resources(struct x11_backend *b) +{ + 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 (b->conn, 0, + strlen(atoms[i].name), + atoms[i].name); + + for (i = 0; i < ARRAY_LENGTH(atoms); i++) { + reply = xcb_intern_atom_reply (b->conn, cookies[i], NULL); + *(xcb_atom_t *) ((char *) b + atoms[i].offset) = reply->atom; + free(reply); + } + + pixmap = xcb_generate_id(b->conn); + gc = xcb_generate_id(b->conn); + xcb_create_pixmap(b->conn, 1, pixmap, b->screen->root, 1, 1); + xcb_create_gc(b->conn, gc, pixmap, 0, NULL); + xcb_put_image(b->conn, XCB_IMAGE_FORMAT_XY_PIXMAP, + pixmap, gc, 1, 1, 0, 0, 0, 32, sizeof data, data); + b->null_cursor = xcb_generate_id(b->conn); + xcb_create_cursor (b->conn, b->null_cursor, + pixmap, pixmap, 0, 0, 0, 0, 0, 0, 1, 1); + xcb_free_gc(b->conn, gc); + xcb_free_pixmap(b->conn, pixmap); +} + +static void +x11_backend_get_wm_info(struct x11_backend *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; + } + + free(reply); +} + +static void +x11_destroy(struct weston_compositor *ec) +{ + struct x11_backend *backend = to_x11_backend(ec); + struct weston_head *base, *next; + + wl_event_source_remove(backend->xcb_source); + x11_input_destroy(backend); + + weston_compositor_shutdown(ec); /* destroys outputs, too */ + + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) + x11_head_destroy(to_x11_head(base)); + + XCloseDisplay(backend->dpy); + free(backend); +} + +static int +init_gl_renderer(struct x11_backend *b) +{ + const struct gl_renderer_display_options options = { + .egl_platform = EGL_PLATFORM_X11_KHR, + .egl_native_display = b->dpy, + .egl_surface_type = EGL_WINDOW_BIT, + .drm_formats = x11_formats, + .drm_formats_count = ARRAY_LENGTH(x11_formats), + }; + + gl_renderer = weston_load_module("gl-renderer.so", + "gl_renderer_interface"); + if (!gl_renderer) + return -1; + + return gl_renderer->display_create(b->compositor, &options); +} + +static const struct weston_windowed_output_api api = { + x11_output_set_size, + x11_head_create, +}; + +static struct x11_backend * +x11_backend_create(struct weston_compositor *compositor, + struct weston_x11_backend_config *config) +{ + struct x11_backend *b; + struct wl_event_loop *loop; + int ret; + + b = zalloc(sizeof *b); + if (b == NULL) + return NULL; + + b->compositor = compositor; + b->fullscreen = config->fullscreen; + b->no_input = config->no_input; + + compositor->backend = &b->base; + + if (weston_compositor_set_presentation_clock_software(compositor) < 0) + goto err_free; + + b->dpy = XOpenDisplay(NULL); + if (b->dpy == NULL) + goto err_free; + + b->conn = XGetXCBConnection(b->dpy); + XSetEventQueueOwner(b->dpy, XCBOwnsEventQueue); + + if (xcb_connection_has_error(b->conn)) + goto err_xdisplay; + + b->screen = x11_compositor_get_default_screen(b); + wl_array_init(&b->keys); + + x11_backend_get_resources(b); + x11_backend_get_wm_info(b); + + if (!b->has_net_wm_state_fullscreen && config->fullscreen) { + weston_log("Can not fullscreen without window manager support" + "(need _NET_WM_STATE_FULLSCREEN)\n"); + config->fullscreen = 0; + } + + b->use_pixman = config->use_pixman; + if (b->use_pixman) { + if (pixman_renderer_init(compositor) < 0) { + weston_log("Failed to initialize pixman renderer for X11 backend\n"); + goto err_xdisplay; + } + } + else if (init_gl_renderer(b) < 0) { + goto err_xdisplay; + } + weston_log("Using %s renderer\n", config->use_pixman ? "pixman" : "gl"); + + b->base.destroy = x11_destroy; + b->base.create_output = x11_output_create; + + if (x11_input_create(b, config->no_input) < 0) { + weston_log("Failed to create X11 input\n"); + goto err_renderer; + } + + loop = wl_display_get_event_loop(compositor->wl_display); + b->xcb_source = + wl_event_loop_add_fd(loop, + xcb_get_file_descriptor(b->conn), + WL_EVENT_READABLE, + x11_backend_handle_event, b); + wl_event_source_check(b->xcb_source); + + if (compositor->renderer->import_dmabuf) { + if (linux_dmabuf_setup(compositor) < 0) + weston_log("Error: initializing dmabuf " + "support failed.\n"); + } + + if (compositor->capabilities & WESTON_CAP_EXPLICIT_SYNC) { + if (linux_explicit_synchronization_setup(compositor) < 0) + weston_log("Error: initializing explicit " + " synchronization support failed.\n"); + } + + ret = weston_plugin_api_register(compositor, WESTON_WINDOWED_OUTPUT_API_NAME, + &api, sizeof(api)); + + if (ret < 0) { + weston_log("Failed to register output API.\n"); + goto err_x11_input; + } + + return b; + +err_x11_input: + x11_input_destroy(b); +err_renderer: + compositor->renderer->destroy(compositor); +err_xdisplay: + XCloseDisplay(b->dpy); +err_free: + free(b); + return NULL; +} + +static void +config_init_to_defaults(struct weston_x11_backend_config *config) +{ +} + +WL_EXPORT int +weston_backend_init(struct weston_compositor *compositor, + struct weston_backend_config *config_base) +{ + struct x11_backend *b; + struct weston_x11_backend_config config = {{ 0, }}; + + if (config_base == NULL || + config_base->struct_version != WESTON_X11_BACKEND_CONFIG_VERSION || + config_base->struct_size > sizeof(struct weston_x11_backend_config)) { + weston_log("X11 backend config structure is invalid\n"); + return -1; + } + + config_init_to_defaults(&config); + memcpy(&config, config_base, config_base->struct_size); + + b = x11_backend_create(compositor, &config); + if (b == NULL) + return -1; + + return 0; +} diff --git a/libweston/backend.h b/libweston/backend.h new file mode 100644 index 0000000..8e91746 --- /dev/null +++ b/libweston/backend.h @@ -0,0 +1,248 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2017, 2018 General Electric Company + * Copyright © 2012, 2017-2019 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * This header contains the libweston ABI exported only for internal backends. + */ + +#ifndef LIBWESTON_BACKEND_INTERNAL_H +#define LIBWESTON_BACKEND_INTERNAL_H + +struct weston_backend { + void (*destroy)(struct weston_compositor *compositor); + + /** Begin a repaint sequence + * + * Provides the backend with explicit markers around repaint + * sequences, which may allow the backend to aggregate state + * application. This call will be bracketed by the repaint_flush (on + * success), or repaint_cancel (when any output in the grouping fails + * repaint). + * + * Returns an opaque pointer, which the backend may use as private + * data referring to the repaint cycle. + */ + void * (*repaint_begin)(struct weston_compositor *compositor); + + /** Cancel a repaint sequence + * + * Cancels a repaint sequence, when an error has occurred during + * one output's repaint; see repaint_begin. + * + * @param repaint_data Data returned by repaint_begin + */ + void (*repaint_cancel)(struct weston_compositor *compositor, + void *repaint_data); + + /** Conclude a repaint sequence + * + * Called on successful completion of a repaint sequence; see + * repaint_begin. + * + * @param repaint_data Data returned by repaint_begin + */ + int (*repaint_flush)(struct weston_compositor *compositor, + void *repaint_data); + + /** Allocate a new output + * + * @param compositor The compositor. + * @param name Name for the new output. + * + * Allocates a new output structure that embeds a weston_output, + * initializes it, and returns the pointer to the weston_output + * member. + * + * Must set weston_output members @c destroy, @c enable and @c disable. + */ + struct weston_output * + (*create_output)(struct weston_compositor *compositor, + const char *name); + + /** Notify of device addition/removal + * + * @param compositor The compositor. + * @param device The device that has changed. + * @param added Where it was added (or removed) + * + * Called when a device has been added/removed from the session. + * The backend can decide what to do based on whether it is a + * device that it is controlling or not. + */ + void (*device_changed)(struct weston_compositor *compositor, + dev_t device, bool added); + + /** Verifies if the dmabuf can be used directly/scanned-out by the HW. + * + * @param compositor The compositor. + * @param buffer The dmabuf to verify. + * + * Determines if the buffer can be imported directly by the display + * controller/HW. Back-ends can use this to check if the supplied + * buffer can be scanned-out, as to void importing it into the GPU. + */ + bool (*can_scanout_dmabuf)(struct weston_compositor *compositor, + struct linux_dmabuf_buffer *buffer); +}; + +/* weston_head */ + +void +weston_head_init(struct weston_head *head, const char *name); + +void +weston_head_release(struct weston_head *head); + +void +weston_head_set_connection_status(struct weston_head *head, bool connected); + +void +weston_head_set_internal(struct weston_head *head); + +void +weston_head_set_monitor_strings(struct weston_head *head, + const char *make, + const char *model, + const char *serialno); +void +weston_head_set_non_desktop(struct weston_head *head, bool non_desktop); + +void +weston_head_set_physical_size(struct weston_head *head, + int32_t mm_width, int32_t mm_height); + +void +weston_head_set_subpixel(struct weston_head *head, + enum wl_output_subpixel sp); + +void +weston_head_set_transform(struct weston_head *head, uint32_t transform); + +/* weston_output */ + +void +weston_output_init(struct weston_output *output, + struct weston_compositor *compositor, + const char *name); +void +weston_output_damage(struct weston_output *output); + +void +weston_output_release(struct weston_output *output); + +void +weston_output_init_zoom(struct weston_output *output); + +void +weston_output_finish_frame(struct weston_output *output, + const struct timespec *stamp, + uint32_t presented_flags); +int +weston_output_mode_set_native(struct weston_output *output, + struct weston_mode *mode, + int32_t scale); +void +weston_output_transform_coordinate(struct weston_output *output, + double device_x, double device_y, + double *x, double *y); + +/* weston_seat */ + +void +notify_axis(struct weston_seat *seat, const struct timespec *time, + struct weston_pointer_axis_event *event); +void +notify_axis_source(struct weston_seat *seat, uint32_t source); + +void +notify_button(struct weston_seat *seat, const struct timespec *time, + int32_t button, enum wl_pointer_button_state state); + +void +notify_key(struct weston_seat *seat, const struct timespec *time, uint32_t key, + enum wl_keyboard_key_state state, + enum weston_key_state_update update_state); +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_motion(struct weston_seat *seat, const struct timespec *time, + struct weston_pointer_motion_event *event); +void +notify_motion_absolute(struct weston_seat *seat, const struct timespec *time, + double x, double y); +void +notify_modifiers(struct weston_seat *seat, uint32_t serial); + +void +notify_pointer_frame(struct weston_seat *seat); + +void +notify_pointer_focus(struct weston_seat *seat, struct weston_output *output, + double x, double y); + +/* weston_touch_device */ + +void +notify_touch_normalized(struct weston_touch_device *device, + const struct timespec *time, + int touch_id, + double x, double y, + const struct weston_point2d_device_normalized *norm, + int touch_type); + +/** Feed in touch down, motion, and up events, non-calibratable device. + * + * @sa notify_touch_cal + */ +static inline void +notify_touch(struct weston_touch_device *device, const struct timespec *time, + int touch_id, double x, double y, int touch_type) +{ + notify_touch_normalized(device, time, touch_id, x, y, NULL, touch_type); +} + +void +notify_touch_frame(struct weston_touch_device *device); + +void +notify_touch_cancel(struct weston_touch_device *device); + +void +notify_touch_calibrator(struct weston_touch_device *device, + const struct timespec *time, int32_t slot, + const struct weston_point2d_device_normalized *norm, + int touch_type); +void +notify_touch_calibrator_cancel(struct weston_touch_device *device); +void +notify_touch_calibrator_frame(struct weston_touch_device *device); + +#endif diff --git a/libweston/bindings.c b/libweston/bindings.c new file mode 100755 index 0000000..f71551a --- /dev/null +++ b/libweston/bindings.c @@ -0,0 +1,590 @@ +/* + * Copyright © 2011-2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include +#include "libweston-internal.h" +#include "shared/helpers.h" +#include "shared/timespec-util.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_modifier_binding(struct weston_compositor *compositor, + uint32_t modifier, + weston_modifier_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->modifier_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; +} + +// OHOS remove debugger +//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); +} + +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, + const struct timespec *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; + uint32_t msecs; + + 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 { + /* Don't send the key press event for the binding key */ + return; + } + } + if (!wl_list_empty(&keyboard->focus_resource_list)) { + serial = wl_display_next_serial(display); + msecs = timespec_to_msec(time); + wl_resource_for_each(resource, &keyboard->focus_resource_list) { + wl_keyboard_send_key(resource, + serial, + msecs, + 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_keyboard *keyboard, + const struct timespec *time, uint32_t key, + struct weston_surface *focus) +{ + struct binding_keyboard_grab *grab; + + grab = malloc(sizeof *grab); + grab->key = key; + grab->grab.interface = &binding_grab; + weston_keyboard_start_grab(keyboard, &grab->grab); + + /* Notify the surface which had the focus before this binding + * triggered that we stole a keypress from under it, by forcing + * a wl_keyboard leave/enter pair. The enter event will contain + * the pressed key in the keys array, so the client will know + * the exact state of the keyboard. + * If the old focus surface is different than the new one it + * means it was changed in the binding handler, so it received + * the enter event already. */ + if (focus && keyboard->focus == focus) { + weston_keyboard_set_focus(keyboard, NULL); + weston_keyboard_set_focus(keyboard, focus); + } +} + +void +weston_compositor_run_key_binding(struct weston_compositor *compositor, + struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, + enum wl_keyboard_key_state state) +{ + struct weston_binding *b, *tmp; + struct weston_surface *focus; + struct weston_seat *seat = keyboard->seat; + + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + /* Invalidate all active modifier bindings. */ + wl_list_for_each(b, &compositor->modifier_binding_list, link) + b->key = key; + + wl_list_for_each_safe(b, tmp, &compositor->key_binding_list, link) { + if (b->key == key && b->modifier == seat->modifier_state) { + weston_key_binding_handler_t handler = b->handler; + focus = keyboard->focus; + handler(keyboard, 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 press. */ + if (keyboard->grab == + &keyboard->default_grab) + install_binding_grab(keyboard, + time, + key, + focus); + } + } +} + +void +weston_compositor_run_modifier_binding(struct weston_compositor *compositor, + struct weston_keyboard *keyboard, + enum weston_keyboard_modifier modifier, + enum wl_keyboard_key_state state) +{ + struct weston_binding *b, *tmp; + + if (keyboard->grab != &keyboard->default_grab) + return; + + wl_list_for_each_safe(b, tmp, &compositor->modifier_binding_list, link) { + weston_modifier_binding_handler_t handler = b->handler; + + if (b->modifier != modifier) + continue; + + /* Prime the modifier binding. */ + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + b->key = 0; + continue; + } + /* Ignore the binding if a key was pressed in between. */ + else if (b->key != 0) { + return; + } + + handler(keyboard, modifier, b->data); + } +} + +void +weston_compositor_run_button_binding(struct weston_compositor *compositor, + struct weston_pointer *pointer, + const struct timespec *time, + uint32_t button, + enum wl_pointer_button_state state) +{ + struct weston_binding *b, *tmp; + + if (state == WL_POINTER_BUTTON_STATE_RELEASED) + return; + + /* Invalidate all active modifier bindings. */ + wl_list_for_each(b, &compositor->modifier_binding_list, link) + b->key = button; + + wl_list_for_each_safe(b, tmp, &compositor->button_binding_list, link) { + if (b->button == button && + b->modifier == pointer->seat->modifier_state) { + weston_button_binding_handler_t handler = b->handler; + handler(pointer, time, button, b->data); + } + } +} + +void +weston_compositor_run_touch_binding(struct weston_compositor *compositor, + struct weston_touch *touch, + const struct timespec *time, + int touch_type) +{ + struct weston_binding *b, *tmp; + + if (touch->num_tp != 1 || touch_type != WL_TOUCH_DOWN) + return; + + wl_list_for_each_safe(b, tmp, &compositor->touch_binding_list, link) { + if (b->modifier == touch->seat->modifier_state) { + weston_touch_binding_handler_t handler = b->handler; + handler(touch, time, b->data); + } + } +} + +int +weston_compositor_run_axis_binding(struct weston_compositor *compositor, + struct weston_pointer *pointer, + const struct timespec *time, + struct weston_pointer_axis_event *event) +{ + struct weston_binding *b, *tmp; + + /* Invalidate all active modifier bindings. */ + wl_list_for_each(b, &compositor->modifier_binding_list, link) + b->key = event->axis; + + wl_list_for_each_safe(b, tmp, &compositor->axis_binding_list, link) { + if (b->axis == event->axis && + b->modifier == pointer->seat->modifier_state) { + weston_axis_binding_handler_t handler = b->handler; + handler(pointer, time, event, b->data); + return 1; + } + } + + return 0; +} + +int +weston_compositor_run_debug_binding(struct weston_compositor *compositor, + struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, + enum wl_keyboard_key_state state) +{ + weston_key_binding_handler_t handler; + struct weston_binding *binding, *tmp; + int count = 0; + + wl_list_for_each_safe(binding, tmp, &compositor->debug_binding_list, link) { + if (key != binding->key) + continue; + + count++; + handler = binding->handler; + handler(keyboard, time, key, binding->data); + } + + return count; +} + +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, const struct timespec *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; + uint32_t msecs; + + 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, grab->keyboard, + 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; + msecs = timespec_to_msec(time); + wl_resource_for_each(resource, resource_list) { + wl_keyboard_send_key(resource, serial, msecs, 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_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) +{ + struct debug_binding_grab *grab; + + grab = calloc(1, sizeof *grab); + if (!grab) + return; + + grab->seat = keyboard->seat; + grab->key[0] = key; + grab->grab.interface = &debug_binding_keyboard_grab; + weston_keyboard_start_grab(keyboard, &grab->grab); +} + +/** Install the trigger binding for debug bindings. + * + * \param compositor The compositor. + * \param mod The modifier. + * + * This will add a key binding for modifier+SHIFT+SPACE that will trigger + * debug key bindings. + */ +WL_EXPORT void +weston_install_debug_key_binding(struct weston_compositor *compositor, + uint32_t mod) +{ + weston_compositor_add_key_binding(compositor, KEY_SPACE, + mod | MODIFIER_SHIFT, + debug_binding, NULL); +} diff --git a/libweston/clipboard.c b/libweston/clipboard.c new file mode 100644 index 0000000..7d60351 --- /dev/null +++ b/libweston/clipboard.c @@ -0,0 +1,308 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "libweston-internal.h" +#include "shared/helpers.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 = zalloc(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; + source->fd = fd; + + 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 = zalloc(sizeof *client); + if (client == NULL) + return; + + 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 (!mime_types || 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/libweston/compositor.c b/libweston/compositor.c new file mode 100755 index 0000000..2ffc821 --- /dev/null +++ b/libweston/compositor.c @@ -0,0 +1,8005 @@ +/* + * Copyright © 2010-2011 Intel Corporation + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2012-2018 Collabora, Ltd. + * Copyright © 2017, 2018 General Electric Company + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "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 + +// OHOS remove timeline +//#include "timeline.h" + +#include +#include +#include "linux-dmabuf.h" +#include "viewporter-server-protocol.h" +#include "presentation-time-server-protocol.h" +#include "xdg-output-unstable-v1-server-protocol.h" +#include "linux-explicit-synchronization-unstable-v1-server-protocol.h" +#include "linux-explicit-synchronization.h" +#include "shared/fd-util.h" +#include "shared/helpers.h" +#include "shared/os-compatibility.h" +#include "shared/string-helpers.h" +#include "shared/timespec-util.h" +#include "git-version.h" +#include +#include +#include "pixel-formats.h" +#include "backend.h" +#include "libweston-internal.h" + +// OHOS remove logger +//#include "weston-log-internal.h" + +/** + * \defgroup head Head + * \defgroup output Output + * \defgroup compositor Compositor + */ + +#define DEFAULT_REPAINT_WINDOW 7 /* milliseconds */ + +static void +weston_output_update_matrix(struct weston_output *output); + +static void +weston_output_transform_scale_init(struct weston_output *output, + uint32_t transform, uint32_t scale); + +static void +weston_compositor_build_view_list(struct weston_compositor *compositor); + +static char * +weston_output_create_heads_string(struct weston_output *output); + +/** Send wl_output events for mode and scale changes + * + * \param head Send on all resources bound to this head. + * \param mode_changed If true, send the current mode. + * \param scale_changed If true, send the current scale. + */ +static void +weston_mode_switch_send_events(struct weston_head *head, + bool mode_changed, bool scale_changed) +{ + struct weston_output *output = head->output; + struct wl_resource *resource; + int version; + + wl_resource_for_each(resource, &head->resource_list) { + if (mode_changed) { + wl_output_send_mode(resource, + output->current_mode->flags, + output->current_mode->width, + output->current_mode->height, + output->current_mode->refresh); + } + + version = wl_resource_get_version(resource); + if (version >= WL_OUTPUT_SCALE_SINCE_VERSION && scale_changed) + wl_output_send_scale(resource, output->current_scale); + + if (version >= WL_OUTPUT_DONE_SINCE_VERSION) + wl_output_send_done(resource); + } + wl_resource_for_each(resource, &head->xdg_output_resource_list) { + zxdg_output_v1_send_logical_position(resource, + output->x, + output->y); + zxdg_output_v1_send_logical_size(resource, + output->width, + output->height); + zxdg_output_v1_send_done(resource); + } +} + +static void +weston_mode_switch_finish(struct weston_output *output, + int mode_changed, int scale_changed) +{ + struct weston_seat *seat; + struct weston_head *head; + pixman_region32_t old_output_region; + + 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_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 = weston_seat_get_pointer(seat); + 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); + + if (!mode_changed && !scale_changed) + return; + + /* notify clients of the changes */ + wl_list_for_each(head, &output->head_list, output_link) + weston_mode_switch_send_events(head, + mode_changed, scale_changed); +} + +static void +weston_compositor_reflow_outputs(struct weston_compositor *compositor, + struct weston_output *resized_output, int delta_width); + +/** + * \ingroup output + */ +WL_EXPORT int +weston_output_mode_set_native(struct weston_output *output, + struct weston_mode *mode, + int32_t scale) +{ + int ret; + int mode_changed = 0, scale_changed = 0; + int32_t old_width; + + if (!output->switch_mode) + return -1; + + if (!output->original_mode) { + mode_changed = 1; + ret = output->switch_mode(output, mode); + if (ret < 0) + return ret; + if (output->current_scale != scale) { + scale_changed = 1; + output->current_scale = scale; + } + } + + old_width = output->width; + output->native_mode = mode; + output->native_scale = scale; + + weston_mode_switch_finish(output, mode_changed, scale_changed); + + if (mode_changed || scale_changed) { + weston_compositor_reflow_outputs(output->compositor, output, output->width - old_width); + + wl_signal_emit(&output->compositor->output_resized_signal, output); + } + return 0; +} + +/** + * \ingroup output + */ +WL_EXPORT int +weston_output_mode_switch_to_native(struct weston_output *output) +{ + int ret; + int mode_changed = 0, scale_changed = 0; + + if (!output->switch_mode) + return -1; + + if (!output->original_mode) { + weston_log("already in the native mode\n"); + return -1; + } + /* the non fullscreen clients haven't seen a mode set since we + * switched into a temporary, so we need to notify them if the + * mode at that time is different from the native mode now. + */ + mode_changed = (output->original_mode != output->native_mode); + scale_changed = (output->original_scale != output->native_scale); + + ret = output->switch_mode(output, output->native_mode); + if (ret < 0) + return ret; + + output->current_scale = output->native_scale; + + output->original_mode = NULL; + output->original_scale = 0; + + weston_mode_switch_finish(output, mode_changed, scale_changed); + + return 0; +} + +/** + * \ingroup output + */ +WL_EXPORT int +weston_output_mode_switch_to_temporary(struct weston_output *output, + struct weston_mode *mode, + int32_t scale) +{ + int ret; + + if (!output->switch_mode) + return -1; + + /* original_mode is the last mode non full screen clients have seen, + * so we shouldn't change it if we already have one set. + */ + if (!output->original_mode) { + output->original_mode = output->native_mode; + output->original_scale = output->native_scale; + } + ret = output->switch_mode(output, mode); + if (ret < 0) + return ret; + + output->current_scale = scale; + + weston_mode_switch_finish(output, 0, 0); + + return 0; +} + +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_view * +weston_view_create(struct weston_surface *surface) +{ + struct weston_view *view; + + view = zalloc(sizeof *view); + if (view == NULL) + return NULL; + + view->surface = surface; + view->plane = &surface->compositor->primary_plane; + + /* Assign to surface */ + wl_list_insert(&surface->views, &view->surface_link); + + wl_signal_init(&view->destroy_signal); + wl_list_init(&view->link); + wl_list_init(&view->layer_link.link); + + pixman_region32_init(&view->clip); + + view->alpha = 1.0; + pixman_region32_init(&view->transform.opaque); + + wl_list_init(&view->geometry.transformation_list); + wl_list_insert(&view->geometry.transformation_list, + &view->transform.position.link); + weston_matrix_init(&view->transform.position.matrix); + wl_list_init(&view->geometry.child_list); + pixman_region32_init(&view->geometry.scissor); + pixman_region32_init(&view->transform.boundingbox); + view->transform.dirty = 1; + + return view; +} + +struct weston_frame_callback { + struct wl_resource *resource; + struct wl_list link; +}; + +struct weston_presentation_feedback { + struct wl_resource *resource; + + /* XXX: could use just wl_resource_get_link() instead */ + struct wl_list link; + + /* The per-surface feedback flags */ + uint32_t psf_flags; +}; + +static void +weston_presentation_feedback_discard( + struct weston_presentation_feedback *feedback) +{ + wp_presentation_feedback_send_discarded(feedback->resource); + wl_resource_destroy(feedback->resource); +} + +static void +weston_presentation_feedback_discard_list(struct wl_list *list) +{ + struct weston_presentation_feedback *feedback, *tmp; + + wl_list_for_each_safe(feedback, tmp, list, link) + weston_presentation_feedback_discard(feedback); +} + +static void +weston_presentation_feedback_present( + struct weston_presentation_feedback *feedback, + struct weston_output *output, + uint32_t refresh_nsec, + const struct timespec *ts, + uint64_t seq, + uint32_t flags) +{ + struct wl_client *client = wl_resource_get_client(feedback->resource); + struct weston_head *head; + struct wl_resource *o; + uint32_t tv_sec_hi; + uint32_t tv_sec_lo; + uint32_t tv_nsec; + bool done = false; + + wl_list_for_each(head, &output->head_list, output_link) { + wl_resource_for_each(o, &head->resource_list) { + if (wl_resource_get_client(o) != client) + continue; + + wp_presentation_feedback_send_sync_output(feedback->resource, o); + done = true; + } + + /* For clone mode, send it for just one wl_output global, + * they are all equivalent anyway. + */ + if (done) + break; + } + + timespec_to_proto(ts, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + wp_presentation_feedback_send_presented(feedback->resource, + tv_sec_hi, tv_sec_lo, tv_nsec, + refresh_nsec, + seq >> 32, seq & 0xffffffff, + flags | feedback->psf_flags); + wl_resource_destroy(feedback->resource); +} + +static void +weston_presentation_feedback_present_list(struct wl_list *list, + struct weston_output *output, + uint32_t refresh_nsec, + const struct timespec *ts, + uint64_t seq, + uint32_t flags) +{ + struct weston_presentation_feedback *feedback, *tmp; + + assert(!(flags & WP_PRESENTATION_FEEDBACK_INVALID) || + wl_list_empty(list)); + + wl_list_for_each_safe(feedback, tmp, list, link) + weston_presentation_feedback_present(feedback, output, + refresh_nsec, ts, seq, + flags); +} + +static void +surface_state_handle_buffer_destroy(struct wl_listener *listener, void *data) +{ + struct weston_surface_state *state = + container_of(listener, struct weston_surface_state, + buffer_destroy_listener); + + state->buffer = NULL; +} + +static void +weston_surface_state_init(struct weston_surface_state *state) +{ + state->newly_attached = 0; + state->buffer = NULL; + state->buffer_destroy_listener.notify = + surface_state_handle_buffer_destroy; + state->sx = 0; + state->sy = 0; + + pixman_region32_init(&state->damage_surface); + pixman_region32_init(&state->damage_buffer); + pixman_region32_init(&state->opaque); + region_init_infinite(&state->input); + + wl_list_init(&state->frame_callback_list); + wl_list_init(&state->feedback_list); + + state->buffer_viewport.buffer.transform = WL_OUTPUT_TRANSFORM_NORMAL; + state->buffer_viewport.buffer.scale = 1; + state->buffer_viewport.buffer.src_width = wl_fixed_from_int(-1); + state->buffer_viewport.surface.width = -1; + state->buffer_viewport.changed = 0; + + state->acquire_fence_fd = -1; + + state->desired_protection = WESTON_HDCP_DISABLE; + state->protection_mode = WESTON_SURFACE_PROTECTION_MODE_RELAXED; +} + +static void +weston_surface_state_fini(struct weston_surface_state *state) +{ + struct weston_frame_callback *cb, *next; + + wl_list_for_each_safe(cb, next, + &state->frame_callback_list, link) + wl_resource_destroy(cb->resource); + + weston_presentation_feedback_discard_list(&state->feedback_list); + + pixman_region32_fini(&state->input); + pixman_region32_fini(&state->opaque); + pixman_region32_fini(&state->damage_surface); + pixman_region32_fini(&state->damage_buffer); + + if (state->buffer) + wl_list_remove(&state->buffer_destroy_listener.link); + state->buffer = NULL; + + fd_clear(&state->acquire_fence_fd); + weston_buffer_release_reference(&state->buffer_release_ref, NULL); +} + +static void +weston_surface_state_set_buffer(struct weston_surface_state *state, + struct weston_buffer *buffer) +{ + if (state->buffer == buffer) + return; + + if (state->buffer) + wl_list_remove(&state->buffer_destroy_listener.link); + state->buffer = buffer; + if (state->buffer) + wl_signal_add(&state->buffer->destroy_signal, + &state->buffer_destroy_listener); +} + +WL_EXPORT struct weston_surface * +weston_surface_create(struct weston_compositor *compositor) +{ + struct weston_surface *surface; + + surface = zalloc(sizeof *surface); + if (surface == NULL) + return NULL; + + wl_signal_init(&surface->destroy_signal); + wl_signal_init(&surface->commit_signal); + + surface->compositor = compositor; + surface->ref_count = 1; + + surface->buffer_viewport.buffer.transform = WL_OUTPUT_TRANSFORM_NORMAL; + surface->buffer_viewport.buffer.scale = 1; + surface->buffer_viewport.buffer.src_width = wl_fixed_from_int(-1); + surface->buffer_viewport.surface.width = -1; + + weston_surface_state_init(&surface->pending); + + pixman_region32_init(&surface->damage); + pixman_region32_init(&surface->opaque); + region_init_infinite(&surface->input); + + wl_list_init(&surface->views); + + wl_list_init(&surface->frame_callback_list); + wl_list_init(&surface->feedback_list); + + wl_list_init(&surface->subsurface_list); + wl_list_init(&surface->subsurface_list_pending); + + weston_matrix_init(&surface->buffer_to_surface_matrix); + weston_matrix_init(&surface->surface_to_buffer_matrix); + + wl_list_init(&surface->pointer_constraints); + + surface->acquire_fence_fd = -1; + + surface->desired_protection = WESTON_HDCP_DISABLE; + surface->current_protection = WESTON_HDCP_DISABLE; + surface->protection_mode = WESTON_SURFACE_PROTECTION_MODE_RELAXED; + + 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); + surface->is_opaque = !(alpha < 1.0); +} + +WL_EXPORT void +weston_view_to_global_float(struct weston_view *view, + float sx, float sy, float *x, float *y) +{ + if (view->transform.enabled) { + struct weston_vector v = { { sx, sy, 0.0f, 1.0f } }; + + weston_matrix_transform(&view->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 + view->geometry.x; + *y = sy + view->geometry.y; + } +} + +/** Transform a point to buffer coordinates + * + * \param width Surface width. + * \param height Surface height. + * \param transform Buffer transform. + * \param scale Buffer scale. + * \param sx Surface x coordinate of a point. + * \param sy Surface y coordinate of a point. + * \param[out] bx Buffer x coordinate of the point. + * \param[out] by Buffer Y coordinate of the point. + * + * Converts the given surface-local coordinates to buffer coordinates + * according to the given buffer transform and scale. + * This ignores wp_viewport. + * + * The given width and height must be the result of inverse scaled and + * inverse transformed buffer size. + */ +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 = sy; + *by = width - sx; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + *bx = sy; + *by = 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 = height - sy; + *by = sx; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + *bx = height - sy; + *by = width - sx; + break; + } + + *bx *= scale; + *by *= scale; +} + +/** Transform a rectangle to buffer coordinates + * + * \param width Surface width. + * \param height Surface height. + * \param transform Buffer transform. + * \param scale Buffer scale. + * \param rect Rectangle in surface coordinates. + * \return Rectangle in buffer coordinates. + * + * Converts the given surface-local rectangle to buffer coordinates + * according to the given buffer transform and scale. The resulting + * rectangle is guaranteed to be well-formed. + * This ignores wp_viewport. + * + * The given width and height must be the result of inverse scaled and + * inverse transformed buffer size. + */ +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; +} + +/** Transform a region by a matrix, restricted to axis-aligned transformations + * + * Warning: This function does not work for projective, affine, or matrices + * that encode arbitrary rotations. Only 90-degree step rotations are + * supported. + */ +WL_EXPORT void +weston_matrix_transform_region(pixman_region32_t *dest, + struct weston_matrix *matrix, + pixman_region32_t *src) +{ + pixman_box32_t *src_rects, *dest_rects; + int nrects, i; + + src_rects = pixman_region32_rectangles(src, &nrects); + dest_rects = malloc(nrects * sizeof(*dest_rects)); + if (!dest_rects) + return; + + for (i = 0; i < nrects; i++) { + struct weston_vector vec1 = {{ + src_rects[i].x1, src_rects[i].y1, 0, 1 + }}; + weston_matrix_transform(matrix, &vec1); + vec1.f[0] /= vec1.f[3]; + vec1.f[1] /= vec1.f[3]; + + struct weston_vector vec2 = {{ + src_rects[i].x2, src_rects[i].y2, 0, 1 + }}; + weston_matrix_transform(matrix, &vec2); + vec2.f[0] /= vec2.f[3]; + vec2.f[1] /= vec2.f[3]; + + if (vec1.f[0] < vec2.f[0]) { + dest_rects[i].x1 = floor(vec1.f[0]); + dest_rects[i].x2 = ceil(vec2.f[0]); + } else { + dest_rects[i].x1 = floor(vec2.f[0]); + dest_rects[i].x2 = ceil(vec1.f[0]); + } + + if (vec1.f[1] < vec2.f[1]) { + dest_rects[i].y1 = floor(vec1.f[1]); + dest_rects[i].y2 = ceil(vec2.f[1]); + } else { + dest_rects[i].y1 = floor(vec2.f[1]); + dest_rects[i].y2 = ceil(vec1.f[1]); + } + } + + pixman_region32_clear(dest); + pixman_region32_init_rects(dest, dest_rects, nrects); + free(dest_rects); +} + +/** Transform a region to buffer coordinates + * + * \param width Surface width. + * \param height Surface height. + * \param transform Buffer transform. + * \param scale Buffer scale. + * \param[in] src Region in surface coordinates. + * \param[out] dest Resulting region in buffer coordinates. + * + * Converts the given surface-local region to buffer coordinates + * according to the given buffer transform and scale. + * This ignores wp_viewport. + * + * The given width and height must be the result of inverse scaled and + * inverse transformed buffer size. + * + * src and dest are allowed to point to the same memory for in-place conversion. + */ +WL_EXPORT void +weston_transformed_region(int width, int height, + enum wl_output_transform transform, + int32_t scale, + pixman_region32_t *src, pixman_region32_t *dest) +{ + pixman_box32_t *src_rects, *dest_rects; + int nrects, i; + + if (transform == WL_OUTPUT_TRANSFORM_NORMAL && scale == 1) { + if (src != dest) + pixman_region32_copy(dest, src); + return; + } + + src_rects = pixman_region32_rectangles(src, &nrects); + dest_rects = malloc(nrects * sizeof(*dest_rects)); + if (!dest_rects) + return; + + if (transform == WL_OUTPUT_TRANSFORM_NORMAL) { + memcpy(dest_rects, src_rects, nrects * sizeof(*dest_rects)); + } else { + for (i = 0; i < nrects; i++) { + switch (transform) { + default: + case WL_OUTPUT_TRANSFORM_NORMAL: + dest_rects[i].x1 = src_rects[i].x1; + dest_rects[i].y1 = src_rects[i].y1; + dest_rects[i].x2 = src_rects[i].x2; + dest_rects[i].y2 = src_rects[i].y2; + break; + case WL_OUTPUT_TRANSFORM_90: + dest_rects[i].x1 = src_rects[i].y1; + dest_rects[i].y1 = width - src_rects[i].x2; + dest_rects[i].x2 = src_rects[i].y2; + dest_rects[i].y2 = width - src_rects[i].x1; + break; + case WL_OUTPUT_TRANSFORM_180: + dest_rects[i].x1 = width - src_rects[i].x2; + dest_rects[i].y1 = height - src_rects[i].y2; + dest_rects[i].x2 = width - src_rects[i].x1; + dest_rects[i].y2 = height - src_rects[i].y1; + break; + case WL_OUTPUT_TRANSFORM_270: + dest_rects[i].x1 = height - src_rects[i].y2; + dest_rects[i].y1 = src_rects[i].x1; + dest_rects[i].x2 = height - src_rects[i].y1; + dest_rects[i].y2 = src_rects[i].x2; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + dest_rects[i].x1 = width - src_rects[i].x2; + dest_rects[i].y1 = src_rects[i].y1; + dest_rects[i].x2 = width - src_rects[i].x1; + dest_rects[i].y2 = src_rects[i].y2; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + dest_rects[i].x1 = src_rects[i].y1; + dest_rects[i].y1 = src_rects[i].x1; + dest_rects[i].x2 = src_rects[i].y2; + dest_rects[i].y2 = src_rects[i].x2; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + dest_rects[i].x1 = src_rects[i].x1; + dest_rects[i].y1 = height - src_rects[i].y2; + dest_rects[i].x2 = src_rects[i].x2; + dest_rects[i].y2 = height - src_rects[i].y1; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + dest_rects[i].x1 = height - src_rects[i].y2; + dest_rects[i].y1 = width - src_rects[i].x2; + dest_rects[i].x2 = height - src_rects[i].y1; + dest_rects[i].y2 = width - src_rects[i].x1; + break; + } + } + } + + if (scale != 1) { + for (i = 0; i < nrects; i++) { + dest_rects[i].x1 *= scale; + dest_rects[i].x2 *= scale; + dest_rects[i].y1 *= scale; + dest_rects[i].y2 *= scale; + } + } + + pixman_region32_clear(dest); + pixman_region32_init_rects(dest, dest_rects, nrects); + free(dest_rects); +} + +static void +viewport_surface_to_buffer(struct weston_surface *surface, + float sx, float sy, float *bx, float *by) +{ + struct weston_buffer_viewport *vp = &surface->buffer_viewport; + double src_width, src_height; + double src_x, src_y; + + if (vp->buffer.src_width == wl_fixed_from_int(-1)) { + if (vp->surface.width == -1) { + *bx = sx; + *by = sy; + return; + } + + src_x = 0.0; + src_y = 0.0; + src_width = surface->width_from_buffer; + src_height = surface->height_from_buffer; + } else { + src_x = wl_fixed_to_double(vp->buffer.src_x); + src_y = wl_fixed_to_double(vp->buffer.src_y); + src_width = wl_fixed_to_double(vp->buffer.src_width); + src_height = wl_fixed_to_double(vp->buffer.src_height); + } + + *bx = sx * src_width / surface->width + src_x; + *by = sy * src_height / surface->height + src_y; +} + +WL_EXPORT void +weston_surface_to_buffer_float(struct weston_surface *surface, + float sx, float sy, float *bx, float *by) +{ + struct weston_buffer_viewport *vp = &surface->buffer_viewport; + + /* first transform coordinates if the viewport is set */ + viewport_surface_to_buffer(surface, sx, sy, bx, by); + + weston_transformed_coord(surface->width_from_buffer, + surface->height_from_buffer, + vp->buffer.transform, vp->buffer.scale, + *bx, *by, bx, by); +} + +/** Transform a rectangle from surface coordinates to buffer coordinates + * + * \param surface The surface to fetch wp_viewport and buffer transformation + * from. + * \param rect The rectangle to transform. + * \return The transformed rectangle. + * + * Viewport and buffer transformations can only do translation, scaling, + * and rotations in 90-degree steps. Therefore the only loss in the + * conversion is coordinate rounding. + * + * However, some coordinate rounding takes place as an intermediate + * step before the buffer scale factor is applied, so the rectangle + * boundary may not be exactly as expected. + * + * This is OK for damage tracking since a little extra coverage is + * not a problem. + */ +WL_EXPORT pixman_box32_t +weston_surface_to_buffer_rect(struct weston_surface *surface, + pixman_box32_t rect) +{ + struct weston_buffer_viewport *vp = &surface->buffer_viewport; + float xf, yf; + + /* first transform box coordinates if the viewport is set */ + viewport_surface_to_buffer(surface, rect.x1, rect.y1, &xf, &yf); + rect.x1 = floorf(xf); + rect.y1 = floorf(yf); + + viewport_surface_to_buffer(surface, rect.x2, rect.y2, &xf, &yf); + rect.x2 = ceilf(xf); + rect.y2 = ceilf(yf); + + return weston_transformed_rect(surface->width_from_buffer, + surface->height_from_buffer, + vp->buffer.transform, vp->buffer.scale, + rect); +} + +/** Transform a region from surface coordinates to buffer coordinates + * + * \param surface The surface to fetch wp_viewport and buffer transformation + * from. + * \param[in] surface_region The region in surface coordinates. + * \param[out] buffer_region The region converted to buffer coordinates. + * + * Buffer_region must be init'd, but will be completely overwritten. + * + * Viewport and buffer transformations can only do translation, scaling, + * and rotations in 90-degree steps. Therefore the only loss in the + * conversion is from the coordinate rounding that takes place in + * \ref weston_surface_to_buffer_rect. + * + */ +WL_EXPORT void +weston_surface_to_buffer_region(struct weston_surface *surface, + pixman_region32_t *surface_region, + pixman_region32_t *buffer_region) +{ + pixman_box32_t *src_rects, *dest_rects; + int nrects, i; + + src_rects = pixman_region32_rectangles(surface_region, &nrects); + dest_rects = malloc(nrects * sizeof(*dest_rects)); + if (!dest_rects) + return; + + for (i = 0; i < nrects; i++) { + dest_rects[i] = weston_surface_to_buffer_rect(surface, + src_rects[i]); + } + + pixman_region32_fini(buffer_region); + pixman_region32_init_rects(buffer_region, dest_rects, nrects); + free(dest_rects); +} + +WL_EXPORT void +weston_view_move_to_plane(struct weston_view *view, + struct weston_plane *plane) +{ + if (view->plane == plane) + return; + + weston_view_damage_below(view); + view->plane = plane; + weston_surface_damage(view->surface); +} + +/** Inflict damage on the plane where the view is visible. + * + * \param view The view that causes the damage. + * + * If the view is currently on a plane (including the primary plane), + * take the view's boundingbox, subtract all the opaque views that cover it, + * and add the remaining region as damage to the plane. This corresponds + * to the damage inflicted to the plane if this view disappeared. + * + * A repaint is scheduled for this view. + * + * The region of all opaque views covering this view is stored in + * weston_view::clip and updated by view_accumulate_damage() during + * weston_output_repaint(). Specifically, that region matches the + * scenegraph as it was last painted. + */ +WL_EXPORT void +weston_view_damage_below(struct weston_view *view) +{ + pixman_region32_t damage; + + pixman_region32_init(&damage); + pixman_region32_subtract(&damage, &view->transform.boundingbox, + &view->clip); + if (view->plane) + pixman_region32_union(&view->plane->damage, + &view->plane->damage, &damage); + pixman_region32_fini(&damage); + weston_view_schedule_repaint(view); +} + +/** Send wl_surface.enter/leave events + * + * \param surface The surface. + * \param head A head of the entered/left output. + * \param enter True if entered. + * \param leave True if left. + * + * Send the enter/leave events for all protocol objects bound to the given + * output by the client owning the surface. + */ +static void +weston_surface_send_enter_leave(struct weston_surface *surface, + struct weston_head *head, + bool enter, + bool leave) +{ + struct wl_resource *wloutput; + struct wl_client *client; + + assert(enter != leave); + + client = wl_resource_get_client(surface->resource); + wl_resource_for_each(wloutput, &head->resource_list) { + if (wl_resource_get_client(wloutput) != client) + continue; + + if (enter) + wl_surface_send_enter(surface->resource, wloutput); + if (leave) + wl_surface_send_leave(surface->resource, wloutput); + } +} + +static void +weston_surface_compute_protection(struct protected_surface *psurface) +{ + enum weston_hdcp_protection min_protection; + bool min_protection_valid = false; + struct weston_surface *surface = psurface->surface; + struct weston_output *output; + + wl_list_for_each(output, &surface->compositor->output_list, link) + if (surface->output_mask & (1u << output->id)) { + /* + * If the content-protection is enabled with protection + * mode as RELAXED for a surface, and if + * content-recording features like: screen-shooter, + * recorder, screen-sharing, etc are on, then notify the + * client, that the protection is disabled. + * + * Note: If the protection mode is ENFORCED then there + * is no need to bother the client as the renderer takes + * care of censoring the visibility of the protected + * content. + */ + + if (output->disable_planes > 0 && + surface->protection_mode == WESTON_SURFACE_PROTECTION_MODE_RELAXED) { + min_protection = WESTON_HDCP_DISABLE; + min_protection_valid = true; + break; + } + if (!min_protection_valid) { + min_protection = output->current_protection; + min_protection_valid = true; + } + if (output->current_protection < min_protection) + min_protection = output->current_protection; + } + if (!min_protection_valid) + min_protection = WESTON_HDCP_DISABLE; + + surface->current_protection = min_protection; + + weston_protected_surface_send_event(psurface, surface->current_protection); +} + +static void +notify_surface_protection_change(void *data) +{ + struct weston_compositor *compositor = data; + struct content_protection *cp; + struct protected_surface *psurface; + + cp = compositor->content_protection; + cp->surface_protection_update = NULL; + + /* Notify the clients, whose surfaces are changed */ + wl_list_for_each(psurface, &cp->protected_list, link) + if (psurface && psurface->surface) + weston_surface_compute_protection(psurface); +} + +/** + * \param compositor weston_compositor + * + * Schedule an idle task to notify surface about the update in protection, + * if not already scheduled. + */ +static void +weston_schedule_surface_protection_update(struct weston_compositor *compositor) +{ + struct content_protection *cp = compositor->content_protection; + struct wl_event_loop *loop; + + if (!cp || cp->surface_protection_update) + return; + loop = wl_display_get_event_loop(compositor->wl_display); + cp->surface_protection_update = wl_event_loop_add_idle(loop, + notify_surface_protection_change, + compositor); +} + +/** + * \param es The surface + * \param mask The new set of outputs for the surface + * + * Sets the surface's set of outputs to the ones specified by + * the new output mask provided. Identifies the outputs that + * have changed, the posts enter and leave events for these + * outputs as appropriate. + */ +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; + uint32_t output_bit; + struct weston_output *output; + struct weston_head *head; + + es->output_mask = mask; + if (es->resource == NULL) + return; + if (different == 0) + return; + + wl_list_for_each(output, &es->compositor->output_list, link) { + output_bit = 1u << output->id; + if (!(output_bit & different)) + continue; + + wl_list_for_each(head, &output->head_list, output_link) { + weston_surface_send_enter_leave(es, head, + output_bit & entered, + output_bit & left); + } + } + /* + * Change in surfaces' output mask might trigger a change in its + * protection. + */ + weston_schedule_surface_protection_update(es->compositor); +} + +static void +notify_view_output_destroy(struct wl_listener *listener, void *data) +{ + struct weston_view *view = + container_of(listener, + struct weston_view, output_destroy_listener); + + view->output = NULL; + view->output_destroy_listener.notify = NULL; +} + +/** Set the primary output of the view + * + * \param view The view whose primary output to set + * \param output The new primary output for the view + * + * Set \a output to be the primary output of the \a view. + * + * Notice that the assignment may be temporary; the primary output could be + * automatically changed. Hence, one cannot rely on the value persisting. + * + * Passing NULL as /a output will set the primary output to NULL. + */ +WL_EXPORT void +weston_view_set_output(struct weston_view *view, struct weston_output *output) +{ + if (view->output_destroy_listener.notify) { + wl_list_remove(&view->output_destroy_listener.link); + view->output_destroy_listener.notify = NULL; + } + view->output = output; + if (output) { + view->output_destroy_listener.notify = + notify_view_output_destroy; + wl_signal_add(&output->destroy_signal, + &view->output_destroy_listener); + } +} + +/** Recalculate which output(s) the surface has views displayed on + * + * \param es The surface to remap to outputs + * + * Finds the output that is showing the largest amount of one + * of the surface's various views. This output becomes the + * surface's primary output for vsync and frame callback purposes. + * + * Also notes all outputs of all of the surface's views + * in the output_mask for the surface. + */ +static void +weston_surface_assign_output(struct weston_surface *es) +{ + struct weston_output *new_output; + struct weston_view *view; + 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(view, &es->views, surface_link) { + if (!view->output) + continue; + + pixman_region32_intersect(®ion, &view->transform.boundingbox, + &view->output->region); + + e = pixman_region32_extents(®ion); + area = (e->x2 - e->x1) * (e->y2 - e->y1); + + mask |= view->output_mask; + + if (area >= max) { + new_output = view->output; + max = area; + } + } + pixman_region32_fini(®ion); + + es->output = new_output; + weston_surface_update_output_mask(es, mask); +} + +/** Recalculate which output(s) the view is displayed on + * + * \param ev The view to remap to outputs + * + * Identifies the set of outputs that the view is visible on, + * noting them into the output_mask. The output that the view + * is most visible on is set as the view's primary output. + * + * Also does the same for the view's surface. See + * weston_surface_assign_output(). + */ +static void +weston_view_assign_output(struct weston_view *ev) +{ + struct weston_compositor *ec = ev->surface->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) { + if (output->destroying) + continue; + + pixman_region32_intersect(®ion, &ev->transform.boundingbox, + &output->region); + + e = pixman_region32_extents(®ion); + area = (e->x2 - e->x1) * (e->y2 - e->y1); + + if (area > 0) + mask |= 1u << output->id; + + if (area >= max) { + new_output = output; + max = area; + } + } + pixman_region32_fini(®ion); + + weston_view_set_output(ev, new_output); + ev->output_mask = mask; + + weston_surface_assign_output(ev->surface); +} + +static void +weston_view_to_view_map(struct weston_view *from, struct weston_view *to, + int from_x, int from_y, int *to_x, int *to_y) +{ + float x, y; + + weston_view_to_global_float(from, from_x, from_y, &x, &y); + weston_view_from_global_float(to, x, y, &x, &y); + + *to_x = round(x); + *to_y = round(y); +} + +static void +weston_view_transfer_scissor(struct weston_view *from, struct weston_view *to) +{ + pixman_box32_t *a; + pixman_box32_t b; + + a = pixman_region32_extents(&from->geometry.scissor); + + weston_view_to_view_map(from, to, a->x1, a->y1, &b.x1, &b.y1); + weston_view_to_view_map(from, to, a->x2, a->y2, &b.x2, &b.y2); + + pixman_region32_fini(&to->geometry.scissor); + pixman_region32_init_with_extents(&to->geometry.scissor, &b); +} + +static void +view_compute_bbox(struct weston_view *view, const pixman_box32_t *inbox, + 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] = { + { inbox->x1, inbox->y1 }, + { inbox->x1, inbox->y2 }, + { inbox->x2, inbox->y1 }, + { inbox->x2, inbox->y2 }, + }; + float int_x, int_y; + int i; + + if (inbox->x1 == inbox->x2 || inbox->y1 == inbox->y2) { + /* avoid rounding empty bbox to 1x1 */ + pixman_region32_init(bbox); + return; + } + + for (i = 0; i < 4; ++i) { + float x, y; + weston_view_to_global_float(view, 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_view_update_transform_disable(struct weston_view *view) +{ + view->transform.enabled = 0; + + /* round off fractions when not transformed */ + view->geometry.x = roundf(view->geometry.x); + view->geometry.y = roundf(view->geometry.y); + + /* Otherwise identity matrix, but with x and y translation. */ + view->transform.position.matrix.type = WESTON_MATRIX_TRANSFORM_TRANSLATE; + view->transform.position.matrix.d[12] = view->geometry.x; + view->transform.position.matrix.d[13] = view->geometry.y; + + view->transform.matrix = view->transform.position.matrix; + + view->transform.inverse = view->transform.position.matrix; + view->transform.inverse.d[12] = -view->geometry.x; + view->transform.inverse.d[13] = -view->geometry.y; + + pixman_region32_init_rect(&view->transform.boundingbox, + 0, 0, + view->surface->width, + view->surface->height); + if (view->geometry.scissor_enabled) + pixman_region32_intersect(&view->transform.boundingbox, + &view->transform.boundingbox, + &view->geometry.scissor); + + pixman_region32_translate(&view->transform.boundingbox, + view->geometry.x, view->geometry.y); + + if (view->alpha == 1.0) { + pixman_region32_copy(&view->transform.opaque, + &view->surface->opaque); + pixman_region32_translate(&view->transform.opaque, + view->geometry.x, + view->geometry.y); + } +} + +static int +weston_view_update_transform_enable(struct weston_view *view) +{ + struct weston_view *parent = view->geometry.parent; + struct weston_matrix *matrix = &view->transform.matrix; + struct weston_matrix *inverse = &view->transform.inverse; + struct weston_transform *tform; + pixman_region32_t surfregion; + const pixman_box32_t *surfbox; + + view->transform.enabled = 1; + + /* Otherwise identity matrix, but with x and y translation. */ + view->transform.position.matrix.type = WESTON_MATRIX_TRANSFORM_TRANSLATE; + view->transform.position.matrix.d[12] = view->geometry.x; + view->transform.position.matrix.d[13] = view->geometry.y; + + weston_matrix_init(matrix); + wl_list_for_each(tform, &view->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_view %p" + " transformation not invertible.\n", view); + return -1; + } + + if (view->alpha == 1.0 && + matrix->type == WESTON_MATRIX_TRANSFORM_TRANSLATE) { + pixman_region32_copy(&view->transform.opaque, + &view->surface->opaque); + pixman_region32_translate(&view->transform.opaque, + matrix->d[12], + matrix->d[13]); + } + + pixman_region32_init_rect(&surfregion, 0, 0, + view->surface->width, view->surface->height); + if (view->geometry.scissor_enabled) + pixman_region32_intersect(&surfregion, &surfregion, + &view->geometry.scissor); + surfbox = pixman_region32_extents(&surfregion); + + view_compute_bbox(view, surfbox, &view->transform.boundingbox); + pixman_region32_fini(&surfregion); + + return 0; +} + +static struct weston_layer * +get_view_layer(struct weston_view *view) +{ + if (view->parent_view) + return get_view_layer(view->parent_view); + return view->layer_link.layer; +} + +WL_EXPORT void +weston_view_update_transform(struct weston_view *view) +{ + struct weston_view *parent = view->geometry.parent; + struct weston_layer *layer; + pixman_region32_t mask; + + if (!view->transform.dirty) + return; + + if (parent) + weston_view_update_transform(parent); + + view->transform.dirty = 0; + + weston_view_damage_below(view); + + pixman_region32_fini(&view->transform.boundingbox); + pixman_region32_fini(&view->transform.opaque); + pixman_region32_init(&view->transform.opaque); + + /* transform.position is always in transformation_list */ + if (view->geometry.transformation_list.next == + &view->transform.position.link && + view->geometry.transformation_list.prev == + &view->transform.position.link && + !parent) { + weston_view_update_transform_disable(view); + } else { + if (weston_view_update_transform_enable(view) < 0) + weston_view_update_transform_disable(view); + } + + layer = get_view_layer(view); + if (layer) { + pixman_region32_init_with_extents(&mask, &layer->mask); + pixman_region32_intersect(&view->transform.boundingbox, + &view->transform.boundingbox, &mask); + pixman_region32_intersect(&view->transform.opaque, + &view->transform.opaque, &mask); + pixman_region32_fini(&mask); + } + + if (parent) { + if (parent->geometry.scissor_enabled) { + view->geometry.scissor_enabled = true; + weston_view_transfer_scissor(parent, view); + } else { + view->geometry.scissor_enabled = false; + } + } + + weston_view_damage_below(view); + + weston_view_assign_output(view); + + wl_signal_emit(&view->surface->compositor->transform_signal, + view->surface); +} + +WL_EXPORT void +weston_view_geometry_dirty(struct weston_view *view) +{ + struct weston_view *child; + + /* + * The invariant: if view->geometry.dirty, then all views + * in view->geometry.child_list have geometry.dirty too. + * Corollary: if not parent->geometry.dirty, then all ancestors + * are not dirty. + */ + + if (view->transform.dirty) + return; + + view->transform.dirty = 1; + + wl_list_for_each(child, &view->geometry.child_list, + geometry.parent_link) + weston_view_geometry_dirty(child); +} + +WL_EXPORT void +weston_view_to_global_fixed(struct weston_view *view, + wl_fixed_t vx, wl_fixed_t vy, + wl_fixed_t *x, wl_fixed_t *y) +{ + float xf, yf; + + weston_view_to_global_float(view, + wl_fixed_to_double(vx), + wl_fixed_to_double(vy), + &xf, &yf); + *x = wl_fixed_from_double(xf); + *y = wl_fixed_from_double(yf); +} + +WL_EXPORT void +weston_view_from_global_float(struct weston_view *view, + float x, float y, float *vx, float *vy) +{ + if (view->transform.enabled) { + struct weston_vector v = { { x, y, 0.0f, 1.0f } }; + + weston_matrix_transform(&view->transform.inverse, &v); + + if (fabsf(v.f[3]) < 1e-6) { + weston_log("warning: numerical instability in " + "weston_view_from_global(), divisor = %g\n", + v.f[3]); + *vx = 0; + *vy = 0; + return; + } + + *vx = v.f[0] / v.f[3]; + *vy = v.f[1] / v.f[3]; + } else { + *vx = x - view->geometry.x; + *vy = y - view->geometry.y; + } +} + +WL_EXPORT void +weston_view_from_global_fixed(struct weston_view *view, + wl_fixed_t x, wl_fixed_t y, + wl_fixed_t *vx, wl_fixed_t *vy) +{ + float vxf, vyf; + + weston_view_from_global_float(view, + wl_fixed_to_double(x), + wl_fixed_to_double(y), + &vxf, &vyf); + *vx = wl_fixed_from_double(vxf); + *vy = wl_fixed_from_double(vyf); +} + +WL_EXPORT void +weston_view_from_global(struct weston_view *view, + int32_t x, int32_t y, int32_t *vx, int32_t *vy) +{ + float vxf, vyf; + + weston_view_from_global_float(view, x, y, &vxf, &vyf); + *vx = floorf(vxf); + *vy = floorf(vyf); +} + +/** + * \param surface The surface to be repainted + * + * Marks the output(s) that the surface is shown on as needing to be + * repainted. See weston_output_schedule_repaint(). + */ +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 & (1u << output->id)) + weston_output_schedule_repaint(output); +} + +/** + * \param view The view to be repainted + * + * Marks the output(s) that the view is shown on as needing to be + * repainted. See weston_output_schedule_repaint(). + */ +WL_EXPORT void +weston_view_schedule_repaint(struct weston_view *view) +{ + struct weston_output *output; + + wl_list_for_each(output, &view->surface->compositor->output_list, link) + if (view->output_mask & (1u << output->id)) + weston_output_schedule_repaint(output); +} + +/** + * XXX: This function does it the wrong way. + * surface->damage is the damage from the client, and causes + * surface_flush_damage() to copy pixels. No window management action can + * cause damage to the client-provided content, warranting re-upload! + * + * Instead of surface->damage, this function should record the damage + * with all the views for this surface to avoid extraneous texture + * uploads. + */ +WL_EXPORT void +weston_surface_damage(struct weston_surface *surface) +{ + pixman_region32_union_rect(&surface->damage, &surface->damage, + 0, 0, surface->width, + surface->height); + + weston_surface_schedule_repaint(surface); +} + +WL_EXPORT void +weston_view_set_position(struct weston_view *view, float x, float y) +{ + if (view->geometry.x == x && view->geometry.y == y) + return; + + view->geometry.x = x; + view->geometry.y = y; + weston_view_geometry_dirty(view); +} + +static void +transform_parent_handle_parent_destroy(struct wl_listener *listener, + void *data) +{ + struct weston_view *view = + container_of(listener, struct weston_view, + geometry.parent_destroy_listener); + + weston_view_set_transform_parent(view, NULL); +} + +WL_EXPORT void +weston_view_set_transform_parent(struct weston_view *view, + struct weston_view *parent) +{ + if (view->geometry.parent) { + wl_list_remove(&view->geometry.parent_destroy_listener.link); + wl_list_remove(&view->geometry.parent_link); + + if (!parent) + view->geometry.scissor_enabled = false; + } + + view->geometry.parent = parent; + + view->geometry.parent_destroy_listener.notify = + transform_parent_handle_parent_destroy; + if (parent) { + wl_signal_add(&parent->destroy_signal, + &view->geometry.parent_destroy_listener); + wl_list_insert(&parent->geometry.child_list, + &view->geometry.parent_link); + } + + weston_view_geometry_dirty(view); +} + +/** Set a clip mask rectangle on a view + * + * \param view The view to set the clip mask on. + * \param x Top-left corner X coordinate of the clip rectangle. + * \param y Top-left corner Y coordinate of the clip rectangle. + * \param width Width of the clip rectangle, non-negative. + * \param height Height of the clip rectangle, non-negative. + * + * A shell may set a clip mask rectangle on a view. Everything outside + * the rectangle is cut away for input and output purposes: it is + * not drawn and cannot be hit by hit-test based input like pointer + * motion or touch-downs. Everything inside the rectangle will behave + * normally. Clients are unaware of clipping. + * + * The rectangle is set in surface-local coordinates. Setting a clip + * mask rectangle does not affect the view position, the view is positioned + * as it would be without a clip. The clip also does not change + * weston_surface::width,height. + * + * The clip mask rectangle is part of transformation inheritance + * (weston_view_set_transform_parent()). A clip set in the root of the + * transformation inheritance tree will affect all views in the tree. + * A clip can be set only on the root view. Attempting to set a clip + * on view that has a transformation parent will fail. Assigning a parent + * to a view that has a clip set will cause the clip to be forgotten. + * + * Because the clip mask is an axis-aligned rectangle, it poses restrictions + * on the additional transformations in the child views. These transformations + * may not rotate the coordinate axes, i.e., only translation and scaling + * are allowed. Violating this restriction causes the clipping to malfunction. + * Furthermore, using scaling may cause rounding errors in child clipping. + * + * The clip mask rectangle is not automatically adjusted based on + * wl_surface.attach dx and dy arguments. + * + * A clip mask rectangle can be set only if the compositor capability + * WESTON_CAP_VIEW_CLIP_MASK is present. + * + * This function sets the clip mask rectangle and schedules a repaint for + * the view. + */ +WL_EXPORT void +weston_view_set_mask(struct weston_view *view, + int x, int y, int width, int height) +{ + struct weston_compositor *compositor = view->surface->compositor; + + if (!(compositor->capabilities & WESTON_CAP_VIEW_CLIP_MASK)) { + weston_log("%s not allowed without capability!\n", __func__); + return; + } + + if (view->geometry.parent) { + weston_log("view %p has a parent, clip forbidden!\n", view); + return; + } + + if (width < 0 || height < 0) { + weston_log("%s: illegal args %d, %d, %d, %d\n", __func__, + x, y, width, height); + return; + } + + pixman_region32_fini(&view->geometry.scissor); + pixman_region32_init_rect(&view->geometry.scissor, x, y, width, height); + view->geometry.scissor_enabled = true; + weston_view_geometry_dirty(view); + weston_view_schedule_repaint(view); +} + +/** Remove the clip mask from a view + * + * \param view The view to remove the clip mask from. + * + * Removed the clip mask rectangle and schedules a repaint. + * + * \sa weston_view_set_mask + */ +WL_EXPORT void +weston_view_set_mask_infinite(struct weston_view *view) +{ + view->geometry.scissor_enabled = false; + weston_view_geometry_dirty(view); + weston_view_schedule_repaint(view); +} + +/* Check if view should be displayed + * + * The indicator is set manually when assigning + * a view to a surface. + * + * This needs reworking. See the thread starting at: + * + * https://lists.freedesktop.org/archives/wayland-devel/2016-June/029656.html + */ +WL_EXPORT bool +weston_view_is_mapped(struct weston_view *view) +{ + return view->is_mapped; +} + +/* Check if view is opaque in specified region + * + * \param view The view to check for opacity. + * \param region The region to check for opacity, in view coordinates. + * + * Returns true if the view is opaque in the specified region, because view + * alpha is 1.0 and either the opaque region set by the client contains the + * specified region, or the buffer pixel format or solid color is opaque. + */ +WL_EXPORT bool +weston_view_is_opaque(struct weston_view *ev, pixman_region32_t *region) +{ + pixman_region32_t r; + bool ret = false; + + if (ev->alpha < 1.0) + return false; + + if (ev->surface->is_opaque) + return true; + + if (ev->transform.dirty) + return false; + + pixman_region32_init(&r); + pixman_region32_subtract(&r, region, &ev->transform.opaque); + + if (!pixman_region32_not_empty(&r)) + ret = true; + + pixman_region32_fini(&r); + + return ret; +} + +/** Check if the view has a valid buffer available + * + * @param ev The view to check if it has a valid buffer. + * + * Returns true if the view has a valid buffer or false otherwise. + */ +WL_EXPORT bool +weston_view_has_valid_buffer(struct weston_view *ev) +{ + return ev->surface->buffer_ref.buffer != NULL; +} + +/** Check if the view matches the entire output + * + * @param ev The view to check. + * @param output The output to check against. + * + * Returns true if the view does indeed matches the entire output. + */ +WL_EXPORT bool +weston_view_matches_output_entirely(struct weston_view *ev, + struct weston_output *output) +{ + pixman_box32_t *extents = + pixman_region32_extents(&ev->transform.boundingbox); + + if (extents->x1 != output->x || + extents->y1 != output->y || + extents->x2 != output->x + output->width || + extents->y2 != output->y + output->height) + return false; + + return true; +} + +/* Check if a surface has a view assigned to it + * + * The indicator is set manually when mapping + * a surface and creating a view for it. + * + * This needs to go. See the thread starting at: + * + * https://lists.freedesktop.org/archives/wayland-devel/2016-June/029656.html + * + */ +WL_EXPORT bool +weston_surface_is_mapped(struct weston_surface *surface) +{ + return surface->is_mapped; +} + +static void +surface_set_size(struct weston_surface *surface, int32_t width, int32_t height) +{ + struct weston_view *view; + + if (surface->width == width && surface->height == height) + return; + + surface->width = width; + surface->height = height; + + wl_list_for_each(view, &surface->views, surface_link) + weston_view_geometry_dirty(view); +} + +WL_EXPORT void +weston_surface_set_size(struct weston_surface *surface, + int32_t width, int32_t height) +{ + assert(!surface->resource); + surface_set_size(surface, width, height); +} + +static int +fixed_round_up_to_int(wl_fixed_t f) +{ + return wl_fixed_to_int(wl_fixed_from_int(1) - 1 + f); +} + +static void +convert_size_by_transform_scale(int32_t *width_out, int32_t *height_out, + int32_t width, int32_t height, + uint32_t transform, + int32_t scale) +{ + assert(scale > 0); + + switch (transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + *width_out = width / scale; + *height_out = height / scale; + break; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + *width_out = height / scale; + *height_out = width / scale; + break; + default: + assert(0 && "invalid transform"); + } +} + +static void +weston_surface_calculate_size_from_buffer(struct weston_surface *surface) +{ + struct weston_buffer_viewport *vp = &surface->buffer_viewport; + + if (!surface->buffer_ref.buffer) { + surface->width_from_buffer = 0; + surface->height_from_buffer = 0; + return; + } + + convert_size_by_transform_scale(&surface->width_from_buffer, + &surface->height_from_buffer, + surface->buffer_ref.buffer->width, + surface->buffer_ref.buffer->height, + vp->buffer.transform, + vp->buffer.scale); +} + +static void +weston_surface_update_size(struct weston_surface *surface) +{ + struct weston_buffer_viewport *vp = &surface->buffer_viewport; + int32_t width, height; + + width = surface->width_from_buffer; + height = surface->height_from_buffer; + + if (width != 0 && vp->surface.width != -1) { + surface_set_size(surface, + vp->surface.width, vp->surface.height); + return; + } + + if (width != 0 && vp->buffer.src_width != wl_fixed_from_int(-1)) { + int32_t w = fixed_round_up_to_int(vp->buffer.src_width); + int32_t h = fixed_round_up_to_int(vp->buffer.src_height); + + surface_set_size(surface, w ?: 1, h ?: 1); + return; + } + + surface_set_size(surface, width, height); +} + +/** weston_compositor_get_time + * \ingroup compositor + */ +WL_EXPORT void +weston_compositor_get_time(struct timespec *time) +{ + clock_gettime(CLOCK_REALTIME, time); +} + +/** weston_compositor_pick_view + * \ingroup compositor + */ +WL_EXPORT struct weston_view * +weston_compositor_pick_view(struct weston_compositor *compositor, + wl_fixed_t x, wl_fixed_t y, + wl_fixed_t *vx, wl_fixed_t *vy) +{ + struct weston_view *view; + wl_fixed_t view_x, view_y; + int view_ix, view_iy; + int ix = wl_fixed_to_int(x); + int iy = wl_fixed_to_int(y); + + wl_list_for_each(view, &compositor->view_list, link) { + if (!pixman_region32_contains_point( + &view->transform.boundingbox, ix, iy, NULL)) + continue; + + weston_view_from_global_fixed(view, x, y, &view_x, &view_y); + view_ix = wl_fixed_to_int(view_x); + view_iy = wl_fixed_to_int(view_y); + + if (!pixman_region32_contains_point(&view->surface->input, + view_ix, view_iy, NULL)) + continue; + + if (view->geometry.scissor_enabled && + !pixman_region32_contains_point(&view->geometry.scissor, + view_ix, view_iy, NULL)) + continue; + + *vx = view_x; + *vy = view_y; + return view; + } + + *vx = wl_fixed_from_int(-1000000); + *vy = wl_fixed_from_int(-1000000); + return NULL; +} + +static void +weston_compositor_repick(struct weston_compositor *compositor) +{ + struct weston_seat *seat; + + if (!compositor->session_active) + return; + + wl_list_for_each(seat, &compositor->seat_list, link) + weston_seat_repick(seat); +} + +WL_EXPORT void +weston_view_unmap(struct weston_view *view) +{ + struct weston_seat *seat; + + if (!weston_view_is_mapped(view)) + return; + + weston_view_damage_below(view); + weston_view_set_output(view, NULL); + view->plane = NULL; + view->is_mapped = false; + weston_layer_entry_remove(&view->layer_link); + wl_list_remove(&view->link); + wl_list_init(&view->link); + view->output_mask = 0; + weston_surface_assign_output(view->surface); + + if (weston_surface_is_mapped(view->surface)) + return; + + wl_list_for_each(seat, &view->surface->compositor->seat_list, link) { + struct weston_touch *touch = weston_seat_get_touch(seat); + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(seat); + + if (keyboard && keyboard->focus == view->surface) + weston_keyboard_set_focus(keyboard, NULL); + if (pointer && pointer->focus == view) + weston_pointer_clear_focus(pointer); + if (touch && touch->focus == view) + weston_touch_set_focus(touch, NULL); + } +} + +WL_EXPORT void +weston_surface_unmap(struct weston_surface *surface) +{ + struct weston_view *view; + + surface->is_mapped = false; + wl_list_for_each(view, &surface->views, surface_link) + weston_view_unmap(view); + surface->output = NULL; +} + +static void +weston_surface_reset_pending_buffer(struct weston_surface *surface) +{ + weston_surface_state_set_buffer(&surface->pending, NULL); + surface->pending.sx = 0; + surface->pending.sy = 0; + surface->pending.newly_attached = 0; + surface->pending.buffer_viewport.changed = 0; +} + +WL_EXPORT void +weston_view_destroy(struct weston_view *view) +{ + wl_signal_emit(&view->destroy_signal, view); + + assert(wl_list_empty(&view->geometry.child_list)); + + if (weston_view_is_mapped(view)) { + weston_view_unmap(view); + weston_compositor_build_view_list(view->surface->compositor); + } + + wl_list_remove(&view->link); + weston_layer_entry_remove(&view->layer_link); + + pixman_region32_fini(&view->clip); + pixman_region32_fini(&view->geometry.scissor); + pixman_region32_fini(&view->transform.boundingbox); + pixman_region32_fini(&view->transform.opaque); + + weston_view_set_transform_parent(view, NULL); + weston_view_set_output(view, NULL); + + wl_list_remove(&view->surface_link); + + free(view); +} + +WL_EXPORT void +weston_surface_destroy(struct weston_surface *surface) +{ + struct weston_frame_callback *cb, *next; + struct weston_view *ev, *nv; + struct weston_pointer_constraint *constraint, *next_constraint; + + if (--surface->ref_count > 0) + return; + + assert(surface->resource == NULL); + + wl_signal_emit(&surface->destroy_signal, surface); + + assert(wl_list_empty(&surface->subsurface_list_pending)); + assert(wl_list_empty(&surface->subsurface_list)); + + wl_list_for_each_safe(ev, nv, &surface->views, surface_link) + weston_view_destroy(ev); + + weston_surface_state_fini(&surface->pending); + + weston_buffer_reference(&surface->buffer_ref, NULL); + weston_buffer_release_reference(&surface->buffer_release_ref, NULL); + + pixman_region32_fini(&surface->damage); + pixman_region32_fini(&surface->opaque); + pixman_region32_fini(&surface->input); + + wl_list_for_each_safe(cb, next, &surface->frame_callback_list, link) + wl_resource_destroy(cb->resource); + + weston_presentation_feedback_discard_list(&surface->feedback_list); + + wl_list_for_each_safe(constraint, next_constraint, + &surface->pointer_constraints, + link) + weston_pointer_constraint_destroy(constraint); + + fd_clear(&surface->acquire_fence_fd); + + free(surface); +} + +static void +destroy_surface(struct wl_resource *resource) +{ + struct weston_surface *surface = wl_resource_get_user_data(resource); + + assert(surface); + + /* Set the resource to NULL, since we don't want to leave a + * dangling pointer if the surface was refcounted and survives + * the weston_surface_destroy() call. */ + surface->resource = NULL; + + if (surface->viewport_resource) + wl_resource_set_user_data(surface->viewport_resource, NULL); + + if (surface->synchronization_resource) { + wl_resource_set_user_data(surface->synchronization_resource, + NULL); + } + + 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); +} + +WL_EXPORT 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_buffer_send_release(ref->buffer->resource); + } + 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_buffer_release_reference_handle_destroy(struct wl_listener *listener, + void *data) +{ + struct weston_buffer_release_reference *ref = + container_of(listener, struct weston_buffer_release_reference, + destroy_listener); + + assert((struct wl_resource *)data == ref->buffer_release->resource); + ref->buffer_release = NULL; +} + +static void +weston_buffer_release_destroy(struct weston_buffer_release *buffer_release) +{ + struct wl_resource *resource = buffer_release->resource; + int release_fence_fd = buffer_release->fence_fd; + + if (release_fence_fd >= 0) { + zwp_linux_buffer_release_v1_send_fenced_release( + resource, release_fence_fd); + } else { + zwp_linux_buffer_release_v1_send_immediate_release( + resource); + } + + wl_resource_destroy(resource); +} + +WL_EXPORT void +weston_buffer_release_reference(struct weston_buffer_release_reference *ref, + struct weston_buffer_release *buffer_release) +{ + if (buffer_release == ref->buffer_release) + return; + + if (ref->buffer_release) { + ref->buffer_release->ref_count--; + wl_list_remove(&ref->destroy_listener.link); + if (ref->buffer_release->ref_count == 0) + weston_buffer_release_destroy(ref->buffer_release); + } + + if (buffer_release) { + buffer_release->ref_count++; + wl_resource_add_destroy_listener(buffer_release->resource, + &ref->destroy_listener); + } + + ref->buffer_release = buffer_release; + ref->destroy_listener.notify = + weston_buffer_release_reference_handle_destroy; +} + +WL_EXPORT void +weston_buffer_release_move(struct weston_buffer_release_reference *dest, + struct weston_buffer_release_reference *src) +{ + weston_buffer_release_reference(dest, src->buffer_release); + weston_buffer_release_reference(src, NULL); +} + +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); + + weston_surface_calculate_size_from_buffer(surface); + weston_presentation_feedback_discard_list(&surface->feedback_list); +} + +/** weston_compositor_damage_all + * \ingroup compositor + */ +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); +} + +/** + * \ingroup 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_flush_damage(struct weston_surface *surface) +{ + if (surface->buffer_ref.buffer && + wl_shm_buffer_get(surface->buffer_ref.buffer->resource)) + surface->compositor->renderer->flush_damage(surface); + +// OHOS remove timeline +// if (pixman_region32_not_empty(&surface->damage)) +// TL_POINT(surface->compositor, "core_flush_damage", TLP_SURFACE(surface), +// TLP_OUTPUT(surface->output), TLP_END); + + pixman_region32_clear(&surface->damage); +} + +static void +view_accumulate_damage(struct weston_view *view, + pixman_region32_t *opaque) +{ + pixman_region32_t damage; + + pixman_region32_init(&damage); + if (view->transform.enabled) { + pixman_box32_t *extents; + + extents = pixman_region32_extents(&view->surface->damage); + view_compute_bbox(view, extents, &damage); + } else { + pixman_region32_copy(&damage, &view->surface->damage); + pixman_region32_translate(&damage, + view->geometry.x, view->geometry.y); + } + + pixman_region32_intersect(&damage, &damage, + &view->transform.boundingbox); + pixman_region32_subtract(&damage, &damage, opaque); + pixman_region32_union(&view->plane->damage, + &view->plane->damage, &damage); + pixman_region32_fini(&damage); + pixman_region32_copy(&view->clip, opaque); + pixman_region32_union(opaque, opaque, &view->transform.opaque); +} + +static void +output_accumulate_damage(struct weston_output *output) +{ + struct weston_compositor *ec = output->compositor; + struct weston_plane *plane; + struct weston_view *ev; + 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(ev, &ec->view_list, link) { + if (ev->plane != plane) + continue; + + view_accumulate_damage(ev, &opaque); + } + + pixman_region32_union(&clip, &clip, &opaque); + pixman_region32_fini(&opaque); + } + + pixman_region32_fini(&clip); + + wl_list_for_each(ev, &ec->view_list, link) + ev->surface->touched = false; + + wl_list_for_each(ev, &ec->view_list, link) { + /* Ignore views not visible on the current output */ + if (!(ev->output_mask & (1u << output->id))) + continue; + if (ev->surface->touched) + continue; + ev->surface->touched = true; + + surface_flush_damage(ev->surface); + + /* 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 (!ev->surface->keep_buffer) { + weston_buffer_reference(&ev->surface->buffer_ref, NULL); + weston_buffer_release_reference( + &ev->surface->buffer_release_ref, NULL); + } + } +} + +static void +surface_stash_subsurface_views(struct weston_surface *surface) +{ + struct weston_subsurface *sub; + + wl_list_for_each(sub, &surface->subsurface_list, parent_link) { + if (sub->surface == surface) + continue; + + wl_list_insert_list(&sub->unused_views, &sub->surface->views); + wl_list_init(&sub->surface->views); + + surface_stash_subsurface_views(sub->surface); + } +} + +static void +surface_free_unused_subsurface_views(struct weston_surface *surface) +{ + struct weston_subsurface *sub; + struct weston_view *view, *nv; + + wl_list_for_each(sub, &surface->subsurface_list, parent_link) { + if (sub->surface == surface) + continue; + + wl_list_for_each_safe(view, nv, &sub->unused_views, surface_link) { + weston_view_unmap (view); + weston_view_destroy(view); + } + + surface_free_unused_subsurface_views(sub->surface); + } +} + +static void +view_list_add_subsurface_view(struct weston_compositor *compositor, + struct weston_subsurface *sub, + struct weston_view *parent) +{ + struct weston_subsurface *child; + struct weston_view *view = NULL, *iv; + + if (!weston_surface_is_mapped(sub->surface)) + return; + + wl_list_for_each(iv, &sub->unused_views, surface_link) { + if (iv->geometry.parent == parent) { + view = iv; + break; + } + } + + if (view) { + /* Put it back in the surface's list of views */ + wl_list_remove(&view->surface_link); + wl_list_insert(&sub->surface->views, &view->surface_link); + } else { + view = weston_view_create(sub->surface); + weston_view_set_position(view, + sub->position.x, + sub->position.y); + weston_view_set_transform_parent(view, parent); + } + + view->parent_view = parent; + weston_view_update_transform(view); + view->is_mapped = true; + + if (wl_list_empty(&sub->surface->subsurface_list)) { + wl_list_insert(compositor->view_list.prev, &view->link); + return; + } + + wl_list_for_each(child, &sub->surface->subsurface_list, parent_link) { + if (child->surface == sub->surface) + wl_list_insert(compositor->view_list.prev, &view->link); + else + view_list_add_subsurface_view(compositor, child, view); + } +} + +/* This recursively adds the sub-surfaces for a view, relying on the + * sub-surface order. Thus, if a client restacks the sub-surfaces, that + * change first happens to the sub-surface list, and then automatically + * propagates here. See weston_surface_damage_subsurfaces() for how the + * sub-surfaces receive damage when the client changes the state. + */ +static void +view_list_add(struct weston_compositor *compositor, + struct weston_view *view) +{ + struct weston_subsurface *sub; + + weston_view_update_transform(view); + + if (wl_list_empty(&view->surface->subsurface_list)) { + wl_list_insert(compositor->view_list.prev, &view->link); + return; + } + + wl_list_for_each(sub, &view->surface->subsurface_list, parent_link) { + if (sub->surface == view->surface) + wl_list_insert(compositor->view_list.prev, &view->link); + else + view_list_add_subsurface_view(compositor, sub, view); + } +} + +static void +weston_compositor_build_view_list(struct weston_compositor *compositor) +{ + struct weston_view *view, *tmp; + struct weston_layer *layer; + + wl_list_for_each(layer, &compositor->layer_list, link) + wl_list_for_each(view, &layer->view_list.link, layer_link.link) + surface_stash_subsurface_views(view->surface); + + wl_list_for_each_safe(view, tmp, &compositor->view_list, link) + wl_list_init(&view->link); + wl_list_init(&compositor->view_list); + + wl_list_for_each(layer, &compositor->layer_list, link) { + wl_list_for_each(view, &layer->view_list.link, layer_link.link) { + view_list_add(compositor, view); + } + } + + wl_list_for_each(layer, &compositor->layer_list, link) + wl_list_for_each(view, &layer->view_list.link, layer_link.link) + surface_free_unused_subsurface_views(view->surface); +} + +static void +weston_output_take_feedback_list(struct weston_output *output, + struct weston_surface *surface) +{ + struct weston_view *view; + struct weston_presentation_feedback *feedback; + uint32_t flags = 0xffffffff; + + if (wl_list_empty(&surface->feedback_list)) + return; + + /* All views must have the flag for the flag to survive. */ + wl_list_for_each(view, &surface->views, surface_link) { + /* ignore views that are not on this output at all */ + if (view->output_mask & (1u << output->id)) + flags &= view->psf_flags; + } + + wl_list_for_each(feedback, &surface->feedback_list, link) + feedback->psf_flags = flags; + + wl_list_insert_list(&output->feedback_list, &surface->feedback_list); + wl_list_init(&surface->feedback_list); +} + +static int +weston_output_repaint(struct weston_output *output, void *repaint_data) +{ + struct weston_compositor *ec = output->compositor; + struct weston_view *ev; + struct weston_animation *animation, *next; + struct weston_frame_callback *cb, *cnext; + struct wl_list frame_callback_list; + pixman_region32_t output_damage; + int r; + uint32_t frame_time_msec; + enum weston_hdcp_protection highest_requested = WESTON_HDCP_DISABLE; + + if (output->destroying) + return 0; + +// OHOS remove timeline +// TL_POINT(ec, "core_repaint_begin", TLP_OUTPUT(output), TLP_END); + + /* Rebuild the surface list and update surface transforms up front. */ + weston_compositor_build_view_list(ec); + + /* Find the highest protection desired for an output */ + wl_list_for_each(ev, &ec->view_list, link) { + if (ev->surface->output_mask & (1u << output->id)) { + /* + * The desired_protection of the output should be the + * maximum of the desired_protection of the surfaces, + * that are displayed on that output, to avoid + * reducing the protection for existing surfaces. + */ + if (ev->surface->desired_protection > highest_requested) + highest_requested = + ev->surface->desired_protection; + } + } + + output->desired_protection = highest_requested; + + if (output->assign_planes && !output->disable_planes) { + output->assign_planes(output, repaint_data); + } else { + wl_list_for_each(ev, &ec->view_list, link) { + weston_view_move_to_plane(ev, &ec->primary_plane); + ev->psf_flags = 0; + } + } + + wl_list_init(&frame_callback_list); + wl_list_for_each(ev, &ec->view_list, link) { + /* Note: This operation is safe to do multiple times on the + * same surface. + */ + if (ev->surface->output == output) { + wl_list_insert_list(&frame_callback_list, + &ev->surface->frame_callback_list); + wl_list_init(&ev->surface->frame_callback_list); + + weston_output_take_feedback_list(output, ev->surface); + } + } + + output_accumulate_damage(output); + + 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, repaint_data); + + pixman_region32_fini(&output_damage); + + output->repaint_needed = false; + if (r == 0) + output->repaint_status = REPAINT_AWAITING_COMPLETION; + + weston_compositor_repick(ec); + + frame_time_msec = timespec_to_msec(&output->frame_time); + + wl_list_for_each_safe(cb, cnext, &frame_callback_list, link) { + wl_callback_send_done(cb->resource, frame_time_msec); + wl_resource_destroy(cb->resource); + } + + wl_list_for_each_safe(animation, next, &output->animation_list, link) { + animation->frame_counter++; + animation->frame(animation, output, &output->frame_time); + } + +// OHOS remove timeline +// TL_POINT(ec, "core_repaint_posted", TLP_OUTPUT(output), TLP_END); + + return r; +} + +static void +weston_output_schedule_repaint_reset(struct weston_output *output) +{ + output->repaint_status = REPAINT_NOT_SCHEDULED; +// OHOS remove timeline +// TL_POINT(output->compositor, "core_repaint_exit_loop", +// TLP_OUTPUT(output), TLP_END); +} + +static int +weston_output_maybe_repaint(struct weston_output *output, struct timespec *now, + void *repaint_data) +{ + struct weston_compositor *compositor = output->compositor; + int ret = 0; + int64_t msec_to_repaint; + + /* We're not ready yet; come back to make a decision later. */ + if (output->repaint_status != REPAINT_SCHEDULED) + return ret; + + msec_to_repaint = timespec_sub_to_msec(&output->next_repaint, now); + if (msec_to_repaint > 1) + return ret; + + /* If we're sleeping, drop the repaint machinery entirely; we will + * explicitly repaint all outputs when we come back. */ + if (compositor->state == WESTON_COMPOSITOR_SLEEPING || + compositor->state == WESTON_COMPOSITOR_OFFSCREEN) + goto err; + + /* We don't actually need to repaint this output; drop it from + * repaint until something causes damage. */ + if (!output->repaint_needed) + goto err; + + /* If repaint fails, we aren't going to get weston_output_finish_frame + * to trigger a new repaint, so drop it from repaint and hope + * something schedules a successful repaint later. As repainting may + * take some time, re-read our clock as a courtesy to the next + * output. */ + ret = weston_output_repaint(output, repaint_data); + weston_compositor_read_presentation_clock(compositor, now); + if (ret != 0) + goto err; + + output->repainted = true; + return ret; + +err: + weston_output_schedule_repaint_reset(output); + return ret; +} + +static void +output_repaint_timer_arm(struct weston_compositor *compositor) +{ + struct weston_output *output; + bool any_should_repaint = false; + struct timespec now; + int64_t msec_to_next = INT64_MAX; + + weston_compositor_read_presentation_clock(compositor, &now); + + wl_list_for_each(output, &compositor->output_list, link) { + int64_t msec_to_this; + + if (output->repaint_status != REPAINT_SCHEDULED) + continue; + + msec_to_this = timespec_sub_to_msec(&output->next_repaint, + &now); + if (!any_should_repaint || msec_to_this < msec_to_next) + msec_to_next = msec_to_this; + + any_should_repaint = true; + } + + if (!any_should_repaint) + return; + + /* Even if we should repaint immediately, add the minimum 1 ms delay. + * This is a workaround to allow coalescing multiple output repaints + * particularly from weston_output_finish_frame() + * into the same call, which would not happen if we called + * output_repaint_timer_handler() directly. + */ + if (msec_to_next < 1) + msec_to_next = 1; + + wl_event_source_timer_update(compositor->repaint_timer, msec_to_next); +} + +static int +output_repaint_timer_handler(void *data) +{ + struct weston_compositor *compositor = data; + struct weston_output *output; + struct timespec now; + void *repaint_data = NULL; + int ret = 0; + + weston_compositor_read_presentation_clock(compositor, &now); + + if (compositor->backend->repaint_begin) + repaint_data = compositor->backend->repaint_begin(compositor); + + wl_list_for_each(output, &compositor->output_list, link) { + ret = weston_output_maybe_repaint(output, &now, repaint_data); + if (ret) + break; + } + + if (ret == 0) { + if (compositor->backend->repaint_flush) + ret = compositor->backend->repaint_flush(compositor, + repaint_data); + } else { + if (compositor->backend->repaint_cancel) + compositor->backend->repaint_cancel(compositor, + repaint_data); + } + + if (ret != 0) { + wl_list_for_each(output, &compositor->output_list, link) { + if (output->repainted) + weston_output_schedule_repaint_reset(output); + } + } + + wl_list_for_each(output, &compositor->output_list, link) + output->repainted = false; + + output_repaint_timer_arm(compositor); + + return 0; +} + +/** Convert a presentation timestamp to another clock domain + * + * \param compositor The compositor defines the presentation clock domain. + * \param presentation_stamp The timestamp in presentation clock domain. + * \param presentation_now Current time in presentation clock domain. + * \param target_clock Defines the target clock domain. + * + * This approximation relies on presentation_stamp to be close to current time. + * The further it is from current time and the bigger the speed difference + * between the two clock domains, the bigger the conversion error. + * + * Conversion error due to system load is biased and unbounded. + */ +static struct timespec +convert_presentation_time_now(struct weston_compositor *compositor, + const struct timespec *presentation_stamp, + const struct timespec *presentation_now, + clockid_t target_clock) +{ + struct timespec target_now = {}; + struct timespec target_stamp; + int64_t delta_ns; + + if (compositor->presentation_clock == target_clock) + return *presentation_stamp; + + clock_gettime(target_clock, &target_now); + delta_ns = timespec_sub_to_nsec(presentation_stamp, presentation_now); + timespec_add_nsec(&target_stamp, &target_now, delta_ns); + + return target_stamp; +} + +/** + * \ingroup output + */ +WL_EXPORT void +weston_output_finish_frame(struct weston_output *output, + const struct timespec *stamp, + uint32_t presented_flags) +{ + struct weston_compositor *compositor = output->compositor; + int32_t refresh_nsec; + struct timespec now; + struct timespec vblank_monotonic; + int64_t msec_rel; + + assert(output->repaint_status == REPAINT_AWAITING_COMPLETION); + assert(stamp || (presented_flags & WP_PRESENTATION_FEEDBACK_INVALID)); + + weston_compositor_read_presentation_clock(compositor, &now); + + /* If we haven't been supplied any timestamp at all, we don't have a + * timebase to work against, so any delay just wastes time. Push a + * repaint as soon as possible so we can get on with it. */ + if (!stamp) { + output->next_repaint = now; + goto out; + } + + vblank_monotonic = convert_presentation_time_now(compositor, + stamp, &now, + CLOCK_MONOTONIC); +// OHOS remove timeline +// TL_POINT(compositor, "core_repaint_finished", TLP_OUTPUT(output), +// TLP_VBLANK(&vblank_monotonic), TLP_END); + + refresh_nsec = millihz_to_nsec(output->current_mode->refresh); + weston_presentation_feedback_present_list(&output->feedback_list, + output, refresh_nsec, stamp, + output->msc, + presented_flags); + + output->frame_time = *stamp; + + timespec_add_nsec(&output->next_repaint, stamp, refresh_nsec); + timespec_add_msec(&output->next_repaint, &output->next_repaint, + -compositor->repaint_msec); + msec_rel = timespec_sub_to_msec(&output->next_repaint, &now); + + if (msec_rel < -1000 || msec_rel > 1000) { + static bool warned; + + if (!warned) + weston_log("Warning: computed repaint delay is " + "insane: %lld msec\n", (long long) msec_rel); + warned = true; + + output->next_repaint = now; + } + + /* Called from restart_repaint_loop and restart happens already after + * the deadline given by repaint_msec? In that case we delay until + * the deadline of the next frame, to give clients a more predictable + * timing of the repaint cycle to lock on. */ + if (presented_flags == WP_PRESENTATION_FEEDBACK_INVALID && + msec_rel < 0) { + while (timespec_sub_to_nsec(&output->next_repaint, &now) < 0) { + timespec_add_nsec(&output->next_repaint, + &output->next_repaint, + refresh_nsec); + } + } + +out: + output->repaint_status = REPAINT_SCHEDULED; + output_repaint_timer_arm(compositor); +} + +static void +idle_repaint(void *data) +{ + struct weston_output *output = data; + int ret; + + assert(output->repaint_status == REPAINT_BEGIN_FROM_IDLE); + output->repaint_status = REPAINT_AWAITING_COMPLETION; + output->idle_repaint_source = NULL; + ret = output->start_repaint_loop(output); + if (ret != 0) + weston_output_schedule_repaint_reset(output); +} + +WL_EXPORT void +weston_layer_entry_insert(struct weston_layer_entry *list, + struct weston_layer_entry *entry) +{ + wl_list_insert(&list->link, &entry->link); + entry->layer = list->layer; +} + +WL_EXPORT void +weston_layer_entry_remove(struct weston_layer_entry *entry) +{ + wl_list_remove(&entry->link); + wl_list_init(&entry->link); + entry->layer = NULL; +} + + +/** Initialize the weston_layer struct. + * + * \param compositor The compositor instance + * \param layer The layer to initialize + */ +WL_EXPORT void +weston_layer_init(struct weston_layer *layer, + struct weston_compositor *compositor) +{ + layer->compositor = compositor; + wl_list_init(&layer->link); + wl_list_init(&layer->view_list.link); + layer->view_list.layer = layer; + weston_layer_set_mask_infinite(layer); +} + +/** Sets the position of the layer in the layer list. The layer will be placed + * below any layer with the same position value, if any. + * This function is safe to call if the layer is already on the list, but the + * layer may be moved below other layers at the same position, if any. + * + * \param layer The layer to modify + * \param position The position the layer will be placed at + */ +WL_EXPORT void +weston_layer_set_position(struct weston_layer *layer, + enum weston_layer_position position) +{ + struct weston_layer *below; + + wl_list_remove(&layer->link); + + /* layer_list is ordered from top to bottom, the last layer being the + * background with the smallest position value */ + + layer->position = position; + wl_list_for_each_reverse(below, &layer->compositor->layer_list, link) { + if (below->position >= layer->position) { + wl_list_insert(&below->link, &layer->link); + return; + } + } + wl_list_insert(&layer->compositor->layer_list, &layer->link); +} + +/** Hide a layer by taking it off the layer list. + * This function is safe to call if the layer is not on the list. + * + * \param layer The layer to hide + */ +WL_EXPORT void +weston_layer_unset_position(struct weston_layer *layer) +{ + wl_list_remove(&layer->link); + wl_list_init(&layer->link); +} + +WL_EXPORT void +weston_layer_set_mask(struct weston_layer *layer, + int x, int y, int width, int height) +{ + struct weston_view *view; + + layer->mask.x1 = x; + layer->mask.x2 = x + width; + layer->mask.y1 = y; + layer->mask.y2 = y + height; + + wl_list_for_each(view, &layer->view_list.link, layer_link.link) { + weston_view_geometry_dirty(view); + } +} + +WL_EXPORT void +weston_layer_set_mask_infinite(struct weston_layer *layer) +{ + struct weston_view *view; + + layer->mask.x1 = INT32_MIN; + layer->mask.x2 = INT32_MAX; + layer->mask.y1 = INT32_MIN; + layer->mask.y2 = INT32_MAX; + + wl_list_for_each(view, &layer->view_list.link, layer_link.link) { + weston_view_geometry_dirty(view); + } +} + +WL_EXPORT bool +weston_layer_mask_is_infinite(struct weston_layer *layer) +{ + return layer->mask.x1 == INT32_MIN && + layer->mask.y1 == INT32_MIN && + layer->mask.x2 == INT32_MAX && + layer->mask.y2 == INT32_MAX; +} + +/** + * \ingroup output + */ +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; + +// OHOS remove timeline +// if (!output->repaint_needed) +// TL_POINT(compositor, "core_repaint_req", TLP_OUTPUT(output), TLP_END); + + loop = wl_display_get_event_loop(compositor->wl_display); + output->repaint_needed = true; + + /* If we already have a repaint scheduled for our idle handler, + * no need to set it again. If the repaint has been called but + * not finished, then weston_output_finish_frame() will notice + * that a repaint is needed and schedule one. */ + if (output->repaint_status != REPAINT_NOT_SCHEDULED) + return; + + output->repaint_status = REPAINT_BEGIN_FROM_IDLE; + assert(!output->idle_repaint_source); + output->idle_repaint_source = wl_event_loop_add_idle(loop, idle_repaint, + output); +// OHOS remove timeline +// TL_POINT(compositor, "core_repaint_enter_loop", TLP_OUTPUT(output), TLP_END); +} + +/** weston_compositor_schedule_repaint + * \ingroup compositor + */ +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. */ + weston_surface_state_set_buffer(&surface->pending, buffer); + + surface->pending.sx = sx; + surface->pending.sy = sy; + surface->pending.newly_attached = 1; +} + +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); + + if (width <= 0 || height <= 0) + return; + + pixman_region32_union_rect(&surface->pending.damage_surface, + &surface->pending.damage_surface, + x, y, width, height); +} + +static void +surface_damage_buffer(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); + + if (width <= 0 || height <= 0) + return; + + pixman_region32_union_rect(&surface->pending.damage_buffer, + &surface->pending.damage_buffer, + x, y, width, height); +} + +// OHOS surface type +static void +surface_set_surface_type(struct wl_client *client, + struct wl_resource *resource, int32_t type) +{ + struct weston_surface *surface = wl_resource_get_user_data(resource); + surface->type = type; +} + +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 { + pixman_region32_clear(&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); + } +} + +/* Cause damage to this sub-surface and all its children. + * + * This is useful when there are state changes that need an implicit + * damage, e.g. a z-order change. + */ +static void +weston_surface_damage_subsurfaces(struct weston_subsurface *sub) +{ + struct weston_subsurface *child; + + weston_surface_damage(sub->surface); + sub->reordered = false; + + wl_list_for_each(child, &sub->surface->subsurface_list, parent_link) + if (child != sub) + weston_surface_damage_subsurfaces(child); +} + +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); + + if (sub->reordered) + weston_surface_damage_subsurfaces(sub); + } +} + +static void +weston_surface_build_buffer_matrix(const struct weston_surface *surface, + struct weston_matrix *matrix) +{ + const struct weston_buffer_viewport *vp = &surface->buffer_viewport; + double src_width, src_height, dest_width, dest_height; + + weston_matrix_init(matrix); + + if (vp->buffer.src_width == wl_fixed_from_int(-1)) { + src_width = surface->width_from_buffer; + src_height = surface->height_from_buffer; + } else { + src_width = wl_fixed_to_double(vp->buffer.src_width); + src_height = wl_fixed_to_double(vp->buffer.src_height); + } + + if (vp->surface.width == -1) { + dest_width = src_width; + dest_height = src_height; + } else { + dest_width = vp->surface.width; + dest_height = vp->surface.height; + } + + if (src_width != dest_width || src_height != dest_height) + weston_matrix_scale(matrix, + src_width / dest_width, + src_height / dest_height, 1); + + if (vp->buffer.src_width != wl_fixed_from_int(-1)) + weston_matrix_translate(matrix, + wl_fixed_to_double(vp->buffer.src_x), + wl_fixed_to_double(vp->buffer.src_y), + 0); + + switch (vp->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: + weston_matrix_scale(matrix, -1, 1, 1); + weston_matrix_translate(matrix, + surface->width_from_buffer, 0, 0); + break; + } + + switch (vp->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: + weston_matrix_rotate_xy(matrix, 0, -1); + weston_matrix_translate(matrix, + 0, surface->width_from_buffer, 0); + break; + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + weston_matrix_rotate_xy(matrix, -1, 0); + weston_matrix_translate(matrix, + surface->width_from_buffer, + surface->height_from_buffer, 0); + break; + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + weston_matrix_rotate_xy(matrix, 0, 1); + weston_matrix_translate(matrix, + surface->height_from_buffer, 0, 0); + break; + } + + weston_matrix_scale(matrix, vp->buffer.scale, vp->buffer.scale, 1); +} + +/** + * Compute a + b > c while being safe to overflows. + */ +static bool +fixed_sum_gt(wl_fixed_t a, wl_fixed_t b, wl_fixed_t c) +{ + return (int64_t)a + (int64_t)b > (int64_t)c; +} + +static bool +weston_surface_is_pending_viewport_source_valid( + const struct weston_surface *surface) +{ + const struct weston_surface_state *pend = &surface->pending; + const struct weston_buffer_viewport *vp = &pend->buffer_viewport; + int width_from_buffer = 0; + int height_from_buffer = 0; + wl_fixed_t w; + wl_fixed_t h; + + /* If viewport source rect is not set, it is always ok. */ + if (vp->buffer.src_width == wl_fixed_from_int(-1)) + return true; + + if (pend->newly_attached) { + if (pend->buffer) { + convert_size_by_transform_scale(&width_from_buffer, + &height_from_buffer, + pend->buffer->width, + pend->buffer->height, + vp->buffer.transform, + vp->buffer.scale); + } else { + /* No buffer: viewport is irrelevant. */ + return true; + } + } else { + width_from_buffer = surface->width_from_buffer; + height_from_buffer = surface->height_from_buffer; + } + + assert((width_from_buffer == 0) == (height_from_buffer == 0)); + assert(width_from_buffer >= 0 && height_from_buffer >= 0); + + /* No buffer: viewport is irrelevant. */ + if (width_from_buffer == 0 || height_from_buffer == 0) + return true; + + /* overflow checks for wl_fixed_from_int() */ + if (width_from_buffer > wl_fixed_to_int(INT32_MAX)) + return false; + if (height_from_buffer > wl_fixed_to_int(INT32_MAX)) + return false; + + w = wl_fixed_from_int(width_from_buffer); + h = wl_fixed_from_int(height_from_buffer); + + if (fixed_sum_gt(vp->buffer.src_x, vp->buffer.src_width, w)) + return false; + if (fixed_sum_gt(vp->buffer.src_y, vp->buffer.src_height, h)) + return false; + + return true; +} + +static bool +fixed_is_integer(wl_fixed_t v) +{ + return (v & 0xff) == 0; +} + +static bool +weston_surface_is_pending_viewport_dst_size_int( + const struct weston_surface *surface) +{ + const struct weston_buffer_viewport *vp = + &surface->pending.buffer_viewport; + + if (vp->surface.width != -1) { + assert(vp->surface.width > 0 && vp->surface.height > 0); + return true; + } + + return fixed_is_integer(vp->buffer.src_width) && + fixed_is_integer(vp->buffer.src_height); +} + +/* Translate pending damage in buffer co-ordinates to surface + * co-ordinates and union it with a pixman_region32_t. + * This should only be called after the buffer is attached. + */ +static void +apply_damage_buffer(pixman_region32_t *dest, + struct weston_surface *surface, + struct weston_surface_state *state) +{ + struct weston_buffer *buffer = surface->buffer_ref.buffer; + + /* wl_surface.damage_buffer needs to be clipped to the buffer, + * translated into surface co-ordinates and unioned with + * any other surface damage. + * None of this makes sense if there is no buffer though. + */ + if (buffer && pixman_region32_not_empty(&state->damage_buffer)) { + pixman_region32_t buffer_damage; + + pixman_region32_intersect_rect(&state->damage_buffer, + &state->damage_buffer, + 0, 0, buffer->width, + buffer->height); + pixman_region32_init(&buffer_damage); + weston_matrix_transform_region(&buffer_damage, + &surface->buffer_to_surface_matrix, + &state->damage_buffer); + pixman_region32_union(dest, dest, &buffer_damage); + pixman_region32_fini(&buffer_damage); + } + /* We should clear this on commit even if there was no buffer */ + pixman_region32_clear(&state->damage_buffer); +} + +static void +weston_surface_set_desired_protection(struct weston_surface *surface, + enum weston_hdcp_protection protection) +{ + if (surface->desired_protection == protection) + return; + surface->desired_protection = protection; + weston_surface_damage(surface); +} + +static void +weston_surface_set_protection_mode(struct weston_surface *surface, + enum weston_surface_protection_mode p_mode) +{ + struct content_protection *cp = surface->compositor->content_protection; + struct protected_surface *psurface; + + surface->protection_mode = p_mode; + wl_list_for_each(psurface, &cp->protected_list, link) { + if (!psurface || psurface->surface != surface) + continue; + weston_protected_surface_send_event(psurface, + surface->current_protection); + } +} + +static void +weston_surface_commit_state(struct weston_surface *surface, + struct weston_surface_state *state) +{ + struct weston_view *view; + pixman_region32_t opaque; + + /* wl_surface.set_buffer_transform */ + /* wl_surface.set_buffer_scale */ + /* wp_viewport.set_source */ + /* wp_viewport.set_destination */ + surface->buffer_viewport = state->buffer_viewport; + + /* wl_surface.attach */ + if (state->newly_attached) { + /* zwp_surface_synchronization_v1.set_acquire_fence */ + fd_move(&surface->acquire_fence_fd, + &state->acquire_fence_fd); + /* zwp_surface_synchronization_v1.get_release */ + weston_buffer_release_move(&surface->buffer_release_ref, + &state->buffer_release_ref); + weston_surface_attach(surface, state->buffer); + } + weston_surface_state_set_buffer(state, NULL); + assert(state->acquire_fence_fd == -1); + assert(state->buffer_release_ref.buffer_release == NULL); + + weston_surface_build_buffer_matrix(surface, + &surface->surface_to_buffer_matrix); + weston_matrix_invert(&surface->buffer_to_surface_matrix, + &surface->surface_to_buffer_matrix); + + if (state->newly_attached || state->buffer_viewport.changed) { + weston_surface_update_size(surface); + if (surface->committed) + surface->committed(surface, state->sx, state->sy); + } + + state->sx = 0; + state->sy = 0; + state->newly_attached = 0; + state->buffer_viewport.changed = 0; + +// OHOS remove timeline +// /* wl_surface.damage and wl_surface.damage_buffer */ +// if (pixman_region32_not_empty(&state->damage_surface) || +// pixman_region32_not_empty(&state->damage_buffer)) +// TL_POINT(surface->compositor, "core_commit_damage", TLP_SURFACE(surface), TLP_END); + + pixman_region32_union(&surface->damage, &surface->damage, + &state->damage_surface); + + apply_damage_buffer(&surface->damage, surface, state); + + pixman_region32_intersect_rect(&surface->damage, &surface->damage, + 0, 0, surface->width, surface->height); + pixman_region32_clear(&state->damage_surface); + + /* wl_surface.set_opaque_region */ + pixman_region32_init(&opaque); + pixman_region32_intersect_rect(&opaque, &state->opaque, + 0, 0, surface->width, surface->height); + + if (!pixman_region32_equal(&opaque, &surface->opaque)) { + pixman_region32_copy(&surface->opaque, &opaque); + wl_list_for_each(view, &surface->views, surface_link) + weston_view_geometry_dirty(view); + } + + pixman_region32_fini(&opaque); + + /* wl_surface.set_input_region */ + pixman_region32_intersect_rect(&surface->input, &state->input, + 0, 0, surface->width, surface->height); + + /* wl_surface.frame */ + wl_list_insert_list(&surface->frame_callback_list, + &state->frame_callback_list); + wl_list_init(&state->frame_callback_list); + + /* XXX: + * What should happen with a feedback request, if there + * is no wl_buffer attached for this commit? + */ + + /* presentation.feedback */ + wl_list_insert_list(&surface->feedback_list, + &state->feedback_list); + wl_list_init(&state->feedback_list); + + /* weston_protected_surface.enforced/relaxed */ + if (surface->protection_mode != state->protection_mode) + weston_surface_set_protection_mode(surface, + state->protection_mode); + + /* weston_protected_surface.set_type */ + weston_surface_set_desired_protection(surface, state->desired_protection); + + wl_signal_emit(&surface->commit_signal, surface); +} + +static void +weston_surface_commit(struct weston_surface *surface) +{ + weston_surface_commit_state(surface, &surface->pending); + + 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 (!weston_surface_is_pending_viewport_source_valid(surface)) { + assert(surface->viewport_resource); + + wl_resource_post_error(surface->viewport_resource, + WP_VIEWPORT_ERROR_OUT_OF_BUFFER, + "wl_surface@%d has viewport source outside buffer", + wl_resource_get_id(resource)); + return; + } + + if (!weston_surface_is_pending_viewport_dst_size_int(surface)) { + assert(surface->viewport_resource); + + wl_resource_post_error(surface->viewport_resource, + WP_VIEWPORT_ERROR_BAD_SIZE, + "wl_surface@%d viewport dst size not integer", + wl_resource_get_id(resource)); + return; + } + + if (surface->pending.acquire_fence_fd >= 0) { + assert(surface->synchronization_resource); + + if (!surface->pending.buffer) { + fd_clear(&surface->pending.acquire_fence_fd); + wl_resource_post_error(surface->synchronization_resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_BUFFER, + "wl_surface@%"PRIu32" no buffer for synchronization", + wl_resource_get_id(resource)); + return; + } + + /* We support fences for both wp_linux_dmabuf and opaque EGL + * buffers, as mandated by minor version 2 of the + * zwp_linux_explicit_synchronization_v1 protocol. Since + * renderers that support fences currently only support these + * two buffer types plus SHM buffers, we can just check for the + * SHM buffer case here. + */ + if (wl_shm_buffer_get(surface->pending.buffer->resource)) { + fd_clear(&surface->pending.acquire_fence_fd); + wl_resource_post_error(surface->synchronization_resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_UNSUPPORTED_BUFFER, + "wl_surface@%"PRIu32" unsupported buffer for synchronization", + wl_resource_get_id(resource)); + return; + } + } + + if (surface->pending.buffer_release_ref.buffer_release && + !surface->pending.buffer) { + assert(surface->synchronization_resource); + + wl_resource_post_error(surface->synchronization_resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_BUFFER, + "wl_surface@%"PRIu32" no buffer for synchronization", + wl_resource_get_id(resource)); + return; + } + + 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); + + /* if wl_output.transform grows more members this will need to be updated. */ + if (transform < 0 || + transform > WL_OUTPUT_TRANSFORM_FLIPPED_270) { + wl_resource_post_error(resource, + WL_SURFACE_ERROR_INVALID_TRANSFORM, + "buffer transform must be a valid transform " + "('%d' specified)", transform); + return; + } + + surface->pending.buffer_viewport.buffer.transform = transform; + surface->pending.buffer_viewport.changed = 1; +} + +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); + + if (scale < 1) { + wl_resource_post_error(resource, + WL_SURFACE_ERROR_INVALID_SCALE, + "buffer scale must be at least one " + "('%d' specified)", scale); + return; + } + + surface->pending.buffer_viewport.buffer.scale = scale; + surface->pending.buffer_viewport.changed = 1; +} + +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, + surface_damage_buffer, + surface_set_surface_type // OHOS surface type +}; + +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); + + wl_signal_emit(&ec->create_surface_signal, 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; + + weston_surface_commit_state(surface, &sub->cached); + weston_buffer_reference(&sub->cached_buffer_ref, NULL); + + weston_surface_commit_subsurface_order(surface); + + weston_surface_schedule_repaint(surface); + + sub->has_cached_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 + * origin. + */ + pixman_region32_translate(&sub->cached.damage_surface, + -surface->pending.sx, -surface->pending.sy); + pixman_region32_union(&sub->cached.damage_surface, + &sub->cached.damage_surface, + &surface->pending.damage_surface); + pixman_region32_clear(&surface->pending.damage_surface); + + if (surface->pending.newly_attached) { + sub->cached.newly_attached = 1; + weston_surface_state_set_buffer(&sub->cached, + surface->pending.buffer); + weston_buffer_reference(&sub->cached_buffer_ref, + surface->pending.buffer); + weston_presentation_feedback_discard_list( + &sub->cached.feedback_list); + /* zwp_surface_synchronization_v1.set_acquire_fence */ + fd_move(&sub->cached.acquire_fence_fd, + &surface->pending.acquire_fence_fd); + /* zwp_surface_synchronization_v1.get_release */ + weston_buffer_release_move(&sub->cached.buffer_release_ref, + &surface->pending.buffer_release_ref); + } + sub->cached.desired_protection = surface->pending.desired_protection; + sub->cached.protection_mode = surface->pending.protection_mode; + assert(surface->pending.acquire_fence_fd == -1); + assert(surface->pending.buffer_release_ref.buffer_release == NULL); + sub->cached.sx += surface->pending.sx; + sub->cached.sy += surface->pending.sy; + + apply_damage_buffer(&sub->cached.damage_surface, surface, &surface->pending); + + sub->cached.buffer_viewport.changed |= + surface->pending.buffer_viewport.changed; + sub->cached.buffer_viewport.buffer = + surface->pending.buffer_viewport.buffer; + sub->cached.buffer_viewport.surface = + surface->pending.buffer_viewport.surface; + + weston_surface_reset_pending_buffer(surface); + + 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); + + wl_list_insert_list(&sub->cached.feedback_list, + &surface->pending.feedback_list); + wl_list_init(&surface->pending.feedback_list); + + sub->has_cached_data = 1; +} + +static bool +weston_subsurface_is_synchronized(struct weston_subsurface *sub) +{ + while (sub) { + if (sub->synchronized) + return true; + + if (!sub->parent) + return false; + + sub = weston_surface_to_subsurface(sub->parent); + } + + return false; +} + +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->has_cached_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->has_cached_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) +{ + struct weston_view *view; + if (sub->position.set) { + wl_list_for_each(view, &sub->surface->views, surface_link) + weston_view_set_position(view, + sub->position.x, + sub->position.y); + + sub->position.set = 0; + } + + if (parent_is_synchronized || sub->synchronized) + weston_subsurface_synchronized_commit(sub); +} + +static int +subsurface_get_label(struct weston_surface *surface, char *buf, size_t len) +{ + return snprintf(buf, len, "sub-surface"); +} + +static void +subsurface_committed(struct weston_surface *surface, int32_t dx, int32_t dy) +{ + struct weston_view *view; + + wl_list_for_each(view, &surface->views, surface_link) + weston_view_set_position(view, + view->geometry.x + dx, + view->geometry.y + dy); + + /* 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)) { + surface->is_mapped = true; + + /* Cannot call weston_view_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 force the is_mapped flag on, to make + * weston_surface_is_mapped() return true, so that when the + * parent surface does get mapped, this one will get + * included, too. See view_list_add(). + */ + } +} + +static struct weston_subsurface * +weston_surface_to_subsurface(struct weston_surface *surface) +{ + if (surface->committed == subsurface_committed) + return surface->committed_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; +} + +WL_EXPORT int +weston_surface_set_role(struct weston_surface *surface, + const char *role_name, + struct wl_resource *error_resource, + uint32_t error_code) +{ + assert(role_name); + + if (surface->role_name == NULL || + surface->role_name == role_name || + strcmp(surface->role_name, role_name) == 0) { + surface->role_name = role_name; + + return 0; + } + + wl_resource_post_error(error_resource, error_code, + "Cannot assign role %s to wl_surface@%d," + " already has role %s\n", + role_name, + wl_resource_get_id(surface->resource), + surface->role_name); + return -1; +} + +WL_EXPORT const char * +weston_surface_get_role(struct weston_surface *surface) +{ + return surface->role_name; +} + +WL_EXPORT void +weston_surface_set_label_func(struct weston_surface *surface, + int (*desc)(struct weston_surface *, + char *, size_t)) +{ + surface->get_label = desc; +// OHOS remove timeline +// weston_timeline_refresh_subscription_objects(surface->compositor, +// surface); +} + +/** Get the size of surface contents + * + * \param surface The surface to query. + * \param width Returns the width of raw contents. + * \param height Returns the height of raw contents. + * + * Retrieves the raw surface content size in pixels for the given surface. + * This is the whole content size in buffer pixels. If the surface + * has no content or the renderer does not implement this feature, + * zeroes are returned. + * + * This function is used to determine the buffer size needed for + * a weston_surface_copy_content() call. + */ +WL_EXPORT void +weston_surface_get_content_size(struct weston_surface *surface, + int *width, int *height) +{ + struct weston_renderer *rer = surface->compositor->renderer; + + if (!rer->surface_get_content_size) { + *width = 0; + *height = 0; + return; + } + + rer->surface_get_content_size(surface, width, height); +} + +/** Get the bounding box of a surface and its subsurfaces + * + * \param surface The surface to query. + * \return The bounding box relative to the surface origin. + * + */ +WL_EXPORT struct weston_geometry +weston_surface_get_bounding_box(struct weston_surface *surface) +{ + pixman_region32_t region; + pixman_box32_t *box; + struct weston_subsurface *subsurface; + + pixman_region32_init_rect(®ion, + 0, 0, + surface->width, surface->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->width, + subsurface->surface->height); + + box = pixman_region32_extents(®ion); + struct weston_geometry geometry = { + .x = box->x1, + .y = box->y1, + .width = box->x2 - box->x1, + .height = box->y2 - box->y1, + }; + + pixman_region32_fini(®ion); + + return geometry; +} + +/** Copy surface contents to system memory. + * + * \param surface The surface to copy from. + * \param target Pointer to the target memory buffer. + * \param size Size of the target buffer in bytes. + * \param src_x X location on contents to copy from. + * \param src_y Y location on contents to copy from. + * \param width Width in pixels of the area to copy. + * \param height Height in pixels of the area to copy. + * \return 0 for success, -1 for failure. + * + * Surface contents are maintained by the renderer. They can be in a + * reserved weston_buffer or as a copy, e.g. a GL texture, or something + * else. + * + * Surface contents are copied into memory pointed to by target, + * which has size bytes of space available. The target memory + * may be larger than needed, but being smaller returns an error. + * The extra bytes in target may or may not be written; their content is + * unspecified. Size must be large enough to hold the image. + * + * The image in the target memory will be arranged in rows from + * top to bottom, and pixels on a row from left to right. The pixel + * format is PIXMAN_a8b8g8r8, 4 bytes per pixel, and stride is exactly + * width * 4. + * + * Parameters src_x and src_y define the upper-left corner in buffer + * coordinates (pixels) to copy from. Parameters width and height + * define the size of the area to copy in pixels. + * + * The rectangle defined by src_x, src_y, width, height must fit in + * the surface contents. Otherwise an error is returned. + * + * Use weston_surface_get_content_size to determine the content size; the + * needed target buffer size and rectangle limits. + * + * CURRENT IMPLEMENTATION RESTRICTIONS: + * - the machine must be little-endian due to Pixman formats. + * + * NOTE: Pixman formats are premultiplied. + */ +WL_EXPORT int +weston_surface_copy_content(struct weston_surface *surface, + void *target, size_t size, + int src_x, int src_y, + int width, int height) +{ + struct weston_renderer *rer = surface->compositor->renderer; + int cw, ch; + const size_t bytespp = 4; /* PIXMAN_a8b8g8r8 */ + + if (!rer->surface_copy_content) + return -1; + + weston_surface_get_content_size(surface, &cw, &ch); + + if (src_x < 0 || src_y < 0) + return -1; + + if (width <= 0 || height <= 0) + return -1; + + if (src_x + width > cw || src_y + height > ch) + return -1; + + if (width * bytespp * height > size) + return -1; + + return rer->surface_copy_content(surface, target, size, + src_x, src_y, width, height); +} + +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_find_sibling(struct weston_subsurface *sub, + struct weston_surface *surface) +{ + struct weston_surface *parent = sub->parent; + struct weston_subsurface *sibling; + + wl_list_for_each(sibling, &parent->subsurface_list, parent_link) { + if (sibling->surface == surface && sibling != sub) + return sibling; + } + + 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_find_sibling(sub, 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; + } + + assert(sibling->parent == sub->parent); + + 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); + + sub->reordered = true; +} + +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); + + sub->reordered = true; +} + +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_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); + + /* 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); + 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) +{ + struct weston_view *view, *next; + + assert(sub->surface); + + if (sub->resource) { + assert(weston_surface_to_subsurface(sub->surface) == sub); + assert(sub->parent_destroy_listener.notify == + subsurface_handle_parent_destroy); + + wl_list_for_each_safe(view, next, &sub->surface->views, surface_link) { + weston_view_unmap(view); + weston_view_destroy(view); + } + + if (sub->parent) + weston_subsurface_unlink_parent(sub); + + weston_surface_state_fini(&sub->cached); + weston_buffer_reference(&sub->cached_buffer_ref, NULL); + + sub->surface->committed = NULL; + sub->surface->committed_private = NULL; + weston_surface_set_label_func(sub->surface, 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 = zalloc(sizeof *sub); + if (sub == NULL) + return NULL; + + wl_list_init(&sub->unused_views); + + 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_surface_state_init(&sub->cached); + sub->cached_buffer_ref.buffer = NULL; + sub->synchronized = 1; + + 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 = zalloc(sizeof *sub); + if (sub == NULL) + 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 (weston_surface_set_role(surface, "wl_subsurface", resource, + WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE) < 0) + 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->committed = subsurface_committed; + surface->committed_private = sub; + weston_surface_set_label_func(surface, subsurface_get_label); +} + +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); +} + +/** Set a DPMS mode on all of the compositor's outputs + * + * \param compositor The compositor instance + * \param state The DPMS state the outputs will be set to + */ +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); +} + +/** Restores the compositor to active status + * + * \param compositor The compositor instance + * + * If the compositor was in a sleeping mode, all outputs are powered + * back on via DPMS. Otherwise if the compositor was inactive + * (idle/locked, offscreen, or sleeping) then the compositor's wake + * signal will fire. + * + * Restarts the idle timer. + * \ingroup compositor + */ +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: + case WESTON_COMPOSITOR_IDLE: + case WESTON_COMPOSITOR_OFFSCREEN: + weston_compositor_dpms(compositor, WESTON_DPMS_ON); + wl_signal_emit(&compositor->wake_signal, compositor); + /* fall through */ + default: + wl_event_source_timer_update(compositor->idle_source, + compositor->idle_time * 1000); + } +} + +/** Turns off rendering and frame events for the compositor. + * + * \param compositor The compositor instance + * + * This is used for example to prevent further rendering while the + * compositor is shutting down. + * + * Stops the idle timer. + * + * \ingroup compositor + */ +WL_EXPORT void +weston_compositor_offscreen(struct weston_compositor *compositor) +{ + switch (compositor->state) { + case WESTON_COMPOSITOR_OFFSCREEN: + return; + case WESTON_COMPOSITOR_SLEEPING: + default: + compositor->state = WESTON_COMPOSITOR_OFFSCREEN; + wl_event_source_timer_update(compositor->idle_source, 0); + } +} + +/** Powers down all attached output devices + * + * \param compositor The compositor instance + * + * Causes rendering to the outputs to cease, and no frame events to be + * sent. Only powers down the outputs if the compositor is not already + * in sleep mode. + * + * Stops the idle timer. + * + * \ingroup compositor + */ +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); +} + +/** Sets compositor to idle mode + * + * \param data The compositor instance + * + * This is called when the idle timer fires. Once the compositor is in + * idle mode it requires a wake action (e.g. via + * weston_compositor_wake()) to restore it. The compositor's + * idle_signal will be triggered when the idle event occurs. + * + * Idleness can be inhibited by setting the compositor's idle_inhibit + * property. + */ +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_view *view; + + pixman_region32_fini(&plane->damage); + pixman_region32_fini(&plane->clip); + + wl_list_for_each(view, &plane->compositor->view_list, link) { + if (view->plane == plane) + view->plane = NULL; + } + + wl_list_remove(&plane->link); +} + +/** weston_compositor_stack_plane + * \ingroup compositor + */ +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 +output_release(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct wl_output_interface output_interface = { + output_release, +}; + + +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_head *head = data; + struct weston_output *output = head->output; + struct weston_mode *mode; + struct wl_resource *resource; + + resource = wl_resource_create(client, &wl_output_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_list_insert(&head->resource_list, wl_resource_get_link(resource)); + wl_resource_set_implementation(resource, &output_interface, head, + unbind_resource); + + assert(output); + wl_output_send_geometry(resource, + output->x, + output->y, + head->mm_width, + head->mm_height, + head->subpixel, + head->make, head->model, + output->transform); + if (version >= WL_OUTPUT_SCALE_SINCE_VERSION) + 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 >= WL_OUTPUT_DONE_SINCE_VERSION) + wl_output_send_done(resource); +} + +static void +weston_head_add_global(struct weston_head *head) +{ + head->global = wl_global_create(head->compositor->wl_display, + &wl_output_interface, 3, + head, bind_output); +} + +/** Remove the global wl_output protocol object + * + * \param head The head whose global to remove. + * + * Also orphans the wl_resources for this head (wl_output). + */ +static void +weston_head_remove_global(struct weston_head *head) +{ + struct wl_resource *resource, *tmp; + + if (head->global) + wl_global_destroy(head->global); + head->global = NULL; + + wl_resource_for_each_safe(resource, tmp, &head->resource_list) { + unbind_resource(resource); + wl_resource_set_destructor(resource, NULL); + wl_resource_set_user_data(resource, NULL); + } + + wl_resource_for_each(resource, &head->xdg_output_resource_list) { + /* It's sufficient to unset the destructor, then the list elements + * won't be accessed. + */ + wl_resource_set_destructor(resource, NULL); + } + wl_list_init(&head->xdg_output_resource_list); +} + +/** Get the backing object of wl_output + * + * \param resource A wl_output protocol object. + * \return The backing object (user data) of a wl_resource representing a + * wl_output protocol object. + * + * \ingroup head + */ +WL_EXPORT struct weston_head * +weston_head_from_resource(struct wl_resource *resource) +{ + assert(wl_resource_instance_of(resource, &wl_output_interface, + &output_interface)); + + return wl_resource_get_user_data(resource); +} + +/** Initialize a pre-allocated weston_head + * + * \param head The head to initialize. + * \param name The head name, e.g. the connector name or equivalent. + * + * The head will be safe to attach, detach and release. + * + * The name is used in logs, and can be used by compositors as a configuration + * identifier. + * + * \ingroup head + * \internal + */ +WL_EXPORT void +weston_head_init(struct weston_head *head, const char *name) +{ + /* Add some (in)sane defaults which can be used + * for checking if an output was properly configured + */ + memset(head, 0, sizeof *head); + + wl_list_init(&head->compositor_link); + wl_signal_init(&head->destroy_signal); + wl_list_init(&head->output_link); + wl_list_init(&head->resource_list); + wl_list_init(&head->xdg_output_resource_list); + head->name = strdup(name); + head->current_protection = WESTON_HDCP_DISABLE; +} + +/** Send output heads changed signal + * + * \param output The output that changed. + * + * Notify that the enabled output gained and/or lost heads, or that the + * associated heads may have changed their connection status. This does not + * include cases where the output becomes enabled or disabled. The registered + * callbacks are called after the change has successfully happened. + * + * If connection status change causes the compositor to attach or detach a head + * to an enabled output, the registered callbacks may be called multiple times. + * + * \ingroup output + */ +static void +weston_output_emit_heads_changed(struct weston_output *output) +{ + wl_signal_emit(&output->compositor->output_heads_changed_signal, + output); +} + +/** Idle task for emitting heads_changed_signal */ +static void +weston_compositor_call_heads_changed(void *data) +{ + struct weston_compositor *compositor = data; + struct weston_head *head; + + compositor->heads_changed_source = NULL; + + wl_signal_emit(&compositor->heads_changed_signal, compositor); + + wl_list_for_each(head, &compositor->head_list, compositor_link) { + if (head->output && head->output->enabled) + weston_output_emit_heads_changed(head->output); + } +} + +/** Schedule a call on idle to heads_changed callback + * + * \param compositor The Compositor. + * + * \ingroup compositor + * \internal + */ +static void +weston_compositor_schedule_heads_changed(struct weston_compositor *compositor) +{ + struct wl_event_loop *loop; + + if (compositor->heads_changed_source) + return; + + loop = wl_display_get_event_loop(compositor->wl_display); + compositor->heads_changed_source = wl_event_loop_add_idle(loop, + weston_compositor_call_heads_changed, compositor); +} + +/** Register a new head + * + * \param compositor The compositor. + * \param head The head to register, must not be already registered. + * + * This signals the core that a new head has become available, leading to + * heads_changed hook being called later. + * + * \ingroup compositor + * \internal + */ +WL_EXPORT void +weston_compositor_add_head(struct weston_compositor *compositor, + struct weston_head *head) +{ + assert(wl_list_empty(&head->compositor_link)); + assert(head->name); + + wl_list_insert(compositor->head_list.prev, &head->compositor_link); + head->compositor = compositor; + weston_compositor_schedule_heads_changed(compositor); +} + +/** Adds a listener to be called when heads change + * + * \param compositor The compositor. + * \param listener The listener to add. + * + * The listener notify function argument is weston_compositor. + * + * The listener function will be called after heads are added or their + * connection status has changed. Several changes may be accumulated into a + * single call. The user is expected to iterate over the existing heads and + * check their statuses to find out what changed. + * + * \sa weston_compositor_iterate_heads, weston_head_is_connected, + * weston_head_is_enabled + * \ingroup compositor + */ +WL_EXPORT void +weston_compositor_add_heads_changed_listener(struct weston_compositor *compositor, + struct wl_listener *listener) +{ + wl_signal_add(&compositor->heads_changed_signal, listener); +} + +/** Iterate over available heads + * + * \param compositor The compositor. + * \param iter The iterator, or NULL for start. + * \return The next available head in the list. + * + * Returns all available heads, regardless of being connected or enabled. + * + * You can iterate over all heads as follows: + * \code + * struct weston_head *head = NULL; + * + * while ((head = weston_compositor_iterate_heads(compositor, head))) { + * ... + * } + * \endcode + * + * If you cause \c iter to be removed from the list, you cannot use it to + * continue iterating. Removing any other item is safe. + * + * \ingroup compositor + */ +WL_EXPORT struct weston_head * +weston_compositor_iterate_heads(struct weston_compositor *compositor, + struct weston_head *iter) +{ + struct wl_list *list = &compositor->head_list; + struct wl_list *node; + + assert(compositor); + assert(!iter || iter->compositor == compositor); + + if (iter) + node = iter->compositor_link.next; + else + node = list->next; + + assert(node); + assert(!iter || node != &iter->compositor_link); + + if (node == list) + return NULL; + + return container_of(node, struct weston_head, compositor_link); +} + +/** Iterate over attached heads + * + * \param output The output whose heads to iterate. + * \param iter The iterator, or NULL for start. + * \return The next attached head in the list. + * + * Returns all heads currently attached to the output. + * + * You can iterate over all heads as follows: + * \code + * struct weston_head *head = NULL; + * + * while ((head = weston_output_iterate_heads(output, head))) { + * ... + * } + * \endcode + * + * If you cause \c iter to be removed from the list, you cannot use it to + * continue iterating. Removing any other item is safe. + * + * \ingroup ouput + */ +WL_EXPORT struct weston_head * +weston_output_iterate_heads(struct weston_output *output, + struct weston_head *iter) +{ + struct wl_list *list = &output->head_list; + struct wl_list *node; + + assert(output); + assert(!iter || iter->output == output); + + if (iter) + node = iter->output_link.next; + else + node = list->next; + + assert(node); + assert(!iter || node != &iter->output_link); + + if (node == list) + return NULL; + + return container_of(node, struct weston_head, output_link); +} + +/** Attach a head to an output + * + * \param output The output to attach to. + * \param head The head that is not yet attached. + * \return 0 on success, -1 on failure. + * + * Attaches the given head to the output. All heads of an output are clones + * and share the resolution and timings. + * + * Cloning heads this way uses less resources than creating an output for + * each head, but is not always possible due to environment, driver and hardware + * limitations. + * + * On failure, the head remains unattached. Success of this function does not + * guarantee the output configuration is actually valid. The final checks are + * made on weston_output_enable() unless the output was already enabled. + * + * \ingroup output + */ +WL_EXPORT int +weston_output_attach_head(struct weston_output *output, + struct weston_head *head) +{ + char *head_names; + + if (!wl_list_empty(&head->output_link)) + return -1; + + if (output->attach_head) { + if (output->attach_head(output, head) < 0) + return -1; + } else if (!wl_list_empty(&output->head_list)) { + /* No support for clones in the legacy path. */ + return -1; + } + + head->output = output; + wl_list_insert(output->head_list.prev, &head->output_link); + + if (output->enabled) { + weston_head_add_global(head); + + head_names = weston_output_create_heads_string(output); + weston_log("Output '%s' updated to have head(s) %s\n", + output->name, head_names); + free(head_names); + + weston_output_emit_heads_changed(output); + } + + return 0; +} + +/** Detach a head from its output + * + * \param head The head to detach. + * + * It is safe to detach a non-attached head. + * + * If the head is attached to an enabled output and the output will be left + * with no heads, the output will be disabled. + * + * \ingroup head + * \sa weston_output_disable + */ +WL_EXPORT void +weston_head_detach(struct weston_head *head) +{ + struct weston_output *output = head->output; + char *head_names; + + wl_list_remove(&head->output_link); + wl_list_init(&head->output_link); + head->output = NULL; + + if (!output) + return; + + if (output->detach_head) + output->detach_head(output, head); + + if (output->enabled) { + weston_head_remove_global(head); + + if (wl_list_empty(&output->head_list)) { + weston_log("Output '%s' no heads left, disabling.\n", + output->name); + weston_output_disable(output); + } else { + head_names = weston_output_create_heads_string(output); + weston_log("Output '%s' updated to have head(s) %s\n", + output->name, head_names); + free(head_names); + + weston_output_emit_heads_changed(output); + } + } +} + +/** Destroy a head + * + * \param head The head to be released. + * + * Destroys the head. The caller is responsible for freeing the memory pointed + * to by \c head. + * + * \ingroup head + * \internal + */ +WL_EXPORT void +weston_head_release(struct weston_head *head) +{ + wl_signal_emit(&head->destroy_signal, head); + + weston_head_detach(head); + + free(head->make); + free(head->model); + free(head->serial_number); + free(head->name); + + wl_list_remove(&head->compositor_link); +} + +static void +weston_head_set_device_changed(struct weston_head *head) +{ + head->device_changed = true; + + if (head->compositor) + weston_compositor_schedule_heads_changed(head->compositor); +} + +/** String equal comparison with NULLs being equal */ +static bool +str_null_eq(const char *a, const char *b) +{ + if (!a && !b) + return true; + + if (!!a != !!b) + return false; + + return strcmp(a, b) == 0; +} + +/** Store monitor make, model and serial number + * + * \param head The head to modify. + * \param make The monitor make. If EDID is available, the PNP ID. Otherwise + * any string, or NULL for none. + * \param model The monitor model or name, or a made-up string, or NULL for + * none. + * \param serialno The monitor serial number, a made-up string, or NULL for + * none. + * + * This may set the device_changed flag. + * + * \ingroup head + * \internal + */ +WL_EXPORT void +weston_head_set_monitor_strings(struct weston_head *head, + const char *make, + const char *model, + const char *serialno) +{ + if (str_null_eq(head->make, make) && + str_null_eq(head->model, model) && + str_null_eq(head->serial_number, serialno)) + return; + + free(head->make); + free(head->model); + free(head->serial_number); + + head->make = make ? strdup(make) : NULL; + head->model = model ? strdup(model) : NULL; + head->serial_number = serialno ? strdup(serialno) : NULL; + + weston_head_set_device_changed(head); +} + +/** Store display non-desktop status + * + * \param head The head to modify. + * \param non_desktop Whether the head connects to a non-desktop display. + * + * \ingroup head + * \internal + */ +WL_EXPORT void +weston_head_set_non_desktop(struct weston_head *head, bool non_desktop) +{ + if (head->non_desktop == non_desktop) + return; + + head->non_desktop = non_desktop; + + weston_head_set_device_changed(head); +} + +/** Store display transformation + * + * \param head The head to modify. + * \param transform The transformation to apply for this head + * + * This may set the device_changed flag. + * + * \ingroup head + * \internal + */ +WL_EXPORT void +weston_head_set_transform(struct weston_head *head, uint32_t transform) +{ + if (head->transform == transform) + return; + + head->transform = transform; + + weston_head_set_device_changed(head); +} + + +/** Store physical image size + * + * \param head The head to modify. + * \param mm_width Image area width in millimeters. + * \param mm_height Image area height in millimeters. + * + * This may set the device_changed flag. + * + * \ingroup head + * \internal + */ +WL_EXPORT void +weston_head_set_physical_size(struct weston_head *head, + int32_t mm_width, int32_t mm_height) +{ + if (head->mm_width == mm_width && + head->mm_height == mm_height) + return; + + head->mm_width = mm_width; + head->mm_height = mm_height; + + weston_head_set_device_changed(head); +} + +/** Store monitor sub-pixel layout + * + * \param head The head to modify. + * \param sp Sub-pixel layout. The possible values are: + * - WL_OUTPUT_SUBPIXEL_UNKNOWN, + * - WL_OUTPUT_SUBPIXEL_NONE, + * - WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB, + * - WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR, + * - WL_OUTPUT_SUBPIXEL_VERTICAL_RGB, + * - WL_OUTPUT_SUBPIXEL_VERTICAL_BGR + * + * This may set the device_changed flag. + * + * \ingroup head + * \internal + */ +WL_EXPORT void +weston_head_set_subpixel(struct weston_head *head, + enum wl_output_subpixel sp) +{ + if (head->subpixel == sp) + return; + + head->subpixel = sp; + + weston_head_set_device_changed(head); +} + +/** Mark the monitor as internal + * + * This is used for embedded screens, like laptop panels. + * + * \param head The head to mark as internal. + * + * By default a head is external. The type is often inferred from the physical + * connector type. + * + * \ingroup head + * \internal + */ +WL_EXPORT void +weston_head_set_internal(struct weston_head *head) +{ + head->connection_internal = true; +} + +/** Store connector status + * + * \param head The head to modify. + * \param connected Whether the head is connected. + * + * Connectors are created as disconnected. This function can be used to + * set the connector status. + * + * The status should be set to true when a physical connector is connected to + * a video sink device like a monitor and to false when the connector is + * disconnected. For nested backends, the connection status should reflect the + * connection to the parent display server. + * + * When the connection status changes, it schedules a call to the heads_changed + * hook and sets the device_changed flag. + * + * \sa weston_compositor_set_heads_changed_cb + * \ingroup head + * \internal + */ +WL_EXPORT void +weston_head_set_connection_status(struct weston_head *head, bool connected) +{ + if (head->connected == connected) + return; + + head->connected = connected; + + weston_head_set_device_changed(head); +} + +static void +weston_output_compute_protection(struct weston_output *output) +{ + struct weston_head *head; + enum weston_hdcp_protection op_protection; + bool op_protection_valid = false; + struct weston_compositor *wc = output->compositor; + + wl_list_for_each(head, &output->head_list, output_link) { + if (!op_protection_valid) { + op_protection = head->current_protection; + op_protection_valid = true; + } + if (head->current_protection < op_protection) + op_protection = head->current_protection; + } + + if (!op_protection_valid) + op_protection = WESTON_HDCP_DISABLE; + + if (output->current_protection != op_protection) { + output->current_protection = op_protection; + weston_output_damage(output); + weston_schedule_surface_protection_update(wc); + } +} + +WL_EXPORT void +weston_head_set_content_protection_status(struct weston_head *head, + enum weston_hdcp_protection status) +{ + head->current_protection = status; + if (head->output) + weston_output_compute_protection(head->output); +} + +/** Is the head currently connected? + * + * \param head The head to query. + * \return Connection status. + * + * Returns true if the head is physically connected to a monitor, or in + * case of a nested backend returns true when there is a connection to the + * parent display server. + * + * This is independent from the head being enabled. + * + * \sa weston_head_is_enabled + * \ingroup head + */ +WL_EXPORT bool +weston_head_is_connected(struct weston_head *head) +{ + return head->connected; +} + +/** Is the head currently enabled? + * + * \param head The head to query. + * \return Video status. + * + * Returns true if the head is currently transmitting a video stream. + * + * This is independent of the head being connected. + * + * \sa weston_head_is_connected + * \ingroup head + */ +WL_EXPORT bool +weston_head_is_enabled(struct weston_head *head) +{ + if (!head->output) + return false; + + return head->output->enabled; +} + +/** Has the device information changed? + * + * \param head The head to query. + * \return True if the device information has changed since last reset. + * + * The information about the connected display device, e.g. a monitor, may + * change without being disconnected in between. Changing information + * causes a call to the heads_changed hook. + * + * The information includes make, model, serial number, physical size, + * and sub-pixel type. The connection status is also included. + * + * \sa weston_head_reset_device_changed, weston_compositor_set_heads_changed_cb + * \ingroup head + */ +WL_EXPORT bool +weston_head_is_device_changed(struct weston_head *head) +{ + return head->device_changed; +} + +/** Does the head represent a non-desktop display? + * + * \param head The head to query. + * \return True if the device is a non-desktop display. + * + * Non-desktop heads are not attached to outputs by default. + * This stops weston from extending the desktop onto head mounted displays. + * + * \ingroup head + */ +WL_EXPORT bool +weston_head_is_non_desktop(struct weston_head *head) +{ + return head->non_desktop; +} + +/** Acknowledge device information change + * + * \param head The head to acknowledge. + * + * Clears the device changed flag on this head. When a compositor has processed + * device information, it should call this to be able to notice further + * changes. + * + * \sa weston_head_is_device_changed + * \ingroup head + */ +WL_EXPORT void +weston_head_reset_device_changed(struct weston_head *head) +{ + head->device_changed = false; +} + +/** Get the name of a head + * + * \param head The head to query. + * \return The head's name, not NULL. + * + * The name depends on the backend. The DRM backend uses connector names, + * other backends may use hardcoded names or user-given names. + * + * \ingroup head + */ +WL_EXPORT const char * +weston_head_get_name(struct weston_head *head) +{ + return head->name; +} + +/** Get the output the head is attached to + * + * \param head The head to query. + * \return The output the head is attached to, or NULL if detached. + * \ingroup head + */ +WL_EXPORT struct weston_output * +weston_head_get_output(struct weston_head *head) +{ + return head->output; +} + +/** Get the head's native transformation + * + * \param head The head to query. + * \return The head's native transform, as a WL_OUTPUT_TRANSFORM_* value + * + * A weston_head may have a 'native' transform provided by the backend. + * Examples include panels which are physically rotated, where the rotation + * is recorded and described as part of the system configuration. This call + * will return any known native transform for the head. + * + * \ingroup head + */ +WL_EXPORT uint32_t +weston_head_get_transform(struct weston_head *head) +{ + return head->transform; +} + +/** Add destroy callback for a head + * + * \param head The head to watch for. + * \param listener The listener to add. The \c notify member must be set. + * + * Heads may get destroyed for various reasons by the backends. If a head is + * attached to an output, the compositor should listen for head destruction + * and reconfigure or destroy the output if necessary. + * + * The destroy callbacks will be called on weston_head destruction before any + * automatic detaching from an associated weston_output and before any + * weston_head information is lost. + * + * The \c data argument to the notify callback is the weston_head being + * destroyed. + * + * \ingroup head + */ +WL_EXPORT void +weston_head_add_destroy_listener(struct weston_head *head, + struct wl_listener *listener) +{ + wl_signal_add(&head->destroy_signal, listener); +} + +/** Look up destroy listener for a head + * + * \param head The head to query. + * \param notify The notify function used used for the added destroy listener. + * \return The listener, or NULL if not found. + * + * This looks up the previously added destroy listener struct based on the + * notify function it has. The listener can be used to access user data + * through \c container_of(). + * + * \sa wl_signal_get() + * \ingroup head + */ +WL_EXPORT struct wl_listener * +weston_head_get_destroy_listener(struct weston_head *head, + wl_notify_func_t notify) +{ + return wl_signal_get(&head->destroy_signal, notify); +} + +/* Move other outputs when one is resized so the space remains contiguous. */ +static void +weston_compositor_reflow_outputs(struct weston_compositor *compositor, + struct weston_output *resized_output, int delta_width) +{ + struct weston_output *output; + bool start_resizing = false; + + if (!delta_width) + return; + + wl_list_for_each(output, &compositor->output_list, link) { + if (output == resized_output) { + start_resizing = true; + continue; + } + + if (start_resizing) { + weston_output_move(output, output->x + delta_width, output->y); + output->dirty = 1; + } + } +} + +static void +weston_output_update_matrix(struct weston_output *output) +{ + float magnification; + + weston_matrix_init(&output->matrix); + weston_matrix_translate(&output->matrix, -output->x, -output->y, 0); + + if (output->zoom.active) { + magnification = 1 / (1 - output->zoom.spring_z.current); + weston_output_update_zoom(output); + weston_matrix_translate(&output->matrix, -output->zoom.trans_x, + -output->zoom.trans_y, 0); + weston_matrix_scale(&output->matrix, magnification, + magnification, 1.0); + } + + 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: + weston_matrix_translate(&output->matrix, -output->width, 0, 0); + weston_matrix_scale(&output->matrix, -1, 1, 1); + break; + } + + 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: + weston_matrix_translate(&output->matrix, -output->width, 0, 0); + weston_matrix_rotate_xy(&output->matrix, 0, -1); + break; + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + weston_matrix_translate(&output->matrix, + -output->width, -output->height, 0); + weston_matrix_rotate_xy(&output->matrix, -1, 0); + break; + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + weston_matrix_translate(&output->matrix, 0, -output->height, 0); + weston_matrix_rotate_xy(&output->matrix, 0, 1); + break; + } + + if (output->current_scale != 1) + weston_matrix_scale(&output->matrix, + output->current_scale, + output->current_scale, 1); + + output->dirty = 0; + + weston_matrix_invert(&output->inverse_matrix, &output->matrix); +} + +static void +weston_output_transform_scale_init(struct weston_output *output, uint32_t transform, uint32_t scale) +{ + output->transform = transform; + output->native_scale = scale; + output->current_scale = scale; + + convert_size_by_transform_scale(&output->width, &output->height, + output->current_mode->width, + output->current_mode->height, + transform, scale); +} + +static void +weston_output_init_geometry(struct weston_output *output, int x, int y) +{ + output->x = x; + output->y = y; + + pixman_region32_fini(&output->region); + pixman_region32_init_rect(&output->region, x, y, + output->width, + output->height); +} + +/** + * \ingroup output + */ +WL_EXPORT void +weston_output_move(struct weston_output *output, int x, int y) +{ + struct weston_head *head; + struct wl_resource *resource; + int ver; + + output->move_x = x - output->x; + output->move_y = y - output->y; + + if (output->move_x == 0 && output->move_y == 0) + return; + + weston_output_init_geometry(output, x, y); + + output->dirty = 1; + + /* Move views on this output. */ + wl_signal_emit(&output->compositor->output_moved_signal, output); + + /* Notify clients of the change for output position. */ + wl_list_for_each(head, &output->head_list, output_link) { + wl_resource_for_each(resource, &head->resource_list) { + wl_output_send_geometry(resource, + output->x, + output->y, + head->mm_width, + head->mm_height, + head->subpixel, + head->make, + head->model, + output->transform); + + ver = wl_resource_get_version(resource); + if (ver >= WL_OUTPUT_DONE_SINCE_VERSION) + wl_output_send_done(resource); + } + + wl_resource_for_each(resource, &head->xdg_output_resource_list) { + zxdg_output_v1_send_logical_position(resource, + output->x, + output->y); + zxdg_output_v1_send_done(resource); + } + } +} + +/** Signal that a pending output is taken into use. + * + * Removes the output from the pending list and adds it to the compositor's + * list of enabled outputs. The output created signal is emitted. + * + * The output gets an internal ID assigned, and the wl_output global is + * created. + * + * \param compositor The compositor instance. + * \param output The output to be added. + * + * \internal + * \ingroup compositor + */ +static void +weston_compositor_add_output(struct weston_compositor *compositor, + struct weston_output *output) +{ + struct weston_view *view, *next; + struct weston_head *head; + + assert(!output->enabled); + + /* Verify we haven't reached the limit of 32 available output IDs */ + assert(ffs(~compositor->output_id_pool) > 0); + + /* Invert the output id pool and look for the lowest numbered + * switch (the least significant bit). Take that bit's position + * as our ID, and mark it used in the compositor's output_id_pool. + */ + output->id = ffs(~compositor->output_id_pool) - 1; + compositor->output_id_pool |= 1u << output->id; + + wl_list_remove(&output->link); + wl_list_insert(compositor->output_list.prev, &output->link); + output->enabled = true; + + wl_list_for_each(head, &output->head_list, output_link) + weston_head_add_global(head); + + wl_signal_emit(&compositor->output_created_signal, output); + + wl_list_for_each_safe(view, next, &compositor->view_list, link) + weston_view_geometry_dirty(view); +} + +/** Transform device coordinates into global coordinates + * + * \param output the weston_output object + * \param[in] device_x X coordinate in device units. + * \param[in] device_y Y coordinate in device units. + * \param[out] x X coordinate in the global space. + * \param[out] y Y coordinate in the global space. + * + * Transforms coordinates from the device coordinate space (physical pixel + * units) to the global coordinate space (logical pixel units). This takes + * into account output transform and scale. + * + * \ingroup output + * \internal + */ +WL_EXPORT void +weston_output_transform_coordinate(struct weston_output *output, + double device_x, double device_y, + double *x, double *y) +{ + struct weston_vector p = { { + device_x, + device_y, + 0.0, + 1.0 } }; + + weston_matrix_transform(&output->inverse_matrix, &p); + + *x = p.f[0] / p.f[3]; + *y = p.f[1] / p.f[3]; +} + +/** Removes output from compositor's list of enabled outputs + * + * \param output The weston_output object that is being removed. + * + * The following happens: + * + * - The output assignments of all views in the current scenegraph are + * recomputed. + * + * - Presentation feedback is discarded. + * + * - Compositor is notified that outputs were changed and + * applies the necessary changes to re-layout outputs. + * + * - The output is put back in the pending outputs list. + * + * - Signal is emitted to notify all users of the weston_output + * object that the output is being destroyed. + * + * - wl_output protocol objects referencing this weston_output + * are made inert, and the wl_output global is removed. + * + * - The output's internal ID is released. + * + * \ingroup compositor + * \internal + */ +static void +weston_compositor_remove_output(struct weston_output *output) +{ + struct weston_compositor *compositor = output->compositor; + struct weston_view *view; + struct weston_head *head; + + assert(output->destroying); + assert(output->enabled); + + wl_list_for_each(view, &compositor->view_list, link) { + if (view->output_mask & (1u << output->id)) + weston_view_assign_output(view); + } + + weston_presentation_feedback_discard_list(&output->feedback_list); + + weston_compositor_reflow_outputs(compositor, output, -output->width); + + wl_list_remove(&output->link); + wl_list_insert(compositor->pending_output_list.prev, &output->link); + output->enabled = false; + + wl_signal_emit(&compositor->output_destroyed_signal, output); + wl_signal_emit(&output->destroy_signal, output); + + wl_list_for_each(head, &output->head_list, output_link) + weston_head_remove_global(head); + + compositor->output_id_pool &= ~(1u << output->id); + output->id = 0xffffffff; /* invalid */ +} + +/** Sets the output scale for a given output. + * + * \param output The weston_output object that the scale is set for. + * \param scale Scale factor for the given output. + * + * It only supports setting scale for an output that + * is not enabled and it can only be ran once. + * + * \ingroup ouput + */ +WL_EXPORT void +weston_output_set_scale(struct weston_output *output, + int32_t scale) +{ + /* We can only set scale on a disabled output */ + assert(!output->enabled); + + /* We only want to set scale once */ + assert(!output->scale); + + output->scale = scale; +} + +/** Sets the output transform for a given output. + * + * \param output The weston_output object that the transform is set for. + * \param transform Transform value for the given output. + * + * Refer to wl_output::transform section located at + * https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_output + * for list of values that can be passed to this function. + * + * \ingroup output + */ +WL_EXPORT void +weston_output_set_transform(struct weston_output *output, + uint32_t transform) +{ + struct weston_pointer_motion_event ev; + struct wl_resource *resource; + struct weston_seat *seat; + pixman_region32_t old_region; + int mid_x, mid_y; + struct weston_head *head; + int ver; + + if (!output->enabled && output->transform == UINT32_MAX) { + output->transform = transform; + return; + } + + weston_output_transform_scale_init(output, transform, output->scale); + + pixman_region32_init(&old_region); + pixman_region32_copy(&old_region, &output->region); + + weston_output_init_geometry(output, output->x, output->y); + + output->dirty = 1; + + /* Notify clients of the change for output transform. */ + wl_list_for_each(head, &output->head_list, output_link) { + wl_resource_for_each(resource, &head->resource_list) { + wl_output_send_geometry(resource, + output->x, + output->y, + head->mm_width, + head->mm_height, + head->subpixel, + head->make, + head->model, + output->transform); + + ver = wl_resource_get_version(resource); + if (ver >= WL_OUTPUT_DONE_SINCE_VERSION) + wl_output_send_done(resource); + } + wl_resource_for_each(resource, &head->xdg_output_resource_list) { + zxdg_output_v1_send_logical_position(resource, + output->x, + output->y); + zxdg_output_v1_send_logical_size(resource, + output->width, + output->height); + zxdg_output_v1_send_done(resource); + } + } + + /* we must ensure that pointers are inside output, otherwise they disappear */ + mid_x = output->x + output->width / 2; + mid_y = output->y + output->height / 2; + + ev.mask = WESTON_POINTER_MOTION_ABS; + ev.x = wl_fixed_to_double(wl_fixed_from_int(mid_x)); + ev.y = wl_fixed_to_double(wl_fixed_from_int(mid_y)); + + wl_list_for_each(seat, &output->compositor->seat_list, link) { + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + if (pointer && pixman_region32_contains_point(&old_region, + wl_fixed_to_int(pointer->x), + wl_fixed_to_int(pointer->y), + NULL)) + weston_pointer_move(pointer, &ev); + } +} + +/** Initializes a weston_output object with enough data so + ** an output can be configured. + * + * \param output The weston_output object to initialize + * \param compositor The compositor instance. + * \param name Name for the output (the string is copied). + * + * Sets initial values for fields that are expected to be + * configured either by compositors or backends. + * + * The name is used in logs, and can be used by compositors as a configuration + * identifier. + * + * \ingroup output + * \internal + */ +WL_EXPORT void +weston_output_init(struct weston_output *output, + struct weston_compositor *compositor, + const char *name) +{ + output->compositor = compositor; + output->destroying = 0; + output->name = strdup(name); + wl_list_init(&output->link); + wl_signal_init(&output->user_destroy_signal); + output->enabled = false; + output->desired_protection = WESTON_HDCP_DISABLE; + output->allow_protection = true; + + wl_list_init(&output->head_list); + + /* Add some (in)sane defaults which can be used + * for checking if an output was properly configured + */ + output->scale = 0; + /* Can't use -1 on uint32_t and 0 is valid enum value */ + output->transform = UINT32_MAX; + + pixman_region32_init(&output->region); + wl_list_init(&output->mode_list); +} + +/** Adds weston_output object to pending output list. + * + * \param output The weston_output object to add + * \param compositor The compositor instance. + * + * The opposite of this operation is built into weston_output_release(). + * + * \ingroup compositor + * \internal + */ +WL_EXPORT void +weston_compositor_add_pending_output(struct weston_output *output, + struct weston_compositor *compositor) +{ + assert(output->disable); + assert(output->enable); + + wl_list_remove(&output->link); + wl_list_insert(compositor->pending_output_list.prev, &output->link); +} + +/** Create a string with the attached heads' names. + * + * The string must be free()'d. + * + * \ingroup output + */ +static char * +weston_output_create_heads_string(struct weston_output *output) +{ + FILE *fp; + char *str = NULL; + size_t size = 0; + struct weston_head *head; + const char *sep = ""; + + fp = open_memstream(&str, &size); + if (!fp) + return NULL; + + wl_list_for_each(head, &output->head_list, output_link) { + fprintf(fp, "%s%s", sep, head->name); + sep = ", "; + } + fclose(fp); + + return str; +} + +/** Constructs a weston_output object that can be used by the compositor. + * + * \param output The weston_output object that needs to be enabled. Must not + * be enabled already. Must have at least one head attached. + * + * Output coordinates are calculated and each new output is by default + * assigned to the right of previous one. + * + * Sets up the transformation, zoom, and geometry of the output using + * the properties that need to be configured by the compositor. + * + * Establishes a repaint timer for the output with the relevant display + * object's event loop. See output_repaint_timer_handler(). + * + * The output is assigned an ID. Weston can support up to 32 distinct + * outputs, with IDs numbered from 0-31; the compositor's output_id_pool + * is referred to and used to find the first available ID number, and + * then this ID is marked as used in output_id_pool. + * + * The output is also assigned a Wayland global with the wl_output + * external interface. + * + * Backend specific function is called to set up the output output. + * + * Output is added to the compositor's output list + * + * If the backend specific function fails, the weston_output object + * is returned to a state it was before calling this function and + * is added to the compositor's pending_output_list in case it needs + * to be reconfigured or just so it can be destroyed at shutdown. + * + * 0 is returned on success, -1 on failure. + * + * \ingroup output + */ +WL_EXPORT int +weston_output_enable(struct weston_output *output) +{ + struct weston_compositor *c = output->compositor; + struct weston_output *iterator; + struct weston_head *head; + char *head_names; + int x = 0, y = 0; + + if (output->enabled) { + weston_log("Error: attempt to enable an enabled output '%s'\n", + output->name); + return -1; + } + + if (wl_list_empty(&output->head_list)) { + weston_log("Error: cannot enable output '%s' without heads.\n", + output->name); + return -1; + } + + if (wl_list_empty(&output->mode_list) || !output->current_mode) { + weston_log("Error: no video mode for output '%s'.\n", + output->name); + return -1; + } + + wl_list_for_each(head, &output->head_list, output_link) { + assert(head->make); + assert(head->model); + } + + iterator = container_of(c->output_list.prev, + struct weston_output, link); + + if (!wl_list_empty(&c->output_list)) + x = iterator->x + iterator->width; + + /* Make sure the scale is set up */ + assert(output->scale); + + /* Make sure we have a transform set */ + assert(output->transform != UINT32_MAX); + + output->x = x; + output->y = y; + output->dirty = 1; + output->original_scale = output->scale; + + wl_signal_init(&output->frame_signal); + wl_signal_init(&output->destroy_signal); + + weston_output_transform_scale_init(output, output->transform, output->scale); + weston_output_init_zoom(output); + + weston_output_init_geometry(output, x, y); + weston_output_damage(output); + + wl_list_init(&output->animation_list); + wl_list_init(&output->feedback_list); + + /* Enable the output (set up the crtc or create a + * window representing the output, set up the + * renderer, etc) + */ + if (output->enable(output) < 0) { + weston_log("Enabling output \"%s\" failed.\n", output->name); + return -1; + } + + weston_compositor_add_output(output->compositor, output); + + head_names = weston_output_create_heads_string(output); + weston_log("Output '%s' enabled with head(s) %s\n", + output->name, head_names); + free(head_names); + + return 0; +} + +/** Converts a weston_output object to a pending output state, so it + ** can be configured again or destroyed. + * + * \param output The weston_output object that needs to be disabled. + * + * Calls a backend specific function to disable an output, in case + * such function exists. + * + * The backend specific disable function may choose to postpone the disabling + * by returning a negative value, in which case this function returns early. + * In that case the backend will guarantee the output will be disabled soon + * by the backend calling this function again. One must not attempt to re-enable + * the output until that happens. + * + * Otherwise, if the output is being used by the compositor, it is removed + * from weston's output_list (see weston_compositor_remove_output()) + * and is returned to a state it was before weston_output_enable() + * was ran (see weston_output_enable_undo()). + * + * See weston_output_init() for more information on the + * state output is returned to. + * + * If the output has never been enabled yet, this function can still be + * called to ensure that the output is actually turned off rather than left + * in the state it was discovered in. + * + * \ingroup output + */ +WL_EXPORT void +weston_output_disable(struct weston_output *output) +{ + /* Should we rename this? */ + output->destroying = 1; + + /* Disable is called unconditionally also for not-enabled outputs, + * because at compositor start-up, if there is an output that is + * already on but the compositor wants to turn it off, we have to + * forward the turn-off to the backend so it knows to do it. + * The backend cannot initially turn off everything, because it + * would cause unnecessary mode-sets for all outputs the compositor + * wants to be on. + */ + if (output->disable(output) < 0) + return; + + if (output->enabled) + weston_compositor_remove_output(output); + + output->destroying = 0; +} + +/** Forces a synchronous call to heads_changed hook + * + * \param compositor The compositor instance + * + * If there are new or changed heads, calls the heads_changed hook and + * returns after the hook returns. + * + * \ingroup compositor + */ +WL_EXPORT void +weston_compositor_flush_heads_changed(struct weston_compositor *compositor) +{ + if (compositor->heads_changed_source) { + wl_event_source_remove(compositor->heads_changed_source); + weston_compositor_call_heads_changed(compositor); + } +} + +/** Add destroy callback for an output + * + * \param output The output to watch. + * \param listener The listener to add. The \c notify member must be set. + * + * The listener callback will be called when user destroys an output. This + * may be delayed by a backend in some cases. The main purpose of the + * listener is to allow hooking up custom data to the output. The custom data + * can be fetched via weston_output_get_destroy_listener() followed by + * container_of(). + * + * The \c data argument to the notify callback is the weston_output being + * destroyed. + * + * @note This is for the final destruction of an output, not when it gets + * disabled. If you want to keep track of enabled outputs, this is not it. + * + * \ingroup ouput + */ +WL_EXPORT void +weston_output_add_destroy_listener(struct weston_output *output, + struct wl_listener *listener) +{ + wl_signal_add(&output->user_destroy_signal, listener); +} + +/** Look up destroy listener for an output + * + * \param output The output to query. + * \param notify The notify function used used for the added destroy listener. + * \return The listener, or NULL if not found. + * + * This looks up the previously added destroy listener struct based on the + * notify function it has. The listener can be used to access user data + * through \c container_of(). + * + * \sa wl_signal_get() weston_output_add_destroy_listener() + * \ingroup output + */ +WL_EXPORT struct wl_listener * +weston_output_get_destroy_listener(struct weston_output *output, + wl_notify_func_t notify) +{ + return wl_signal_get(&output->user_destroy_signal, notify); +} + +/** Uninitialize an output + * + * Removes the output from the list of enabled outputs if necessary, but + * does not call the backend's output disable function. The output will no + * longer be in the list of pending outputs either. + * + * All fields of weston_output become uninitialized, i.e. should not be used + * anymore. The caller can free the memory after this. + * + * \ingroup ouput + * \internal + */ +WL_EXPORT void +weston_output_release(struct weston_output *output) +{ + struct weston_head *head, *tmp; + + output->destroying = 1; + + wl_signal_emit(&output->user_destroy_signal, output); + + if (output->idle_repaint_source) + wl_event_source_remove(output->idle_repaint_source); + + if (output->enabled) + weston_compositor_remove_output(output); + + pixman_region32_fini(&output->region); + wl_list_remove(&output->link); + + wl_list_for_each_safe(head, tmp, &output->head_list, output_link) + weston_head_detach(head); + + free(output->name); +} + +/** Find an output by its given name + * + * \param compositor The compositor to search in. + * \param name The output name to search for. + * \return An existing output with the given name, or NULL if not found. + * + * \ingroup compositor + */ +WL_EXPORT struct weston_output * +weston_compositor_find_output_by_name(struct weston_compositor *compositor, + const char *name) +{ + struct weston_output *output; + + wl_list_for_each(output, &compositor->output_list, link) + if (strcmp(output->name, name) == 0) + return output; + + wl_list_for_each(output, &compositor->pending_output_list, link) + if (strcmp(output->name, name) == 0) + return output; + + return NULL; +} + +/** Create a named output + * + * \param compositor The compositor. + * \param name The name for the output. + * \return A new \c weston_output, or NULL on failure. + * + * This creates a new weston_output that starts with no heads attached. + * + * An output must be configured and it must have at least one head before + * it can be enabled. + * + * \ingroup compositor + */ +WL_EXPORT struct weston_output * +weston_compositor_create_output(struct weston_compositor *compositor, + const char *name) +{ + assert(compositor->backend->create_output); + + if (weston_compositor_find_output_by_name(compositor, name)) { + weston_log("Warning: attempted to create an output with a " + "duplicate name '%s'.\n", name); + return NULL; + } + + return compositor->backend->create_output(compositor, name); +} + +/** Create an output for an unused head + * + * \param compositor The compositor. + * \param head The head to attach to the output. + * \return A new \c weston_output, or NULL on failure. + * + * This creates a new weston_output that starts with the given head attached. + * The output inherits the name of the head. The head must not be already + * attached to another output. + * + * An output must be configured before it can be enabled. + * + * \ingroup compositor + */ +WL_EXPORT struct weston_output * +weston_compositor_create_output_with_head(struct weston_compositor *compositor, + struct weston_head *head) +{ + struct weston_output *output; + + output = weston_compositor_create_output(compositor, head->name); + if (!output) + return NULL; + + if (weston_output_attach_head(output, head) < 0) { + weston_output_destroy(output); + return NULL; + } + + return output; +} + +/** Destroy an output + * + * \param output The output to destroy. + * + * The heads attached to the given output are detached and become unused again. + * + * It is not necessary to explicitly destroy all outputs at compositor exit. + * weston_compositor_destroy() will automatically destroy any remaining + * outputs. + * + * \ingroup ouput + */ +WL_EXPORT void +weston_output_destroy(struct weston_output *output) +{ + output->destroy(output); +} + +/** When you need a head... + * + * This function is a hack, used until all code has been converted to become + * multi-head aware. + * + * \param output The weston_output whose head to get. + * \return The first head in the output's list. + * + * \ingroup ouput + */ +WL_EXPORT struct weston_head * +weston_output_get_first_head(struct weston_output *output) +{ + if (wl_list_empty(&output->head_list)) + return NULL; + + return container_of(output->head_list.next, + struct weston_head, output_link); +} + +/** Allow/Disallow content-protection support for an output + * + * This function sets the allow_protection member for an output. Setting of + * this field will allow the compositor to attempt content-protection for this + * output, for a backend that supports the content-protection protocol. + * + * \param output The weston_output for whom the content-protection is to be + * allowed. + * \param allow_protection The bool value which is to be set. + */ +WL_EXPORT void +weston_output_allow_protection(struct weston_output *output, + bool allow_protection) +{ + output->allow_protection = allow_protection; +} + +static void +xdg_output_unlist(struct wl_resource *resource) +{ + wl_list_remove(wl_resource_get_link(resource)); +} + +static void +xdg_output_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct zxdg_output_v1_interface xdg_output_interface = { + xdg_output_destroy +}; + +static void +xdg_output_manager_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +xdg_output_manager_get_xdg_output(struct wl_client *client, + struct wl_resource *manager, + uint32_t id, + struct wl_resource *output_resource) +{ + int version = wl_resource_get_version(manager); + struct weston_head *head = wl_resource_get_user_data(output_resource); + struct weston_output *output = head->output; + struct wl_resource *resource; + + resource = wl_resource_create(client, &zxdg_output_v1_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_list_insert(&head->xdg_output_resource_list, + wl_resource_get_link(resource)); + + wl_resource_set_implementation(resource, &xdg_output_interface, + NULL, xdg_output_unlist); + + zxdg_output_v1_send_logical_position(resource, output->x, output->y); + zxdg_output_v1_send_logical_size(resource, + output->width, + output->height); + if (version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) + zxdg_output_v1_send_name(resource, head->name); + + zxdg_output_v1_send_done(resource); +} + +static const struct zxdg_output_manager_v1_interface xdg_output_manager_interface = { + xdg_output_manager_destroy, + xdg_output_manager_get_xdg_output +}; + +static void +bind_xdg_output_manager(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct wl_resource *resource; + + resource = wl_resource_create(client, &zxdg_output_manager_v1_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &xdg_output_manager_interface, + NULL, NULL); +} + +static void +destroy_viewport(struct wl_resource *resource) +{ + struct weston_surface *surface = + wl_resource_get_user_data(resource); + + if (!surface) + return; + + surface->viewport_resource = NULL; + surface->pending.buffer_viewport.buffer.src_width = + wl_fixed_from_int(-1); + surface->pending.buffer_viewport.surface.width = -1; + surface->pending.buffer_viewport.changed = 1; +} + +static void +viewport_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +viewport_set_source(struct wl_client *client, + struct wl_resource *resource, + wl_fixed_t src_x, + wl_fixed_t src_y, + wl_fixed_t src_width, + wl_fixed_t src_height) +{ + struct weston_surface *surface = + wl_resource_get_user_data(resource); + + if (!surface) { + wl_resource_post_error(resource, + WP_VIEWPORT_ERROR_NO_SURFACE, + "wl_surface for this viewport is no longer exists"); + return; + } + + assert(surface->viewport_resource == resource); + assert(surface->resource); + + if (src_width == wl_fixed_from_int(-1) && + src_height == wl_fixed_from_int(-1) && + src_x == wl_fixed_from_int(-1) && + src_y == wl_fixed_from_int(-1)) { + /* unset source rect */ + surface->pending.buffer_viewport.buffer.src_width = + wl_fixed_from_int(-1); + surface->pending.buffer_viewport.changed = 1; + return; + } + + if (src_width <= 0 || src_height <= 0 || src_x < 0 || src_y < 0) { + wl_resource_post_error(resource, + WP_VIEWPORT_ERROR_BAD_VALUE, + "wl_surface@%d viewport source " + "w=%f <= 0, h=%f <= 0, x=%f < 0, or y=%f < 0", + wl_resource_get_id(surface->resource), + wl_fixed_to_double(src_width), + wl_fixed_to_double(src_height), + wl_fixed_to_double(src_x), + wl_fixed_to_double(src_y)); + return; + } + + surface->pending.buffer_viewport.buffer.src_x = src_x; + surface->pending.buffer_viewport.buffer.src_y = src_y; + surface->pending.buffer_viewport.buffer.src_width = src_width; + surface->pending.buffer_viewport.buffer.src_height = src_height; + surface->pending.buffer_viewport.changed = 1; +} + +static void +viewport_set_destination(struct wl_client *client, + struct wl_resource *resource, + int32_t dst_width, + int32_t dst_height) +{ + struct weston_surface *surface = + wl_resource_get_user_data(resource); + + if (!surface) { + wl_resource_post_error(resource, + WP_VIEWPORT_ERROR_NO_SURFACE, + "wl_surface for this viewport no longer exists"); + return; + } + + assert(surface->viewport_resource == resource); + + if (dst_width == -1 && dst_height == -1) { + /* unset destination size */ + surface->pending.buffer_viewport.surface.width = -1; + surface->pending.buffer_viewport.changed = 1; + return; + } + + if (dst_width <= 0 || dst_height <= 0) { + wl_resource_post_error(resource, + WP_VIEWPORT_ERROR_BAD_VALUE, + "destination size must be positive (%dx%d)", + dst_width, dst_height); + return; + } + + surface->pending.buffer_viewport.surface.width = dst_width; + surface->pending.buffer_viewport.surface.height = dst_height; + surface->pending.buffer_viewport.changed = 1; +} + +static const struct wp_viewport_interface viewport_interface = { + viewport_destroy, + viewport_set_source, + viewport_set_destination +}; + +static void +viewporter_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +viewporter_get_viewport(struct wl_client *client, + struct wl_resource *viewporter, + uint32_t id, + struct wl_resource *surface_resource) +{ + int version = wl_resource_get_version(viewporter); + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + struct wl_resource *resource; + + if (surface->viewport_resource) { + wl_resource_post_error(viewporter, + WP_VIEWPORTER_ERROR_VIEWPORT_EXISTS, + "a viewport for that surface already exists"); + return; + } + + resource = wl_resource_create(client, &wp_viewport_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &viewport_interface, + surface, destroy_viewport); + + surface->viewport_resource = resource; +} + +static const struct wp_viewporter_interface viewporter_interface = { + viewporter_destroy, + viewporter_get_viewport +}; + +static void +bind_viewporter(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct wl_resource *resource; + + resource = wl_resource_create(client, &wp_viewporter_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &viewporter_interface, + NULL, NULL); +} + +static void +destroy_presentation_feedback(struct wl_resource *feedback_resource) +{ + struct weston_presentation_feedback *feedback; + + feedback = wl_resource_get_user_data(feedback_resource); + + wl_list_remove(&feedback->link); + free(feedback); +} + +static void +presentation_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +presentation_feedback(struct wl_client *client, + struct wl_resource *presentation_resource, + struct wl_resource *surface_resource, + uint32_t callback) +{ + struct weston_surface *surface; + struct weston_presentation_feedback *feedback; + + surface = wl_resource_get_user_data(surface_resource); + + feedback = zalloc(sizeof *feedback); + if (feedback == NULL) + goto err_calloc; + + feedback->resource = wl_resource_create(client, + &wp_presentation_feedback_interface, + 1, callback); + if (!feedback->resource) + goto err_create; + + wl_resource_set_implementation(feedback->resource, NULL, feedback, + destroy_presentation_feedback); + + wl_list_insert(&surface->pending.feedback_list, &feedback->link); + + return; + +err_create: + free(feedback); + +err_calloc: + wl_client_post_no_memory(client); +} + +static const struct wp_presentation_interface presentation_implementation = { + presentation_destroy, + presentation_feedback +}; + +static void +bind_presentation(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, &wp_presentation_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &presentation_implementation, + compositor, NULL); + wp_presentation_send_clock_id(resource, compositor->presentation_clock); +} + +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, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &compositor_interface, + compositor, NULL); +} + +// OHOS remove logger +//static const char * +//output_repaint_status_text(struct weston_output *output) +//{ +// switch (output->repaint_status) { +// case REPAINT_NOT_SCHEDULED: +// return "no repaint"; +// case REPAINT_BEGIN_FROM_IDLE: +// return "start_repaint_loop scheduled"; +// case REPAINT_SCHEDULED: +// return "repaint scheduled"; +// case REPAINT_AWAITING_COMPLETION: +// return "awaiting completion"; +// } +// +// assert(!"output_repaint_status_text missing enum"); +// return NULL; +//} +// +//static void +//debug_scene_view_print_buffer(FILE *fp, struct weston_view *view) +//{ +// struct weston_buffer *buffer = view->surface->buffer_ref.buffer; +// struct wl_shm_buffer *shm; +// struct linux_dmabuf_buffer *dmabuf; +// const struct pixel_format_info *pixel_info = NULL; +// +// if (!buffer) { +// fprintf(fp, "\t\t[buffer not available]\n"); +// return; +// } +// +// shm = wl_shm_buffer_get(buffer->resource); +// if (shm) { +// uint32_t _format = wl_shm_buffer_get_format(shm); +// pixel_info = pixel_format_get_info_shm(_format); +// fprintf(fp, "\t\tSHM buffer\n"); +// fprintf(fp, "\t\t\tformat: 0x%lx %s\n", +// (unsigned long) _format, +// pixel_info ? pixel_info->drm_format_name : "UNKNOWN"); +// return; +// } +// +// dmabuf = linux_dmabuf_buffer_get(buffer->resource); +// if (dmabuf) { +// pixel_info = pixel_format_get_info(dmabuf->attributes.format); +// fprintf(fp, "\t\tdmabuf buffer\n"); +// fprintf(fp, "\t\t\tformat: 0x%lx %s\n", +// (unsigned long) dmabuf->attributes.format, +// pixel_info ? pixel_info->drm_format_name : "UNKNOWN"); +// fprintf(fp, "\t\t\tmodifier: 0x%llx\n", +// (unsigned long long) dmabuf->attributes.modifier[0]); +// return; +// } +// +// fprintf(fp, "\t\tEGL buffer\n"); +//} +// +//static void +//debug_scene_view_print(FILE *fp, struct weston_view *view, int view_idx) +//{ +// struct weston_compositor *ec = view->surface->compositor; +// struct weston_output *output; +// char desc[512]; +// pixman_box32_t *box; +// uint32_t surface_id = 0; +// pid_t pid = 0; +// +// if (view->surface->resource) { +// struct wl_resource *resource = view->surface->resource; +// wl_client_get_credentials(wl_resource_get_client(resource), +// &pid, NULL, NULL); +// surface_id = wl_resource_get_id(view->surface->resource); +// } +// +// if (!view->surface->get_label || +// view->surface->get_label(view->surface, desc, sizeof(desc)) < 0) { +// strcpy(desc, "[no description available]"); +// } +// fprintf(fp, "\tView %d (role %s, PID %d, surface ID %u, %s, %p):\n", +// view_idx, view->surface->role_name, pid, surface_id, +// desc, view); +// +// box = pixman_region32_extents(&view->transform.boundingbox); +// fprintf(fp, "\t\tposition: (%d, %d) -> (%d, %d)\n", +// box->x1, box->y1, box->x2, box->y2); +// box = pixman_region32_extents(&view->transform.opaque); +// +// if (weston_view_is_opaque(view, &view->transform.boundingbox)) { +// fprintf(fp, "\t\t[fully opaque]\n"); +// } else if (!pixman_region32_not_empty(&view->transform.opaque)) { +// fprintf(fp, "\t\t[not opaque]\n"); +// } else { +// fprintf(fp, "\t\t[opaque: (%d, %d) -> (%d, %d)]\n", +// box->x1, box->y1, box->x2, box->y2); +// } +// +// if (view->alpha < 1.0) +// fprintf(fp, "\t\talpha: %f\n", view->alpha); +// +// if (view->output_mask != 0) { +// bool first_output = true; +// fprintf(fp, "\t\toutputs: "); +// wl_list_for_each(output, &ec->output_list, link) { +// if (!(view->output_mask & (1 << output->id))) +// continue; +// fprintf(fp, "%s%d (%s)%s", +// (first_output) ? "" : ", ", +// output->id, output->name, +// (view->output == output) ? " (primary)" : ""); +// first_output = false; +// } +// } else { +// fprintf(fp, "\t\t[no outputs]"); +// } +// +// fprintf(fp, "\n"); +// +// debug_scene_view_print_buffer(fp, view); +//} +// +//static void +//debug_scene_view_print_tree(struct weston_view *view, +// FILE *fp, int *view_idx) +//{ +// struct weston_subsurface *sub; +// struct weston_view *ev; +// +// /* +// * print the view first, then we recursively go on printing +// * sub-surfaces. We bail out once no more sub-surfaces are available. +// */ +// debug_scene_view_print(fp, view, *view_idx); +// +// /* no more sub-surfaces */ +// if (wl_list_empty(&view->surface->subsurface_list)) +// return; +// +// wl_list_for_each(sub, &view->surface->subsurface_list, parent_link) { +// wl_list_for_each(ev, &sub->surface->views, surface_link) { +// /* only print the child views of the current view */ +// if (ev->parent_view != view) +// continue; +// +// (*view_idx)++; +// debug_scene_view_print_tree(ev, fp, view_idx); +// } +// } +//} + +/** + * Output information on how libweston is currently composing the scene + * graph. + * + * \ingroup compositor + */ +// OHOS remove logger +//WL_EXPORT char * +//weston_compositor_print_scene_graph(struct weston_compositor *ec) +//{ +// struct weston_output *output; +// struct weston_layer *layer; +// struct timespec now; +// int layer_idx = 0; +// FILE *fp; +// char *ret; +// size_t len; +// int err; +// +// fp = open_memstream(&ret, &len); +// assert(fp); +// +// weston_compositor_read_presentation_clock(ec, &now); +// fprintf(fp, "Weston scene graph at %ld.%09ld:\n\n", +// now.tv_sec, now.tv_nsec); +// +// wl_list_for_each(output, &ec->output_list, link) { +// struct weston_head *head; +// int head_idx = 0; +// +// fprintf(fp, "Output %d (%s):\n", output->id, output->name); +// assert(output->enabled); +// +// fprintf(fp, "\tposition: (%d, %d) -> (%d, %d)\n", +// output->x, output->y, +// output->x + output->width, +// output->y + output->height); +// fprintf(fp, "\tmode: %dx%d@%.3fHz\n", +// output->current_mode->width, +// output->current_mode->height, +// output->current_mode->refresh / 1000.0); +// fprintf(fp, "\tscale: %d\n", output->scale); +// +// fprintf(fp, "\trepaint status: %s\n", +// output_repaint_status_text(output)); +// if (output->repaint_status == REPAINT_SCHEDULED) +// fprintf(fp, "\tnext repaint: %ld.%09ld\n", +// output->next_repaint.tv_sec, +// output->next_repaint.tv_nsec); +// +// wl_list_for_each(head, &output->head_list, output_link) { +// fprintf(fp, "\tHead %d (%s): %sconnected\n", +// head_idx++, head->name, +// (head->connected) ? "" : "not "); +// } +// } +// +// fprintf(fp, "\n"); +// +// wl_list_for_each(layer, &ec->layer_list, link) { +// struct weston_view *view; +// int view_idx = 0; +// +// fprintf(fp, "Layer %d (pos 0x%lx):\n", layer_idx++, +// (unsigned long) layer->position); +// +// if (!weston_layer_mask_is_infinite(layer)) { +// fprintf(fp, "\t[mask: (%d, %d) -> (%d,%d)]\n\n", +// layer->mask.x1, layer->mask.y1, +// layer->mask.x2, layer->mask.y2); +// } +// +// wl_list_for_each(view, &layer->view_list.link, layer_link.link) { +// debug_scene_view_print_tree(view, fp, &view_idx); +// view_idx++; +// } +// +// if (wl_list_empty(&layer->view_list.link)) +// fprintf(fp, "\t[no views]\n"); +// +// fprintf(fp, "\n"); +// } +// +// err = fclose(fp); +// assert(err == 0); +// +// return ret; +//} + +/** + * Called when the 'scene-graph' debug scope is bound by a client. This + * one-shot weston-debug scope prints the current scene graph when bound, + * and then terminates the stream. + */ +// OHOS remove logger +//static void +//debug_scene_graph_cb(struct weston_log_subscription *sub, void *data) +//{ +// struct weston_compositor *ec = data; +// char *str = weston_compositor_print_scene_graph(ec); +// +// weston_log_subscription_printf(sub, "%s", str); +// free(str); +// weston_log_subscription_complete(sub); +//} + +/** Create the compositor. + * + * This functions creates and initializes a compositor instance. + * + * \param display The Wayland display to be used. + * \param user_data A pointer to an object that can later be retrieved + * \param log_ctx A pointer to weston_debug_compositor + * using the \ref weston_compositor_get_user_data function. + * \return The compositor instance on success or NULL on failure. + * + * \ingroup compositor + */ +WL_EXPORT struct weston_compositor * +weston_compositor_create(struct wl_display *display, +// OHOS remove logger +// struct weston_log_context *log_ctx, + void *user_data) +{ + struct weston_compositor *ec; + struct wl_event_loop *loop; + +// OHOS remove logger +// if (!log_ctx) +// return NULL; + + ec = zalloc(sizeof *ec); + if (!ec) + return NULL; + +// OHOS remove logger +// ec->weston_log_ctx = log_ctx; + ec->wl_display = display; + ec->user_data = user_data; + wl_signal_init(&ec->destroy_signal); + wl_signal_init(&ec->create_surface_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->output_destroyed_signal); + wl_signal_init(&ec->output_moved_signal); + wl_signal_init(&ec->output_resized_signal); + wl_signal_init(&ec->heads_changed_signal); + wl_signal_init(&ec->output_heads_changed_signal); + wl_signal_init(&ec->session_signal); + ec->session_active = true; + + ec->output_id_pool = 0; + ec->repaint_msec = DEFAULT_REPAINT_WINDOW; + + ec->activate_serial = 1; + + ec->touch_mode = WESTON_TOUCH_MODE_NORMAL; + + ec->content_protection = NULL; + + if (!wl_global_create(ec->wl_display, &wl_compositor_interface, 4, + ec, compositor_bind)) + goto fail; + + if (!wl_global_create(ec->wl_display, &wl_subcompositor_interface, 1, + ec, bind_subcompositor)) + goto fail; + + if (!wl_global_create(ec->wl_display, &wp_viewporter_interface, 1, + ec, bind_viewporter)) + goto fail; + + if (!wl_global_create(ec->wl_display, &zxdg_output_manager_v1_interface, 2, + ec, bind_xdg_output_manager)) + goto fail; + + if (!wl_global_create(ec->wl_display, &wp_presentation_interface, 1, + ec, bind_presentation)) + goto fail; + + if (weston_input_init(ec) != 0) + goto fail; + + wl_list_init(&ec->view_list); + wl_list_init(&ec->plane_list); + wl_list_init(&ec->layer_list); + wl_list_init(&ec->seat_list); + wl_list_init(&ec->pending_output_list); + wl_list_init(&ec->output_list); + wl_list_init(&ec->head_list); + wl_list_init(&ec->key_binding_list); + wl_list_init(&ec->modifier_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); + + wl_list_init(&ec->plugin_api_list); + + weston_plane_init(&ec->primary_plane, ec, 0, 0); + weston_compositor_stack_plane(ec, &ec->primary_plane, NULL); + + wl_data_device_manager_init(ec->wl_display); + + wl_display_init_shm(ec->wl_display); + + loop = wl_display_get_event_loop(ec->wl_display); + ec->idle_source = wl_event_loop_add_timer(loop, idle_handler, ec); + ec->repaint_timer = + wl_event_loop_add_timer(loop, output_repaint_timer_handler, + ec); + + weston_layer_init(&ec->fade_layer, ec); + weston_layer_init(&ec->cursor_layer, ec); + + weston_layer_set_position(&ec->fade_layer, WESTON_LAYER_POSITION_FADE); + weston_layer_set_position(&ec->cursor_layer, + WESTON_LAYER_POSITION_CURSOR); + +// OHOS remove debugger +// ec->debug_scene = +// weston_compositor_add_log_scope(ec, "scene-graph", +// "Scene graph details\n", +// debug_scene_graph_cb, NULL, +// ec); +// +// ec->timeline = +// weston_compositor_add_log_scope(ec, "timeline", +// "Timeline event points\n", +// weston_timeline_create_subscription, +// weston_timeline_destroy_subscription, +// ec); + return ec; + +fail: + free(ec); + return NULL; +} + +/** weston_compositor_shutdown + * \ingroup compositor + */ +WL_EXPORT void +weston_compositor_shutdown(struct weston_compositor *ec) +{ + struct weston_output *output, *next; + + wl_event_source_remove(ec->idle_source); + + /* Destroy all outputs associated with this compositor */ + wl_list_for_each_safe(output, next, &ec->output_list, link) + output->destroy(output); + + /* Destroy all pending outputs associated with this compositor */ + wl_list_for_each_safe(output, next, &ec->pending_output_list, link) + output->destroy(output); + + if (ec->renderer) + ec->renderer->destroy(ec); + + weston_binding_list_destroy_all(&ec->key_binding_list); + weston_binding_list_destroy_all(&ec->modifier_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); +} + +/** weston_compositor_exit_with_code + * \ingroup compositor + */ +WL_EXPORT void +weston_compositor_exit_with_code(struct weston_compositor *compositor, + int exit_code) +{ + if (compositor->exit_code == EXIT_SUCCESS) + compositor->exit_code = exit_code; + + weston_compositor_exit(compositor); +} + +/** weston_compositor_set_default_pointer_grab + * \ingroup compositor + */ +WL_EXPORT void +weston_compositor_set_default_pointer_grab(struct weston_compositor *ec, + const struct weston_pointer_grab_interface *interface) +{ + struct weston_seat *seat; + + ec->default_pointer_grab = interface; + wl_list_for_each(seat, &ec->seat_list, link) { + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + if (pointer) + weston_pointer_set_default_grab(pointer, interface); + } +} + +/** weston_compositor_set_presentation_clock + * \ingroup compositor + */ +WL_EXPORT int +weston_compositor_set_presentation_clock(struct weston_compositor *compositor, + clockid_t clk_id) +{ + struct timespec ts; + + if (clock_gettime(clk_id, &ts) < 0) + return -1; + + compositor->presentation_clock = clk_id; + + return 0; +} + +/** For choosing the software clock, when the display hardware or API + * does not expose a compatible presentation timestamp. + * + * \ingroup compositor + */ +WL_EXPORT int +weston_compositor_set_presentation_clock_software( + struct weston_compositor *compositor) +{ + /* In order of preference */ + static const clockid_t clocks[] = { + CLOCK_MONOTONIC_RAW, /* no jumps, no crawling */ + CLOCK_MONOTONIC_COARSE, /* no jumps, may crawl, fast & coarse */ + CLOCK_MONOTONIC, /* no jumps, may crawl */ + CLOCK_REALTIME_COARSE, /* may jump and crawl, fast & coarse */ + CLOCK_REALTIME /* may jump and crawl */ + }; + unsigned i; + + for (i = 0; i < ARRAY_LENGTH(clocks); i++) + if (weston_compositor_set_presentation_clock(compositor, + clocks[i]) == 0) + return 0; + + weston_log("Error: no suitable presentation clock available.\n"); + + return -1; +} + +/** Read the current time from the Presentation clock + * + * \param compositor + * \param[out] ts The current time. + * + * \note Reading the current time in user space is always imprecise to some + * degree. + * + * This function is never meant to fail. If reading the clock does fail, + * an error message is logged and a zero time is returned. Callers are not + * supposed to detect or react to failures. + * + * \ingroup compositor + */ +WL_EXPORT void +weston_compositor_read_presentation_clock( + const struct weston_compositor *compositor, + struct timespec *ts) +{ + static bool warned; + int ret; + + ret = clock_gettime(compositor->presentation_clock, ts); + if (ret < 0) { + ts->tv_sec = 0; + ts->tv_nsec = 0; + + if (!warned) + weston_log("Error: failure to read " + "the presentation clock %#x: '%s' (%d)\n", + compositor->presentation_clock, + strerror(errno), errno); + warned = true; + } +} + +/** Import dmabuf buffer into current renderer + * + * \param compositor + * \param buffer the dmabuf buffer to import + * \return true on usable buffers, false otherwise + * + * This function tests that the linux_dmabuf_buffer is usable + * for the current renderer. Returns false on unusable buffers. Usually + * usability is tested by importing the dmabufs for composition. + * + * This hook is also used for detecting if the renderer supports + * dmabufs at all. If the renderer hook is NULL, dmabufs are not + * supported. + * + * \ingroup compositor + */ +WL_EXPORT bool +weston_compositor_import_dmabuf(struct weston_compositor *compositor, + struct linux_dmabuf_buffer *buffer) +{ + struct weston_renderer *renderer; + + renderer = compositor->renderer; + + if (renderer->import_dmabuf == NULL) + return false; + + return renderer->import_dmabuf(compositor, buffer); +} + +WL_EXPORT bool +weston_compositor_dmabuf_can_scanout(struct weston_compositor *compositor, + struct linux_dmabuf_buffer *buffer) +{ + struct weston_backend *backend = compositor->backend; + + if (backend->can_scanout_dmabuf == NULL) + return false; + + return backend->can_scanout_dmabuf(compositor, buffer); +} + +WL_EXPORT void +weston_version(int *major, int *minor, int *micro) +{ + *major = WESTON_VERSION_MAJOR; + *minor = WESTON_VERSION_MINOR; + *micro = WESTON_VERSION_MICRO; +} + +/** + * Attempts to find a module path from the module map specified in the + * environment. If found, writes the full path into the path variable. + * + * The module map is a string in environment variable WESTON_MODULE_MAP, where + * each entry is of the form "name=path" and entries are separated by + * semicolons. Whitespace is significant. + * + * \param name The name to search for. + * \param path Where the path is written to if found. + * \param path_len Allocated bytes at \c path . + * \returns The length of the string written to path on success, or 0 if the + * module was not specified in the environment map or path_len was too small. + */ +WL_EXPORT size_t +weston_module_path_from_env(const char *name, char *path, size_t path_len) +{ + const char *mapping = getenv("WESTON_MODULE_MAP"); + const char *end; + const int name_len = strlen(name); + + if (!mapping) + return 0; + + end = mapping + strlen(mapping); + while (mapping < end && *mapping) { + const char *filename, *next; + + /* early out: impossibly short string */ + if (end - mapping < name_len + 1) + return 0; + + filename = &mapping[name_len + 1]; + next = strchrnul(mapping, ';'); + + if (strncmp(mapping, name, name_len) == 0 && + mapping[name_len] == '=') { + size_t file_len = next - filename; /* no trailing NUL */ + if (file_len >= path_len) + return 0; + strncpy(path, filename, file_len); + path[file_len] = '\0'; + return file_len; + } + + mapping = next + 1; + } + + return 0; +} + +WL_EXPORT void * +weston_load_module(const char *name, const char *entrypoint) +{ + char path[PATH_MAX]; + void *module, *init; + size_t len; + + if (name == NULL) + return NULL; + + if (name[0] != '/') { + len = weston_module_path_from_env(name, path, sizeof path); + if (len == 0) + len = snprintf(path, sizeof path, "%s/%s", + LIBWESTON_MODULEDIR, name); + } else { + len = snprintf(path, sizeof path, "%s", name); + } + + /* snprintf returns the length of the string it would've written, + * _excluding_ the NUL byte. So even being equal to the size of + * our buffer is an error here. */ + if (len >= sizeof path) + return NULL; + + module = dlopen(path, RTLD_NOW | RTLD_NOLOAD); + if (module) { + weston_log("Module '%s' already loaded\n", path); + } else { + 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; +} + +/** Add a compositor destroy listener only once + * + * \param compositor The compositor whose destroy to watch for. + * \param listener The listener struct to initialize. + * \param destroy_handler The callback when compositor is destroyed. + * \return True if listener is added, or false if there already is a listener + * with the given \c destroy_handler. + * + * This function does nothing and returns false if the given callback function + * is already present in the weston_compositor destroy callbacks list. + * Otherwise, this function initializes the given listener with the given + * callback pointer and adds it to the compositor's destroy callbacks list. + * + * This can be used to ensure that plugin initialization is done only once + * in case the same plugin is loaded multiple times. If this function returns + * false, the plugin should be already initialized successfully. + * + * All plugins should register a destroy listener for cleaning up. Note, that + * the plugin destruction order is not guaranteed: plugins that depend on other + * plugins must be able to be torn down in arbitrary order. + * + * \sa weston_compositor_destroy + */ +WL_EXPORT bool +weston_compositor_add_destroy_listener_once(struct weston_compositor *compositor, + struct wl_listener *listener, + wl_notify_func_t destroy_handler) +{ + if (wl_signal_get(&compositor->destroy_signal, destroy_handler)) + return false; + + listener->notify = destroy_handler; + wl_signal_add(&compositor->destroy_signal, listener); + return true; +} + +/** Destroys the compositor. + * + * This function cleans up the compositor state and then destroys it. + * + * @param compositor The compositor to be destroyed. + * + * @ingroup compositor + */ +WL_EXPORT void +weston_compositor_destroy(struct weston_compositor *compositor) +{ + /* prevent further rendering while shutting down */ + compositor->state = WESTON_COMPOSITOR_OFFSCREEN; + + wl_signal_emit(&compositor->destroy_signal, compositor); + + weston_compositor_xkb_destroy(compositor); + + if (compositor->backend) + compositor->backend->destroy(compositor); + + /* The backend is responsible for destroying the heads. */ + assert(wl_list_empty(&compositor->head_list)); + + weston_plugin_api_destroy_list(compositor); + + if (compositor->heads_changed_source) + wl_event_source_remove(compositor->heads_changed_source); + +// OHOS remove logger +// weston_log_scope_destroy(compositor->debug_scene); +// compositor->debug_scene = NULL; +// +// weston_log_scope_destroy(compositor->timeline); +// compositor->timeline = NULL; + + free(compositor); +} + +/** Instruct the compositor to exit. + * + * This functions does not directly destroy the compositor object, it merely + * command it to start the tear down process. It is not guaranteed that the + * tear down will happen immediately. + * + * \param compositor The compositor to tear down. + * + * \ingroup compositor + */ +WL_EXPORT void +weston_compositor_exit(struct weston_compositor *compositor) +{ + compositor->exit(compositor); +} + +/** Return the user data stored in the compositor. + * + * This function returns the user data pointer set with user_data parameter + * to the \ref weston_compositor_create function. + * + * \ingroup compositor + */ +WL_EXPORT void * +weston_compositor_get_user_data(struct weston_compositor *compositor) +{ + return compositor->user_data; +} + +static const char * const backend_map[] = { + [WESTON_BACKEND_DRM] = "drm-backend.so", + [WESTON_BACKEND_FBDEV] = "fbdev-backend.so", + [WESTON_BACKEND_HEADLESS] = "headless-backend.so", + [WESTON_BACKEND_RDP] = "rdp-backend.so", + [WESTON_BACKEND_WAYLAND] = "wayland-backend.so", + [WESTON_BACKEND_X11] = "x11-backend.so", +}; + +/** Load a backend into a weston_compositor + * + * A backend must be loaded to make a weston_compositor work. A backend + * provides input and output capabilities, and determines the renderer to use. + * + * \param compositor A compositor that has not had a backend loaded yet. + * \param backend Name of the backend file. + * \param config_base A pointer to a backend-specific configuration + * structure's 'base' member. + * + * \return 0 on success, or -1 on error. + * + * \ingroup compositor + */ +WL_EXPORT int +weston_compositor_load_backend(struct weston_compositor *compositor, + enum weston_compositor_backend backend, + struct weston_backend_config *config_base) +{ + int (*backend_init)(struct weston_compositor *c, + struct weston_backend_config *config_base); + + if (compositor->backend) { + weston_log("Error: attempt to load a backend when one is already loaded\n"); + return -1; + } + + if (backend >= ARRAY_LENGTH(backend_map)) + return -1; + + backend_init = weston_load_module(backend_map[backend], "weston_backend_init"); + if (!backend_init) + return -1; + + if (backend_init(compositor, config_base) < 0) { + compositor->backend = NULL; + return -1; + } + + return 0; +} + +/** weston_compositor_load_xwayland + * \ingroup compositor + */ +WL_EXPORT int +weston_compositor_load_xwayland(struct weston_compositor *compositor) +{ + int (*module_init)(struct weston_compositor *ec); + + module_init = weston_load_module("xwayland.so", "weston_module_init"); + if (!module_init) + return -1; + if (module_init(compositor) < 0) + return -1; + return 0; +} + +/** Resolve an internal compositor error by disconnecting the client. + * + * This function is used in cases when the wl_buffer turns out + * unusable and there is no fallback path. + * + * It is possible the fault is caused by a compositor bug, the underlying + * graphics stack bug or normal behaviour, or perhaps a client mistake. + * In any case, the options are to either composite garbage or nothing, + * or disconnect the client. This is a helper function for the latter. + * + * The error is sent as an INVALID_OBJECT error on the client's wl_display. + * + * \param buffer The weston buffer that is unusable. + * \param msg A custom error message attached to the protocol error. + */ +WL_EXPORT void +weston_buffer_send_server_error(struct weston_buffer *buffer, + const char *msg) +{ + struct wl_client *client; + struct wl_resource *display_resource; + uint32_t id; + + assert(buffer->resource); + id = wl_resource_get_id(buffer->resource); + client = wl_resource_get_client(buffer->resource); + display_resource = wl_client_get_object(client, 1); + + assert(display_resource); + wl_resource_post_error(display_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "server error with " + "wl_buffer@%u: %s", id, msg); +} + +WL_EXPORT void +weston_output_disable_planes_incr(struct weston_output *output) +{ + output->disable_planes++; + /* + * If disable_planes changes from 0 to non-zero, it means some type of + * recording of content has started, and therefore protection level of + * the protected surfaces must be updated to avoid the recording of + * the protected content. + */ + if (output->disable_planes == 1) + weston_schedule_surface_protection_update(output->compositor); +} + +WL_EXPORT void +weston_output_disable_planes_decr(struct weston_output *output) +{ + output->disable_planes--; + /* + * If disable_planes changes from non-zero to 0, it means no content + * recording is going on any more, and the protected and surfaces can be + * shown without any apprehensions about content being recorded. + */ + if (output->disable_planes == 0) + weston_schedule_surface_protection_update(output->compositor); + +} diff --git a/libweston/content-protection.c b/libweston/content-protection.c new file mode 100755 index 0000000..0f5f893 --- /dev/null +++ b/libweston/content-protection.c @@ -0,0 +1,352 @@ +/* + * Copyright © 2019 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "libweston-internal.h" +#include "weston-content-protection-server-protocol.h" +#include "shared/helpers.h" +#include "shared/timespec-util.h" + +// OHOS logcat +//#define content_protection_log(cp, ...) \ +// weston_log_scope_printf((cp)->debug, __VA_ARGS__) +#define content_protection_log(cp, ...) \ + weston_log(__VA_ARGS__) + +static const char * const content_type_name [] = { + [WESTON_PROTECTED_SURFACE_TYPE_UNPROTECTED] = "UNPROTECTED", + [WESTON_PROTECTED_SURFACE_TYPE_HDCP_0] = "TYPE-0", + [WESTON_PROTECTED_SURFACE_TYPE_HDCP_1] = "TYPE-1", +}; + +void +weston_protected_surface_send_event(struct protected_surface *psurface, + enum weston_hdcp_protection protection) +{ + struct wl_resource *p_resource; + enum weston_protected_surface_type protection_type; + struct content_protection *cp; + struct wl_resource *surface_resource; + + p_resource = psurface->protection_resource; + if (!p_resource) + return; + /* No event to be sent to client, in case of enforced mode */ + if (psurface->surface->protection_mode == WESTON_SURFACE_PROTECTION_MODE_ENFORCED) + return; + protection_type = (enum weston_protected_surface_type) protection; + weston_protected_surface_send_status(p_resource, protection_type); + + cp = psurface->cp_backptr; + surface_resource = psurface->surface->resource; + content_protection_log(cp, "wl_surface@%"PRIu32" Protection type set to %s\n", + wl_resource_get_id(surface_resource), + content_type_name[protection_type]); +} + +static void +set_type(struct wl_client *client, struct wl_resource *resource, + enum weston_protected_surface_type content_type) +{ + struct content_protection *cp; + struct protected_surface *psurface; + enum weston_hdcp_protection weston_cp; + struct wl_resource *surface_resource; + + psurface = wl_resource_get_user_data(resource); + if (!psurface) + return; + cp = psurface->cp_backptr; + surface_resource = psurface->surface->resource; + + if (content_type < WESTON_PROTECTED_SURFACE_TYPE_UNPROTECTED || + content_type > WESTON_PROTECTED_SURFACE_TYPE_HDCP_1) { + wl_resource_post_error(resource, + WESTON_PROTECTED_SURFACE_ERROR_INVALID_TYPE, + "wl_surface@%"PRIu32" Invalid content-type %d for request:set_type\n", + wl_resource_get_id(surface_resource), content_type); + + content_protection_log(cp, "wl_surface@%"PRIu32" Invalid content-type %d for resquest:set_type\n", + wl_resource_get_id(surface_resource), content_type); + return; + } + + content_protection_log(cp, "wl_surface@%"PRIu32" Request: Enable Content-Protection Type: %s\n", + wl_resource_get_id(surface_resource), + content_type_name[content_type]); + + weston_cp = (enum weston_hdcp_protection) content_type; + psurface->surface->pending.desired_protection = weston_cp; +} + +static void +protected_surface_destroy(struct wl_client *client, struct wl_resource *resource) +{ + struct protected_surface *psurface; + + psurface = wl_resource_get_user_data(resource); + if (!psurface) + return; + psurface->surface->pending.desired_protection = WESTON_HDCP_DISABLE; +} + +static void +set_enforce_mode(struct wl_client *client, struct wl_resource *resource) +{ + /* + * Enforce Censored-Visibility. Compositor censors the protected + * surface on an unsecured output. + * In case of a surface, being shown on an unprotected output, the + * compositor hides the surface, not allowing it to be displayed on + * the unprotected output, without bothering the client. No difference + * for the protected outputs. + * + * The member 'protection_mode' is "double-buffered", so setting it in + * pending_state will cause the setting of the corresponding + * 'protection_mode' in weston_surface, after the commit. + * + * This function sets the 'protection_mode' of the weston_surface_state + * to 'enfoced'. The renderers inspect the flag and compare the + * desired_protection of the surface, to the current_protection of the + * output, based on that the real surface or a place-holder content, + * (e.g. solid color) are shown. + */ + + struct protected_surface *psurface; + + psurface = wl_resource_get_user_data(resource); + if (!psurface) + return; + + psurface->surface->pending.protection_mode = + WESTON_SURFACE_PROTECTION_MODE_ENFORCED; +} + +static void +set_relax_mode(struct wl_client *client, struct wl_resource *resource) +{ + /* + * Relaxed mode. By default this mode will be activated. + * In case of a surface, being shown in unprotected output, + * compositor just sends the event for protection status changed. + * + * On setting the relaxed mode, the 'protection_mode' member is queued + * to be set to 'relax' from the existing 'enforce' mode. + */ + + struct protected_surface *psurface; + + psurface = wl_resource_get_user_data(resource); + if (!psurface) + return; + + psurface->surface->pending.protection_mode = + WESTON_SURFACE_PROTECTION_MODE_RELAXED; +} + +static const struct weston_protected_surface_interface protected_surface_implementation = { + protected_surface_destroy, + set_type, + set_enforce_mode, + set_relax_mode, +}; + +static void +cp_destroy_listener(struct wl_listener *listener, void *data) +{ + struct content_protection *cp; + + cp = container_of(listener, struct content_protection, + destroy_listener); + wl_list_remove(&cp->destroy_listener.link); + wl_list_remove(&cp->protected_list); + // OHOS remove logger + // weston_log_scope_destroy(cp->debug); + // cp->debug = NULL; + cp->surface_protection_update = NULL; + free(cp); +} + +static void +free_protected_surface(struct protected_surface *psurface) +{ + psurface->surface->pending.desired_protection = WESTON_HDCP_DISABLE; + wl_resource_set_user_data(psurface->protection_resource, NULL); + wl_list_remove(&psurface->surface_destroy_listener.link); + wl_list_remove(&psurface->link); + free(psurface); +} + +static void +surface_destroyed(struct wl_listener *listener, void *data) +{ + struct protected_surface *psurface; + + psurface = container_of(listener, struct protected_surface, + surface_destroy_listener); + free_protected_surface(psurface); +} + +static void +destroy_protected_surface(struct wl_resource *resource) +{ + struct protected_surface *psurface; + + psurface = wl_resource_get_user_data(resource); + if (!psurface) + return; + free_protected_surface(psurface); +} + +static void +get_protection(struct wl_client *client, struct wl_resource *cp_resource, + uint32_t id, struct wl_resource *surface_resource) +{ + struct wl_resource *resource; + struct weston_surface *surface; + struct content_protection *cp; + struct protected_surface *psurface; + struct wl_listener *listener; + + surface = wl_resource_get_user_data(surface_resource); + assert(surface); + cp = wl_resource_get_user_data(cp_resource); + assert(cp); + + /* + * Check if this client has a corresponding protected-surface + */ + + listener = wl_resource_get_destroy_listener(surface->resource, + surface_destroyed); + + if (listener) { + wl_resource_post_error(cp_resource, + WESTON_CONTENT_PROTECTION_ERROR_SURFACE_EXISTS, + "wl_surface@%"PRIu32" Protection already exists", + wl_resource_get_id(surface_resource)); + return; + } + + psurface = zalloc(sizeof(struct protected_surface)); + if (!psurface) { + wl_client_post_no_memory(client); + return; + } + psurface->cp_backptr = cp; + resource = wl_resource_create(client, &weston_protected_surface_interface, + 1, id); + if (!resource) { + free(psurface); + wl_client_post_no_memory(client); + return; + } + + wl_list_insert(&cp->protected_list, &psurface->link); + wl_resource_set_implementation(resource, &protected_surface_implementation, + psurface, + destroy_protected_surface); + + psurface->protection_resource = resource; + psurface->surface = surface; + psurface->surface_destroy_listener.notify = surface_destroyed; + wl_resource_add_destroy_listener(surface->resource, + &psurface->surface_destroy_listener); + weston_protected_surface_send_event(psurface, + psurface->surface->current_protection); +} + +static void +destroy_protection(struct wl_client *client, struct wl_resource *cp_resource) +{ + wl_resource_destroy(cp_resource); +} + +static const +struct weston_content_protection_interface content_protection_implementation = { + destroy_protection, + get_protection, +}; + +static void +bind_weston_content_protection(struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct content_protection *cp = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, + &weston_content_protection_interface, + 1, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, + &content_protection_implementation, + cp, NULL); +} +/* Advertise the content-protection support. + * + * Calling this function sets up the content-protection support via HDCP. + * This exposes the global interface, visible to the client, enabling them to + * request for content-protection for their surfaces according to the type of + * content. + */ + +WL_EXPORT int +weston_compositor_enable_content_protection(struct weston_compositor *compositor) +{ + struct content_protection *cp; + + cp = zalloc(sizeof(*cp)); + if (cp == NULL) + return -1; + cp->compositor = compositor; + + compositor->content_protection = cp; + wl_list_init(&cp->protected_list); + if (wl_global_create(compositor->wl_display, + &weston_content_protection_interface, 1, cp, + bind_weston_content_protection) == NULL) + return -1; + + cp->destroy_listener.notify = cp_destroy_listener; + wl_signal_add(&compositor->destroy_signal, &cp->destroy_listener); +// OHOS remove logger +// cp->debug = weston_compositor_add_log_scope(compositor, "content-protection-debug", +// "debug-logs for content-protection", +// NULL, NULL, NULL); + return 0; +} diff --git a/libweston/data-device.c b/libweston/data-device.c new file mode 100644 index 0000000..8b0a282 --- /dev/null +++ b/libweston/data-device.c @@ -0,0 +1,1370 @@ +/* + * Copyright © 2011 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include "libweston-internal.h" +#include "shared/helpers.h" +#include "shared/timespec-util.h" + +struct weston_drag { + struct wl_client *client; + struct weston_data_source *data_source; + struct wl_listener data_source_listener; + struct weston_view *focus; + struct wl_resource *focus_resource; + struct wl_listener focus_listener; + struct weston_view *icon; + struct wl_listener icon_destroy_listener; + int32_t dx, dy; + struct weston_keyboard_grab keyboard_grab; +}; + +struct weston_pointer_drag { + struct weston_drag base; + struct weston_pointer_grab grab; +}; + +struct weston_touch_drag { + struct weston_drag base; + struct weston_touch_grab grab; +}; + +#define ALL_ACTIONS (WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | \ + WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | \ + WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) + +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); + + /* Protect against untimely calls from older data offers */ + if (!offer->source || offer != offer->source->offer) + return; + + /* 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? */ + + offer->source->accept(offer->source, serial, mime_type); + offer->source->accepted = mime_type != NULL; +} + +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 == offer->source->offer) + 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 void +data_source_notify_finish(struct weston_data_source *source) +{ + if (!source->actions_set) + return; + + if (source->offer->in_ask && + wl_resource_get_version(source->resource) >= + WL_DATA_SOURCE_ACTION_SINCE_VERSION) { + wl_data_source_send_action(source->resource, + source->current_dnd_action); + } + + if (wl_resource_get_version(source->resource) >= + WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION) { + wl_data_source_send_dnd_finished(source->resource); + } + + source->offer = NULL; +} + +static uint32_t +data_offer_choose_action(struct weston_data_offer *offer) +{ + uint32_t available_actions, preferred_action = 0; + uint32_t source_actions, offer_actions; + + if (wl_resource_get_version(offer->resource) >= + WL_DATA_OFFER_ACTION_SINCE_VERSION) { + offer_actions = offer->dnd_actions; + preferred_action = offer->preferred_dnd_action; + } else { + offer_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + } + + if (wl_resource_get_version(offer->source->resource) >= + WL_DATA_SOURCE_ACTION_SINCE_VERSION) + source_actions = offer->source->dnd_actions; + else + source_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + + available_actions = offer_actions & source_actions; + + if (!available_actions) + return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + + if (offer->source->seat && + offer->source->compositor_action & available_actions) + return offer->source->compositor_action; + + /* If the dest side has a preferred DnD action, use it */ + if ((preferred_action & available_actions) != 0) + return preferred_action; + + /* Use the first found action, in bit order */ + return 1 << (ffs(available_actions) - 1); +} + +static void +data_offer_update_action(struct weston_data_offer *offer) +{ + uint32_t action; + + if (!offer->source) + return; + + action = data_offer_choose_action(offer); + + if (offer->source->current_dnd_action == action) + return; + + offer->source->current_dnd_action = action; + + if (offer->in_ask) + return; + + if (wl_resource_get_version(offer->source->resource) >= + WL_DATA_SOURCE_ACTION_SINCE_VERSION) + wl_data_source_send_action(offer->source->resource, action); + + if (wl_resource_get_version(offer->resource) >= + WL_DATA_OFFER_ACTION_SINCE_VERSION) + wl_data_offer_send_action(offer->resource, action); +} + +static void +data_offer_set_actions(struct wl_client *client, + struct wl_resource *resource, + uint32_t dnd_actions, uint32_t preferred_action) +{ + struct weston_data_offer *offer = wl_resource_get_user_data(resource); + + if (dnd_actions & ~ALL_ACTIONS) { + wl_resource_post_error(offer->resource, + WL_DATA_OFFER_ERROR_INVALID_ACTION_MASK, + "invalid action mask %x", dnd_actions); + return; + } + + if (preferred_action && + (!(preferred_action & dnd_actions) || + __builtin_popcount(preferred_action) > 1)) { + wl_resource_post_error(offer->resource, + WL_DATA_OFFER_ERROR_INVALID_ACTION, + "invalid action %x", preferred_action); + return; + } + + offer->dnd_actions = dnd_actions; + offer->preferred_dnd_action = preferred_action; + data_offer_update_action(offer); +} + +static void +data_offer_finish(struct wl_client *client, struct wl_resource *resource) +{ + struct weston_data_offer *offer = wl_resource_get_user_data(resource); + + if (!offer->source || offer->source->offer != offer) + return; + + if (offer->source->set_selection) { + wl_resource_post_error(offer->resource, + WL_DATA_OFFER_ERROR_INVALID_FINISH, + "finish only valid for drag n drop"); + return; + } + + /* Disallow finish while we have a grab driving drag-and-drop, or + * if the negotiation is not at the right stage + */ + if (offer->source->seat || + !offer->source->accepted) { + wl_resource_post_error(offer->resource, + WL_DATA_OFFER_ERROR_INVALID_FINISH, + "premature finish request"); + return; + } + + switch (offer->source->current_dnd_action) { + case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE: + case WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK: + wl_resource_post_error(offer->resource, + WL_DATA_OFFER_ERROR_INVALID_OFFER, + "offer finished with an invalid action"); + return; + default: + break; + } + + data_source_notify_finish(offer->source); +} + +static const struct wl_data_offer_interface data_offer_interface = { + data_offer_accept, + data_offer_receive, + data_offer_destroy, + data_offer_finish, + data_offer_set_actions, +}; + +static void +destroy_data_offer(struct wl_resource *resource) +{ + struct weston_data_offer *offer = wl_resource_get_user_data(resource); + + if (!offer->source) + goto out; + + wl_list_remove(&offer->source_destroy_listener.link); + + if (offer->source->offer != offer) + goto out; + + /* If the drag destination has version < 3, wl_data_offer.finish + * won't be called, so do this here as a safety net, because + * we still want the version >=3 drag source to be happy. + */ + if (wl_resource_get_version(offer->resource) < + WL_DATA_OFFER_ACTION_SINCE_VERSION) { + data_source_notify_finish(offer->source); + } else if (offer->source->resource && + wl_resource_get_version(offer->source->resource) >= + WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION) { + wl_data_source_send_cancelled(offer->source->resource); + } + + offer->source->offer = NULL; +out: + 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 weston_data_offer * +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, + wl_resource_get_version(target), 0); + if (offer->resource == NULL) { + free(offer); + return NULL; + } + + wl_resource_set_implementation(offer->resource, &data_offer_interface, + offer, destroy_data_offer); + + offer->in_ask = false; + offer->dnd_actions = 0; + offer->preferred_dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + 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); + + source->offer = offer; + source->accepted = false; + + return offer; +} + +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 void +data_source_set_actions(struct wl_client *client, + struct wl_resource *resource, + uint32_t dnd_actions) +{ + struct weston_data_source *source = + wl_resource_get_user_data(resource); + + if (source->actions_set) { + wl_resource_post_error(source->resource, + WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK, + "cannot set actions more than once"); + return; + } + + if (dnd_actions & ~ALL_ACTIONS) { + wl_resource_post_error(source->resource, + WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK, + "invalid action mask %x", dnd_actions); + return; + } + + if (source->seat) { + wl_resource_post_error(source->resource, + WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK, + "invalid action change after " + "wl_data_device.start_drag"); + return; + } + + source->dnd_actions = dnd_actions; + source->actions_set = true; +} + +static struct wl_data_source_interface data_source_interface = { + data_source_offer, + data_source_destroy, + data_source_set_actions +}; + +static void +drag_surface_configure(struct weston_drag *drag, + struct weston_pointer *pointer, + struct weston_touch *touch, + struct weston_surface *es, + int32_t sx, int32_t sy) +{ + struct weston_layer_entry *list; + float fx, fy; + + assert((pointer != NULL && touch == NULL) || + (pointer == NULL && touch != NULL)); + + if (!weston_surface_is_mapped(es) && es->buffer_ref.buffer) { + if (pointer && pointer->sprite && + weston_view_is_mapped(pointer->sprite)) + list = &pointer->sprite->layer_link; + else + list = &es->compositor->cursor_layer.view_list; + + weston_layer_entry_remove(&drag->icon->layer_link); + weston_layer_entry_insert(list, &drag->icon->layer_link); + weston_view_update_transform(drag->icon); + pixman_region32_clear(&es->pending.input); + es->is_mapped = true; + drag->icon->is_mapped = true; + } + + drag->dx += sx; + drag->dy += sy; + + /* init to 0 for avoiding a compile warning */ + fx = fy = 0; + if (pointer) { + fx = wl_fixed_to_double(pointer->x) + drag->dx; + fy = wl_fixed_to_double(pointer->y) + drag->dy; + } else if (touch) { + fx = wl_fixed_to_double(touch->grab_x) + drag->dx; + fy = wl_fixed_to_double(touch->grab_y) + drag->dy; + } + weston_view_set_position(drag->icon, fx, fy); +} + +static int +pointer_drag_surface_get_label(struct weston_surface *surface, + char *buf, size_t len) +{ + return snprintf(buf, len, "pointer drag icon"); +} + +static void +pointer_drag_surface_committed(struct weston_surface *es, + int32_t sx, int32_t sy) +{ + struct weston_pointer_drag *drag = es->committed_private; + struct weston_pointer *pointer = drag->grab.pointer; + + assert(es->committed == pointer_drag_surface_committed); + + drag_surface_configure(&drag->base, pointer, NULL, es, sx, sy); +} + +static int +touch_drag_surface_get_label(struct weston_surface *surface, + char *buf, size_t len) +{ + return snprintf(buf, len, "touch drag icon"); +} + +static void +touch_drag_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) +{ + struct weston_touch_drag *drag = es->committed_private; + struct weston_touch *touch = drag->grab.touch; + + assert(es->committed == touch_drag_surface_committed); + + drag_surface_configure(&drag->base, NULL, touch, es, sx, sy); +} + +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_seat *seat, + struct weston_view *view, + wl_fixed_t sx, wl_fixed_t sy) +{ + struct wl_resource *resource, *offer_resource = NULL; + struct wl_display *display = seat->compositor->wl_display; + struct weston_data_offer *offer; + uint32_t serial; + + if (drag->focus && view && drag->focus->surface == view->surface) { + drag->focus = view; + return; + } + + 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 (!view || !view->surface->resource) + return; + + if (!drag->data_source && + wl_resource_get_client(view->surface->resource) != drag->client) + return; + + if (drag->data_source && + drag->data_source->offer) { + /* Unlink the offer from the source */ + offer = drag->data_source->offer; + offer->source = NULL; + drag->data_source->offer = NULL; + wl_list_remove(&offer->source_destroy_listener.link); + } + + resource = wl_resource_find_for_client(&seat->drag_resource_list, + wl_resource_get_client(view->surface->resource)); + if (!resource) + return; + + serial = wl_display_next_serial(display); + + if (drag->data_source) { + drag->data_source->accepted = false; + offer = weston_data_source_send_offer(drag->data_source, resource); + if (offer == NULL) + return; + + data_offer_update_action(offer); + + offer_resource = offer->resource; + if (wl_resource_get_version (offer_resource) >= + WL_DATA_OFFER_SOURCE_ACTIONS_SINCE_VERSION) { + wl_data_offer_send_source_actions (offer_resource, + drag->data_source->dnd_actions); + } + } + + wl_data_device_send_enter(resource, serial, view->surface->resource, + sx, sy, offer_resource); + + drag->focus = view; + 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_pointer_drag *drag = + container_of(grab, struct weston_pointer_drag, grab); + struct weston_pointer *pointer = grab->pointer; + struct weston_view *view; + wl_fixed_t sx, sy; + + view = weston_compositor_pick_view(pointer->seat->compositor, + pointer->x, pointer->y, + &sx, &sy); + if (drag->base.focus != view) + weston_drag_set_focus(&drag->base, pointer->seat, view, sx, sy); +} + +static void +drag_grab_motion(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + struct weston_pointer_drag *drag = + container_of(grab, struct weston_pointer_drag, grab); + struct weston_pointer *pointer = drag->grab.pointer; + float fx, fy; + wl_fixed_t sx, sy; + uint32_t msecs; + + weston_pointer_move(pointer, event); + + if (drag->base.icon) { + fx = wl_fixed_to_double(pointer->x) + drag->base.dx; + fy = wl_fixed_to_double(pointer->y) + drag->base.dy; + weston_view_set_position(drag->base.icon, fx, fy); + weston_view_schedule_repaint(drag->base.icon); + } + + if (drag->base.focus_resource) { + msecs = timespec_to_msec(time); + weston_view_from_global_fixed(drag->base.focus, + pointer->x, pointer->y, + &sx, &sy); + + wl_data_device_send_motion(drag->base.focus_resource, msecs, sx, sy); + } +} + +static void +data_device_end_drag_grab(struct weston_drag *drag, + struct weston_seat *seat) +{ + if (drag->icon) { + if (weston_view_is_mapped(drag->icon)) + weston_view_unmap(drag->icon); + + drag->icon->surface->committed = NULL; + weston_surface_set_label_func(drag->icon->surface, NULL); + pixman_region32_clear(&drag->icon->surface->pending.input); + wl_list_remove(&drag->icon_destroy_listener.link); + weston_view_destroy(drag->icon); + } + + weston_drag_set_focus(drag, seat, NULL, 0, 0); +} + +static void +data_device_end_pointer_drag_grab(struct weston_pointer_drag *drag) +{ + struct weston_pointer *pointer = drag->grab.pointer; + struct weston_keyboard *keyboard = drag->base.keyboard_grab.keyboard; + + data_device_end_drag_grab(&drag->base, pointer->seat); + weston_pointer_end_grab(pointer); + weston_keyboard_end_grab(keyboard); + free(drag); +} + +static void +drag_grab_button(struct weston_pointer_grab *grab, + const struct timespec *time, + uint32_t button, uint32_t state_w) +{ + struct weston_pointer_drag *drag = + container_of(grab, struct weston_pointer_drag, grab); + struct weston_pointer *pointer = drag->grab.pointer; + enum wl_pointer_button_state state = state_w; + struct weston_data_source *data_source = drag->base.data_source; + + if (data_source && + pointer->grab_button == button && + state == WL_POINTER_BUTTON_STATE_RELEASED) { + if (drag->base.focus_resource && + data_source->accepted && + data_source->current_dnd_action) { + wl_data_device_send_drop(drag->base.focus_resource); + + if (wl_resource_get_version(data_source->resource) >= + WL_DATA_SOURCE_DND_DROP_PERFORMED_SINCE_VERSION) + wl_data_source_send_dnd_drop_performed(data_source->resource); + + data_source->offer->in_ask = + data_source->current_dnd_action == + WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + + data_source->seat = NULL; + } else if (wl_resource_get_version(data_source->resource) >= + WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION) { + wl_data_source_send_cancelled(data_source->resource); + } + } + + if (pointer->button_count == 0 && + state == WL_POINTER_BUTTON_STATE_RELEASED) { + if (drag->base.data_source) + wl_list_remove(&drag->base.data_source_listener.link); + data_device_end_pointer_drag_grab(drag); + } +} + +static void +drag_grab_axis(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_axis_event *event) +{ +} + +static void +drag_grab_axis_source(struct weston_pointer_grab *grab, uint32_t source) +{ +} + +static void +drag_grab_frame(struct weston_pointer_grab *grab) +{ +} + +static void +drag_grab_cancel(struct weston_pointer_grab *grab) +{ + struct weston_pointer_drag *drag = + container_of(grab, struct weston_pointer_drag, grab); + + if (drag->base.data_source) + wl_list_remove(&drag->base.data_source_listener.link); + + data_device_end_pointer_drag_grab(drag); +} + +static const struct weston_pointer_grab_interface pointer_drag_grab_interface = { + drag_grab_focus, + drag_grab_motion, + drag_grab_button, + drag_grab_axis, + drag_grab_axis_source, + drag_grab_frame, + drag_grab_cancel, +}; + +static void +drag_grab_touch_down(struct weston_touch_grab *grab, + const struct timespec *time, int touch_id, + wl_fixed_t sx, wl_fixed_t sy) +{ +} + +static void +data_device_end_touch_drag_grab(struct weston_touch_drag *drag) +{ + struct weston_touch *touch = drag->grab.touch; + struct weston_keyboard *keyboard = drag->base.keyboard_grab.keyboard; + + data_device_end_drag_grab(&drag->base, touch->seat); + weston_touch_end_grab(touch); + weston_keyboard_end_grab(keyboard); + free(drag); +} + +static void +drag_grab_touch_up(struct weston_touch_grab *grab, + const struct timespec *time, int touch_id) +{ + struct weston_touch_drag *touch_drag = + container_of(grab, struct weston_touch_drag, grab); + struct weston_touch *touch = grab->touch; + + if (touch_id != touch->grab_touch_id) + return; + + if (touch_drag->base.focus_resource) + wl_data_device_send_drop(touch_drag->base.focus_resource); + if (touch_drag->base.data_source) + wl_list_remove(&touch_drag->base.data_source_listener.link); + data_device_end_touch_drag_grab(touch_drag); +} + +static void +drag_grab_touch_focus(struct weston_touch_drag *drag) +{ + struct weston_touch *touch = drag->grab.touch; + struct weston_view *view; + wl_fixed_t view_x, view_y; + + view = weston_compositor_pick_view(touch->seat->compositor, + touch->grab_x, touch->grab_y, + &view_x, &view_y); + if (drag->base.focus != view) + weston_drag_set_focus(&drag->base, touch->seat, + view, view_x, view_y); +} + +static void +drag_grab_touch_motion(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id, wl_fixed_t x, wl_fixed_t y) +{ + struct weston_touch_drag *touch_drag = + container_of(grab, struct weston_touch_drag, grab); + struct weston_touch *touch = grab->touch; + wl_fixed_t view_x, view_y; + float fx, fy; + uint32_t msecs; + + if (touch_id != touch->grab_touch_id) + return; + + drag_grab_touch_focus(touch_drag); + if (touch_drag->base.icon) { + fx = wl_fixed_to_double(touch->grab_x) + touch_drag->base.dx; + fy = wl_fixed_to_double(touch->grab_y) + touch_drag->base.dy; + weston_view_set_position(touch_drag->base.icon, fx, fy); + weston_view_schedule_repaint(touch_drag->base.icon); + } + + if (touch_drag->base.focus_resource) { + msecs = timespec_to_msec(time); + weston_view_from_global_fixed(touch_drag->base.focus, + touch->grab_x, touch->grab_y, + &view_x, &view_y); + wl_data_device_send_motion(touch_drag->base.focus_resource, + msecs, view_x, view_y); + } +} + +static void +drag_grab_touch_frame(struct weston_touch_grab *grab) +{ +} + +static void +drag_grab_touch_cancel(struct weston_touch_grab *grab) +{ + struct weston_touch_drag *touch_drag = + container_of(grab, struct weston_touch_drag, grab); + + if (touch_drag->base.data_source) + wl_list_remove(&touch_drag->base.data_source_listener.link); + data_device_end_touch_drag_grab(touch_drag); +} + +static const struct weston_touch_grab_interface touch_drag_grab_interface = { + drag_grab_touch_down, + drag_grab_touch_up, + drag_grab_touch_motion, + drag_grab_touch_frame, + drag_grab_touch_cancel +}; + +static void +drag_grab_keyboard_key(struct weston_keyboard_grab *grab, + const struct timespec *time, uint32_t key, uint32_t state) +{ +} + +static void +drag_grab_keyboard_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_drag *drag = + container_of(grab, struct weston_drag, keyboard_grab); + uint32_t compositor_action; + + if (mods_depressed & (1 << keyboard->xkb_info->shift_mod)) + compositor_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + else if (mods_depressed & (1 << keyboard->xkb_info->ctrl_mod)) + compositor_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + else + compositor_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + + drag->data_source->compositor_action = compositor_action; + + if (drag->data_source->offer) + data_offer_update_action(drag->data_source->offer); +} + +static void +drag_grab_keyboard_cancel(struct weston_keyboard_grab *grab) +{ + struct weston_drag *drag = + container_of(grab, struct weston_drag, keyboard_grab); + struct weston_pointer *pointer = grab->keyboard->seat->pointer_state; + struct weston_touch *touch = grab->keyboard->seat->touch_state; + + if (pointer && pointer->grab->interface == &pointer_drag_grab_interface) { + struct weston_touch_drag *touch_drag = + (struct weston_touch_drag *) drag; + drag_grab_touch_cancel(&touch_drag->grab); + } else if (touch && touch->grab->interface == &touch_drag_grab_interface) { + struct weston_pointer_drag *pointer_drag = + (struct weston_pointer_drag *) drag; + drag_grab_cancel(&pointer_drag->grab); + } +} + +static const struct weston_keyboard_grab_interface keyboard_drag_grab_interface = { + drag_grab_keyboard_key, + drag_grab_keyboard_modifiers, + drag_grab_keyboard_cancel +}; + +static void +destroy_pointer_data_device_source(struct wl_listener *listener, void *data) +{ + struct weston_pointer_drag *drag = container_of(listener, + struct weston_pointer_drag, base.data_source_listener); + + data_device_end_pointer_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_pointer_start_drag(struct weston_pointer *pointer, + struct weston_data_source *source, + struct weston_surface *icon, + struct wl_client *client) +{ + struct weston_pointer_drag *drag; + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(pointer->seat); + + drag = zalloc(sizeof *drag); + if (drag == NULL) + return -1; + + drag->grab.interface = &pointer_drag_grab_interface; + drag->base.keyboard_grab.interface = &keyboard_drag_grab_interface; + drag->base.client = client; + drag->base.data_source = source; + + if (icon) { + drag->base.icon = weston_view_create(icon); + if (drag->base.icon == NULL) { + free(drag); + return -1; + } + + drag->base.icon_destroy_listener.notify = handle_drag_icon_destroy; + wl_signal_add(&icon->destroy_signal, + &drag->base.icon_destroy_listener); + + icon->committed = pointer_drag_surface_committed; + icon->committed_private = drag; + weston_surface_set_label_func(icon, + pointer_drag_surface_get_label); + } else { + drag->base.icon = NULL; + } + + if (source) { + drag->base.data_source_listener.notify = destroy_pointer_data_device_source; + wl_signal_add(&source->destroy_signal, + &drag->base.data_source_listener); + } + + weston_pointer_clear_focus(pointer); + weston_keyboard_set_focus(keyboard, NULL); + + weston_pointer_start_grab(pointer, &drag->grab); + weston_keyboard_start_grab(keyboard, &drag->base.keyboard_grab); + + return 0; +} + +static void +destroy_touch_data_device_source(struct wl_listener *listener, void *data) +{ + struct weston_touch_drag *drag = container_of(listener, + struct weston_touch_drag, base.data_source_listener); + + data_device_end_touch_drag_grab(drag); +} + +WL_EXPORT int +weston_touch_start_drag(struct weston_touch *touch, + struct weston_data_source *source, + struct weston_surface *icon, + struct wl_client *client) +{ + struct weston_touch_drag *drag; + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(touch->seat); + + drag = zalloc(sizeof *drag); + if (drag == NULL) + return -1; + + drag->grab.interface = &touch_drag_grab_interface; + drag->base.client = client; + drag->base.data_source = source; + + if (icon) { + drag->base.icon = weston_view_create(icon); + if (drag->base.icon == NULL) { + free(drag); + return -1; + } + + drag->base.icon_destroy_listener.notify = handle_drag_icon_destroy; + wl_signal_add(&icon->destroy_signal, + &drag->base.icon_destroy_listener); + + icon->committed = touch_drag_surface_committed; + icon->committed_private = drag; + weston_surface_set_label_func(icon, + touch_drag_surface_get_label); + } else { + drag->base.icon = NULL; + } + + if (source) { + drag->base.data_source_listener.notify = destroy_touch_data_device_source; + wl_signal_add(&source->destroy_signal, + &drag->base.data_source_listener); + } + + weston_keyboard_set_focus(keyboard, NULL); + + weston_touch_start_grab(touch, &drag->grab); + weston_keyboard_start_grab(keyboard, &drag->base.keyboard_grab); + + drag_grab_touch_focus(drag); + + 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_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_touch *touch = weston_seat_get_touch(seat); + struct weston_surface *origin = wl_resource_get_user_data(origin_resource); + struct weston_data_source *source = NULL; + struct weston_surface *icon = NULL; + int is_pointer_grab, is_touch_grab; + int32_t ret = 0; + + is_pointer_grab = pointer && + pointer->button_count == 1 && + pointer->grab_serial == serial && + pointer->focus && + pointer->focus->surface == origin; + + is_touch_grab = touch && + touch->num_tp == 1 && + touch->grab_serial == serial && + touch->focus && + touch->focus->surface == origin; + + if (!is_pointer_grab && !is_touch_grab) + 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) { + if (weston_surface_set_role(icon, "wl_data_device-icon", + resource, + WL_DATA_DEVICE_ERROR_ROLE) < 0) + return; + } + + if (is_pointer_grab) + ret = weston_pointer_start_drag(pointer, source, icon, client); + else if (is_touch_grab) + ret = weston_touch_start_drag(touch, source, icon, client); + + if (ret < 0) + wl_resource_post_no_memory(resource); + else + source->seat = seat; +} + +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 weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + struct wl_resource *data_device; + struct weston_surface *focus = NULL; + + seat->selection_data_source = NULL; + + if (keyboard) + focus = 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); +} + +/** \brief Send the selection to the specified client + * + * This function creates a new wl_data_offer if there is a wl_data_source + * currently set as the selection and sends it to the specified client, + * followed by the wl_data_device.selection() event. + * If there is no current selection the wl_data_device.selection() event + * will carry a NULL wl_data_offer. + * + * If the client does not have a wl_data_device for the specified seat + * nothing will be done. + * + * \param seat The seat owning the wl_data_device used to send the events. + * \param client The client to which to send the selection. + */ +WL_EXPORT void +weston_seat_send_selection(struct weston_seat *seat, struct wl_client *client) +{ + struct weston_data_offer *offer; + struct wl_resource *data_device; + + wl_resource_for_each(data_device, &seat->drag_resource_list) { + if (wl_resource_get_client(data_device) != client) + continue; + + if (seat->selection_data_source) { + offer = weston_data_source_send_offer(seat->selection_data_source, + data_device); + wl_data_device_send_selection(data_device, offer->resource); + } else { + wl_data_device_send_selection(data_device, NULL); + } + } +} + +WL_EXPORT void +weston_seat_set_selection(struct weston_seat *seat, + struct weston_data_source *source, uint32_t serial) +{ + struct weston_surface *focus = NULL; + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + + 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 (source) + source->set_selection = true; + + if (keyboard) + focus = keyboard->focus; + if (focus && focus->resource) { + weston_seat_send_selection(seat, wl_resource_get_client(focus->resource)); + } + + 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) +{ + struct weston_seat *seat = wl_resource_get_user_data(resource); + struct weston_data_source *source; + + if (!seat || !source_resource) + return; + + source = wl_resource_get_user_data(source_resource); + + if (source->actions_set) { + wl_resource_post_error(source_resource, + WL_DATA_SOURCE_ERROR_INVALID_SOURCE, + "cannot set drag-and-drop source as selection"); + return; + } + + /* FIXME: Store serial and check against incoming serial here. */ + weston_seat_set_selection(seat, source, serial); +} +static void +data_device_release(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct wl_data_device_interface data_device_interface = { + data_device_start_drag, + data_device_set_selection, + data_device_release +}; + +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; + } + + source->resource = + wl_resource_create(client, &wl_data_source_interface, + wl_resource_get_version(resource), id); + if (source->resource == NULL) { + free(source); + 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; + source->offer = NULL; + source->accepted = false; + source->seat = NULL; + source->actions_set = false; + source->dnd_actions = 0; + source->current_dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + source->compositor_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + source->set_selection = false; + + wl_array_init(&source->mime_types); + + 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, + wl_resource_get_version(manager_resource), + id); + if (resource == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + if (seat) { + wl_list_insert(&seat->drag_resource_list, + wl_resource_get_link(resource)); + } else { + wl_list_init(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, + version, 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 weston_surface *focus; + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + + if (!keyboard) + return; + + focus = keyboard->focus; + if (!focus || !focus->resource) + return; + + weston_seat_send_selection(seat, wl_resource_get_client(focus->resource)); +} + +WL_EXPORT int +wl_data_device_manager_init(struct wl_display *display) +{ + if (wl_global_create(display, + &wl_data_device_manager_interface, 3, + NULL, bind_manager) == NULL) + return -1; + + return 0; +} diff --git a/libweston/dbus.c b/libweston/dbus.c new file mode 100644 index 0000000..91f2be7 --- /dev/null +++ b/libweston/dbus.c @@ -0,0 +1,408 @@ +/* + * Copyright © 2013 David Herrmann + * + * 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. + */ + +/* + * DBus Helpers + * This file contains the dbus mainloop integration and several helpers to + * make lowlevel libdbus access easier. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "dbus.h" + +/* + * DBus Mainloop Integration + * weston_dbus_bind() and weston_dbus_unbind() allow to bind an existing + * DBusConnection to an existing wl_event_loop object. All dbus dispatching + * is then nicely integrated into the wayland event loop. + * Note that this only provides basic watch and timeout dispatching. No + * remote thread wakeup, signal handling or other dbus insanity is supported. + * This is fine as long as you don't use any of the deprecated libdbus + * interfaces (like waking up remote threads..). There is really no rational + * reason to support these. + */ + +static int weston_dbus_dispatch_watch(int fd, uint32_t mask, void *data) +{ + DBusWatch *watch = data; + uint32_t flags = 0; + + if (dbus_watch_get_enabled(watch)) { + if (mask & WL_EVENT_READABLE) + flags |= DBUS_WATCH_READABLE; + if (mask & WL_EVENT_WRITABLE) + flags |= DBUS_WATCH_WRITABLE; + if (mask & WL_EVENT_HANGUP) + flags |= DBUS_WATCH_HANGUP; + if (mask & WL_EVENT_ERROR) + flags |= DBUS_WATCH_ERROR; + + dbus_watch_handle(watch, flags); + } + + return 0; +} + +static dbus_bool_t weston_dbus_add_watch(DBusWatch *watch, void *data) +{ + struct wl_event_loop *loop = data; + struct wl_event_source *s; + int fd; + uint32_t mask = 0, flags; + + if (dbus_watch_get_enabled(watch)) { + flags = dbus_watch_get_flags(watch); + if (flags & DBUS_WATCH_READABLE) + mask |= WL_EVENT_READABLE; + if (flags & DBUS_WATCH_WRITABLE) + mask |= WL_EVENT_WRITABLE; + } + + fd = dbus_watch_get_unix_fd(watch); + s = wl_event_loop_add_fd(loop, fd, mask, weston_dbus_dispatch_watch, + watch); + if (!s) + return FALSE; + + dbus_watch_set_data(watch, s, NULL); + return TRUE; +} + +static void weston_dbus_remove_watch(DBusWatch *watch, void *data) +{ + struct wl_event_source *s; + + s = dbus_watch_get_data(watch); + if (!s) + return; + + wl_event_source_remove(s); +} + +static void weston_dbus_toggle_watch(DBusWatch *watch, void *data) +{ + struct wl_event_source *s; + uint32_t mask = 0, flags; + + s = dbus_watch_get_data(watch); + if (!s) + return; + + if (dbus_watch_get_enabled(watch)) { + flags = dbus_watch_get_flags(watch); + if (flags & DBUS_WATCH_READABLE) + mask |= WL_EVENT_READABLE; + if (flags & DBUS_WATCH_WRITABLE) + mask |= WL_EVENT_WRITABLE; + } + + wl_event_source_fd_update(s, mask); +} + +static int weston_dbus_dispatch_timeout(void *data) +{ + DBusTimeout *timeout = data; + + if (dbus_timeout_get_enabled(timeout)) + dbus_timeout_handle(timeout); + + return 0; +} + +static int weston_dbus_adjust_timeout(DBusTimeout *timeout, + struct wl_event_source *s) +{ + int64_t t = 0; + + if (dbus_timeout_get_enabled(timeout)) + t = dbus_timeout_get_interval(timeout); + + return wl_event_source_timer_update(s, t); +} + +static dbus_bool_t weston_dbus_add_timeout(DBusTimeout *timeout, void *data) +{ + struct wl_event_loop *loop = data; + struct wl_event_source *s; + int r; + + s = wl_event_loop_add_timer(loop, weston_dbus_dispatch_timeout, + timeout); + if (!s) + return FALSE; + + r = weston_dbus_adjust_timeout(timeout, s); + if (r < 0) { + wl_event_source_remove(s); + return FALSE; + } + + dbus_timeout_set_data(timeout, s, NULL); + return TRUE; +} + +static void weston_dbus_remove_timeout(DBusTimeout *timeout, void *data) +{ + struct wl_event_source *s; + + s = dbus_timeout_get_data(timeout); + if (!s) + return; + + wl_event_source_remove(s); +} + +static void weston_dbus_toggle_timeout(DBusTimeout *timeout, void *data) +{ + struct wl_event_source *s; + + s = dbus_timeout_get_data(timeout); + if (!s) + return; + + weston_dbus_adjust_timeout(timeout, s); +} + +static int weston_dbus_dispatch(int fd, uint32_t mask, void *data) +{ + DBusConnection *c = data; + int r; + + do { + r = dbus_connection_dispatch(c); + if (r == DBUS_DISPATCH_COMPLETE) + r = 0; + else if (r == DBUS_DISPATCH_DATA_REMAINS) + r = -EAGAIN; + else if (r == DBUS_DISPATCH_NEED_MEMORY) + r = -ENOMEM; + else + r = -EIO; + } while (r == -EAGAIN); + + if (r) + weston_log("cannot dispatch dbus events: %d\n", r); + + return 0; +} + +static int weston_dbus_bind(struct wl_event_loop *loop, DBusConnection *c, + struct wl_event_source **ctx_out) +{ + bool b; + int r, fd; + + /* Idle events cannot reschedule themselves, therefore we use a dummy + * event-fd and mark it for post-dispatch. Hence, the dbus + * dispatcher is called after every dispatch-round. + * This is required as dbus doesn't allow dispatching events from + * within its own event sources. */ + fd = eventfd(0, EFD_CLOEXEC); + if (fd < 0) + return -errno; + + *ctx_out = wl_event_loop_add_fd(loop, fd, 0, weston_dbus_dispatch, c); + close(fd); + + if (!*ctx_out) + return -ENOMEM; + + wl_event_source_check(*ctx_out); + + b = dbus_connection_set_watch_functions(c, + weston_dbus_add_watch, + weston_dbus_remove_watch, + weston_dbus_toggle_watch, + loop, + NULL); + if (!b) { + r = -ENOMEM; + goto error; + } + + b = dbus_connection_set_timeout_functions(c, + weston_dbus_add_timeout, + weston_dbus_remove_timeout, + weston_dbus_toggle_timeout, + loop, + NULL); + if (!b) { + r = -ENOMEM; + goto error; + } + + dbus_connection_ref(c); + return 0; + +error: + dbus_connection_set_timeout_functions(c, NULL, NULL, NULL, + NULL, NULL); + dbus_connection_set_watch_functions(c, NULL, NULL, NULL, + NULL, NULL); + wl_event_source_remove(*ctx_out); + *ctx_out = NULL; + return r; +} + +static void weston_dbus_unbind(DBusConnection *c, struct wl_event_source *ctx) +{ + dbus_connection_set_timeout_functions(c, NULL, NULL, NULL, + NULL, NULL); + dbus_connection_set_watch_functions(c, NULL, NULL, NULL, + NULL, NULL); + dbus_connection_unref(c); + wl_event_source_remove(ctx); +} + +/* + * Convenience Helpers + * Several convenience helpers are provided to make using dbus in weston + * easier. We don't use any of the gdbus or qdbus helpers as they pull in + * huge dependencies and actually are quite awful to use. Instead, we only + * use the basic low-level libdbus library. + */ + +int weston_dbus_open(struct wl_event_loop *loop, DBusBusType bus, + DBusConnection **out, struct wl_event_source **ctx_out) +{ + DBusConnection *c; + int r; + + /* Ihhh, global state.. stupid dbus. */ + dbus_connection_set_change_sigpipe(FALSE); + + /* This is actually synchronous. It blocks for some authentication and + * setup. We just trust the dbus-server here and accept this blocking + * call. There is no real reason to complicate things further and make + * this asynchronous/non-blocking. A context should be created during + * thead/process/app setup, so blocking calls should be fine. */ + c = dbus_bus_get_private(bus, NULL); + if (!c) + return -EIO; + + dbus_connection_set_exit_on_disconnect(c, FALSE); + + r = weston_dbus_bind(loop, c, ctx_out); + if (r < 0) + goto error; + + *out = c; + return r; + +error: + dbus_connection_close(c); + dbus_connection_unref(c); + return r; +} + +void weston_dbus_close(DBusConnection *c, struct wl_event_source *ctx) +{ + weston_dbus_unbind(c, ctx); + dbus_connection_close(c); + dbus_connection_unref(c); +} + +int weston_dbus_add_match(DBusConnection *c, const char *format, ...) +{ + DBusError err; + int r; + va_list list; + char *str; + + va_start(list, format); + r = vasprintf(&str, format, list); + va_end(list); + + if (r < 0) + return -ENOMEM; + + dbus_error_init(&err); + dbus_bus_add_match(c, str, &err); + free(str); + if (dbus_error_is_set(&err)) { + dbus_error_free(&err); + return -EIO; + } + + return 0; +} + +int weston_dbus_add_match_signal(DBusConnection *c, const char *sender, + const char *iface, const char *member, + const char *path) +{ + return weston_dbus_add_match(c, + "type='signal'," + "sender='%s'," + "interface='%s'," + "member='%s'," + "path='%s'", + sender, iface, member, path); +} + +void weston_dbus_remove_match(DBusConnection *c, const char *format, ...) +{ + int r; + va_list list; + char *str; + + va_start(list, format); + r = vasprintf(&str, format, list); + va_end(list); + + if (r < 0) + return; + + dbus_bus_remove_match(c, str, NULL); + free(str); +} + +void weston_dbus_remove_match_signal(DBusConnection *c, const char *sender, + const char *iface, const char *member, + const char *path) +{ + weston_dbus_remove_match(c, + "type='signal'," + "sender='%s'," + "interface='%s'," + "member='%s'," + "path='%s'", + sender, iface, member, path); +} diff --git a/libweston/dbus.h b/libweston/dbus.h new file mode 100644 index 0000000..639946c --- /dev/null +++ b/libweston/dbus.h @@ -0,0 +1,110 @@ +/* + * Copyright © 2013 David Herrmann + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _WESTON_DBUS_H_ +#define _WESTON_DBUS_H_ + +#include "config.h" + +#include +#include + +#include + +#ifdef HAVE_DBUS + +#include + +/* + * weston_dbus_open() - Open new dbus connection + * + * Opens a new dbus connection to the bus given as @bus. It automatically + * integrates the new connection into the main-loop @loop. The connection + * itself is returned in @out. + * This also returns a context source used for dbus dispatching. It is + * returned on success in @ctx_out and must be passed to weston_dbus_close() + * unchanged. You must not access it from outside of a dbus helper! + * + * Returns 0 on success, negative error code on failure. + */ +int weston_dbus_open(struct wl_event_loop *loop, DBusBusType bus, + DBusConnection **out, struct wl_event_source **ctx_out); + +/* + * weston_dbus_close() - Close dbus connection + * + * Closes a dbus connection that was previously opened via weston_dbus_open(). + * It unbinds the connection from the main-loop it was previously bound to, + * closes the dbus connection and frees all resources. If you want to access + * @c after this call returns, you must hold a dbus-reference to it. But + * notice that the connection is closed after this returns so it cannot be + * used to spawn new dbus requests. + * You must pass the context source returns by weston_dbus_open() as @ctx. + */ +void weston_dbus_close(DBusConnection *c, struct wl_event_source *ctx); + +/* + * weston_dbus_add_match() - Add dbus match + * + * Configure a dbus-match on the given dbus-connection. This match is saved + * on the dbus-server as long as the connection is open. See dbus-manual + * for information. Compared to the dbus_bus_add_match() this allows a + * var-arg formatted match-string. + */ +int weston_dbus_add_match(DBusConnection *c, const char *format, ...); + +/* + * weston_dbus_add_match_signal() - Add dbus signal match + * + * Same as weston_dbus_add_match() but does the dbus-match formatting for + * signals internally. + */ +int weston_dbus_add_match_signal(DBusConnection *c, const char *sender, + const char *iface, const char *member, + const char *path); + +/* + * weston_dbus_remove_match() - Remove dbus match + * + * Remove a previously configured dbus-match from the dbus server. There is + * no need to remove dbus-matches if you close the connection, anyway. + * Compared to dbus_bus_remove_match() this allows a var-arg formatted + * match string. + */ +void weston_dbus_remove_match(DBusConnection *c, const char *format, ...); + +/* + * weston_dbus_remove_match_signal() - Remove dbus signal match + * + * Same as weston_dbus_remove_match() but does the dbus-match formatting for + * signals internally. + */ +void weston_dbus_remove_match_signal(DBusConnection *c, const char *sender, + const char *iface, const char *member, + const char *path); + +#endif /* HAVE_DBUS */ + +#endif // _WESTON_DBUS_H_ diff --git a/libweston/git-version.h b/libweston/git-version.h new file mode 100644 index 0000000..09d67da --- /dev/null +++ b/libweston/git-version.h @@ -0,0 +1 @@ +#define BUILD_ID "unknown (not built from git or tarball)" diff --git a/libweston/git-version.h.meson b/libweston/git-version.h.meson new file mode 100644 index 0000000..d91f19c --- /dev/null +++ b/libweston/git-version.h.meson @@ -0,0 +1 @@ +#define BUILD_ID "@VCS_TAG@" diff --git a/libweston/input.c b/libweston/input.c new file mode 100644 index 0000000..292b681 --- /dev/null +++ b/libweston/input.c @@ -0,0 +1,5063 @@ +/* + * Copyright © 2013 Intel Corporation + * Copyright 2017-2018 Collabora, Ltd. + * Copyright 2017-2018 General Electric Company + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +// OHOS no such file +//#include +#include +#include +#include + +#include "shared/helpers.h" +#include "shared/os-compatibility.h" +#include "shared/timespec-util.h" +#include +#include "backend.h" +#include "libweston-internal.h" +#include "relative-pointer-unstable-v1-server-protocol.h" +#include "pointer-constraints-unstable-v1-server-protocol.h" +#include "input-timestamps-unstable-v1-server-protocol.h" + +enum pointer_constraint_type { + POINTER_CONSTRAINT_TYPE_LOCK, + POINTER_CONSTRAINT_TYPE_CONFINE, +}; + +enum motion_direction { + MOTION_DIRECTION_POSITIVE_X = 1 << 0, + MOTION_DIRECTION_NEGATIVE_X = 1 << 1, + MOTION_DIRECTION_POSITIVE_Y = 1 << 2, + MOTION_DIRECTION_NEGATIVE_Y = 1 << 3, +}; + +struct vec2d { + double x, y; +}; + +struct line { + struct vec2d a; + struct vec2d b; +}; + +struct border { + struct line line; + enum motion_direction blocking_dir; +}; + +static void +maybe_warp_confined_pointer(struct weston_pointer_constraint *constraint); + +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 void +send_timestamp(struct wl_resource *resource, + const struct timespec *time) +{ + uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; + + timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + zwp_input_timestamps_v1_send_timestamp(resource, tv_sec_hi, tv_sec_lo, + tv_nsec); +} + +static void +send_timestamps_for_input_resource(struct wl_resource *input_resource, + struct wl_list *list, + const struct timespec *time) +{ + struct wl_resource *resource; + + wl_resource_for_each(resource, list) { + if (wl_resource_get_user_data(resource) == input_resource) + send_timestamp(resource, time); + } +} + +static void +remove_input_resource_from_timestamps(struct wl_resource *input_resource, + struct wl_list *list) +{ + struct wl_resource *resource; + + wl_resource_for_each(resource, list) { + if (wl_resource_get_user_data(resource) == input_resource) + wl_resource_set_user_data(resource, NULL); + } +} + +/** Register a touchscreen input device + * + * \param touch The parent weston_touch that identifies the seat. + * \param syspath Unique device name. + * \param backend_data Backend private data if necessary. + * \param ops Calibration operations, or NULL for not able to run calibration. + * \return New touch device, or NULL on failure. + */ +WL_EXPORT struct weston_touch_device * +weston_touch_create_touch_device(struct weston_touch *touch, + const char *syspath, + void *backend_data, + const struct weston_touch_device_ops *ops) +{ + struct weston_touch_device *device; + + assert(syspath); + if (ops) { + assert(ops->get_output); + assert(ops->get_calibration_head_name); + assert(ops->get_calibration); + assert(ops->set_calibration); + } + + device = zalloc(sizeof *device); + if (!device) + return NULL; + + wl_signal_init(&device->destroy_signal); + + device->syspath = strdup(syspath); + if (!device->syspath) { + free(device); + return NULL; + } + + device->backend_data = backend_data; + device->ops = ops; + + device->aggregate = touch; + wl_list_insert(touch->device_list.prev, &device->link); + + return device; +} + +/** Destroy the touch device. */ +WL_EXPORT void +weston_touch_device_destroy(struct weston_touch_device *device) +{ + wl_list_remove(&device->link); + wl_signal_emit(&device->destroy_signal, device); + free(device->syspath); + free(device); +} + +/** Is it possible to run calibration on this touch device? */ +WL_EXPORT bool +weston_touch_device_can_calibrate(struct weston_touch_device *device) +{ + return !!device->ops; +} + +static enum weston_touch_mode +weston_touch_device_get_mode(struct weston_touch_device *device) +{ + return device->aggregate->seat->compositor->touch_mode; +} + +static struct weston_pointer_client * +weston_pointer_client_create(struct wl_client *client) +{ + struct weston_pointer_client *pointer_client; + + pointer_client = zalloc(sizeof *pointer_client); + if (!pointer_client) + return NULL; + + pointer_client->client = client; + wl_list_init(&pointer_client->pointer_resources); + wl_list_init(&pointer_client->relative_pointer_resources); + + return pointer_client; +} + +static void +weston_pointer_client_destroy(struct weston_pointer_client *pointer_client) +{ + struct wl_resource *resource; + + wl_resource_for_each(resource, &pointer_client->pointer_resources) { + wl_resource_set_user_data(resource, NULL); + } + + wl_resource_for_each(resource, + &pointer_client->relative_pointer_resources) { + wl_resource_set_user_data(resource, NULL); + } + + wl_list_remove(&pointer_client->pointer_resources); + wl_list_remove(&pointer_client->relative_pointer_resources); + free(pointer_client); +} + +static bool +weston_pointer_client_is_empty(struct weston_pointer_client *pointer_client) +{ + return (wl_list_empty(&pointer_client->pointer_resources) && + wl_list_empty(&pointer_client->relative_pointer_resources)); +} + +static struct weston_pointer_client * +weston_pointer_get_pointer_client(struct weston_pointer *pointer, + struct wl_client *client) +{ + struct weston_pointer_client *pointer_client; + + wl_list_for_each(pointer_client, &pointer->pointer_clients, link) { + if (pointer_client->client == client) + return pointer_client; + } + + return NULL; +} + +static struct weston_pointer_client * +weston_pointer_ensure_pointer_client(struct weston_pointer *pointer, + struct wl_client *client) +{ + struct weston_pointer_client *pointer_client; + + pointer_client = weston_pointer_get_pointer_client(pointer, client); + if (pointer_client) + return pointer_client; + + pointer_client = weston_pointer_client_create(client); + wl_list_insert(&pointer->pointer_clients, &pointer_client->link); + + if (pointer->focus && + pointer->focus->surface->resource && + wl_resource_get_client(pointer->focus->surface->resource) == client) { + pointer->focus_client = pointer_client; + } + + return pointer_client; +} + +static void +weston_pointer_cleanup_pointer_client(struct weston_pointer *pointer, + struct weston_pointer_client *pointer_client) +{ + if (weston_pointer_client_is_empty(pointer_client)) { + if (pointer->focus_client == pointer_client) + pointer->focus_client = NULL; + wl_list_remove(&pointer_client->link); + weston_pointer_client_destroy(pointer_client); + } +} + +static void +unbind_pointer_client_resource(struct wl_resource *resource) +{ + struct weston_pointer *pointer = wl_resource_get_user_data(resource); + struct wl_client *client = wl_resource_get_client(resource); + struct weston_pointer_client *pointer_client; + + wl_list_remove(wl_resource_get_link(resource)); + + if (pointer) { + pointer_client = weston_pointer_get_pointer_client(pointer, + client); + assert(pointer_client); + remove_input_resource_from_timestamps(resource, + &pointer->timestamps_list); + weston_pointer_cleanup_pointer_client(pointer, pointer_client); + } +} + +static void unbind_resource(struct wl_resource *resource) +{ + wl_list_remove(wl_resource_get_link(resource)); +} + +WL_EXPORT void +weston_pointer_motion_to_abs(struct weston_pointer *pointer, + struct weston_pointer_motion_event *event, + wl_fixed_t *x, wl_fixed_t *y) +{ + if (event->mask & WESTON_POINTER_MOTION_ABS) { + *x = wl_fixed_from_double(event->x); + *y = wl_fixed_from_double(event->y); + } else if (event->mask & WESTON_POINTER_MOTION_REL) { + *x = pointer->x + wl_fixed_from_double(event->dx); + *y = pointer->y + wl_fixed_from_double(event->dy); + } else { + assert(!"invalid motion event"); + *x = *y = 0; + } +} + +static bool +weston_pointer_motion_to_rel(struct weston_pointer *pointer, + struct weston_pointer_motion_event *event, + double *dx, double *dy, + double *dx_unaccel, double *dy_unaccel) +{ + if (event->mask & WESTON_POINTER_MOTION_REL && + event->mask & WESTON_POINTER_MOTION_REL_UNACCEL) { + *dx = event->dx; + *dy = event->dy; + *dx_unaccel = event->dx_unaccel; + *dy_unaccel = event->dy_unaccel; + return true; + } else if (event->mask & WESTON_POINTER_MOTION_REL) { + *dx_unaccel = *dx = event->dx; + *dy_unaccel = *dy = event->dy; + return true; + } else if (event->mask & WESTON_POINTER_MOTION_REL_UNACCEL) { + *dx_unaccel = *dx = event->dx_unaccel; + *dy_unaccel = *dy = event->dy_unaccel; + return true; + } else { + return false; + } +} + +WL_EXPORT void +weston_seat_repick(struct weston_seat *seat) +{ + const struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + if (!pointer) + return; + + pointer->grab->interface->focus(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 +pointer_focus_view_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_pointer *pointer = + container_of(listener, struct weston_pointer, + focus_view_listener); + + weston_pointer_clear_focus(pointer); +} + +static void +pointer_focus_resource_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_pointer *pointer = + container_of(listener, struct weston_pointer, + focus_resource_listener); + + weston_pointer_clear_focus(pointer); +} + +static void +keyboard_focus_resource_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_keyboard *keyboard = + container_of(listener, struct weston_keyboard, + focus_resource_listener); + + weston_keyboard_set_focus(keyboard, NULL); +} + +static void +touch_focus_view_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_touch *touch = + container_of(listener, struct weston_touch, + focus_view_listener); + + weston_touch_set_focus(touch, NULL); +} + +static void +touch_focus_resource_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_touch *touch = + container_of(listener, struct weston_touch, + focus_resource_listener); + + weston_touch_set_focus(touch, NULL); +} + +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_pointer_focus(struct weston_pointer_grab *grab) +{ + struct weston_pointer *pointer = grab->pointer; + struct weston_view *view; + wl_fixed_t sx, sy; + + if (pointer->button_count > 0) + return; + + view = weston_compositor_pick_view(pointer->seat->compositor, + pointer->x, pointer->y, + &sx, &sy); + + if (pointer->focus != view || pointer->sx != sx || pointer->sy != sy) + weston_pointer_set_focus(pointer, view, sx, sy); +} + +static void +pointer_send_relative_motion(struct weston_pointer *pointer, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + uint64_t time_usec; + double dx, dy, dx_unaccel, dy_unaccel; + wl_fixed_t dxf, dyf, dxf_unaccel, dyf_unaccel; + struct wl_list *resource_list; + struct wl_resource *resource; + + if (!pointer->focus_client) + return; + + if (!weston_pointer_motion_to_rel(pointer, event, + &dx, &dy, + &dx_unaccel, &dy_unaccel)) + return; + + resource_list = &pointer->focus_client->relative_pointer_resources; + time_usec = timespec_to_usec(&event->time); + if (time_usec == 0) + time_usec = timespec_to_usec(time); + + dxf = wl_fixed_from_double(dx); + dyf = wl_fixed_from_double(dy); + dxf_unaccel = wl_fixed_from_double(dx_unaccel); + dyf_unaccel = wl_fixed_from_double(dy_unaccel); + + wl_resource_for_each(resource, resource_list) { + zwp_relative_pointer_v1_send_relative_motion( + resource, + (uint32_t) (time_usec >> 32), + (uint32_t) time_usec, + dxf, dyf, + dxf_unaccel, dyf_unaccel); + } +} + +static void +pointer_send_motion(struct weston_pointer *pointer, + const struct timespec *time, + wl_fixed_t sx, wl_fixed_t sy) +{ + struct wl_list *resource_list; + struct wl_resource *resource; + uint32_t msecs; + + if (!pointer->focus_client) + return; + + resource_list = &pointer->focus_client->pointer_resources; + msecs = timespec_to_msec(time); + wl_resource_for_each(resource, resource_list) { + send_timestamps_for_input_resource(resource, + &pointer->timestamps_list, + time); + wl_pointer_send_motion(resource, msecs, sx, sy); + } +} + +WL_EXPORT void +weston_pointer_send_motion(struct weston_pointer *pointer, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + wl_fixed_t x, y; + wl_fixed_t old_sx = pointer->sx; + wl_fixed_t old_sy = pointer->sy; + + if (pointer->focus) { + weston_pointer_motion_to_abs(pointer, event, &x, &y); + weston_view_from_global_fixed(pointer->focus, x, y, + &pointer->sx, &pointer->sy); + } + + weston_pointer_move(pointer, event); + + if (old_sx != pointer->sx || old_sy != pointer->sy) { + pointer_send_motion(pointer, time, + pointer->sx, pointer->sy); + } + + pointer_send_relative_motion(pointer, time, event); +} + +static void +default_grab_pointer_motion(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + weston_pointer_send_motion(grab->pointer, time, event); +} + +/** Check if the pointer has focused resources. + * + * \param pointer The pointer to check for focused resources. + * \return Whether or not this pointer has focused resources + */ +WL_EXPORT bool +weston_pointer_has_focus_resource(struct weston_pointer *pointer) +{ + if (!pointer->focus_client) + return false; + + if (wl_list_empty(&pointer->focus_client->pointer_resources)) + return false; + + return true; +} + +/** Send wl_pointer.button events to focused resources. + * + * \param pointer The pointer where the button events originates from. + * \param time The timestamp of the event + * \param button The button value of the event + * \param state The state enum value of the event + * + * For every resource that is currently in focus, send a wl_pointer.button event + * with the passed parameters. The focused resources are the wl_pointer + * resources of the client which currently has the surface with pointer focus. + */ +WL_EXPORT void +weston_pointer_send_button(struct weston_pointer *pointer, + const struct timespec *time, uint32_t button, + enum wl_pointer_button_state state) +{ + struct wl_display *display = pointer->seat->compositor->wl_display; + struct wl_list *resource_list; + struct wl_resource *resource; + uint32_t serial; + uint32_t msecs; + + if (!weston_pointer_has_focus_resource(pointer)) + return; + + resource_list = &pointer->focus_client->pointer_resources; + serial = wl_display_next_serial(display); + msecs = timespec_to_msec(time); + wl_resource_for_each(resource, resource_list) { + send_timestamps_for_input_resource(resource, + &pointer->timestamps_list, + time); + wl_pointer_send_button(resource, serial, msecs, button, state); + } +} + +static void +default_grab_pointer_button(struct weston_pointer_grab *grab, + const struct timespec *time, uint32_t button, + enum wl_pointer_button_state state) +{ + struct weston_pointer *pointer = grab->pointer; + struct weston_compositor *compositor = pointer->seat->compositor; + struct weston_view *view; + wl_fixed_t sx, sy; + + weston_pointer_send_button(pointer, time, button, state); + + if (pointer->button_count == 0 && + state == WL_POINTER_BUTTON_STATE_RELEASED) { + view = weston_compositor_pick_view(compositor, + pointer->x, pointer->y, + &sx, &sy); + + weston_pointer_set_focus(pointer, view, sx, sy); + } +} + +/** Send wl_pointer.axis events to focused resources. + * + * \param pointer The pointer where the axis events originates from. + * \param time The timestamp of the event + * \param event The axis value of the event + * + * For every resource that is currently in focus, send a wl_pointer.axis event + * with the passed parameters. The focused resources are the wl_pointer + * resources of the client which currently has the surface with pointer focus. + */ +WL_EXPORT void +weston_pointer_send_axis(struct weston_pointer *pointer, + const struct timespec *time, + struct weston_pointer_axis_event *event) +{ + struct wl_resource *resource; + struct wl_list *resource_list; + uint32_t msecs; + + if (!weston_pointer_has_focus_resource(pointer)) + return; + + resource_list = &pointer->focus_client->pointer_resources; + msecs = timespec_to_msec(time); + wl_resource_for_each(resource, resource_list) { + if (event->has_discrete && + wl_resource_get_version(resource) >= + WL_POINTER_AXIS_DISCRETE_SINCE_VERSION) + wl_pointer_send_axis_discrete(resource, event->axis, + event->discrete); + + if (event->value) { + send_timestamps_for_input_resource(resource, + &pointer->timestamps_list, + time); + wl_pointer_send_axis(resource, msecs, + event->axis, + wl_fixed_from_double(event->value)); + } else if (wl_resource_get_version(resource) >= + WL_POINTER_AXIS_STOP_SINCE_VERSION) { + send_timestamps_for_input_resource(resource, + &pointer->timestamps_list, + time); + wl_pointer_send_axis_stop(resource, msecs, + event->axis); + } + } +} + +/** Send wl_pointer.axis_source events to focused resources. + * + * \param pointer The pointer where the axis_source events originates from. + * \param source The axis_source enum value of the event + * + * For every resource that is currently in focus, send a wl_pointer.axis_source + * event with the passed parameter. The focused resources are the wl_pointer + * resources of the client which currently has the surface with pointer focus. + */ +WL_EXPORT void +weston_pointer_send_axis_source(struct weston_pointer *pointer, + enum wl_pointer_axis_source source) +{ + struct wl_resource *resource; + struct wl_list *resource_list; + + if (!weston_pointer_has_focus_resource(pointer)) + return; + + resource_list = &pointer->focus_client->pointer_resources; + wl_resource_for_each(resource, resource_list) { + if (wl_resource_get_version(resource) >= + WL_POINTER_AXIS_SOURCE_SINCE_VERSION) { + wl_pointer_send_axis_source(resource, source); + } + } +} + +static void +pointer_send_frame(struct wl_resource *resource) +{ + if (wl_resource_get_version(resource) >= + WL_POINTER_FRAME_SINCE_VERSION) { + wl_pointer_send_frame(resource); + } +} + +/** Send wl_pointer.frame events to focused resources. + * + * \param pointer The pointer where the frame events originates from. + * + * For every resource that is currently in focus, send a wl_pointer.frame event. + * The focused resources are the wl_pointer resources of the client which + * currently has the surface with pointer focus. + */ +WL_EXPORT void +weston_pointer_send_frame(struct weston_pointer *pointer) +{ + struct wl_resource *resource; + struct wl_list *resource_list; + + if (!weston_pointer_has_focus_resource(pointer)) + return; + + resource_list = &pointer->focus_client->pointer_resources; + wl_resource_for_each(resource, resource_list) + pointer_send_frame(resource); +} + +static void +default_grab_pointer_axis(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_axis_event *event) +{ + weston_pointer_send_axis(grab->pointer, time, event); +} + +static void +default_grab_pointer_axis_source(struct weston_pointer_grab *grab, + enum wl_pointer_axis_source source) +{ + weston_pointer_send_axis_source(grab->pointer, source); +} + +static void +default_grab_pointer_frame(struct weston_pointer_grab *grab) +{ + weston_pointer_send_frame(grab->pointer); +} + +static void +default_grab_pointer_cancel(struct weston_pointer_grab *grab) +{ +} + +static const struct weston_pointer_grab_interface + default_pointer_grab_interface = { + default_grab_pointer_focus, + default_grab_pointer_motion, + default_grab_pointer_button, + default_grab_pointer_axis, + default_grab_pointer_axis_source, + default_grab_pointer_frame, + default_grab_pointer_cancel, +}; + +/** Check if the touch has focused resources. + * + * \param touch The touch to check for focused resources. + * \return Whether or not this touch has focused resources + */ +WL_EXPORT bool +weston_touch_has_focus_resource(struct weston_touch *touch) +{ + if (!touch->focus) + return false; + + if (wl_list_empty(&touch->focus_resource_list)) + return false; + + return true; +} + +/** Send wl_touch.down events to focused resources. + * + * \param touch The touch where the down events originates from. + * \param time The timestamp of the event + * \param touch_id The touch_id value of the event + * \param x The x value of the event + * \param y The y value of the event + * + * For every resource that is currently in focus, send a wl_touch.down event + * with the passed parameters. The focused resources are the wl_touch + * resources of the client which currently has the surface with touch focus. + */ +WL_EXPORT void +weston_touch_send_down(struct weston_touch *touch, const struct timespec *time, + int touch_id, wl_fixed_t x, wl_fixed_t y) +{ + struct wl_display *display = touch->seat->compositor->wl_display; + uint32_t serial; + struct wl_resource *resource; + struct wl_list *resource_list; + wl_fixed_t sx, sy; + uint32_t msecs; + + if (!weston_touch_has_focus_resource(touch)) + return; + + weston_view_from_global_fixed(touch->focus, x, y, &sx, &sy); + + resource_list = &touch->focus_resource_list; + serial = wl_display_next_serial(display); + msecs = timespec_to_msec(time); + wl_resource_for_each(resource, resource_list) { + send_timestamps_for_input_resource(resource, + &touch->timestamps_list, + time); + wl_touch_send_down(resource, serial, msecs, + touch->focus->surface->resource, + touch_id, sx, sy); + } +} + +static void +default_grab_touch_down(struct weston_touch_grab *grab, + const struct timespec *time, int touch_id, + wl_fixed_t x, wl_fixed_t y) +{ + weston_touch_send_down(grab->touch, time, touch_id, x, y); +} + +/** Send wl_touch.up events to focused resources. + * + * \param touch The touch where the up events originates from. + * \param time The timestamp of the event + * \param touch_id The touch_id value of the event + * + * For every resource that is currently in focus, send a wl_touch.up event + * with the passed parameters. The focused resources are the wl_touch + * resources of the client which currently has the surface with touch focus. + */ +WL_EXPORT void +weston_touch_send_up(struct weston_touch *touch, const struct timespec *time, + int touch_id) +{ + struct wl_display *display = touch->seat->compositor->wl_display; + uint32_t serial; + struct wl_resource *resource; + struct wl_list *resource_list; + uint32_t msecs; + + if (!weston_touch_has_focus_resource(touch)) + return; + + resource_list = &touch->focus_resource_list; + serial = wl_display_next_serial(display); + msecs = timespec_to_msec(time); + wl_resource_for_each(resource, resource_list) { + send_timestamps_for_input_resource(resource, + &touch->timestamps_list, + time); + wl_touch_send_up(resource, serial, msecs, touch_id); + } +} + +static void +default_grab_touch_up(struct weston_touch_grab *grab, + const struct timespec *time, int touch_id) +{ + weston_touch_send_up(grab->touch, time, touch_id); +} + +/** Send wl_touch.motion events to focused resources. + * + * \param touch The touch where the motion events originates from. + * \param time The timestamp of the event + * \param touch_id The touch_id value of the event + * \param x The x value of the event + * \param y The y value of the event + * + * For every resource that is currently in focus, send a wl_touch.motion event + * with the passed parameters. The focused resources are the wl_touch + * resources of the client which currently has the surface with touch focus. + */ +WL_EXPORT void +weston_touch_send_motion(struct weston_touch *touch, + const struct timespec *time, int touch_id, + wl_fixed_t x, wl_fixed_t y) +{ + struct wl_resource *resource; + struct wl_list *resource_list; + wl_fixed_t sx, sy; + uint32_t msecs; + + if (!weston_touch_has_focus_resource(touch)) + return; + + weston_view_from_global_fixed(touch->focus, x, y, &sx, &sy); + + resource_list = &touch->focus_resource_list; + msecs = timespec_to_msec(time); + wl_resource_for_each(resource, resource_list) { + send_timestamps_for_input_resource(resource, + &touch->timestamps_list, + time); + wl_touch_send_motion(resource, msecs, + touch_id, sx, sy); + } +} + +static void +default_grab_touch_motion(struct weston_touch_grab *grab, + const struct timespec *time, int touch_id, + wl_fixed_t x, wl_fixed_t y) +{ + weston_touch_send_motion(grab->touch, time, touch_id, x, y); +} + + +/** Send wl_touch.frame events to focused resources. + * + * \param touch The touch where the frame events originates from. + * + * For every resource that is currently in focus, send a wl_touch.frame event. + * The focused resources are the wl_touch resources of the client which + * currently has the surface with touch focus. + */ +WL_EXPORT void +weston_touch_send_frame(struct weston_touch *touch) +{ + struct wl_resource *resource; + + if (!weston_touch_has_focus_resource(touch)) + return; + + wl_resource_for_each(resource, &touch->focus_resource_list) + wl_touch_send_frame(resource); +} + +static void +default_grab_touch_frame(struct weston_touch_grab *grab) +{ + weston_touch_send_frame(grab->touch); +} + +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_frame, + default_grab_touch_cancel, +}; + +/** Check if the keyboard has focused resources. + * + * \param keyboard The keyboard to check for focused resources. + * \return Whether or not this keyboard has focused resources + */ +WL_EXPORT bool +weston_keyboard_has_focus_resource(struct weston_keyboard *keyboard) +{ + if (!keyboard->focus) + return false; + + if (wl_list_empty(&keyboard->focus_resource_list)) + return false; + + return true; +} + +/** Send wl_keyboard.key events to focused resources. + * + * \param keyboard The keyboard where the key events originates from. + * \param time The timestamp of the event + * \param key The key value of the event + * \param state The state enum value of the event + * + * For every resource that is currently in focus, send a wl_keyboard.key event + * with the passed parameters. The focused resources are the wl_keyboard + * resources of the client which currently has the surface with keyboard focus. + */ +WL_EXPORT void +weston_keyboard_send_key(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, + enum wl_keyboard_key_state state) +{ + struct wl_resource *resource; + struct wl_display *display = keyboard->seat->compositor->wl_display; + uint32_t serial; + struct wl_list *resource_list; + uint32_t msecs; + + if (!weston_keyboard_has_focus_resource(keyboard)) + return; + + resource_list = &keyboard->focus_resource_list; + serial = wl_display_next_serial(display); + msecs = timespec_to_msec(time); + wl_resource_for_each(resource, resource_list) { + send_timestamps_for_input_resource(resource, + &keyboard->timestamps_list, + time); + wl_keyboard_send_key(resource, serial, msecs, key, state); + } +}; + +static void +default_grab_keyboard_key(struct weston_keyboard_grab *grab, + const struct timespec *time, uint32_t key, + uint32_t state) +{ + weston_keyboard_send_key(grab->keyboard, 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 weston_pointer_client * +find_pointer_client_for_surface(struct weston_pointer *pointer, + struct weston_surface *surface) +{ + struct wl_client *client; + + if (!surface) + return NULL; + + if (!surface->resource) + return NULL; + + client = wl_resource_get_client(surface->resource); + return weston_pointer_get_pointer_client(pointer, client); +} + +static struct weston_pointer_client * +find_pointer_client_for_view(struct weston_pointer *pointer, struct weston_view *view) +{ + if (!view) + return NULL; + + return find_pointer_client_for_surface(pointer, view->surface); +} + +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)); +} + +/** Send wl_keyboard.modifiers events to focused resources and pointer + * focused resources. + * + * \param keyboard The keyboard where the modifiers events originates from. + * \param serial The serial of the event + * \param mods_depressed The mods_depressed value of the event + * \param mods_latched The mods_latched value of the event + * \param mods_locked The mods_locked value of the event + * \param group The group value of the event + * + * For every resource that is currently in focus, send a wl_keyboard.modifiers + * event with the passed parameters. The focused resources are the wl_keyboard + * resources of the client which currently has the surface with keyboard focus. + * This also sends wl_keyboard.modifiers events to the wl_keyboard resources of + * the client having pointer focus (if different from the keyboard focus client). + */ +WL_EXPORT void +weston_keyboard_send_modifiers(struct weston_keyboard *keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) +{ + struct weston_pointer *pointer = + weston_seat_get_pointer(keyboard->seat); + + if (weston_keyboard_has_focus_resource(keyboard)) { + struct wl_list *resource_list; + struct wl_resource *resource; + + 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->surface->resource && + pointer->focus->surface != keyboard->focus) { + struct wl_client *pointer_client = + wl_resource_get_client(pointer->focus->surface->resource); + + send_modifiers_to_client_in_list(pointer_client, + &keyboard->resource_list, + serial, + keyboard); + } +} + +static void +default_grab_keyboard_modifiers(struct weston_keyboard_grab *grab, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) +{ + weston_keyboard_send_modifiers(grab->keyboard, serial, mods_depressed, + mods_latched, mods_locked, group); +} + +static void +default_grab_keyboard_cancel(struct weston_keyboard_grab *grab) +{ +} + +static const struct weston_keyboard_grab_interface + default_keyboard_grab_interface = { + default_grab_keyboard_key, + default_grab_keyboard_modifiers, + default_grab_keyboard_cancel, +}; + +static void +pointer_unmap_sprite(struct weston_pointer *pointer) +{ + struct weston_surface *surface = pointer->sprite->surface; + + if (weston_surface_is_mapped(surface)) + weston_surface_unmap(surface); + + wl_list_remove(&pointer->sprite_destroy_listener.link); + surface->committed = NULL; + surface->committed_private = NULL; + weston_surface_set_label_func(surface, NULL); + weston_view_destroy(pointer->sprite); + 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; +} + +static void +weston_pointer_reset_state(struct weston_pointer *pointer) +{ + pointer->button_count = 0; +} + +static void +weston_pointer_handle_output_destroy(struct wl_listener *listener, void *data); + +static struct weston_pointer * +weston_pointer_create(struct weston_seat *seat) +{ + struct weston_pointer *pointer; + + pointer = zalloc(sizeof *pointer); + if (pointer == NULL) + return NULL; + + wl_list_init(&pointer->pointer_clients); + weston_pointer_set_default_grab(pointer, + seat->compositor->default_pointer_grab); + wl_list_init(&pointer->focus_resource_listener.link); + pointer->focus_resource_listener.notify = pointer_focus_resource_destroyed; + pointer->default_grab.pointer = pointer; + pointer->grab = &pointer->default_grab; + wl_signal_init(&pointer->motion_signal); + wl_signal_init(&pointer->focus_signal); + wl_list_init(&pointer->focus_view_listener.link); + wl_signal_init(&pointer->destroy_signal); + wl_list_init(&pointer->timestamps_list); + + 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); + + pointer->output_destroy_listener.notify = + weston_pointer_handle_output_destroy; + wl_signal_add(&seat->compositor->output_destroyed_signal, + &pointer->output_destroy_listener); + + pointer->sx = wl_fixed_from_int(-1000000); + pointer->sy = wl_fixed_from_int(-1000000); + + return pointer; +} + +static void +weston_pointer_destroy(struct weston_pointer *pointer) +{ + struct weston_pointer_client *pointer_client, *tmp; + + wl_signal_emit(&pointer->destroy_signal, pointer); + + if (pointer->sprite) + pointer_unmap_sprite(pointer); + + wl_list_for_each_safe(pointer_client, tmp, &pointer->pointer_clients, + link) { + wl_list_remove(&pointer_client->link); + weston_pointer_client_destroy(pointer_client); + } + + wl_list_remove(&pointer->focus_resource_listener.link); + wl_list_remove(&pointer->focus_view_listener.link); + wl_list_remove(&pointer->output_destroy_listener.link); + wl_list_remove(&pointer->timestamps_list); + free(pointer); +} + +void +weston_pointer_set_default_grab(struct weston_pointer *pointer, + const struct weston_pointer_grab_interface *interface) +{ + if (interface) + pointer->default_grab.interface = interface; + else + pointer->default_grab.interface = + &default_pointer_grab_interface; +} + +static 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_list_init(&keyboard->focus_resource_listener.link); + keyboard->focus_resource_listener.notify = keyboard_focus_resource_destroyed; + 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); + wl_list_init(&keyboard->timestamps_list); + + return keyboard; +} + +static void +weston_xkb_info_destroy(struct weston_xkb_info *xkb_info); + +static void +weston_keyboard_destroy(struct weston_keyboard *keyboard) +{ + struct wl_resource *resource; + + wl_resource_for_each(resource, &keyboard->resource_list) { + wl_resource_set_user_data(resource, NULL); + } + + wl_resource_for_each(resource, &keyboard->focus_resource_list) { + wl_resource_set_user_data(resource, NULL); + } + + wl_list_remove(&keyboard->resource_list); + wl_list_remove(&keyboard->focus_resource_list); + + xkb_state_unref(keyboard->xkb_state.state); + if (keyboard->xkb_info) + weston_xkb_info_destroy(keyboard->xkb_info); + xkb_keymap_unref(keyboard->pending_keymap); + + wl_array_release(&keyboard->keys); + wl_list_remove(&keyboard->focus_resource_listener.link); + wl_list_remove(&keyboard->timestamps_list); + free(keyboard); +} + +static void +weston_touch_reset_state(struct weston_touch *touch) +{ + touch->num_tp = 0; +} + +static struct weston_touch * +weston_touch_create(void) +{ + struct weston_touch *touch; + + touch = zalloc(sizeof *touch); + if (touch == NULL) + return NULL; + + wl_list_init(&touch->device_list); + wl_list_init(&touch->resource_list); + wl_list_init(&touch->focus_resource_list); + wl_list_init(&touch->focus_view_listener.link); + touch->focus_view_listener.notify = touch_focus_view_destroyed; + wl_list_init(&touch->focus_resource_listener.link); + touch->focus_resource_listener.notify = touch_focus_resource_destroyed; + touch->default_grab.interface = &default_touch_grab_interface; + touch->default_grab.touch = touch; + touch->grab = &touch->default_grab; + wl_signal_init(&touch->focus_signal); + wl_list_init(&touch->timestamps_list); + + return touch; +} + +static void +weston_touch_destroy(struct weston_touch *touch) +{ + struct wl_resource *resource; + + assert(wl_list_empty(&touch->device_list)); + + wl_resource_for_each(resource, &touch->resource_list) { + wl_resource_set_user_data(resource, NULL); + } + + wl_resource_for_each(resource, &touch->focus_resource_list) { + wl_resource_set_user_data(resource, NULL); + } + + wl_list_remove(&touch->resource_list); + wl_list_remove(&touch->focus_resource_list); + wl_list_remove(&touch->focus_view_listener.link); + wl_list_remove(&touch->focus_resource_listener.link); + wl_list_remove(&touch->timestamps_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_signal_emit(&seat->updated_caps_signal, seat); +} + + +/** Clear the pointer focus + * + * \param pointer the pointer to clear focus for. + * + * This can be used to unset pointer focus and set the co-ordinates to the + * arbitrary values we use for the no focus case. + * + * There's no requirement to use this function. For example, passing the + * results of a weston_compositor_pick_view() directly to + * weston_pointer_set_focus() will do the right thing when no view is found. + */ +WL_EXPORT void +weston_pointer_clear_focus(struct weston_pointer *pointer) +{ + weston_pointer_set_focus(pointer, NULL, + wl_fixed_from_int(-1000000), + wl_fixed_from_int(-1000000)); +} + +WL_EXPORT void +weston_pointer_set_focus(struct weston_pointer *pointer, + struct weston_view *view, + wl_fixed_t sx, wl_fixed_t sy) +{ + struct weston_pointer_client *pointer_client; + struct weston_keyboard *kbd = weston_seat_get_keyboard(pointer->seat); + struct wl_resource *resource; + struct wl_resource *surface_resource; + struct wl_display *display = pointer->seat->compositor->wl_display; + uint32_t serial; + struct wl_list *focus_resource_list; + int refocus = 0; + + if ((!pointer->focus && view) || + (pointer->focus && !view) || + (pointer->focus && pointer->focus->surface != view->surface) || + pointer->sx != sx || pointer->sy != sy) + refocus = 1; + + if (pointer->focus_client && refocus) { + focus_resource_list = &pointer->focus_client->pointer_resources; + if (!wl_list_empty(focus_resource_list)) { + serial = wl_display_next_serial(display); + surface_resource = pointer->focus->surface->resource; + wl_resource_for_each(resource, focus_resource_list) { + wl_pointer_send_leave(resource, serial, + surface_resource); + pointer_send_frame(resource); + } + } + + pointer->focus_client = NULL; + } + + pointer_client = find_pointer_client_for_view(pointer, view); + if (pointer_client && refocus) { + struct wl_client *surface_client = pointer_client->client; + + serial = wl_display_next_serial(display); + + if (kbd && kbd->focus != view->surface) + send_modifiers_to_client_in_list(surface_client, + &kbd->resource_list, + serial, + kbd); + + pointer->focus_client = pointer_client; + + focus_resource_list = &pointer->focus_client->pointer_resources; + wl_resource_for_each(resource, focus_resource_list) { + wl_pointer_send_enter(resource, + serial, + view->surface->resource, + sx, sy); + pointer_send_frame(resource); + } + + pointer->focus_serial = serial; + } + + wl_list_remove(&pointer->focus_view_listener.link); + wl_list_init(&pointer->focus_view_listener.link); + wl_list_remove(&pointer->focus_resource_listener.link); + wl_list_init(&pointer->focus_resource_listener.link); + if (view) + wl_signal_add(&view->destroy_signal, &pointer->focus_view_listener); + if (view && view->surface->resource) + wl_resource_add_destroy_listener(view->surface->resource, + &pointer->focus_resource_listener); + + pointer->focus = view; + pointer->focus_view_listener.notify = pointer_focus_view_destroyed; + pointer->sx = sx; + pointer->sy = sy; + + assert(view || sx == wl_fixed_from_int(-1000000)); + assert(view || sy == wl_fixed_from_int(-1000000)); + + 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) { + wl_keyboard_send_enter(resource, serial, + surface->resource, + &keyboard->keys); + send_modifiers_to_resource(keyboard, resource, serial); + } +} + +WL_EXPORT void +weston_keyboard_set_focus(struct weston_keyboard *keyboard, + struct weston_surface *surface) +{ + struct weston_seat *seat = keyboard->seat; + struct wl_resource *resource; + struct wl_display *display = keyboard->seat->compositor->wl_display; + uint32_t serial; + struct wl_list *focus_resource_list; + + /* Keyboard focus on a surface without a client is equivalent to NULL + * focus as nothing would react to the keyboard events anyway. + * Just set focus to NULL instead - the destroy listener hangs on the + * wl_resource anyway. + */ + if (surface && !surface->resource) + surface = NULL; + + 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; + } + + if (seat->saved_kbd_focus) { + wl_list_remove(&seat->saved_kbd_focus_listener.link); + seat->saved_kbd_focus = NULL; + } + + wl_list_remove(&keyboard->focus_resource_listener.link); + wl_list_init(&keyboard->focus_resource_listener.link); + if (surface) + wl_resource_add_destroy_listener(surface->resource, + &keyboard->focus_resource_listener); + + keyboard->focus = surface; + wl_signal_emit(&keyboard->focus_signal, keyboard); +} + +/* Users of this function must manually manage the keyboard focus */ +WL_EXPORT void +weston_keyboard_start_grab(struct weston_keyboard *keyboard, + struct weston_keyboard_grab *grab) +{ + keyboard->grab = grab; + grab->keyboard = keyboard; +} + +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); +} + +static void +weston_pointer_clamp_for_output(struct weston_pointer *pointer, + struct weston_output *output, + wl_fixed_t *fx, wl_fixed_t *fy) +{ + int x, y; + + x = wl_fixed_to_int(*fx); + y = wl_fixed_to_int(*fy); + + if (x < output->x) + *fx = wl_fixed_from_int(output->x); + else if (x >= output->x + output->width) + *fx = wl_fixed_from_int(output->x + + output->width - 1); + if (y < output->y) + *fy = wl_fixed_from_int(output->y); + else if (y >= output->y + output->height) + *fy = wl_fixed_from_int(output->y + + output->height - 1); +} + +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) + weston_pointer_clamp_for_output(pointer, prev, fx, fy); +} + +static void +weston_pointer_move_to(struct weston_pointer *pointer, + wl_fixed_t x, wl_fixed_t y) +{ + 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); + + if (pointer->sprite) { + weston_view_set_position(pointer->sprite, + ix - pointer->hotspot_x, + iy - pointer->hotspot_y); + weston_view_schedule_repaint(pointer->sprite); + } + + pointer->grab->interface->focus(pointer->grab); + wl_signal_emit(&pointer->motion_signal, pointer); +} + +WL_EXPORT void +weston_pointer_move(struct weston_pointer *pointer, + struct weston_pointer_motion_event *event) +{ + wl_fixed_t x, y; + + weston_pointer_motion_to_abs(pointer, event, &x, &y); + weston_pointer_move_to(pointer, x, y); +} + +/** Verify if the pointer is in a valid position and move it if it isn't. + */ +static void +weston_pointer_handle_output_destroy(struct wl_listener *listener, void *data) +{ + struct weston_pointer *pointer; + struct weston_compositor *ec; + struct weston_output *output, *closest = NULL; + int x, y, distance, min = INT_MAX; + wl_fixed_t fx, fy; + + pointer = container_of(listener, struct weston_pointer, + output_destroy_listener); + ec = pointer->seat->compositor; + + x = wl_fixed_to_int(pointer->x); + y = wl_fixed_to_int(pointer->y); + + wl_list_for_each(output, &ec->output_list, link) { + if (pixman_region32_contains_point(&output->region, + x, y, NULL)) + return; + + /* Aproximante the distance from the pointer to the center of + * the output. */ + distance = abs(output->x + output->width / 2 - x) + + abs(output->y + output->height / 2 - y); + if (distance < min) { + min = distance; + closest = output; + } + } + + /* Nothing to do if there's no output left. */ + if (!closest) + return; + + fx = pointer->x; + fy = pointer->y; + + weston_pointer_clamp_for_output(pointer, closest, &fx, &fy); + weston_pointer_move_to(pointer, fx, fy); +} + +WL_EXPORT void +notify_motion(struct weston_seat *seat, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + struct weston_compositor *ec = seat->compositor; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + weston_compositor_wake(ec); + pointer->grab->interface->motion(pointer->grab, time, event); +} + +static void +run_modifier_bindings(struct weston_seat *seat, uint32_t old, uint32_t new) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + uint32_t diff; + unsigned int i; + struct { + uint32_t xkb; + enum weston_keyboard_modifier weston; + } mods[] = { + { keyboard->xkb_info->ctrl_mod, MODIFIER_CTRL }, + { keyboard->xkb_info->alt_mod, MODIFIER_ALT }, + { keyboard->xkb_info->super_mod, MODIFIER_SUPER }, + { keyboard->xkb_info->shift_mod, MODIFIER_SHIFT }, + }; + + diff = new & ~old; + for (i = 0; i < ARRAY_LENGTH(mods); i++) { + if (diff & (1 << mods[i].xkb)) + weston_compositor_run_modifier_binding(compositor, + keyboard, + mods[i].weston, + WL_KEYBOARD_KEY_STATE_PRESSED); + } + + diff = old & ~new; + for (i = 0; i < ARRAY_LENGTH(mods); i++) { + if (diff & (1 << mods[i].xkb)) + weston_compositor_run_modifier_binding(compositor, + keyboard, + mods[i].weston, + WL_KEYBOARD_KEY_STATE_RELEASED); + } +} + +WL_EXPORT void +notify_motion_absolute(struct weston_seat *seat, const struct timespec *time, + double x, double y) +{ + struct weston_compositor *ec = seat->compositor; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_pointer_motion_event event = { 0 }; + + weston_compositor_wake(ec); + + event = (struct weston_pointer_motion_event) { + .mask = WESTON_POINTER_MOTION_ABS, + .x = x, + .y = y, + }; + + pointer->grab->interface->motion(pointer->grab, time, &event); +} + +static unsigned int +peek_next_activate_serial(struct weston_compositor *c) +{ + unsigned serial = c->activate_serial + 1; + + return serial == 0 ? 1 : serial; +} + +static void +inc_activate_serial(struct weston_compositor *c) +{ + c->activate_serial = peek_next_activate_serial (c); +} + +WL_EXPORT void +weston_view_activate(struct weston_view *view, + struct weston_seat *seat, + uint32_t flags) +{ + struct weston_compositor *compositor = seat->compositor; + + if (flags & WESTON_ACTIVATE_FLAG_CLICKED) { + view->click_to_activate_serial = + peek_next_activate_serial(compositor); + } + + weston_seat_set_keyboard_focus(seat, view->surface); +} + +WL_EXPORT void +notify_button(struct weston_seat *seat, const struct timespec *time, + int32_t button, enum wl_pointer_button_state state) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + 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, pointer, 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, const struct timespec *time, + struct weston_pointer_axis_event *event) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + weston_compositor_wake(compositor); + + if (weston_compositor_run_axis_binding(compositor, pointer, + time, event)) + return; + + pointer->grab->interface->axis(pointer->grab, time, event); +} + +WL_EXPORT void +notify_axis_source(struct weston_seat *seat, uint32_t source) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + weston_compositor_wake(compositor); + + pointer->grab->interface->axis_source(pointer->grab, source); +} + +WL_EXPORT void +notify_pointer_frame(struct weston_seat *seat) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + weston_compositor_wake(compositor); + + pointer->grab->interface->frame(pointer->grab); +} + +WL_EXPORT int +weston_keyboard_set_locks(struct weston_keyboard *keyboard, + uint32_t mask, uint32_t value) +{ + uint32_t serial; + xkb_mod_mask_t mods_depressed, mods_latched, mods_locked, group; + xkb_mod_mask_t num, caps; + + /* We don't want the leds to go out of sync with the actual state + * so if the backend has no way to change the leds don't try to + * change the state */ + if (!keyboard->seat->led_update) + return -1; + + mods_depressed = xkb_state_serialize_mods(keyboard->xkb_state.state, + XKB_STATE_DEPRESSED); + mods_latched = xkb_state_serialize_mods(keyboard->xkb_state.state, + XKB_STATE_LATCHED); + mods_locked = xkb_state_serialize_mods(keyboard->xkb_state.state, + XKB_STATE_LOCKED); + group = xkb_state_serialize_group(keyboard->xkb_state.state, + XKB_STATE_EFFECTIVE); + + num = (1 << keyboard->xkb_info->mod2_mod); + caps = (1 << keyboard->xkb_info->caps_mod); + if (mask & WESTON_NUM_LOCK) { + if (value & WESTON_NUM_LOCK) + mods_locked |= num; + else + mods_locked &= ~num; + } + if (mask & WESTON_CAPS_LOCK) { + if (value & WESTON_CAPS_LOCK) + mods_locked |= caps; + else + mods_locked &= ~caps; + } + + xkb_state_update_mask(keyboard->xkb_state.state, mods_depressed, + mods_latched, mods_locked, 0, 0, group); + + serial = wl_display_next_serial( + keyboard->seat->compositor->wl_display); + notify_modifiers(keyboard->seat, serial); + + return 0; +} + +WL_EXPORT void +notify_modifiers(struct weston_seat *seat, uint32_t serial) +{ + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + 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(keyboard->xkb_state.state, + XKB_STATE_MODS_DEPRESSED); + mods_latched = xkb_state_serialize_mods(keyboard->xkb_state.state, + XKB_STATE_MODS_LATCHED); + mods_locked = xkb_state_serialize_mods(keyboard->xkb_state.state, + XKB_STATE_MODS_LOCKED); + group = xkb_state_serialize_layout(keyboard->xkb_state.state, + XKB_STATE_LAYOUT_EFFECTIVE); + + if (mods_depressed != keyboard->modifiers.mods_depressed || + mods_latched != keyboard->modifiers.mods_latched || + mods_locked != keyboard->modifiers.mods_locked || + group != keyboard->modifiers.group) + changed = 1; + + run_modifier_bindings(seat, keyboard->modifiers.mods_depressed, + mods_depressed); + + keyboard->modifiers.mods_depressed = mods_depressed; + keyboard->modifiers.mods_latched = mods_latched; + keyboard->modifiers.mods_locked = mods_locked; + 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 << keyboard->xkb_info->ctrl_mod)) + seat->modifier_state |= MODIFIER_CTRL; + if (mods_lookup & (1 << keyboard->xkb_info->alt_mod)) + seat->modifier_state |= MODIFIER_ALT; + if (mods_lookup & (1 << keyboard->xkb_info->super_mod)) + seat->modifier_state |= MODIFIER_SUPER; + if (mods_lookup & (1 << keyboard->xkb_info->shift_mod)) + seat->modifier_state |= MODIFIER_SHIFT; + + /* Finally, notify the compositor that LEDs have changed. */ + if (xkb_state_led_index_is_active(keyboard->xkb_state.state, + keyboard->xkb_info->num_led)) + leds |= LED_NUM_LOCK; + if (xkb_state_led_index_is_active(keyboard->xkb_state.state, + keyboard->xkb_info->caps_led)) + leds |= LED_CAPS_LOCK; + if (xkb_state_led_index_is_active(keyboard->xkb_state.state, + keyboard->xkb_info->scroll_led)) + leds |= LED_SCROLL_LOCK; + if (leds != keyboard->xkb_state.leds && seat->led_update) + seat->led_update(seat, leds); + keyboard->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) +{ + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + enum xkb_key_direction direction; + + 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(keyboard->xkb_state.state, key + 8, direction); + + notify_modifiers(seat, serial); +} + +WL_EXPORT void +weston_keyboard_send_keymap(struct weston_keyboard *kbd, struct wl_resource *resource) +{ + struct weston_xkb_info *xkb_info = kbd->xkb_info; + int fd; + size_t size; + enum ro_anonymous_file_mapmode mapmode; + + if (wl_resource_get_version(resource) < 7) + mapmode = RO_ANONYMOUS_FILE_MAPMODE_SHARED; + else + mapmode = RO_ANONYMOUS_FILE_MAPMODE_PRIVATE; + + fd = os_ro_anonymous_file_get_fd(xkb_info->keymap_rofile, mapmode); + size = os_ro_anonymous_file_size(xkb_info->keymap_rofile); + + if (fd == -1) { + weston_log("creating a keymap file failed: %s\n", + strerror(errno)); + return; + } + + wl_keyboard_send_keymap(resource, + WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, + fd, + size); + + os_ro_anonymous_file_put_fd(fd); +} + +static void +send_modifiers(struct wl_resource *resource, uint32_t serial, struct weston_keyboard *keyboard) +{ + wl_keyboard_send_modifiers(resource, serial, + keyboard->modifiers.mods_depressed, + keyboard->modifiers.mods_latched, + keyboard->modifiers.mods_locked, + keyboard->modifiers.group); +} + +static struct weston_xkb_info * +weston_xkb_info_create(struct xkb_keymap *keymap); + +static void +update_keymap(struct weston_seat *seat) +{ + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + struct wl_resource *resource; + struct weston_xkb_info *xkb_info; + struct xkb_state *state; + xkb_mod_mask_t latched_mods; + xkb_mod_mask_t locked_mods; + + xkb_info = weston_xkb_info_create(keyboard->pending_keymap); + + xkb_keymap_unref(keyboard->pending_keymap); + keyboard->pending_keymap = NULL; + + if (!xkb_info) { + weston_log("failed to create XKB info\n"); + return; + } + + state = xkb_state_new(xkb_info->keymap); + if (!state) { + weston_log("failed to initialise XKB state\n"); + weston_xkb_info_destroy(xkb_info); + return; + } + + latched_mods = xkb_state_serialize_mods(keyboard->xkb_state.state, + XKB_STATE_MODS_LATCHED); + locked_mods = xkb_state_serialize_mods(keyboard->xkb_state.state, + XKB_STATE_MODS_LOCKED); + xkb_state_update_mask(state, + 0, /* depressed */ + latched_mods, + locked_mods, + 0, 0, 0); + + weston_xkb_info_destroy(keyboard->xkb_info); + keyboard->xkb_info = xkb_info; + + xkb_state_unref(keyboard->xkb_state.state); + keyboard->xkb_state.state = state; + + wl_resource_for_each(resource, &keyboard->resource_list) + weston_keyboard_send_keymap(keyboard, resource); + wl_resource_for_each(resource, &keyboard->focus_resource_list) + weston_keyboard_send_keymap(keyboard, resource); + + notify_modifiers(seat, wl_display_next_serial(seat->compositor->wl_display)); + + if (!latched_mods && !locked_mods) + return; + + wl_resource_for_each(resource, &keyboard->resource_list) + send_modifiers(resource, wl_display_get_serial(seat->compositor->wl_display), keyboard); + wl_resource_for_each(resource, &keyboard->focus_resource_list) + send_modifiers(resource, wl_display_get_serial(seat->compositor->wl_display), keyboard); +} + +WL_EXPORT void +notify_key(struct weston_seat *seat, const struct timespec *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 = weston_seat_get_keyboard(seat); + struct weston_keyboard_grab *grab = keyboard->grab; + uint32_t *k, *end; + + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + weston_compositor_idle_inhibit(compositor); + } 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, keyboard, time, + key, state); + grab = keyboard->grab; + } + + grab->interface->key(grab, time, key, state); + + if (keyboard->pending_keymap && + keyboard->keys.size == 0) + update_keymap(seat); + + if (update_state == STATE_UPDATE_AUTOMATIC) { + update_modifier_state(seat, + wl_display_get_serial(compositor->wl_display), + key, + state); + } + + keyboard->grab_serial = wl_display_get_serial(compositor->wl_display); + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + keyboard->grab_time = *time; + keyboard->grab_key = key; + } +} + +WL_EXPORT void +notify_pointer_focus(struct weston_seat *seat, struct weston_output *output, + double x, double y) +{ + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + if (output) { + weston_pointer_move_to(pointer, + wl_fixed_from_double(x), + wl_fixed_from_double(y)); + } else { + /* 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 = weston_seat_get_keyboard(seat); + 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); + } + + surface = seat->saved_kbd_focus; + if (surface) { + weston_keyboard_set_focus(keyboard, surface); + } +} + +WL_EXPORT void +notify_keyboard_focus_out(struct weston_seat *seat) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_surface *focus = keyboard->focus; + 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; + + weston_keyboard_set_focus(keyboard, NULL); + weston_keyboard_cancel_grab(keyboard); + if (pointer) + weston_pointer_cancel_grab(pointer); + + if (focus) { + seat->saved_kbd_focus = focus; + seat->saved_kbd_focus_listener.notify = + destroy_device_saved_kbd_focus; + wl_signal_add(&focus->destroy_signal, + &seat->saved_kbd_focus_listener); + } +} + +WL_EXPORT void +weston_touch_set_focus(struct weston_touch *touch, struct weston_view *view) +{ + struct wl_list *focus_resource_list; + + focus_resource_list = &touch->focus_resource_list; + + if (view && touch->focus && + touch->focus->surface == view->surface) { + touch->focus = view; + return; + } + + wl_list_remove(&touch->focus_resource_listener.link); + wl_list_init(&touch->focus_resource_listener.link); + wl_list_remove(&touch->focus_view_listener.link); + wl_list_init(&touch->focus_view_listener.link); + + if (!wl_list_empty(focus_resource_list)) { + move_resources(&touch->resource_list, + focus_resource_list); + } + + if (view) { + struct wl_client *surface_client; + + if (!view->surface->resource) { + touch->focus = NULL; + return; + } + + surface_client = wl_resource_get_client(view->surface->resource); + move_resources_for_client(focus_resource_list, + &touch->resource_list, + surface_client); + wl_resource_add_destroy_listener(view->surface->resource, + &touch->focus_resource_listener); + wl_signal_add(&view->destroy_signal, &touch->focus_view_listener); + } + touch->focus = view; +} + +static void +process_touch_normal(struct weston_touch_device *device, + const struct timespec *time, int touch_id, + double double_x, double double_y, int touch_type) +{ + struct weston_touch *touch = device->aggregate; + struct weston_touch_grab *grab = device->aggregate->grab; + struct weston_compositor *ec = device->aggregate->seat->compositor; + struct weston_view *ev; + wl_fixed_t sx, sy; + wl_fixed_t x = wl_fixed_from_double(double_x); + wl_fixed_t y = wl_fixed_from_double(double_y); + + /* 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: + /* the first finger down picks the view, and all further go + * to that view for the remainder of the touch session i.e. + * until all touch points are up again. */ + if (touch->num_tp == 1) { + ev = weston_compositor_pick_view(ec, x, y, &sx, &sy); + weston_touch_set_focus(touch, ev); + } else if (!touch->focus) { + /* 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", touch->num_tp); + return; + } + + weston_compositor_run_touch_binding(ec, touch, + time, touch_type); + + grab->interface->down(grab, time, touch_id, x, y); + if (touch->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: + ev = touch->focus; + if (!ev) + break; + + grab->interface->motion(grab, time, touch_id, x, y); + break; + case WL_TOUCH_UP: + grab->interface->up(grab, time, touch_id); + if (touch->num_tp == 0) + weston_touch_set_focus(touch, NULL); + break; + } +} + +static enum weston_touch_mode +get_next_touch_mode(enum weston_touch_mode from) +{ + switch (from) { + case WESTON_TOUCH_MODE_PREP_NORMAL: + return WESTON_TOUCH_MODE_NORMAL; + + case WESTON_TOUCH_MODE_PREP_CALIB: + return WESTON_TOUCH_MODE_CALIB; + + case WESTON_TOUCH_MODE_NORMAL: + case WESTON_TOUCH_MODE_CALIB: + return from; + } + + return WESTON_TOUCH_MODE_NORMAL; +} + +/** Global touch mode update + * + * If no seat has a touch down and the compositor is in a PREP touch mode, + * set the compositor to the goal touch mode. + * + * Calls calibrator if touch mode changed. + */ +static void +weston_compositor_update_touch_mode(struct weston_compositor *compositor) +{ + struct weston_seat *seat; + struct weston_touch *touch; + enum weston_touch_mode goal; + + wl_list_for_each(seat, &compositor->seat_list, link) { + touch = weston_seat_get_touch(seat); + if (!touch) + continue; + + if (touch->num_tp > 0) + return; + } + + goal = get_next_touch_mode(compositor->touch_mode); + if (compositor->touch_mode != goal) { + compositor->touch_mode = goal; + touch_calibrator_mode_changed(compositor); + } +} + +/** Start transition to normal touch event handling + * + * The touch event mode changes when all touches on all touch devices have + * been lifted. If no touches are currently down, the transition is immediate. + * + * \sa weston_touch_mode + */ +void +weston_compositor_set_touch_mode_normal(struct weston_compositor *compositor) +{ + switch (compositor->touch_mode) { + case WESTON_TOUCH_MODE_PREP_NORMAL: + case WESTON_TOUCH_MODE_NORMAL: + return; + case WESTON_TOUCH_MODE_PREP_CALIB: + compositor->touch_mode = WESTON_TOUCH_MODE_NORMAL; + touch_calibrator_mode_changed(compositor); + return; + case WESTON_TOUCH_MODE_CALIB: + compositor->touch_mode = WESTON_TOUCH_MODE_PREP_NORMAL; + } + + weston_compositor_update_touch_mode(compositor); +} + +/** Start transition to calibrator touch event handling + * + * The touch event mode changes when all touches on all touch devices have + * been lifted. If no touches are currently down, the transition is immediate. + * + * \sa weston_touch_mode + */ +void +weston_compositor_set_touch_mode_calib(struct weston_compositor *compositor) +{ + switch (compositor->touch_mode) { + case WESTON_TOUCH_MODE_PREP_CALIB: + case WESTON_TOUCH_MODE_CALIB: + assert(0); + return; + case WESTON_TOUCH_MODE_PREP_NORMAL: + compositor->touch_mode = WESTON_TOUCH_MODE_CALIB; + touch_calibrator_mode_changed(compositor); + return; + case WESTON_TOUCH_MODE_NORMAL: + compositor->touch_mode = WESTON_TOUCH_MODE_PREP_CALIB; + } + + weston_compositor_update_touch_mode(compositor); +} + +/** Feed in touch down, motion, and up events, calibratable device. + * + * 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. + * + * \param device The physical device that generated the event. + * \param time The event timestamp. + * \param touch_id ID for the touch point of this event (multi-touch). + * \param x X coordinate in compositor global space. + * \param y Y coordinate in compositor global space. + * \param norm Normalized device X, Y coordinates in calibration space, or NULL. + * \param touch_type Either WL_TOUCH_DOWN, WL_TOUCH_UP, or WL_TOUCH_MOTION. + * + * Coordinates double_x and double_y are used for normal operation. + * + * Coordinates norm are only used for touch device calibration. If and only if + * the weston_touch_device does not support calibrating, norm must be NULL. + * + * The calibration space is the normalized coordinate space + * [0.0, 1.0]×[0.0, 1.0] of the weston_touch_device. This is assumed to + * map to the similar normalized coordinate space of the associated + * weston_output. + */ +WL_EXPORT void +notify_touch_normalized(struct weston_touch_device *device, + const struct timespec *time, + int touch_id, + double x, double y, + const struct weston_point2d_device_normalized *norm, + int touch_type) +{ + struct weston_seat *seat = device->aggregate->seat; + struct weston_touch *touch = device->aggregate; + + if (touch_type != WL_TOUCH_UP) { + if (weston_touch_device_can_calibrate(device)) + assert(norm != NULL); + else + assert(norm == NULL); + } + + /* Update touchpoints count regardless of the current mode. */ + switch (touch_type) { + case WL_TOUCH_DOWN: + weston_compositor_idle_inhibit(seat->compositor); + + touch->num_tp++; + break; + case WL_TOUCH_UP: + if (touch->num_tp == 0) { + /* This can happen if we start out with one or + * more fingers on the touch screen, in which + * case we didn't get the corresponding down + * event. */ + weston_log("Unmatched touch up event on seat %s, device %s\n", + seat->seat_name, device->syspath); + return; + } + weston_compositor_idle_release(seat->compositor); + + touch->num_tp--; + break; + default: + break; + } + + /* Properly forward the touch event */ + switch (weston_touch_device_get_mode(device)) { + case WESTON_TOUCH_MODE_NORMAL: + case WESTON_TOUCH_MODE_PREP_CALIB: + process_touch_normal(device, time, touch_id, x, y, touch_type); + break; + case WESTON_TOUCH_MODE_CALIB: + case WESTON_TOUCH_MODE_PREP_NORMAL: + notify_touch_calibrator(device, time, touch_id, + norm, touch_type); + break; + } +} + +WL_EXPORT void +notify_touch_frame(struct weston_touch_device *device) +{ + struct weston_touch_grab *grab; + + switch (weston_touch_device_get_mode(device)) { + case WESTON_TOUCH_MODE_NORMAL: + case WESTON_TOUCH_MODE_PREP_CALIB: + grab = device->aggregate->grab; + grab->interface->frame(grab); + break; + case WESTON_TOUCH_MODE_CALIB: + case WESTON_TOUCH_MODE_PREP_NORMAL: + notify_touch_calibrator_frame(device); + break; + } + + weston_compositor_update_touch_mode(device->aggregate->seat->compositor); +} + +WL_EXPORT void +notify_touch_cancel(struct weston_touch_device *device) +{ + struct weston_touch_grab *grab; + + switch (weston_touch_device_get_mode(device)) { + case WESTON_TOUCH_MODE_NORMAL: + case WESTON_TOUCH_MODE_PREP_CALIB: + grab = device->aggregate->grab; + grab->interface->cancel(grab); + break; + case WESTON_TOUCH_MODE_CALIB: + case WESTON_TOUCH_MODE_PREP_NORMAL: + notify_touch_calibrator_cancel(device); + break; + } + + weston_compositor_update_touch_mode(device->aggregate->seat->compositor); +} + +static int +pointer_cursor_surface_get_label(struct weston_surface *surface, + char *buf, size_t len) +{ + return snprintf(buf, len, "cursor"); +} + +static void +pointer_cursor_surface_committed(struct weston_surface *es, + int32_t dx, int32_t dy) +{ + struct weston_pointer *pointer = es->committed_private; + int x, y; + + if (es->width == 0) + return; + + assert(es == pointer->sprite->surface); + + 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_view_set_position(pointer->sprite, x, y); + + empty_region(&es->pending.input); + empty_region(&es->input); + + if (!weston_surface_is_mapped(es)) { + weston_layer_entry_insert(&es->compositor->cursor_layer.view_list, + &pointer->sprite->layer_link); + weston_view_update_transform(pointer->sprite); + es->is_mapped = true; + pointer->sprite->is_mapped = true; + } +} + +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 (!pointer) + return; + + if (surface_resource) + surface = wl_resource_get_user_data(surface_resource); + + if (pointer->focus == NULL) + return; + /* pointer->focus->surface->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->surface->resource == NULL) + return; + if (wl_resource_get_client(pointer->focus->surface->resource) != client) + return; + if (pointer->focus_serial - serial > UINT32_MAX / 2) + return; + + if (!surface) { + if (pointer->sprite) + pointer_unmap_sprite(pointer); + return; + } + + if (pointer->sprite && pointer->sprite->surface == surface && + pointer->hotspot_x == x && pointer->hotspot_y == y) + return; + + if (!pointer->sprite || pointer->sprite->surface != surface) { + if (weston_surface_set_role(surface, "wl_pointer-cursor", + resource, + WL_POINTER_ERROR_ROLE) < 0) + return; + + if (pointer->sprite) + pointer_unmap_sprite(pointer); + + wl_signal_add(&surface->destroy_signal, + &pointer->sprite_destroy_listener); + + surface->committed = pointer_cursor_surface_committed; + surface->committed_private = pointer; + weston_surface_set_label_func(surface, + pointer_cursor_surface_get_label); + pointer->sprite = weston_view_create(surface); + } + + pointer->hotspot_x = x; + pointer->hotspot_y = y; + + if (surface->buffer_ref.buffer) { + pointer_cursor_surface_committed(surface, 0, 0); + weston_view_schedule_repaint(pointer->sprite); + } +} + +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); + /* We use the pointer_state directly, which means we'll + * give a wl_pointer if the seat has ever had one - even though + * the spec explicitly states that this request only takes effect + * if the seat has the pointer capability. + * + * This prevents a race between the compositor sending new + * capabilities and the client trying to use the old ones. + */ + struct weston_pointer *pointer = seat ? seat->pointer_state : NULL; + struct wl_resource *cr; + struct weston_pointer_client *pointer_client; + + cr = wl_resource_create(client, &wl_pointer_interface, + wl_resource_get_version(resource), id); + if (cr == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_list_init(wl_resource_get_link(cr)); + wl_resource_set_implementation(cr, &pointer_interface, pointer, + unbind_pointer_client_resource); + + /* If we don't have a pointer_state, the resource is inert, so there + * is nothing more to set up */ + if (!pointer) + return; + + pointer_client = weston_pointer_ensure_pointer_client(pointer, client); + if (!pointer_client) { + wl_client_post_no_memory(client); + return; + } + + wl_list_insert(&pointer_client->pointer_resources, + wl_resource_get_link(cr)); + + if (pointer->focus && pointer->focus->surface->resource && + wl_resource_get_client(pointer->focus->surface->resource) == client) { + wl_fixed_t sx, sy; + + weston_view_from_global_fixed(pointer->focus, + pointer->x, + pointer->y, + &sx, &sy); + + wl_pointer_send_enter(cr, + pointer->focus_serial, + pointer->focus->surface->resource, + sx, sy); + pointer_send_frame(cr); + } +} + +static void +destroy_keyboard_resource(struct wl_resource *resource) +{ + struct weston_keyboard *keyboard = wl_resource_get_user_data(resource); + + wl_list_remove(wl_resource_get_link(resource)); + + if (keyboard) { + remove_input_resource_from_timestamps(resource, + &keyboard->timestamps_list); + } +} + +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 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); + /* We use the keyboard_state directly, which means we'll + * give a wl_keyboard if the seat has ever had one - even though + * the spec explicitly states that this request only takes effect + * if the seat has the keyboard capability. + * + * This prevents a race between the compositor sending new + * capabilities and the client trying to use the old ones. + */ + struct weston_keyboard *keyboard = seat ? seat->keyboard_state : NULL; + struct wl_resource *cr; + + cr = wl_resource_create(client, &wl_keyboard_interface, + wl_resource_get_version(resource), id); + if (cr == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_list_init(wl_resource_get_link(cr)); + wl_resource_set_implementation(cr, &keyboard_interface, + keyboard, destroy_keyboard_resource); + + /* If we don't have a keyboard_state, the resource is inert, so there + * is nothing more to set up */ + if (!keyboard) + 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(&keyboard->resource_list, wl_resource_get_link(cr)); + + if (wl_resource_get_version(cr) >= WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION) { + wl_keyboard_send_repeat_info(cr, + seat->compositor->kb_repeat_rate, + seat->compositor->kb_repeat_delay); + } + + weston_keyboard_send_keymap(keyboard, cr); + + if (keyboard->focus && keyboard->focus->resource && + wl_resource_get_client(keyboard->focus->resource) == client) { + struct weston_surface *surface = + (struct weston_surface *)keyboard->focus; + + wl_list_remove(wl_resource_get_link(cr)); + wl_list_insert(&keyboard->focus_resource_list, + wl_resource_get_link(cr)); + wl_keyboard_send_enter(cr, + keyboard->focus_serial, + surface->resource, + &keyboard->keys); + + send_modifiers_to_resource(keyboard, + cr, + keyboard->focus_serial); + + /* If this is the first keyboard resource for this + * client... */ + if (keyboard->focus_resource_list.prev == + wl_resource_get_link(cr)) + wl_data_device_set_keyboard_focus(seat); + } +} + +static void +destroy_touch_resource(struct wl_resource *resource) +{ + struct weston_touch *touch = wl_resource_get_user_data(resource); + + wl_list_remove(wl_resource_get_link(resource)); + + if (touch) { + remove_input_resource_from_timestamps(resource, + &touch->timestamps_list); + } +} + +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); + /* We use the touch_state directly, which means we'll + * give a wl_touch if the seat has ever had one - even though + * the spec explicitly states that this request only takes effect + * if the seat has the touch capability. + * + * This prevents a race between the compositor sending new + * capabilities and the client trying to use the old ones. + */ + struct weston_touch *touch = seat ? seat->touch_state : NULL; + struct wl_resource *cr; + + cr = wl_resource_create(client, &wl_touch_interface, + wl_resource_get_version(resource), id); + if (cr == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_list_init(wl_resource_get_link(cr)); + wl_resource_set_implementation(cr, &touch_interface, + touch, destroy_touch_resource); + + /* If we don't have a touch_state, the resource is inert, so there + * is nothing more to set up */ + if (!touch) + return; + + if (touch->focus && + wl_resource_get_client(touch->focus->surface->resource) == client) { + wl_list_insert(&touch->focus_resource_list, + wl_resource_get_link(cr)); + } else { + wl_list_insert(&touch->resource_list, + wl_resource_get_link(cr)); + } +} + +static void +seat_release(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct wl_seat_interface seat_interface = { + seat_get_pointer, + seat_get_keyboard, + seat_get_touch, + seat_release, +}; + +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, version, id); + wl_list_insert(&seat->base_resource_list, wl_resource_get_link(resource)); + wl_resource_set_implementation(resource, &seat_interface, data, + unbind_resource); + + if (weston_seat_get_pointer(seat)) + caps |= WL_SEAT_CAPABILITY_POINTER; + if (weston_seat_get_keyboard(seat)) + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + if (weston_seat_get_touch(seat)) + caps |= WL_SEAT_CAPABILITY_TOUCH; + + wl_seat_send_capabilities(resource, caps); + if (version >= WL_SEAT_NAME_SINCE_VERSION) + wl_seat_send_name(resource, seat->seat_name); +} + +static void +relative_pointer_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct zwp_relative_pointer_v1_interface relative_pointer_interface = { + relative_pointer_destroy +}; + +static void +relative_pointer_manager_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +relative_pointer_manager_get_relative_pointer(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *pointer_resource) +{ + struct weston_pointer *pointer = + wl_resource_get_user_data(pointer_resource); + struct weston_pointer_client *pointer_client; + struct wl_resource *cr; + + cr = wl_resource_create(client, &zwp_relative_pointer_v1_interface, + wl_resource_get_version(resource), id); + if (cr == NULL) { + wl_client_post_no_memory(client); + return; + } + + pointer_client = weston_pointer_ensure_pointer_client(pointer, client); + if (!pointer_client) { + wl_client_post_no_memory(client); + return; + } + + wl_list_insert(&pointer_client->relative_pointer_resources, + wl_resource_get_link(cr)); + wl_resource_set_implementation(cr, &relative_pointer_interface, + pointer, + unbind_pointer_client_resource); +} + +static const struct zwp_relative_pointer_manager_v1_interface relative_pointer_manager = { + relative_pointer_manager_destroy, + relative_pointer_manager_get_relative_pointer, +}; + +static void +bind_relative_pointer_manager(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, + &zwp_relative_pointer_manager_v1_interface, + 1, id); + + wl_resource_set_implementation(resource, &relative_pointer_manager, + compositor, + NULL); +} + +WL_EXPORT int +weston_compositor_set_xkb_rule_names(struct weston_compositor *ec, + struct xkb_rule_names *names) +{ + if (ec->xkb_context == NULL) { + ec->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + 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; + + xkb_keymap_unref(xkb_info->keymap); + + os_ro_anonymous_file_destroy(xkb_info->keymap_rofile); + free(xkb_info); +} + +void +weston_compositor_xkb_destroy(struct weston_compositor *ec) +{ + 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) +{ + char *keymap_string; + size_t keymap_size; + struct weston_xkb_info *xkb_info = zalloc(sizeof *xkb_info); + if (xkb_info == NULL) + return NULL; + + xkb_info->keymap = xkb_keymap_ref(keymap); + xkb_info->ref_count = 1; + + xkb_info->shift_mod = xkb_keymap_mod_get_index(xkb_info->keymap, + XKB_MOD_NAME_SHIFT); + xkb_info->caps_mod = xkb_keymap_mod_get_index(xkb_info->keymap, + XKB_MOD_NAME_CAPS); + xkb_info->ctrl_mod = xkb_keymap_mod_get_index(xkb_info->keymap, + XKB_MOD_NAME_CTRL); + xkb_info->alt_mod = xkb_keymap_mod_get_index(xkb_info->keymap, + XKB_MOD_NAME_ALT); + xkb_info->mod2_mod = xkb_keymap_mod_get_index(xkb_info->keymap, + "Mod2"); + xkb_info->mod3_mod = xkb_keymap_mod_get_index(xkb_info->keymap, + "Mod3"); + xkb_info->super_mod = xkb_keymap_mod_get_index(xkb_info->keymap, + XKB_MOD_NAME_LOGO); + xkb_info->mod5_mod = xkb_keymap_mod_get_index(xkb_info->keymap, + "Mod5"); + + xkb_info->num_led = xkb_keymap_led_get_index(xkb_info->keymap, + XKB_LED_NAME_NUM); + xkb_info->caps_led = xkb_keymap_led_get_index(xkb_info->keymap, + XKB_LED_NAME_CAPS); + xkb_info->scroll_led = xkb_keymap_led_get_index(xkb_info->keymap, + XKB_LED_NAME_SCROLL); + + keymap_string = xkb_keymap_get_as_string(xkb_info->keymap, + XKB_KEYMAP_FORMAT_TEXT_V1); + if (keymap_string == NULL) { + weston_log("failed to get string version of keymap\n"); + goto err_keymap; + } + keymap_size = strlen(keymap_string) + 1; + + xkb_info->keymap_rofile = os_ro_anonymous_file_create(keymap_size, + keymap_string); + free(keymap_string); + + if (!xkb_info->keymap_rofile) { + weston_log("failed to create anonymous file for keymap\n"); + goto err_keymap; + } + + return xkb_info; + +err_keymap: + xkb_keymap_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_keymap_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); + xkb_keymap_unref(keymap); + if (ec->xkb_info == NULL) + return -1; + + return 0; +} + +WL_EXPORT void +weston_seat_update_keymap(struct weston_seat *seat, struct xkb_keymap *keymap) +{ + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + + if (!keyboard || !keymap) + return; + + xkb_keymap_unref(keyboard->pending_keymap); + keyboard->pending_keymap = xkb_keymap_ref(keymap); + + if (keyboard->keys.size == 0) + update_keymap(seat); +} + +WL_EXPORT int +weston_seat_init_keyboard(struct weston_seat *seat, struct xkb_keymap *keymap) +{ + struct weston_keyboard *keyboard; + + if (seat->keyboard_state) { + seat->keyboard_device_count += 1; + if (seat->keyboard_device_count == 1) + seat_send_updated_caps(seat); + return 0; + } + + keyboard = weston_keyboard_create(); + if (keyboard == NULL) { + weston_log("failed to allocate weston keyboard struct\n"); + return -1; + } + + if (keymap != NULL) { + keyboard->xkb_info = weston_xkb_info_create(keymap); + if (keyboard->xkb_info == NULL) + goto err; + } else { + if (weston_compositor_build_global_keymap(seat->compositor) < 0) + goto err; + keyboard->xkb_info = seat->compositor->xkb_info; + keyboard->xkb_info->ref_count++; + } + + keyboard->xkb_state.state = xkb_state_new(keyboard->xkb_info->keymap); + if (keyboard->xkb_state.state == NULL) { + weston_log("failed to initialise XKB state\n"); + goto err; + } + + keyboard->xkb_state.leds = 0; + + seat->keyboard_state = keyboard; + seat->keyboard_device_count = 1; + keyboard->seat = seat; + + seat_send_updated_caps(seat); + + return 0; + +err: + if (keyboard->xkb_info) + weston_xkb_info_destroy(keyboard->xkb_info); + free(keyboard); + + return -1; +} + +static void +weston_keyboard_reset_state(struct weston_keyboard *keyboard) +{ + struct weston_seat *seat = keyboard->seat; + struct xkb_state *state; + + state = xkb_state_new(keyboard->xkb_info->keymap); + if (!state) { + weston_log("failed to reset XKB state\n"); + return; + } + xkb_state_unref(keyboard->xkb_state.state); + keyboard->xkb_state.state = state; + + keyboard->xkb_state.leds = 0; + + seat->modifier_state = 0; +} + +WL_EXPORT void +weston_seat_release_keyboard(struct weston_seat *seat) +{ + seat->keyboard_device_count--; + assert(seat->keyboard_device_count >= 0); + if (seat->keyboard_device_count == 0) { + weston_keyboard_set_focus(seat->keyboard_state, NULL); + weston_keyboard_cancel_grab(seat->keyboard_state); + weston_keyboard_reset_state(seat->keyboard_state); + seat_send_updated_caps(seat); + } +} + +WL_EXPORT void +weston_seat_init_pointer(struct weston_seat *seat) +{ + struct weston_pointer *pointer; + + if (seat->pointer_state) { + seat->pointer_device_count += 1; + if (seat->pointer_device_count == 1) + seat_send_updated_caps(seat); + return; + } + + pointer = weston_pointer_create(seat); + if (pointer == NULL) + return; + + seat->pointer_state = 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_state; + + seat->pointer_device_count--; + if (seat->pointer_device_count == 0) { + weston_pointer_clear_focus(pointer); + weston_pointer_cancel_grab(pointer); + + if (pointer->sprite) + pointer_unmap_sprite(pointer); + + weston_pointer_reset_state(pointer); + seat_send_updated_caps(seat); + + /* seat->pointer is intentionally not destroyed so that + * a newly attached pointer on this seat will retain + * the previous cursor co-ordinates. + */ + } +} + +WL_EXPORT void +weston_seat_init_touch(struct weston_seat *seat) +{ + struct weston_touch *touch; + + if (seat->touch_state) { + 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_state = 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->touch_state, NULL); + weston_touch_cancel_grab(seat->touch_state); + weston_touch_reset_state(seat->touch_state); + 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); + wl_signal_init(&seat->updated_caps_signal); + + seat->global = wl_global_create(ec->wl_display, &wl_seat_interface, + MIN(wl_seat_interface.version, 7), + seat, bind_seat); + + seat->compositor = ec; + seat->modifier_state = 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) +{ + struct wl_resource *resource; + + wl_resource_for_each(resource, &seat->base_resource_list) { + wl_resource_set_user_data(resource, NULL); + } + + wl_resource_for_each(resource, &seat->drag_resource_list) { + wl_resource_set_user_data(resource, NULL); + } + + wl_list_remove(&seat->base_resource_list); + wl_list_remove(&seat->drag_resource_list); + + wl_list_remove(&seat->link); + + if (seat->saved_kbd_focus) + wl_list_remove(&seat->saved_kbd_focus_listener.link); + + if (seat->pointer_state) + weston_pointer_destroy(seat->pointer_state); + if (seat->keyboard_state) + weston_keyboard_destroy(seat->keyboard_state); + if (seat->touch_state) + weston_touch_destroy(seat->touch_state); + + free (seat->seat_name); + + wl_global_destroy(seat->global); + + wl_signal_emit(&seat->destroy_signal, seat); +} + +/** Get a seat's keyboard pointer + * + * \param seat The seat to query + * \return The seat's keyboard pointer, or NULL if no keyboard is present + * + * The keyboard pointer for a seat isn't freed when all keyboards are removed, + * so it should only be used when the seat's keyboard_device_count is greater + * than zero. This function does that test and only returns a pointer + * when a keyboard is present. + */ +WL_EXPORT struct weston_keyboard * +weston_seat_get_keyboard(struct weston_seat *seat) +{ + if (!seat) + return NULL; + + if (seat->keyboard_device_count) + return seat->keyboard_state; + + return NULL; +} + +/** Get a seat's pointer pointer + * + * \param seat The seat to query + * \return The seat's pointer pointer, or NULL if no pointer device is present + * + * The pointer pointer for a seat isn't freed when all mice are removed, + * so it should only be used when the seat's pointer_device_count is greater + * than zero. This function does that test and only returns a pointer + * when a pointing device is present. + */ +WL_EXPORT struct weston_pointer * +weston_seat_get_pointer(struct weston_seat *seat) +{ + if (!seat) + return NULL; + + if (seat->pointer_device_count) + return seat->pointer_state; + + return NULL; +} + +static const struct zwp_locked_pointer_v1_interface locked_pointer_interface; +static const struct zwp_confined_pointer_v1_interface confined_pointer_interface; + +static enum pointer_constraint_type +pointer_constraint_get_type(struct weston_pointer_constraint *constraint) +{ + if (wl_resource_instance_of(constraint->resource, + &zwp_locked_pointer_v1_interface, + &locked_pointer_interface)) { + return POINTER_CONSTRAINT_TYPE_LOCK; + } else if (wl_resource_instance_of(constraint->resource, + &zwp_confined_pointer_v1_interface, + &confined_pointer_interface)) { + return POINTER_CONSTRAINT_TYPE_CONFINE; + } + + abort(); + return 0; +} + +static void +pointer_constraint_notify_activated(struct weston_pointer_constraint *constraint) +{ + struct wl_resource *resource = constraint->resource; + + switch (pointer_constraint_get_type(constraint)) { + case POINTER_CONSTRAINT_TYPE_LOCK: + zwp_locked_pointer_v1_send_locked(resource); + break; + case POINTER_CONSTRAINT_TYPE_CONFINE: + zwp_confined_pointer_v1_send_confined(resource); + break; + } +} + +static void +pointer_constraint_notify_deactivated(struct weston_pointer_constraint *constraint) +{ + struct wl_resource *resource = constraint->resource; + + switch (pointer_constraint_get_type(constraint)) { + case POINTER_CONSTRAINT_TYPE_LOCK: + zwp_locked_pointer_v1_send_unlocked(resource); + break; + case POINTER_CONSTRAINT_TYPE_CONFINE: + zwp_confined_pointer_v1_send_unconfined(resource); + break; + } +} + +static struct weston_pointer_constraint * +get_pointer_constraint_for_pointer(struct weston_surface *surface, + struct weston_pointer *pointer) +{ + struct weston_pointer_constraint *constraint; + + wl_list_for_each(constraint, &surface->pointer_constraints, link) { + if (constraint->pointer == pointer) + return constraint; + } + + return NULL; +} + +/** Get a seat's touch pointer + * + * \param seat The seat to query + * \return The seat's touch pointer, or NULL if no touch device is present + * + * The touch pointer for a seat isn't freed when all touch devices are removed, + * so it should only be used when the seat's touch_device_count is greater + * than zero. This function does that test and only returns a pointer + * when a touch device is present. + */ +WL_EXPORT struct weston_touch * +weston_seat_get_touch(struct weston_seat *seat) +{ + if (!seat) + return NULL; + + if (seat->touch_device_count) + return seat->touch_state; + + return NULL; +} + +/** Sets the keyboard focus to the given surface + * + * \param surface the surface to focus on + * \param seat The seat to query + */ +WL_EXPORT void +weston_seat_set_keyboard_focus(struct weston_seat *seat, + struct weston_surface *surface) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + struct weston_surface_activation_data activation_data; + + if (keyboard && keyboard->focus != surface) { + weston_keyboard_set_focus(keyboard, surface); + wl_data_device_set_keyboard_focus(seat); + } + + inc_activate_serial(compositor); + + activation_data = (struct weston_surface_activation_data) { + .surface = surface, + .seat = seat, + }; + wl_signal_emit(&compositor->activate_signal, &activation_data); +} + +static void +enable_pointer_constraint(struct weston_pointer_constraint *constraint, + struct weston_view *view) +{ + assert(constraint->view == NULL); + constraint->view = view; + pointer_constraint_notify_activated(constraint); + weston_pointer_start_grab(constraint->pointer, &constraint->grab); + wl_list_remove(&constraint->surface_destroy_listener.link); + wl_list_init(&constraint->surface_destroy_listener.link); +} + +static bool +is_pointer_constraint_enabled(struct weston_pointer_constraint *constraint) +{ + return constraint->view != NULL; +} + +static void +weston_pointer_constraint_disable(struct weston_pointer_constraint *constraint) +{ + constraint->view = NULL; + pointer_constraint_notify_deactivated(constraint); + weston_pointer_end_grab(constraint->grab.pointer); +} + +void +weston_pointer_constraint_destroy(struct weston_pointer_constraint *constraint) +{ + if (is_pointer_constraint_enabled(constraint)) + weston_pointer_constraint_disable(constraint); + + wl_list_remove(&constraint->pointer_destroy_listener.link); + wl_list_remove(&constraint->surface_destroy_listener.link); + wl_list_remove(&constraint->surface_commit_listener.link); + wl_list_remove(&constraint->surface_activate_listener.link); + + wl_resource_set_user_data(constraint->resource, NULL); + pixman_region32_fini(&constraint->region); + wl_list_remove(&constraint->link); + free(constraint); +} + +static void +disable_pointer_constraint(struct weston_pointer_constraint *constraint) +{ + switch (constraint->lifetime) { + case ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT: + weston_pointer_constraint_destroy(constraint); + break; + case ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT: + weston_pointer_constraint_disable(constraint); + break; + } +} + +static bool +is_within_constraint_region(struct weston_pointer_constraint *constraint, + wl_fixed_t sx, wl_fixed_t sy) +{ + struct weston_surface *surface = constraint->surface; + pixman_region32_t constraint_region; + bool result; + + pixman_region32_init(&constraint_region); + pixman_region32_intersect(&constraint_region, + &surface->input, + &constraint->region); + result = pixman_region32_contains_point(&constraint_region, + wl_fixed_to_int(sx), + wl_fixed_to_int(sy), + NULL); + pixman_region32_fini(&constraint_region); + + return result; +} + +static void +maybe_enable_pointer_constraint(struct weston_pointer_constraint *constraint) +{ + struct weston_surface *surface = constraint->surface; + struct weston_view *vit; + struct weston_view *view = NULL; + struct weston_pointer *pointer = constraint->pointer; + struct weston_keyboard *keyboard; + struct weston_seat *seat = pointer->seat; + int32_t x, y; + + /* Postpone if no view of the surface was most recently clicked. */ + wl_list_for_each(vit, &surface->views, surface_link) { + if (vit->click_to_activate_serial == + surface->compositor->activate_serial) { + view = vit; + } + } + if (view == NULL) + return; + + /* Postpone if surface doesn't have keyboard focus. */ + keyboard = weston_seat_get_keyboard(seat); + if (!keyboard || keyboard->focus != surface) + return; + + /* Postpone constraint if the pointer is not within the + * constraint region. + */ + weston_view_from_global(view, + wl_fixed_to_int(pointer->x), + wl_fixed_to_int(pointer->y), + &x, &y); + if (!is_within_constraint_region(constraint, + wl_fixed_from_int(x), + wl_fixed_from_int(y))) + return; + + enable_pointer_constraint(constraint, view); +} + +static void +locked_pointer_grab_pointer_focus(struct weston_pointer_grab *grab) +{ +} + +static void +locked_pointer_grab_pointer_motion(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + pointer_send_relative_motion(grab->pointer, time, event); +} + +static void +locked_pointer_grab_pointer_button(struct weston_pointer_grab *grab, + const struct timespec *time, + uint32_t button, + uint32_t state_w) +{ + weston_pointer_send_button(grab->pointer, time, button, state_w); +} + +static void +locked_pointer_grab_pointer_axis(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_axis_event *event) +{ + weston_pointer_send_axis(grab->pointer, time, event); +} + +static void +locked_pointer_grab_pointer_axis_source(struct weston_pointer_grab *grab, + uint32_t source) +{ + weston_pointer_send_axis_source(grab->pointer, source); +} + +static void +locked_pointer_grab_pointer_frame(struct weston_pointer_grab *grab) +{ + weston_pointer_send_frame(grab->pointer); +} + +static void +locked_pointer_grab_pointer_cancel(struct weston_pointer_grab *grab) +{ + struct weston_pointer_constraint *constraint = + container_of(grab, struct weston_pointer_constraint, grab); + + disable_pointer_constraint(constraint); +} + +static const struct weston_pointer_grab_interface + locked_pointer_grab_interface = { + locked_pointer_grab_pointer_focus, + locked_pointer_grab_pointer_motion, + locked_pointer_grab_pointer_button, + locked_pointer_grab_pointer_axis, + locked_pointer_grab_pointer_axis_source, + locked_pointer_grab_pointer_frame, + locked_pointer_grab_pointer_cancel, +}; + +static void +pointer_constraint_constrain_resource_destroyed(struct wl_resource *resource) +{ + struct weston_pointer_constraint *constraint = + wl_resource_get_user_data(resource); + + if (!constraint) + return; + + weston_pointer_constraint_destroy(constraint); +} + +static void +pointer_constraint_surface_activate(struct wl_listener *listener, void *data) +{ + struct weston_surface_activation_data *activation = data; + struct weston_pointer *pointer; + struct weston_surface *focus = activation->surface; + struct weston_pointer_constraint *constraint = + container_of(listener, struct weston_pointer_constraint, + surface_activate_listener); + bool is_constraint_surface; + + pointer = weston_seat_get_pointer(activation->seat); + if (!pointer) + return; + + is_constraint_surface = + get_pointer_constraint_for_pointer(focus, pointer) == constraint; + + if (is_constraint_surface && + !is_pointer_constraint_enabled(constraint)) + maybe_enable_pointer_constraint(constraint); + else if (!is_constraint_surface && + is_pointer_constraint_enabled(constraint)) + disable_pointer_constraint(constraint); +} + +static void +pointer_constraint_pointer_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_pointer_constraint *constraint = + container_of(listener, struct weston_pointer_constraint, + pointer_destroy_listener); + + weston_pointer_constraint_destroy(constraint); +} + +static void +pointer_constraint_surface_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_pointer_constraint *constraint = + container_of(listener, struct weston_pointer_constraint, + surface_destroy_listener); + + weston_pointer_constraint_destroy(constraint); +} + +static void +pointer_constraint_surface_committed(struct wl_listener *listener, void *data) +{ + struct weston_pointer_constraint *constraint = + container_of(listener, struct weston_pointer_constraint, + surface_commit_listener); + + if (constraint->region_is_pending) { + constraint->region_is_pending = false; + pixman_region32_copy(&constraint->region, + &constraint->region_pending); + pixman_region32_fini(&constraint->region_pending); + pixman_region32_init(&constraint->region_pending); + } + + if (constraint->hint_is_pending) { + constraint->hint_is_pending = false; + + constraint->hint_is_pending = true; + constraint->hint_x = constraint->hint_x_pending; + constraint->hint_y = constraint->hint_y_pending; + } + + if (pointer_constraint_get_type(constraint) == + POINTER_CONSTRAINT_TYPE_CONFINE && + is_pointer_constraint_enabled(constraint)) + maybe_warp_confined_pointer(constraint); +} + +static struct weston_pointer_constraint * +weston_pointer_constraint_create(struct weston_surface *surface, + struct weston_pointer *pointer, + struct weston_region *region, + enum zwp_pointer_constraints_v1_lifetime lifetime, + struct wl_resource *cr, + const struct weston_pointer_grab_interface *grab_interface) +{ + struct weston_pointer_constraint *constraint; + + constraint = zalloc(sizeof *constraint); + if (!constraint) + return NULL; + + constraint->lifetime = lifetime; + pixman_region32_init(&constraint->region); + pixman_region32_init(&constraint->region_pending); + wl_list_insert(&surface->pointer_constraints, &constraint->link); + constraint->surface = surface; + constraint->pointer = pointer; + constraint->resource = cr; + constraint->grab.interface = grab_interface; + if (region) { + pixman_region32_copy(&constraint->region, + ®ion->region); + } else { + pixman_region32_fini(&constraint->region); + region_init_infinite(&constraint->region); + } + + constraint->surface_activate_listener.notify = + pointer_constraint_surface_activate; + constraint->surface_destroy_listener.notify = + pointer_constraint_surface_destroyed; + constraint->surface_commit_listener.notify = + pointer_constraint_surface_committed; + constraint->pointer_destroy_listener.notify = + pointer_constraint_pointer_destroyed; + + wl_signal_add(&surface->compositor->activate_signal, + &constraint->surface_activate_listener); + wl_signal_add(&pointer->destroy_signal, + &constraint->pointer_destroy_listener); + wl_signal_add(&surface->destroy_signal, + &constraint->surface_destroy_listener); + wl_signal_add(&surface->commit_signal, + &constraint->surface_commit_listener); + + return constraint; +} + +static void +init_pointer_constraint(struct wl_resource *pointer_constraints_resource, + uint32_t id, + struct weston_surface *surface, + struct weston_pointer *pointer, + struct weston_region *region, + enum zwp_pointer_constraints_v1_lifetime lifetime, + const struct wl_interface *interface, + const void *implementation, + const struct weston_pointer_grab_interface *grab_interface) +{ + struct wl_client *client = + wl_resource_get_client(pointer_constraints_resource); + struct wl_resource *cr; + struct weston_pointer_constraint *constraint; + + if (pointer && get_pointer_constraint_for_pointer(surface, pointer)) { + wl_resource_post_error(pointer_constraints_resource, + ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED, + "the pointer has a lock/confine request on this surface"); + return; + } + + cr = wl_resource_create(client, interface, + wl_resource_get_version(pointer_constraints_resource), + id); + if (cr == NULL) { + wl_client_post_no_memory(client); + return; + } + + if (pointer) { + constraint = weston_pointer_constraint_create(surface, pointer, + region, lifetime, + cr, grab_interface); + if (constraint == NULL) { + wl_client_post_no_memory(client); + return; + } + } else { + constraint = NULL; + } + + wl_resource_set_implementation(cr, implementation, constraint, + pointer_constraint_constrain_resource_destroyed); + + if (constraint) + maybe_enable_pointer_constraint(constraint); +} + +static void +pointer_constraints_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +locked_pointer_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + struct weston_pointer_constraint *constraint = + wl_resource_get_user_data(resource); + wl_fixed_t x, y; + + if (constraint && constraint->view && constraint->hint_is_pending && + is_within_constraint_region(constraint, + constraint->hint_x, + constraint->hint_y)) { + weston_view_to_global_fixed(constraint->view, + constraint->hint_x, + constraint->hint_y, + &x, &y); + weston_pointer_move_to(constraint->pointer, x, y); + } + wl_resource_destroy(resource); +} + +static void +locked_pointer_set_cursor_position_hint(struct wl_client *client, + struct wl_resource *resource, + wl_fixed_t surface_x, + wl_fixed_t surface_y) +{ + struct weston_pointer_constraint *constraint = + wl_resource_get_user_data(resource); + + /* Ignore a set cursor hint that was sent after the lock was cancelled. + */ + if (!constraint || + !constraint->resource || + constraint->resource != resource) + return; + + constraint->hint_is_pending = true; + constraint->hint_x_pending = surface_x; + constraint->hint_y_pending = surface_y; +} + +static void +locked_pointer_set_region(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *region_resource) +{ + struct weston_pointer_constraint *constraint = + wl_resource_get_user_data(resource); + struct weston_region *region = region_resource ? + wl_resource_get_user_data(region_resource) : NULL; + + if (!constraint) + return; + + if (region) { + pixman_region32_copy(&constraint->region_pending, + ®ion->region); + } else { + pixman_region32_fini(&constraint->region_pending); + region_init_infinite(&constraint->region_pending); + } + constraint->region_is_pending = true; +} + + +static const struct zwp_locked_pointer_v1_interface locked_pointer_interface = { + locked_pointer_destroy, + locked_pointer_set_cursor_position_hint, + locked_pointer_set_region, +}; + +static void +pointer_constraints_lock_pointer(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface_resource, + struct wl_resource *pointer_resource, + struct wl_resource *region_resource, + uint32_t lifetime) +{ + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + struct weston_pointer *pointer = wl_resource_get_user_data(pointer_resource); + struct weston_region *region = region_resource ? + wl_resource_get_user_data(region_resource) : NULL; + + init_pointer_constraint(resource, id, surface, pointer, region, lifetime, + &zwp_locked_pointer_v1_interface, + &locked_pointer_interface, + &locked_pointer_grab_interface); +} + +static void +confined_pointer_grab_pointer_focus(struct weston_pointer_grab *grab) +{ +} + +static double +vec2d_cross_product(struct vec2d a, struct vec2d b) +{ + return a.x * b.y - a.y * b.x; +} + +static struct vec2d +vec2d_add(struct vec2d a, struct vec2d b) +{ + return (struct vec2d) { + .x = a.x + b.x, + .y = a.y + b.y, + }; +} + +static struct vec2d +vec2d_subtract(struct vec2d a, struct vec2d b) +{ + return (struct vec2d) { + .x = a.x - b.x, + .y = a.y - b.y, + }; +} + +static struct vec2d +vec2d_multiply_constant(double c, struct vec2d a) +{ + return (struct vec2d) { + .x = c * a.x, + .y = c * a.y, + }; +} + +static bool +lines_intersect(struct line *line1, struct line *line2, + struct vec2d *intersection) +{ + struct vec2d p = line1->a; + struct vec2d r = vec2d_subtract(line1->b, line1->a); + struct vec2d q = line2->a; + struct vec2d s = vec2d_subtract(line2->b, line2->a); + double rxs; + double sxr; + double t; + double u; + + /* + * The line (p, r) and (q, s) intersects where + * + * p + t r = q + u s + * + * Calculate t: + * + * (p + t r) × s = (q + u s) × s + * p × s + t (r × s) = q × s + u (s × s) + * p × s + t (r × s) = q × s + * t (r × s) = q × s - p × s + * t (r × s) = (q - p) × s + * t = ((q - p) × s) / (r × s) + * + * Using the same method, for u we get: + * + * u = ((p - q) × r) / (s × r) + */ + + rxs = vec2d_cross_product(r, s); + sxr = vec2d_cross_product(s, r); + + /* If r × s = 0 then the lines are either parallel or collinear. */ + if (fabs(rxs) < DBL_MIN) + return false; + + t = vec2d_cross_product(vec2d_subtract(q, p), s) / rxs; + u = vec2d_cross_product(vec2d_subtract(p, q), r) / sxr; + + /* The lines only intersect if 0 ≤ t ≤ 1 and 0 ≤ u ≤ 1. */ + if (t < 0.0 || t > 1.0 || u < 0.0 || u > 1.0) + return false; + + *intersection = vec2d_add(p, vec2d_multiply_constant(t, r)); + return true; +} + +static struct border * +add_border(struct wl_array *array, + double x1, double y1, + double x2, double y2, + enum motion_direction blocking_dir) +{ + struct border *border = wl_array_add(array, sizeof *border); + + *border = (struct border) { + .line = (struct line) { + .a = (struct vec2d) { + .x = x1, + .y = y1, + }, + .b = (struct vec2d) { + .x = x2, + .y = y2, + }, + }, + .blocking_dir = blocking_dir, + }; + + return border; +} + +static int +compare_lines_x(const void *a, const void *b) +{ + const struct border *border_a = a; + const struct border *border_b = b; + + + if (border_a->line.a.x == border_b->line.a.x) + return border_a->line.b.x < border_b->line.b.x; + else + return border_a->line.a.x > border_b->line.a.x; +} + +static void +add_non_overlapping_edges(pixman_box32_t *boxes, + int band_above_start, + int band_below_start, + int band_below_end, + struct wl_array *borders) +{ + int i; + struct wl_array band_merge; + struct border *border; + struct border *prev_border; + struct border *new_border; + + wl_array_init(&band_merge); + + /* Add bottom band of previous row, and top band of current row, and + * sort them so lower left x coordinate comes first. If there are two + * borders with the same left x coordinate, the wider one comes first. + */ + for (i = band_above_start; i < band_below_start; i++) { + pixman_box32_t *box = &boxes[i]; + add_border(&band_merge, box->x1, box->y2, box->x2, box->y2, + MOTION_DIRECTION_POSITIVE_Y); + } + for (i = band_below_start; i < band_below_end; i++) { + pixman_box32_t *box= &boxes[i]; + add_border(&band_merge, box->x1, box->y1, box->x2, box->y1, + MOTION_DIRECTION_NEGATIVE_Y); + } + qsort(band_merge.data, + band_merge.size / sizeof *border, + sizeof *border, + compare_lines_x); + + /* Combine the two combined bands so that any overlapping border is + * eliminated. */ + prev_border = NULL; + wl_array_for_each(border, &band_merge) { + assert(border->line.a.y == border->line.b.y); + assert(!prev_border || + prev_border->line.a.y == border->line.a.y); + assert(!prev_border || + (prev_border->line.a.x != border->line.a.x || + prev_border->line.b.x != border->line.b.x)); + assert(!prev_border || + prev_border->line.a.x <= border->line.a.x); + + if (prev_border && + prev_border->line.a.x == border->line.a.x) { + /* + * ------------ + + * ------- = + * [ ]----- + */ + prev_border->line.a.x = border->line.b.x; + } else if (prev_border && + prev_border->line.b.x == border->line.b.x) { + /* + * ------------ + + * ------ = + * ------[ ] + */ + prev_border->line.b.x = border->line.a.x; + } else if (prev_border && + prev_border->line.b.x == border->line.a.x) { + /* + * -------- + + * ------ = + * -------------- + */ + prev_border->line.b.x = border->line.b.x; + } else if (prev_border && + prev_border->line.b.x >= border->line.a.x) { + /* + * --------------- + + * ------ = + * -----[ ]---- + */ + new_border = add_border(borders, + border->line.b.x, + border->line.b.y, + prev_border->line.b.x, + prev_border->line.b.y, + prev_border->blocking_dir); + prev_border->line.b.x = border->line.a.x; + prev_border = new_border; + } else { + assert(!prev_border || + prev_border->line.b.x < border->line.a.x); + /* + * First border or non-overlapping. + * + * ----- + + * ----- = + * ----- ----- + */ + new_border = wl_array_add(borders, sizeof *border); + *new_border = *border; + prev_border = new_border; + } + } + + wl_array_release(&band_merge); +} + +static void +add_band_bottom_edges(pixman_box32_t *boxes, + int band_start, + int band_end, + struct wl_array *borders) +{ + int i; + + for (i = band_start; i < band_end; i++) { + add_border(borders, + boxes[i].x1, boxes[i].y2, + boxes[i].x2, boxes[i].y2, + MOTION_DIRECTION_POSITIVE_Y); + } +} + +static void +region_to_outline(pixman_region32_t *region, struct wl_array *borders) +{ + pixman_box32_t *boxes; + int num_boxes; + int i; + int top_most, bottom_most; + int current_roof; + int prev_top; + int band_start, prev_band_start; + + /* + * Remove any overlapping lines from the set of rectangles. Note that + * pixman regions are grouped as rows of rectangles, where rectangles + * in one row never touch or overlap and are all of the same height. + * + * -------- --- -------- --- + * | | | | | | | | + * ----------====---- --- ----------- ----- --- + * | | => | | + * ----==========--------- ----- ---------- + * | | | | + * ------------------- ------------------- + * + */ + + boxes = pixman_region32_rectangles(region, &num_boxes); + prev_top = 0; + top_most = boxes[0].y1; + current_roof = top_most; + bottom_most = boxes[num_boxes - 1].y2; + band_start = 0; + prev_band_start = 0; + for (i = 0; i < num_boxes; i++) { + /* Detect if there is a vertical empty space, and add the lower + * level of the previous band if so was the case. */ + if (i > 0 && + boxes[i].y1 != prev_top && + boxes[i].y1 != boxes[i - 1].y2) { + current_roof = boxes[i].y1; + add_band_bottom_edges(boxes, + band_start, + i, + borders); + } + + /* Special case adding the last band, since it won't be handled + * by the band change detection below. */ + if (boxes[i].y1 != current_roof && i == num_boxes - 1) { + if (boxes[i].y1 != prev_top) { + /* The last band is a single box, so we don't + * have a prev_band_start to tell us when the + * previous band started. */ + add_non_overlapping_edges(boxes, + band_start, + i, + i + 1, + borders); + } else { + add_non_overlapping_edges(boxes, + prev_band_start, + band_start, + i + 1, + borders); + } + } + + /* Detect when passing a band and combine the top border of the + * just passed band with the bottom band of the previous band. + */ + if (boxes[i].y1 != top_most && boxes[i].y1 != prev_top) { + /* Combine the two passed bands. */ + if (prev_top != current_roof) { + add_non_overlapping_edges(boxes, + prev_band_start, + band_start, + i, + borders); + } + + prev_band_start = band_start; + band_start = i; + } + + /* Add the top border if the box is part of the current roof. */ + if (boxes[i].y1 == current_roof) { + add_border(borders, + boxes[i].x1, boxes[i].y1, + boxes[i].x2, boxes[i].y1, + MOTION_DIRECTION_NEGATIVE_Y); + } + + /* Add the bottom border of the last band. */ + if (boxes[i].y2 == bottom_most) { + add_border(borders, + boxes[i].x1, boxes[i].y2, + boxes[i].x2, boxes[i].y2, + MOTION_DIRECTION_POSITIVE_Y); + } + + /* Always add the left border. */ + add_border(borders, + boxes[i].x1, boxes[i].y1, + boxes[i].x1, boxes[i].y2, + MOTION_DIRECTION_NEGATIVE_X); + + /* Always add the right border. */ + add_border(borders, + boxes[i].x2, boxes[i].y1, + boxes[i].x2, boxes[i].y2, + MOTION_DIRECTION_POSITIVE_X); + + prev_top = boxes[i].y1; + } +} + +static bool +is_border_horizontal (struct border *border) +{ + return border->line.a.y == border->line.b.y; +} + +static bool +is_border_blocking_directions(struct border *border, + uint32_t directions) +{ + /* Don't block parallel motions. */ + if (is_border_horizontal(border)) { + if ((directions & (MOTION_DIRECTION_POSITIVE_Y | + MOTION_DIRECTION_NEGATIVE_Y)) == 0) + return false; + } else { + if ((directions & (MOTION_DIRECTION_POSITIVE_X | + MOTION_DIRECTION_NEGATIVE_X)) == 0) + return false; + } + + return (~border->blocking_dir & directions) != directions; +} + +static struct border * +get_closest_border(struct wl_array *borders, + struct line *motion, + uint32_t directions) +{ + struct border *border; + struct vec2d intersection; + struct vec2d delta; + double distance_2; + struct border *closest_border = NULL; + double closest_distance_2 = DBL_MAX; + + wl_array_for_each(border, borders) { + if (!is_border_blocking_directions(border, directions)) + continue; + + if (!lines_intersect(&border->line, motion, &intersection)) + continue; + + delta = vec2d_subtract(intersection, motion->a); + distance_2 = delta.x*delta.x + delta.y*delta.y; + if (distance_2 < closest_distance_2) { + closest_border = border; + closest_distance_2 = distance_2; + } + } + + return closest_border; +} + +static void +clamp_to_border(struct border *border, + struct line *motion, + uint32_t *motion_dir) +{ + /* + * When clamping either rightward or downward motions, the motion needs + * to be clamped so that the destination coordinate does not end up on + * the border (see weston_pointer_clamp_event_to_region). Do this by + * clamping such motions to the border minus the smallest possible + * wl_fixed_t value. + */ + if (is_border_horizontal(border)) { + if (*motion_dir & MOTION_DIRECTION_POSITIVE_Y) + motion->b.y = border->line.a.y - wl_fixed_to_double(1); + else + motion->b.y = border->line.a.y; + *motion_dir &= ~(MOTION_DIRECTION_POSITIVE_Y | + MOTION_DIRECTION_NEGATIVE_Y); + } else { + if (*motion_dir & MOTION_DIRECTION_POSITIVE_X) + motion->b.x = border->line.a.x - wl_fixed_to_double(1); + else + motion->b.x = border->line.a.x; + *motion_dir &= ~(MOTION_DIRECTION_POSITIVE_X | + MOTION_DIRECTION_NEGATIVE_X); + } +} + +static uint32_t +get_motion_directions(struct line *motion) +{ + uint32_t directions = 0; + + if (motion->a.x < motion->b.x) + directions |= MOTION_DIRECTION_POSITIVE_X; + else if (motion->a.x > motion->b.x) + directions |= MOTION_DIRECTION_NEGATIVE_X; + if (motion->a.y < motion->b.y) + directions |= MOTION_DIRECTION_POSITIVE_Y; + else if (motion->a.y > motion->b.y) + directions |= MOTION_DIRECTION_NEGATIVE_Y; + + return directions; +} + +static void +weston_pointer_clamp_event_to_region(struct weston_pointer *pointer, + struct weston_pointer_motion_event *event, + pixman_region32_t *region, + wl_fixed_t *clamped_x, + wl_fixed_t *clamped_y) +{ + wl_fixed_t x, y; + wl_fixed_t sx, sy; + wl_fixed_t old_sx = pointer->sx; + wl_fixed_t old_sy = pointer->sy; + struct wl_array borders; + struct line motion; + struct border *closest_border; + float new_x_f, new_y_f; + uint32_t directions; + + weston_pointer_motion_to_abs(pointer, event, &x, &y); + weston_view_from_global_fixed(pointer->focus, x, y, &sx, &sy); + + wl_array_init(&borders); + + /* + * Generate borders given the confine region we are to use. The borders + * are defined to be the outer region of the allowed area. This means + * top/left borders are "within" the allowed area, while bottom/right + * borders are outside. This needs to be considered when clamping + * confined motion vectors. + */ + region_to_outline(region, &borders); + + motion = (struct line) { + .a = (struct vec2d) { + .x = wl_fixed_to_double(old_sx), + .y = wl_fixed_to_double(old_sy), + }, + .b = (struct vec2d) { + .x = wl_fixed_to_double(sx), + .y = wl_fixed_to_double(sy), + }, + }; + directions = get_motion_directions(&motion); + + while (directions) { + closest_border = get_closest_border(&borders, + &motion, + directions); + if (closest_border) + clamp_to_border(closest_border, &motion, &directions); + else + break; + } + + weston_view_to_global_float(pointer->focus, + (float) motion.b.x, (float) motion.b.y, + &new_x_f, &new_y_f); + *clamped_x = wl_fixed_from_double(new_x_f); + *clamped_y = wl_fixed_from_double(new_y_f); + + wl_array_release(&borders); +} + +static double +point_to_border_distance_2(struct border *border, double x, double y) +{ + double orig_x, orig_y; + double dx, dy; + + if (is_border_horizontal(border)) { + if (x < border->line.a.x) + orig_x = border->line.a.x; + else if (x > border->line.b.x) + orig_x = border->line.b.x; + else + orig_x = x; + orig_y = border->line.a.y; + } else { + if (y < border->line.a.y) + orig_y = border->line.a.y; + else if (y > border->line.b.y) + orig_y = border->line.b.y; + else + orig_y = y; + orig_x = border->line.a.x; + } + + + dx = fabs(orig_x - x); + dy = fabs(orig_y - y); + return dx*dx + dy*dy; +} + +static void +warp_to_behind_border(struct border *border, wl_fixed_t *sx, wl_fixed_t *sy) +{ + switch (border->blocking_dir) { + case MOTION_DIRECTION_POSITIVE_X: + case MOTION_DIRECTION_NEGATIVE_X: + if (border->blocking_dir == MOTION_DIRECTION_POSITIVE_X) + *sx = wl_fixed_from_double(border->line.a.x) - 1; + else + *sx = wl_fixed_from_double(border->line.a.x) + 1; + if (*sy < wl_fixed_from_double(border->line.a.y)) + *sy = wl_fixed_from_double(border->line.a.y) + 1; + else if (*sy > wl_fixed_from_double(border->line.b.y)) + *sy = wl_fixed_from_double(border->line.b.y) - 1; + break; + case MOTION_DIRECTION_POSITIVE_Y: + case MOTION_DIRECTION_NEGATIVE_Y: + if (border->blocking_dir == MOTION_DIRECTION_POSITIVE_Y) + *sy = wl_fixed_from_double(border->line.a.y) - 1; + else + *sy = wl_fixed_from_double(border->line.a.y) + 1; + if (*sx < wl_fixed_from_double(border->line.a.x)) + *sx = wl_fixed_from_double(border->line.a.x) + 1; + else if (*sx > wl_fixed_from_double(border->line.b.x)) + *sx = wl_fixed_from_double(border->line.b.x) - 1; + break; + } +} + +static void +maybe_warp_confined_pointer(struct weston_pointer_constraint *constraint) +{ + wl_fixed_t x; + wl_fixed_t y; + wl_fixed_t sx; + wl_fixed_t sy; + + weston_view_from_global_fixed(constraint->view, + constraint->pointer->x, + constraint->pointer->y, + &sx, + &sy); + + if (!is_within_constraint_region(constraint, sx, sy)) { + double xf = wl_fixed_to_double(sx); + double yf = wl_fixed_to_double(sy); + pixman_region32_t confine_region; + struct wl_array borders; + struct border *border; + double closest_distance_2 = DBL_MAX; + struct border *closest_border = NULL; + + wl_array_init(&borders); + + pixman_region32_init(&confine_region); + pixman_region32_intersect(&confine_region, + &constraint->view->surface->input, + &constraint->region); + region_to_outline(&confine_region, &borders); + pixman_region32_fini(&confine_region); + + wl_array_for_each(border, &borders) { + double distance_2; + + distance_2 = point_to_border_distance_2(border, xf, yf); + if (distance_2 < closest_distance_2) { + closest_border = border; + closest_distance_2 = distance_2; + } + } + assert(closest_border); + + warp_to_behind_border(closest_border, &sx, &sy); + + wl_array_release(&borders); + + weston_view_to_global_fixed(constraint->view, sx, sy, &x, &y); + weston_pointer_move_to(constraint->pointer, x, y); + } +} + +static void +confined_pointer_grab_pointer_motion(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + struct weston_pointer_constraint *constraint = + container_of(grab, struct weston_pointer_constraint, grab); + struct weston_pointer *pointer = grab->pointer; + struct weston_surface *surface; + wl_fixed_t x, y; + wl_fixed_t old_sx = pointer->sx; + wl_fixed_t old_sy = pointer->sy; + pixman_region32_t confine_region; + + assert(pointer->focus); + assert(pointer->focus->surface == constraint->surface); + + surface = pointer->focus->surface; + + pixman_region32_init(&confine_region); + pixman_region32_intersect(&confine_region, + &surface->input, + &constraint->region); + weston_pointer_clamp_event_to_region(pointer, event, + &confine_region, &x, &y); + weston_pointer_move_to(pointer, x, y); + pixman_region32_fini(&confine_region); + + weston_view_from_global_fixed(pointer->focus, x, y, + &pointer->sx, &pointer->sy); + + if (old_sx != pointer->sx || old_sy != pointer->sy) { + pointer_send_motion(pointer, time, + pointer->sx, pointer->sy); + } + + pointer_send_relative_motion(pointer, time, event); +} + +static void +confined_pointer_grab_pointer_button(struct weston_pointer_grab *grab, + const struct timespec *time, + uint32_t button, + uint32_t state_w) +{ + weston_pointer_send_button(grab->pointer, time, button, state_w); +} + +static void +confined_pointer_grab_pointer_axis(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_axis_event *event) +{ + weston_pointer_send_axis(grab->pointer, time, event); +} + +static void +confined_pointer_grab_pointer_axis_source(struct weston_pointer_grab *grab, + uint32_t source) +{ + weston_pointer_send_axis_source(grab->pointer, source); +} + +static void +confined_pointer_grab_pointer_frame(struct weston_pointer_grab *grab) +{ + weston_pointer_send_frame(grab->pointer); +} + +static void +confined_pointer_grab_pointer_cancel(struct weston_pointer_grab *grab) +{ + struct weston_pointer_constraint *constraint = + container_of(grab, struct weston_pointer_constraint, grab); + + disable_pointer_constraint(constraint); +} + +static const struct weston_pointer_grab_interface + confined_pointer_grab_interface = { + confined_pointer_grab_pointer_focus, + confined_pointer_grab_pointer_motion, + confined_pointer_grab_pointer_button, + confined_pointer_grab_pointer_axis, + confined_pointer_grab_pointer_axis_source, + confined_pointer_grab_pointer_frame, + confined_pointer_grab_pointer_cancel, +}; + +static void +confined_pointer_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +confined_pointer_set_region(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *region_resource) +{ + struct weston_pointer_constraint *constraint = + wl_resource_get_user_data(resource); + struct weston_region *region = region_resource ? + wl_resource_get_user_data(region_resource) : NULL; + + if (!constraint) + return; + + if (region) { + pixman_region32_copy(&constraint->region_pending, + ®ion->region); + } else { + pixman_region32_fini(&constraint->region_pending); + region_init_infinite(&constraint->region_pending); + } + constraint->region_is_pending = true; +} + +static const struct zwp_confined_pointer_v1_interface confined_pointer_interface = { + confined_pointer_destroy, + confined_pointer_set_region, +}; + +static void +pointer_constraints_confine_pointer(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface_resource, + struct wl_resource *pointer_resource, + struct wl_resource *region_resource, + uint32_t lifetime) +{ + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + struct weston_pointer *pointer = wl_resource_get_user_data(pointer_resource); + struct weston_region *region = region_resource ? + wl_resource_get_user_data(region_resource) : NULL; + + init_pointer_constraint(resource, id, surface, pointer, region, lifetime, + &zwp_confined_pointer_v1_interface, + &confined_pointer_interface, + &confined_pointer_grab_interface); +} + +static const struct zwp_pointer_constraints_v1_interface pointer_constraints_interface = { + pointer_constraints_destroy, + pointer_constraints_lock_pointer, + pointer_constraints_confine_pointer, +}; + +static void +bind_pointer_constraints(struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct wl_resource *resource; + + resource = wl_resource_create(client, + &zwp_pointer_constraints_v1_interface, + 1, id); + + wl_resource_set_implementation(resource, &pointer_constraints_interface, + NULL, NULL); +} + +static void +input_timestamps_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct zwp_input_timestamps_v1_interface + input_timestamps_interface = { + input_timestamps_destroy, +}; + +static void +input_timestamps_manager_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +input_timestamps_manager_get_keyboard_timestamps(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *keyboard_resource) +{ + struct weston_keyboard *keyboard = + wl_resource_get_user_data(keyboard_resource); + struct wl_resource *input_ts; + + input_ts = wl_resource_create(client, + &zwp_input_timestamps_v1_interface, + 1, id); + if (!input_ts) { + wl_client_post_no_memory(client); + return; + } + + if (keyboard) { + wl_list_insert(&keyboard->timestamps_list, + wl_resource_get_link(input_ts)); + } else { + wl_list_init(wl_resource_get_link(input_ts)); + } + + wl_resource_set_implementation(input_ts, + &input_timestamps_interface, + keyboard_resource, + unbind_resource); +} + +static void +input_timestamps_manager_get_pointer_timestamps(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *pointer_resource) +{ + struct weston_pointer *pointer = + wl_resource_get_user_data(pointer_resource); + struct wl_resource *input_ts; + + input_ts = wl_resource_create(client, + &zwp_input_timestamps_v1_interface, + 1, id); + if (!input_ts) { + wl_client_post_no_memory(client); + return; + } + + if (pointer) { + wl_list_insert(&pointer->timestamps_list, + wl_resource_get_link(input_ts)); + } else { + wl_list_init(wl_resource_get_link(input_ts)); + } + + wl_resource_set_implementation(input_ts, + &input_timestamps_interface, + pointer_resource, + unbind_resource); +} + +static void +input_timestamps_manager_get_touch_timestamps(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *touch_resource) +{ + struct weston_touch *touch = wl_resource_get_user_data(touch_resource); + struct wl_resource *input_ts; + + input_ts = wl_resource_create(client, + &zwp_input_timestamps_v1_interface, + 1, id); + if (!input_ts) { + wl_client_post_no_memory(client); + return; + } + + if (touch) { + wl_list_insert(&touch->timestamps_list, + wl_resource_get_link(input_ts)); + } else { + wl_list_init(wl_resource_get_link(input_ts)); + } + + wl_resource_set_implementation(input_ts, + &input_timestamps_interface, + touch_resource, + unbind_resource); +} + +static const struct zwp_input_timestamps_manager_v1_interface + input_timestamps_manager_interface = { + input_timestamps_manager_destroy, + input_timestamps_manager_get_keyboard_timestamps, + input_timestamps_manager_get_pointer_timestamps, + input_timestamps_manager_get_touch_timestamps, +}; + +static void +bind_input_timestamps_manager(struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct wl_resource *resource = + wl_resource_create(client, + &zwp_input_timestamps_manager_v1_interface, + 1, id); + + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, + &input_timestamps_manager_interface, + NULL, NULL); +} + +int +weston_input_init(struct weston_compositor *compositor) +{ + if (!wl_global_create(compositor->wl_display, + &zwp_relative_pointer_manager_v1_interface, 1, + compositor, bind_relative_pointer_manager)) + return -1; + + if (!wl_global_create(compositor->wl_display, + &zwp_pointer_constraints_v1_interface, 1, + NULL, bind_pointer_constraints)) + return -1; + + if (!wl_global_create(compositor->wl_display, + &zwp_input_timestamps_manager_v1_interface, 1, + NULL, bind_input_timestamps_manager)) + return -1; + + return 0; +} diff --git a/libweston/launcher-direct.c b/libweston/launcher-direct.c new file mode 100755 index 0000000..f83f567 --- /dev/null +++ b/libweston/launcher-direct.c @@ -0,0 +1,351 @@ +/* + * Copyright © 2012 Benjamin Franzke + * Copyright © 2013 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "launcher-impl.h" + +#define DRM_MAJOR 226 + +#ifndef KDSKBMUTE +#define KDSKBMUTE 0x4B51 +#endif + +/* major()/minor() */ +#ifdef MAJOR_IN_MKDEV +#include +#endif +#ifdef MAJOR_IN_SYSMACROS +#include +#endif + +#ifdef BUILD_DRM_COMPOSITOR + +#include + +static inline int +is_drm_master(int drm_fd) +{ + drm_magic_t magic; + + return drmGetMagic(drm_fd, &magic) == 0 && + drmAuthMagic(drm_fd, magic) == 0; +} + +#else + +static inline int +drmDropMaster(int drm_fd) +{ + return 0; +} + +static inline int +drmSetMaster(int drm_fd) +{ + return 0; +} + +static inline int +is_drm_master(int drm_fd) +{ + return 0; +} + +#endif + +struct launcher_direct { + struct weston_launcher base; + struct weston_compositor *compositor; + int kb_mode, tty, drm_fd; + struct wl_event_source *vt_source; +}; + +static int +vt_handler(int signal_number, void *data) +{ + struct launcher_direct *launcher = data; + struct weston_compositor *compositor = launcher->compositor; + + if (compositor->session_active) { + compositor->session_active = false; + wl_signal_emit(&compositor->session_signal, compositor); + drmDropMaster(launcher->drm_fd); + ioctl(launcher->tty, VT_RELDISP, 1); + } else { + ioctl(launcher->tty, VT_RELDISP, VT_ACKACQ); + drmSetMaster(launcher->drm_fd); + compositor->session_active = true; + wl_signal_emit(&compositor->session_signal, compositor); + } + + return 1; +} + +static int +setup_tty(struct launcher_direct *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: %s\n", + strerror(errno)); + 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: %s\n", tty_device, + strerror(errno)); + 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: %s\n", strerror(errno)); + return -1; + } + if (kd_mode != KD_TEXT) { + weston_log("%s is already in graphics mode, " + "is another display server running?\n", tty_device); + } + + 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: %s\n", + strerror(errno)); + goto err_close; + } + + if (ioctl(launcher->tty, KDSKBMUTE, 1) && + ioctl(launcher->tty, KDSKBMODE, K_OFF)) { + weston_log("failed to set K_OFF keyboard mode: %s\n", + strerror(errno)); + goto err_close; + } + + ret = ioctl(launcher->tty, KDSETMODE, KD_GRAPHICS); + if (ret) { + weston_log("failed to set KD_GRAPHICS mode on tty: %s\n", + strerror(errno)); + goto err_close; + } + + /* + * SIGRTMIN is used as global VT-acquire+release signal. Note that + * SIGRT* must be tested on runtime, as their exact values are not + * known at compile-time. POSIX requires 32 of them to be available. + */ + if (SIGRTMIN > SIGRTMAX) { + weston_log("not enough RT signals available: %u-%u\n", + SIGRTMIN, SIGRTMAX); + ret = -EINVAL; + goto err_close; + } + + mode.mode = VT_PROCESS; + mode.relsig = SIGRTMIN; + mode.acqsig = SIGRTMIN; + 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, SIGRTMIN, vt_handler, launcher); + if (!launcher->vt_source) + goto err_close; + + return 0; + + err_close: + close(launcher->tty); + return -1; +} + +static int +launcher_direct_open(struct weston_launcher *launcher_base, const char *path, int flags) +{ + struct launcher_direct *launcher = wl_container_of(launcher_base, launcher, base); + struct stat s; + int fd; + + fd = open(path, flags | O_CLOEXEC); + if (fd == -1) + return -1; + + if (fstat(fd, &s) == -1) { + close(fd); + return -1; + } + // OHOS + weston_log("launcher_direct_open %s\n", path); + + if (major(s.st_rdev) == DRM_MAJOR) { + launcher->drm_fd = fd; +// OHOS +// if (!is_drm_master(fd)) { +// weston_log("drm fd not master %s\n", path); +// close(fd); +// return -1; +// } + } + + return fd; +} + +static void +launcher_direct_close(struct weston_launcher *launcher_base, int fd) +{ + close(fd); +} + +static void +launcher_direct_restore(struct weston_launcher *launcher_base) +{ + struct launcher_direct *launcher = wl_container_of(launcher_base, launcher, base); + 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: %s\n", + strerror(errno)); + + if (ioctl(launcher->tty, KDSETMODE, KD_TEXT)) + weston_log("failed to set KD_TEXT mode on tty: %s\n", + strerror(errno)); + + /* 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(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 +launcher_direct_activate_vt(struct weston_launcher *launcher_base, int vt) +{ + struct launcher_direct *launcher = wl_container_of(launcher_base, launcher, base); + return ioctl(launcher->tty, VT_ACTIVATE, vt); +} + +static int +launcher_direct_connect(struct weston_launcher **out, struct weston_compositor *compositor, + int tty, const char *seat_id, bool sync_drm) +{ + struct launcher_direct *launcher; + + if (geteuid() != 0) + return -EINVAL; + + launcher = zalloc(sizeof(*launcher)); + if (launcher == NULL) + return -ENOMEM; + + launcher->base.iface = &launcher_direct_iface; + launcher->compositor = compositor; + + if (strcmp("seat0", seat_id) == 0) { + if (setup_tty(launcher, tty) == -1) { + free(launcher); + return -1; + } + } else { + launcher->tty = -1; + } + + * (struct launcher_direct **) out = launcher; + return 0; +} + +static void +launcher_direct_destroy(struct weston_launcher *launcher_base) +{ + struct launcher_direct *launcher = wl_container_of(launcher_base, launcher, base); + + if (launcher->tty >= 0) { + launcher_direct_restore(&launcher->base); + wl_event_source_remove(launcher->vt_source); + close(launcher->tty); + } + + free(launcher); +} + +static int +launcher_direct_get_vt(struct weston_launcher *base) +{ + struct launcher_direct *launcher = wl_container_of(base, launcher, base); + struct stat s; + if (fstat(launcher->tty, &s) < 0) + return -1; + + return minor(s.st_rdev); +} + +const struct launcher_interface launcher_direct_iface = { + .connect = launcher_direct_connect, + .destroy = launcher_direct_destroy, + .open = launcher_direct_open, + .close = launcher_direct_close, + .activate_vt = launcher_direct_activate_vt, + .get_vt = launcher_direct_get_vt, +}; diff --git a/libweston/launcher-impl.h b/libweston/launcher-impl.h new file mode 100644 index 0000000..4161caf --- /dev/null +++ b/libweston/launcher-impl.h @@ -0,0 +1,49 @@ +/* + * Copyright © 2015 Jasper St. Pierre + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include + +struct weston_launcher; + +struct launcher_interface { + int (* connect) (struct weston_launcher **launcher_out, struct weston_compositor *compositor, + int tty, const char *seat_id, bool sync_drm); + void (* destroy) (struct weston_launcher *launcher); + int (* open) (struct weston_launcher *launcher, const char *path, int flags); + void (* close) (struct weston_launcher *launcher, int fd); + int (* activate_vt) (struct weston_launcher *launcher, int vt); + /* Get the number of the VT weston is running in */ + int (* get_vt) (struct weston_launcher *launcher); +}; + +struct weston_launcher { + const struct launcher_interface *iface; +}; + +extern const struct launcher_interface launcher_logind_iface; +extern const struct launcher_interface launcher_weston_launch_iface; +extern const struct launcher_interface launcher_direct_iface; diff --git a/libweston/launcher-logind.c b/libweston/launcher-logind.c new file mode 100644 index 0000000..9b3c52f --- /dev/null +++ b/libweston/launcher-logind.c @@ -0,0 +1,856 @@ +/* + * Copyright © 2013 David Herrmann + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "backend.h" +#include "dbus.h" +#include "launcher-impl.h" + +#define DRM_MAJOR 226 + +/* major()/minor() */ +#ifdef MAJOR_IN_MKDEV +#include +#endif +#ifdef MAJOR_IN_SYSMACROS +#include +#endif + +struct launcher_logind { + struct weston_launcher base; + struct weston_compositor *compositor; + bool sync_drm; + char *seat; + char *sid; + unsigned int vtnr; + int vt; + int kb_mode; + + DBusConnection *dbus; + struct wl_event_source *dbus_ctx; + char *spath; + DBusPendingCall *pending_active; +}; + +static int +launcher_logind_take_device(struct launcher_logind *wl, uint32_t major, + uint32_t minor, bool *paused_out) +{ + DBusMessage *m, *reply; + bool b; + int r, fd; + dbus_bool_t paused; + + m = dbus_message_new_method_call("org.freedesktop.login1", + wl->spath, + "org.freedesktop.login1.Session", + "TakeDevice"); + if (!m) + return -ENOMEM; + + b = dbus_message_append_args(m, + DBUS_TYPE_UINT32, &major, + DBUS_TYPE_UINT32, &minor, + DBUS_TYPE_INVALID); + if (!b) { + r = -ENOMEM; + goto err_unref; + } + + reply = dbus_connection_send_with_reply_and_block(wl->dbus, m, + -1, NULL); + if (!reply) { + r = -ENODEV; + goto err_unref; + } + + b = dbus_message_get_args(reply, NULL, + DBUS_TYPE_UNIX_FD, &fd, + DBUS_TYPE_BOOLEAN, &paused, + DBUS_TYPE_INVALID); + if (!b) { + r = -ENODEV; + goto err_reply; + } + + r = fd; + if (paused_out) + *paused_out = paused; + +err_reply: + dbus_message_unref(reply); +err_unref: + dbus_message_unref(m); + return r; +} + +static void +launcher_logind_release_device(struct launcher_logind *wl, uint32_t major, + uint32_t minor) +{ + DBusMessage *m; + bool b; + + m = dbus_message_new_method_call("org.freedesktop.login1", + wl->spath, + "org.freedesktop.login1.Session", + "ReleaseDevice"); + if (m) { + b = dbus_message_append_args(m, + DBUS_TYPE_UINT32, &major, + DBUS_TYPE_UINT32, &minor, + DBUS_TYPE_INVALID); + if (b) + dbus_connection_send(wl->dbus, m, NULL); + dbus_message_unref(m); + } +} + +static void +launcher_logind_pause_device_complete(struct launcher_logind *wl, uint32_t major, + uint32_t minor) +{ + DBusMessage *m; + bool b; + + m = dbus_message_new_method_call("org.freedesktop.login1", + wl->spath, + "org.freedesktop.login1.Session", + "PauseDeviceComplete"); + if (m) { + b = dbus_message_append_args(m, + DBUS_TYPE_UINT32, &major, + DBUS_TYPE_UINT32, &minor, + DBUS_TYPE_INVALID); + if (b) + dbus_connection_send(wl->dbus, m, NULL); + dbus_message_unref(m); + } +} + +static int +launcher_logind_open(struct weston_launcher *launcher, const char *path, int flags) +{ + struct launcher_logind *wl = wl_container_of(launcher, wl, base); + struct stat st; + int fl, r, fd; + + r = stat(path, &st); + if (r < 0) + return -1; + if (!S_ISCHR(st.st_mode)) { + errno = ENODEV; + return -1; + } + + fd = launcher_logind_take_device(wl, major(st.st_rdev), + minor(st.st_rdev), NULL); + if (fd < 0) + return fd; + + /* Compared to weston_launcher_open() we cannot specify the open-mode + * directly. Instead, logind passes us an fd with sane default modes. + * For DRM and evdev this means O_RDWR | O_CLOEXEC. If we want + * something else, we need to change it afterwards. We currently + * only support setting O_NONBLOCK. Changing access-modes is not + * possible so accept whatever logind passes us. */ + + fl = fcntl(fd, F_GETFL); + if (fl < 0) { + r = -errno; + goto err_close; + } + + if (flags & O_NONBLOCK) + fl |= O_NONBLOCK; + + r = fcntl(fd, F_SETFL, fl); + if (r < 0) { + r = -errno; + goto err_close; + } + return fd; + +err_close: + close(fd); + launcher_logind_release_device(wl, major(st.st_rdev), + minor(st.st_rdev)); + errno = -r; + return -1; +} + +static void +launcher_logind_close(struct weston_launcher *launcher, int fd) +{ + struct launcher_logind *wl = wl_container_of(launcher, wl, base); + struct stat st; + int r; + + r = fstat(fd, &st); + close(fd); + if (r < 0) { + weston_log("logind: cannot fstat fd: %s\n", strerror(errno)); + return; + } + + if (!S_ISCHR(st.st_mode)) { + weston_log("logind: invalid device passed\n"); + return; + } + + launcher_logind_release_device(wl, major(st.st_rdev), + minor(st.st_rdev)); +} + +static int +launcher_logind_activate_vt(struct weston_launcher *launcher, int vt) +{ + struct launcher_logind *wl = wl_container_of(launcher, wl, base); + DBusMessage *m; + bool b; + int r; + + m = dbus_message_new_method_call("org.freedesktop.login1", + "/org/freedesktop/login1/seat/self", + "org.freedesktop.login1.Seat", + "SwitchTo"); + if (!m) + return -ENOMEM; + + b = dbus_message_append_args(m, + DBUS_TYPE_UINT32, &vt, + DBUS_TYPE_INVALID); + if (!b) { + r = -ENOMEM; + goto err_unref; + } + + dbus_connection_send(wl->dbus, m, NULL); + r = 0; + + err_unref: + dbus_message_unref(m); + return r; +} + +static void +launcher_logind_set_active(struct launcher_logind *wl, bool active) +{ + if (wl->compositor->session_active == active) + return; + + wl->compositor->session_active = active; + + wl_signal_emit(&wl->compositor->session_signal, + wl->compositor); +} + +static void +parse_active(struct launcher_logind *wl, DBusMessage *m, DBusMessageIter *iter) +{ + DBusMessageIter sub; + dbus_bool_t b; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT) + return; + + dbus_message_iter_recurse(iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) + return; + + dbus_message_iter_get_basic(&sub, &b); + + /* If the backend requested DRM master-device synchronization, we only + * wake-up the compositor once the master-device is up and running. For + * other backends, we immediately forward the Active-change event. */ + if (!wl->sync_drm || !b) + launcher_logind_set_active(wl, b); +} + +static void +get_active_cb(DBusPendingCall *pending, void *data) +{ + struct launcher_logind *wl = data; + DBusMessageIter iter; + DBusMessage *m; + int type; + + dbus_pending_call_unref(wl->pending_active); + wl->pending_active = NULL; + + m = dbus_pending_call_steal_reply(pending); + if (!m) + return; + + type = dbus_message_get_type(m); + if (type == DBUS_MESSAGE_TYPE_METHOD_RETURN && + dbus_message_iter_init(m, &iter)) + parse_active(wl, m, &iter); + + dbus_message_unref(m); +} + +static void +launcher_logind_get_active(struct launcher_logind *wl) +{ + DBusPendingCall *pending; + DBusMessage *m; + bool b; + const char *iface, *name; + + m = dbus_message_new_method_call("org.freedesktop.login1", + wl->spath, + "org.freedesktop.DBus.Properties", + "Get"); + if (!m) + return; + + iface = "org.freedesktop.login1.Session"; + name = "Active"; + b = dbus_message_append_args(m, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + if (!b) + goto err_unref; + + b = dbus_connection_send_with_reply(wl->dbus, m, &pending, -1); + if (!b) + goto err_unref; + + b = dbus_pending_call_set_notify(pending, get_active_cb, wl, NULL); + if (!b) { + dbus_pending_call_cancel(pending); + dbus_pending_call_unref(pending); + goto err_unref; + } + + if (wl->pending_active) { + dbus_pending_call_cancel(wl->pending_active); + dbus_pending_call_unref(wl->pending_active); + } + wl->pending_active = pending; + return; + +err_unref: + dbus_message_unref(m); +} + +static void +disconnected_dbus(struct launcher_logind *wl) +{ + weston_log("logind: dbus connection lost, exiting..\n"); + exit(-1); +} + +static void +session_removed(struct launcher_logind *wl, DBusMessage *m) +{ + const char *name, *obj; + bool r; + + r = dbus_message_get_args(m, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_OBJECT_PATH, &obj, + DBUS_TYPE_INVALID); + if (!r) { + weston_log("logind: cannot parse SessionRemoved dbus signal\n"); + return; + } + + if (!strcmp(name, wl->sid)) { + weston_log("logind: our session got closed, exiting..\n"); + exit(-1); + } +} + +static void +property_changed(struct launcher_logind *wl, DBusMessage *m) +{ + DBusMessageIter iter, sub, entry; + const char *interface, *name; + + if (!dbus_message_iter_init(m, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + goto error; + + dbus_message_iter_get_basic(&iter, &interface); + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) + goto error; + + dbus_message_iter_recurse(&iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY) { + dbus_message_iter_recurse(&sub, &entry); + + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + goto error; + + dbus_message_iter_get_basic(&entry, &name); + if (!dbus_message_iter_next(&entry)) + goto error; + + if (!strcmp(name, "Active")) { + parse_active(wl, m, &entry); + return; + } + + dbus_message_iter_next(&sub); + } + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) + goto error; + + dbus_message_iter_recurse(&iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) { + dbus_message_iter_get_basic(&sub, &name); + + if (!strcmp(name, "Active")) { + launcher_logind_get_active(wl); + return; + } + + dbus_message_iter_next(&sub); + } + + return; + +error: + weston_log("logind: cannot parse PropertiesChanged dbus signal\n"); +} + +static void +device_paused(struct launcher_logind *wl, DBusMessage *m) +{ + bool r; + const char *type; + uint32_t major, minor; + + r = dbus_message_get_args(m, NULL, + DBUS_TYPE_UINT32, &major, + DBUS_TYPE_UINT32, &minor, + DBUS_TYPE_STRING, &type, + DBUS_TYPE_INVALID); + if (!r) { + weston_log("logind: cannot parse PauseDevice dbus signal\n"); + return; + } + + /* "pause" means synchronous pausing. Acknowledge it unconditionally + * as we support asynchronous device shutdowns, anyway. + * "force" means asynchronous pausing. + * "gone" means the device is gone. We handle it the same as "force" as + * a following udev event will be caught, too. + * + * If it's our main DRM device, tell the compositor to go asleep. */ + + if (!strcmp(type, "pause")) + launcher_logind_pause_device_complete(wl, major, minor); + + if (wl->sync_drm && wl->compositor->backend->device_changed) + wl->compositor->backend->device_changed(wl->compositor, + makedev(major,minor), + false); +} + +static void +device_resumed(struct launcher_logind *wl, DBusMessage *m) +{ + bool r; + uint32_t major, minor; + + r = dbus_message_get_args(m, NULL, + DBUS_TYPE_UINT32, &major, + DBUS_TYPE_UINT32, &minor, + DBUS_TYPE_INVALID); + if (!r) { + weston_log("logind: cannot parse ResumeDevice dbus signal\n"); + return; + } + + /* DeviceResumed messages provide us a new file-descriptor for + * resumed devices. For DRM devices it's the same as before, for evdev + * devices it's a new open-file. As we reopen evdev devices, anyway, + * there is no need for us to handle this event for evdev. For DRM, we + * notify the compositor to wake up. */ + + if (wl->sync_drm && wl->compositor->backend->device_changed) + wl->compositor->backend->device_changed(wl->compositor, + makedev(major,minor), + true); +} + +static DBusHandlerResult +filter_dbus(DBusConnection *c, DBusMessage *m, void *data) +{ + struct launcher_logind *wl = data; + + if (dbus_message_is_signal(m, DBUS_INTERFACE_LOCAL, "Disconnected")) { + disconnected_dbus(wl); + } else if (dbus_message_is_signal(m, "org.freedesktop.login1.Manager", + "SessionRemoved")) { + session_removed(wl, m); + } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.Properties", + "PropertiesChanged")) { + property_changed(wl, m); + } else if (dbus_message_is_signal(m, "org.freedesktop.login1.Session", + "PauseDevice")) { + device_paused(wl, m); + } else if (dbus_message_is_signal(m, "org.freedesktop.login1.Session", + "ResumeDevice")) { + device_resumed(wl, m); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static int +launcher_logind_setup_dbus(struct launcher_logind *wl) +{ + bool b; + int r; + + r = asprintf(&wl->spath, "/org/freedesktop/login1/session/%s", + wl->sid); + if (r < 0) + return -ENOMEM; + + b = dbus_connection_add_filter(wl->dbus, filter_dbus, wl, NULL); + if (!b) { + weston_log("logind: cannot add dbus filter\n"); + r = -ENOMEM; + goto err_spath; + } + + r = weston_dbus_add_match_signal(wl->dbus, + "org.freedesktop.login1", + "org.freedesktop.login1.Manager", + "SessionRemoved", + "/org/freedesktop/login1"); + if (r < 0) { + weston_log("logind: cannot add dbus match\n"); + goto err_spath; + } + + r = weston_dbus_add_match_signal(wl->dbus, + "org.freedesktop.login1", + "org.freedesktop.login1.Session", + "PauseDevice", + wl->spath); + if (r < 0) { + weston_log("logind: cannot add dbus match\n"); + goto err_spath; + } + + r = weston_dbus_add_match_signal(wl->dbus, + "org.freedesktop.login1", + "org.freedesktop.login1.Session", + "ResumeDevice", + wl->spath); + if (r < 0) { + weston_log("logind: cannot add dbus match\n"); + goto err_spath; + } + + r = weston_dbus_add_match_signal(wl->dbus, + "org.freedesktop.login1", + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + wl->spath); + if (r < 0) { + weston_log("logind: cannot add dbus match\n"); + goto err_spath; + } + + return 0; + +err_spath: + /* don't remove any dbus-match as the connection is closed, anyway */ + free(wl->spath); + return r; +} + +static void +launcher_logind_destroy_dbus(struct launcher_logind *wl) +{ + /* don't remove any dbus-match as the connection is closed, anyway */ + free(wl->spath); +} + +static int +launcher_logind_take_control(struct launcher_logind *wl) +{ + DBusError err; + DBusMessage *m, *reply; + dbus_bool_t force; + bool b; + int r; + + dbus_error_init(&err); + + m = dbus_message_new_method_call("org.freedesktop.login1", + wl->spath, + "org.freedesktop.login1.Session", + "TakeControl"); + if (!m) + return -ENOMEM; + + force = false; + b = dbus_message_append_args(m, + DBUS_TYPE_BOOLEAN, &force, + DBUS_TYPE_INVALID); + if (!b) { + r = -ENOMEM; + goto err_unref; + } + + reply = dbus_connection_send_with_reply_and_block(wl->dbus, + m, -1, &err); + if (!reply) { + if (dbus_error_has_name(&err, DBUS_ERROR_UNKNOWN_METHOD)) + weston_log("logind: old systemd version detected\n"); + else + weston_log("logind: cannot take control over session %s\n", wl->sid); + + dbus_error_free(&err); + r = -EIO; + goto err_unref; + } + + dbus_message_unref(reply); + dbus_message_unref(m); + return 0; + +err_unref: + dbus_message_unref(m); + return r; +} + +static void +launcher_logind_release_control(struct launcher_logind *wl) +{ + DBusMessage *m; + + m = dbus_message_new_method_call("org.freedesktop.login1", + wl->spath, + "org.freedesktop.login1.Session", + "ReleaseControl"); + if (m) { + dbus_connection_send(wl->dbus, m, NULL); + dbus_message_unref(m); + } +} + +static int +weston_sd_session_get_vt(const char *sid, unsigned int *out) +{ +#ifdef HAVE_SYSTEMD_LOGIN_209 + return sd_session_get_vt(sid, out); +#else + int r; + char *tty; + + r = sd_session_get_tty(sid, &tty); + if (r < 0) + return r; + + r = sscanf(tty, "tty%u", out); + free(tty); + + if (r != 1) + return -EINVAL; + + return 0; +#endif +} + +static int +launcher_logind_activate(struct launcher_logind *wl) +{ + DBusMessage *m; + + m = dbus_message_new_method_call("org.freedesktop.login1", + wl->spath, + "org.freedesktop.login1.Session", + "Activate"); + if (!m) + return -ENOMEM; + + dbus_connection_send(wl->dbus, m, NULL); + return 0; +} + +static int +launcher_logind_connect(struct weston_launcher **out, struct weston_compositor *compositor, + int tty, const char *seat_id, bool sync_drm) +{ + struct launcher_logind *wl; + struct wl_event_loop *loop; + char *t; + int r; + + wl = zalloc(sizeof(*wl)); + if (wl == NULL) { + r = -ENOMEM; + goto err_out; + } + + wl->base.iface = &launcher_logind_iface; + wl->compositor = compositor; + wl->sync_drm = sync_drm; + + wl->seat = strdup(seat_id); + if (!wl->seat) { + r = -ENOMEM; + goto err_wl; + } + + r = sd_pid_get_session(getpid(), &wl->sid); + if (r < 0) { + weston_log("logind: not running in a systemd session\n"); + goto err_seat; + } + + t = NULL; + r = sd_session_get_seat(wl->sid, &t); + if (r < 0) { + weston_log("logind: failed to get session seat\n"); + free(t); + goto err_session; + } else if (strcmp(seat_id, t)) { + weston_log("logind: weston's seat '%s' differs from session-seat '%s'\n", + seat_id, t); + r = -EINVAL; + free(t); + goto err_session; + } + + r = strcmp(t, "seat0"); + free(t); + if (r == 0) { + r = weston_sd_session_get_vt(wl->sid, &wl->vtnr); + if (r < 0) { + weston_log("logind: session not running on a VT\n"); + goto err_session; + } else if (tty > 0 && wl->vtnr != (unsigned int )tty) { + weston_log("logind: requested VT --tty=%d differs from real session VT %u\n", + tty, wl->vtnr); + r = -EINVAL; + goto err_session; + } + } + + loop = wl_display_get_event_loop(compositor->wl_display); + r = weston_dbus_open(loop, DBUS_BUS_SYSTEM, &wl->dbus, &wl->dbus_ctx); + if (r < 0) { + weston_log("logind: cannot connect to system dbus\n"); + goto err_session; + } + + r = launcher_logind_setup_dbus(wl); + if (r < 0) + goto err_dbus; + + r = launcher_logind_take_control(wl); + if (r < 0) + goto err_dbus_cleanup; + + r = launcher_logind_activate(wl); + if (r < 0) + goto err_dbus_cleanup; + + weston_log("logind: session control granted\n"); + * (struct launcher_logind **) out = wl; + return 0; + +err_dbus_cleanup: + launcher_logind_destroy_dbus(wl); +err_dbus: + weston_dbus_close(wl->dbus, wl->dbus_ctx); +err_session: + free(wl->sid); +err_seat: + free(wl->seat); +err_wl: + free(wl); +err_out: + weston_log("logind: cannot setup systemd-logind helper (%d), using legacy fallback\n", r); + errno = -r; + return -1; +} + +static void +launcher_logind_destroy(struct weston_launcher *launcher) +{ + struct launcher_logind *wl = wl_container_of(launcher, wl, base); + + if (wl->pending_active) { + dbus_pending_call_cancel(wl->pending_active); + dbus_pending_call_unref(wl->pending_active); + } + + launcher_logind_release_control(wl); + launcher_logind_destroy_dbus(wl); + weston_dbus_close(wl->dbus, wl->dbus_ctx); + free(wl->sid); + free(wl->seat); + free(wl); +} + +static int +launcher_logind_get_vt(struct weston_launcher *launcher) +{ + struct launcher_logind *wl = wl_container_of(launcher, wl, base); + return wl->vtnr; +} + +const struct launcher_interface launcher_logind_iface = { + .connect = launcher_logind_connect, + .destroy = launcher_logind_destroy, + .open = launcher_logind_open, + .close = launcher_logind_close, + .activate_vt = launcher_logind_activate_vt, + .get_vt = launcher_logind_get_vt, +}; diff --git a/libweston/launcher-util.c b/libweston/launcher-util.c new file mode 100644 index 0000000..5cbb0ab --- /dev/null +++ b/libweston/launcher-util.c @@ -0,0 +1,120 @@ +/* + * Copyright © 2012 Benjamin Franzke + * Copyright © 2013 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include + +#include "launcher-util.h" +#include "launcher-impl.h" + +#include +#include +#include + +static const struct launcher_interface *ifaces[] = { +#ifdef HAVE_SYSTEMD_LOGIN + &launcher_logind_iface, +#endif + &launcher_weston_launch_iface, + &launcher_direct_iface, + NULL, +}; + +WL_EXPORT struct weston_launcher * +weston_launcher_connect(struct weston_compositor *compositor, int tty, + const char *seat_id, bool sync_drm) +{ + const struct launcher_interface **it; + + for (it = ifaces; *it != NULL; it++) { + const struct launcher_interface *iface = *it; + struct weston_launcher *launcher; + + if (iface->connect(&launcher, compositor, tty, seat_id, sync_drm) == 0) + return launcher; + } + + return NULL; +} + +WL_EXPORT void +weston_launcher_destroy(struct weston_launcher *launcher) +{ + launcher->iface->destroy(launcher); +} + +WL_EXPORT int +weston_launcher_open(struct weston_launcher *launcher, + const char *path, int flags) +{ + return launcher->iface->open(launcher, path, flags); +} + +WL_EXPORT void +weston_launcher_close(struct weston_launcher *launcher, int fd) +{ + launcher->iface->close(launcher, fd); +} + +WL_EXPORT int +weston_launcher_activate_vt(struct weston_launcher *launcher, int vt) +{ + return launcher->iface->activate_vt(launcher, vt); +} + +static void +switch_vt_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) +{ + struct weston_compositor *compositor = data; + struct weston_launcher *launcher = compositor->launcher; + int vt = key - KEY_F1 + 1; + + if (vt == launcher->iface->get_vt(launcher)) + return; + + weston_launcher_activate_vt(launcher, vt); +} + +WL_EXPORT void +weston_setup_vt_switch_bindings(struct weston_compositor *compositor) +{ + uint32_t key; + struct weston_launcher *launcher = compositor->launcher; + + if (launcher->iface->get_vt(launcher) <= 0) + return; + + if (compositor->vt_switching == false) + return; + + for (key = KEY_F1; key < KEY_F9; key++) + weston_compositor_add_key_binding(compositor, key, + MODIFIER_CTRL | MODIFIER_ALT, + switch_vt_binding, + compositor); +} diff --git a/libweston/launcher-util.h b/libweston/launcher-util.h new file mode 100644 index 0000000..dd7b770 --- /dev/null +++ b/libweston/launcher-util.h @@ -0,0 +1,55 @@ +/* + * Copyright © 2012 Benjamin Franzke + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _WESTON_LAUNCHER_UTIL_H_ +#define _WESTON_LAUNCHER_UTIL_H_ + +#include "config.h" + +#include + +struct weston_launcher; + +struct weston_launcher * +weston_launcher_connect(struct weston_compositor *compositor, int tty, + const char *seat_id, bool sync_drm); + +void +weston_launcher_destroy(struct weston_launcher *launcher); + +int +weston_launcher_open(struct weston_launcher *launcher, + const char *path, int flags); + +void +weston_launcher_close(struct weston_launcher *launcher, int fd); + +int +weston_launcher_activate_vt(struct weston_launcher *launcher, int vt); + +void +weston_setup_vt_switch_bindings(struct weston_compositor *compositor); + +#endif diff --git a/libweston/launcher-weston-launch.c b/libweston/launcher-weston-launch.c new file mode 100644 index 0000000..e3b4e1a --- /dev/null +++ b/libweston/launcher-weston-launch.c @@ -0,0 +1,345 @@ +/* + * Copyright © 2012 Benjamin Franzke + * Copyright © 2013 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "weston-launch.h" +#include "launcher-impl.h" +#include "shared/string-helpers.h" + +#define DRM_MAJOR 226 + +#ifndef KDSKBMUTE +#define KDSKBMUTE 0x4B51 +#endif + +#ifdef BUILD_DRM_COMPOSITOR + +#include + +#else + +static inline int +drmDropMaster(int drm_fd) +{ + return 0; +} + +static inline int +drmSetMaster(int drm_fd) +{ + return 0; +} + +#endif + +/* major()/minor() */ +#ifdef MAJOR_IN_MKDEV +#include +#endif +#ifdef MAJOR_IN_SYSMACROS +#include +#endif + +union cmsg_data { unsigned char b[4]; int fd; }; + +struct launcher_weston_launch { + struct weston_launcher base; + struct weston_compositor *compositor; + struct wl_event_loop *loop; + int fd; + struct wl_event_source *source; + + int kb_mode, tty, drm_fd; +}; + +static ssize_t +launcher_weston_launch_send(int sockfd, void *buf, size_t buflen) +{ + ssize_t len; + + do { + len = send(sockfd, buf, buflen, 0); + } while (len < 0 && errno == EINTR); + + return len; +} + +static int +launcher_weston_launch_open(struct weston_launcher *launcher_base, + const char *path, int flags) +{ + struct launcher_weston_launch *launcher = wl_container_of(launcher_base, launcher, base); + int n, ret; + 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; + + 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); + + launcher_weston_launch_send(launcher->fd, message, n); + 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; +} + +static void +launcher_weston_launch_close(struct weston_launcher *launcher_base, int fd) +{ + close(fd); +} + +static void +launcher_weston_launch_restore(struct weston_launcher *launcher_base) +{ + struct launcher_weston_launch *launcher = wl_container_of(launcher_base, launcher, base); + 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: %s\n", + strerror(errno)); + + if (ioctl(launcher->tty, KDSETMODE, KD_TEXT)) + weston_log("failed to set KD_TEXT mode on tty: %s\n", + strerror(errno)); + + /* 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(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 +launcher_weston_launch_data(int fd, uint32_t mask, void *data) +{ + struct launcher_weston_launch *launcher = data; + int len, ret, reply; + + 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. */ + launcher_weston_launch_restore(&launcher->base); + 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 = true; + wl_signal_emit(&launcher->compositor->session_signal, + launcher->compositor); + break; + case WESTON_LAUNCHER_DEACTIVATE: + launcher->compositor->session_active = false; + wl_signal_emit(&launcher->compositor->session_signal, + launcher->compositor); + + reply = WESTON_LAUNCHER_DEACTIVATE_DONE; + launcher_weston_launch_send(launcher->fd, &reply, sizeof reply); + + break; + default: + weston_log("unexpected event from weston-launch\n"); + break; + } + + return 1; +} + +static int +launcher_weston_launch_activate_vt(struct weston_launcher *launcher_base, int vt) +{ + struct launcher_weston_launch *launcher = wl_container_of(launcher_base, launcher, base); + return ioctl(launcher->tty, VT_ACTIVATE, vt); +} + +static int +launcher_weston_environment_get_fd(const char *env) +{ + char *e; + int fd, flags; + + e = getenv(env); + if (!e || !safe_strtoint(e, &fd)) + return -1; + + flags = fcntl(fd, F_GETFD); + if (flags == -1) + return -1; + + fcntl(fd, F_SETFD, flags | FD_CLOEXEC); + unsetenv(env); + + return fd; +} + + +static int +launcher_weston_launch_connect(struct weston_launcher **out, struct weston_compositor *compositor, + int tty, const char *seat_id, bool sync_drm) +{ + struct launcher_weston_launch *launcher; + struct wl_event_loop *loop; + + launcher = malloc(sizeof *launcher); + if (launcher == NULL) + return -ENOMEM; + + launcher->base.iface = &launcher_weston_launch_iface; + * (struct launcher_weston_launch **) out = launcher; + launcher->compositor = compositor; + launcher->drm_fd = -1; + launcher->fd = launcher_weston_environment_get_fd("WESTON_LAUNCHER_SOCK"); + if (launcher->fd != -1) { + launcher->tty = launcher_weston_environment_get_fd("WESTON_TTY_FD"); + /* We don't get a chance to read out the original kb + * mode for the tty, so just hard code K_UNICODE here + * in case we have to clean if weston-launch dies. */ + launcher->kb_mode = K_UNICODE; + + loop = wl_display_get_event_loop(compositor->wl_display); + launcher->source = wl_event_loop_add_fd(loop, launcher->fd, + WL_EVENT_READABLE, + launcher_weston_launch_data, + launcher); + if (launcher->source == NULL) { + free(launcher); + return -ENOMEM; + } + + return 0; + } else { + return -1; + } +} + +static void +launcher_weston_launch_destroy(struct weston_launcher *launcher_base) +{ + struct launcher_weston_launch *launcher = wl_container_of(launcher_base, launcher, base); + + if (launcher->fd != -1) { + close(launcher->fd); + wl_event_source_remove(launcher->source); + } else { + launcher_weston_launch_restore(&launcher->base); + } + + if (launcher->tty >= 0) + close(launcher->tty); + + free(launcher); +} + +static int +launcher_weston_launch_get_vt(struct weston_launcher *base) +{ + struct launcher_weston_launch *launcher = wl_container_of(base, launcher, base); + struct stat s; + if (fstat(launcher->tty, &s) < 0) + return -1; + + return minor(s.st_rdev); +} + +const struct launcher_interface launcher_weston_launch_iface = { + .connect = launcher_weston_launch_connect, + .destroy = launcher_weston_launch_destroy, + .open = launcher_weston_launch_open, + .close = launcher_weston_launch_close, + .activate_vt = launcher_weston_launch_activate_vt, + .get_vt = launcher_weston_launch_get_vt, +}; diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c new file mode 100644 index 0000000..9a084cb --- /dev/null +++ b/libweston/libinput-device.c @@ -0,0 +1,761 @@ +/* + * Copyright © 2010 Intel Corporation + * Copyright © 2013 Jonas Ådahl + * Copyright 2017-2018 Collabora, Ltd. + * Copyright 2017-2018 General Electric Company + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "backend.h" +#include "libweston-internal.h" +#include "libinput-device.h" +#include "shared/helpers.h" +#include "shared/timespec-util.h" + +void +evdev_led_update(struct evdev_device *device, enum weston_led weston_leds) +{ + enum libinput_led leds = 0; + + if (weston_leds & LED_NUM_LOCK) + leds |= LIBINPUT_LED_NUM_LOCK; + if (weston_leds & LED_CAPS_LOCK) + leds |= LIBINPUT_LED_CAPS_LOCK; + if (weston_leds & LED_SCROLL_LOCK) + leds |= LIBINPUT_LED_SCROLL_LOCK; + + libinput_device_led_update(device->device, leds); +} + +static void +handle_keyboard_key(struct libinput_device *libinput_device, + struct libinput_event_keyboard *keyboard_event) +{ + struct evdev_device *device = + libinput_device_get_user_data(libinput_device); + int key_state = + libinput_event_keyboard_get_key_state(keyboard_event); + int seat_key_count = + libinput_event_keyboard_get_seat_key_count(keyboard_event); + struct timespec time; + + /* Ignore key events that are not seat wide state changes. */ + if ((key_state == LIBINPUT_KEY_STATE_PRESSED && + seat_key_count != 1) || + (key_state == LIBINPUT_KEY_STATE_RELEASED && + seat_key_count != 0)) + return; + + timespec_from_usec(&time, + libinput_event_keyboard_get_time_usec(keyboard_event)); + + notify_key(device->seat, &time, + libinput_event_keyboard_get_key(keyboard_event), + key_state, STATE_UPDATE_AUTOMATIC); +} + +static bool +handle_pointer_motion(struct libinput_device *libinput_device, + struct libinput_event_pointer *pointer_event) +{ + struct evdev_device *device = + libinput_device_get_user_data(libinput_device); + struct weston_pointer_motion_event event = { 0 }; + struct timespec time; + double dx_unaccel, dy_unaccel; + + timespec_from_usec(&time, + libinput_event_pointer_get_time_usec(pointer_event)); + dx_unaccel = libinput_event_pointer_get_dx_unaccelerated(pointer_event); + dy_unaccel = libinput_event_pointer_get_dy_unaccelerated(pointer_event); + + event = (struct weston_pointer_motion_event) { + .mask = WESTON_POINTER_MOTION_REL | + WESTON_POINTER_MOTION_REL_UNACCEL, + .time = time, + .dx = libinput_event_pointer_get_dx(pointer_event), + .dy = libinput_event_pointer_get_dy(pointer_event), + .dx_unaccel = dx_unaccel, + .dy_unaccel = dy_unaccel, + }; + + notify_motion(device->seat, &time, &event); + + return true; +} + +static bool +handle_pointer_motion_absolute( + struct libinput_device *libinput_device, + struct libinput_event_pointer *pointer_event) +{ + struct evdev_device *device = + libinput_device_get_user_data(libinput_device); + struct weston_output *output = device->output; + struct timespec time; + double x, y; + uint32_t width, height; + + if (!output) + return false; + + timespec_from_usec(&time, + libinput_event_pointer_get_time_usec(pointer_event)); + width = device->output->current_mode->width; + height = device->output->current_mode->height; + + x = libinput_event_pointer_get_absolute_x_transformed(pointer_event, + width); + y = libinput_event_pointer_get_absolute_y_transformed(pointer_event, + height); + + weston_output_transform_coordinate(device->output, x, y, &x, &y); + notify_motion_absolute(device->seat, &time, x, y); + + return true; +} + +static bool +handle_pointer_button(struct libinput_device *libinput_device, + struct libinput_event_pointer *pointer_event) +{ + struct evdev_device *device = + libinput_device_get_user_data(libinput_device); + int button_state = + libinput_event_pointer_get_button_state(pointer_event); + int seat_button_count = + libinput_event_pointer_get_seat_button_count(pointer_event); + struct timespec time; + + /* Ignore button events that are not seat wide state changes. */ + if ((button_state == LIBINPUT_BUTTON_STATE_PRESSED && + seat_button_count != 1) || + (button_state == LIBINPUT_BUTTON_STATE_RELEASED && + seat_button_count != 0)) + return false; + + timespec_from_usec(&time, + libinput_event_pointer_get_time_usec(pointer_event)); + + notify_button(device->seat, &time, + libinput_event_pointer_get_button(pointer_event), + button_state); + + return true; +} + +static double +normalize_scroll(struct libinput_event_pointer *pointer_event, + enum libinput_pointer_axis axis) +{ + enum libinput_pointer_axis_source source; + double value = 0.0; + + source = libinput_event_pointer_get_axis_source(pointer_event); + /* libinput < 0.8 sent wheel click events with value 10. Since 0.8 + the value is the angle of the click in degrees. To keep + backwards-compat with existing clients, we just send multiples of + the click count. + */ + switch (source) { + case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL: + value = 10 * libinput_event_pointer_get_axis_value_discrete( + pointer_event, + axis); + break; + case LIBINPUT_POINTER_AXIS_SOURCE_FINGER: + case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS: + value = libinput_event_pointer_get_axis_value(pointer_event, + axis); + break; + default: + assert(!"unhandled event source in normalize_scroll"); + } + + return value; +} + +static int32_t +get_axis_discrete(struct libinput_event_pointer *pointer_event, + enum libinput_pointer_axis axis) +{ + enum libinput_pointer_axis_source source; + + source = libinput_event_pointer_get_axis_source(pointer_event); + + if (source != LIBINPUT_POINTER_AXIS_SOURCE_WHEEL) + return 0; + + return libinput_event_pointer_get_axis_value_discrete(pointer_event, + axis); +} + +static bool +handle_pointer_axis(struct libinput_device *libinput_device, + struct libinput_event_pointer *pointer_event) +{ + static int warned; + struct evdev_device *device = + libinput_device_get_user_data(libinput_device); + double vert, horiz; + int32_t vert_discrete, horiz_discrete; + enum libinput_pointer_axis axis; + struct weston_pointer_axis_event weston_event; + enum libinput_pointer_axis_source source; + uint32_t wl_axis_source; + bool has_vert, has_horiz; + struct timespec time; + + has_vert = libinput_event_pointer_has_axis(pointer_event, + LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); + has_horiz = libinput_event_pointer_has_axis(pointer_event, + LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL); + + if (!has_vert && !has_horiz) + return false; + + source = libinput_event_pointer_get_axis_source(pointer_event); + switch (source) { + case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL: + wl_axis_source = WL_POINTER_AXIS_SOURCE_WHEEL; + break; + case LIBINPUT_POINTER_AXIS_SOURCE_FINGER: + wl_axis_source = WL_POINTER_AXIS_SOURCE_FINGER; + break; + case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS: + wl_axis_source = WL_POINTER_AXIS_SOURCE_CONTINUOUS; + break; + default: + if (warned < 5) { + weston_log("Unknown scroll source %d.\n", source); + warned++; + } + return false; + } + + notify_axis_source(device->seat, wl_axis_source); + + timespec_from_usec(&time, + libinput_event_pointer_get_time_usec(pointer_event)); + + if (has_vert) { + axis = LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL; + vert_discrete = get_axis_discrete(pointer_event, axis); + vert = normalize_scroll(pointer_event, axis); + + weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; + weston_event.value = vert; + weston_event.discrete = vert_discrete; + weston_event.has_discrete = (vert_discrete != 0); + + notify_axis(device->seat, &time, &weston_event); + } + + if (has_horiz) { + axis = LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL; + horiz_discrete = get_axis_discrete(pointer_event, axis); + horiz = normalize_scroll(pointer_event, axis); + + weston_event.axis = WL_POINTER_AXIS_HORIZONTAL_SCROLL; + weston_event.value = horiz; + weston_event.discrete = horiz_discrete; + weston_event.has_discrete = (horiz_discrete != 0); + + notify_axis(device->seat, &time, &weston_event); + } + + return true; +} + +static struct weston_output * +touch_get_output(struct weston_touch_device *device) +{ + struct evdev_device *evdev_device = device->backend_data; + + return evdev_device->output; +} + +static const char * +touch_get_calibration_head_name(struct weston_touch_device *device) +{ + struct evdev_device *evdev_device = device->backend_data; + struct weston_output *output = evdev_device->output; + struct weston_head *head; + + if (!output) + return NULL; + + assert(output->enabled); + if (evdev_device->output_name) + return evdev_device->output_name; + + /* No specific head was configured, so the association was made by + * the default rule. Just grab whatever head's name. + */ + wl_list_for_each(head, &output->head_list, output_link) + return head->name; + + assert(0); + return NULL; +} + +static void +touch_get_calibration(struct weston_touch_device *device, + struct weston_touch_device_matrix *cal) +{ + struct evdev_device *evdev_device = device->backend_data; + + libinput_device_config_calibration_get_matrix(evdev_device->device, + cal->m); +} + +static void +do_set_calibration(struct evdev_device *evdev_device, + const struct weston_touch_device_matrix *cal) +{ + enum libinput_config_status status; + + weston_log("input device %s: applying calibration:\n", + libinput_device_get_sysname(evdev_device->device)); + weston_log_continue(STAMP_SPACE " %f %f %f\n", + cal->m[0], cal->m[1], cal->m[2]); + weston_log_continue(STAMP_SPACE " %f %f %f\n", + cal->m[3], cal->m[4], cal->m[5]); + + status = libinput_device_config_calibration_set_matrix(evdev_device->device, + cal->m); + if (status != LIBINPUT_CONFIG_STATUS_SUCCESS) + weston_log("Error: Failed to apply calibration.\n"); +} + +static void +touch_set_calibration(struct weston_touch_device *device, + const struct weston_touch_device_matrix *cal) +{ + struct evdev_device *evdev_device = device->backend_data; + + /* Stop output hotplug from reloading the WL_CALIBRATION values. + * libinput will maintain the latest calibration for us. + */ + evdev_device->override_wl_calibration = true; + + do_set_calibration(evdev_device, cal); +} + +static const struct weston_touch_device_ops touch_calibration_ops = { + .get_output = touch_get_output, + .get_calibration_head_name = touch_get_calibration_head_name, + .get_calibration = touch_get_calibration, + .set_calibration = touch_set_calibration +}; + +static struct weston_touch_device * +create_touch_device(struct evdev_device *device) +{ + const struct weston_touch_device_ops *ops = NULL; + struct weston_touch_device *touch_device; + struct udev_device *udev_device; + + if (libinput_device_config_calibration_has_matrix(device->device)) + ops = &touch_calibration_ops; + + udev_device = libinput_device_get_udev_device(device->device); + if (!udev_device) + return NULL; + + touch_device = weston_touch_create_touch_device(device->seat->touch_state, + udev_device_get_syspath(udev_device), + device, ops); + + udev_device_unref(udev_device); + + if (!touch_device) + return NULL; + + weston_log("Touchscreen - %s - %s\n", + libinput_device_get_name(device->device), + touch_device->syspath); + + return touch_device; +} + +static void +handle_touch_with_coords(struct libinput_device *libinput_device, + struct libinput_event_touch *touch_event, + int touch_type) +{ + struct evdev_device *device = + libinput_device_get_user_data(libinput_device); + double x; + double y; + struct weston_point2d_device_normalized norm; + uint32_t width, height; + struct timespec time; + int32_t slot; + + if (!device->output) + return; + + timespec_from_usec(&time, + libinput_event_touch_get_time_usec(touch_event)); + slot = libinput_event_touch_get_seat_slot(touch_event); + + width = device->output->current_mode->width; + height = device->output->current_mode->height; + x = libinput_event_touch_get_x_transformed(touch_event, width); + y = libinput_event_touch_get_y_transformed(touch_event, height); + + weston_output_transform_coordinate(device->output, + x, y, &x, &y); + + if (weston_touch_device_can_calibrate(device->touch_device)) { + norm.x = libinput_event_touch_get_x_transformed(touch_event, 1); + norm.y = libinput_event_touch_get_y_transformed(touch_event, 1); + notify_touch_normalized(device->touch_device, &time, slot, + x, y, &norm, touch_type); + } else { + notify_touch(device->touch_device, &time, slot, x, y, touch_type); + } +} + +static void +handle_touch_down(struct libinput_device *device, + struct libinput_event_touch *touch_event) +{ + handle_touch_with_coords(device, touch_event, WL_TOUCH_DOWN); +} + +static void +handle_touch_motion(struct libinput_device *device, + struct libinput_event_touch *touch_event) +{ + handle_touch_with_coords(device, touch_event, WL_TOUCH_MOTION); +} + +static void +handle_touch_up(struct libinput_device *libinput_device, + struct libinput_event_touch *touch_event) +{ + struct evdev_device *device = + libinput_device_get_user_data(libinput_device); + struct timespec time; + int32_t slot = libinput_event_touch_get_seat_slot(touch_event); + + timespec_from_usec(&time, + libinput_event_touch_get_time_usec(touch_event)); + + notify_touch(device->touch_device, &time, slot, 0, 0, WL_TOUCH_UP); +} + +static void +handle_touch_frame(struct libinput_device *libinput_device, + struct libinput_event_touch *touch_event) +{ + struct evdev_device *device = + libinput_device_get_user_data(libinput_device); + + notify_touch_frame(device->touch_device); +} + +int +evdev_device_process_event(struct libinput_event *event) +{ + struct libinput_device *libinput_device = + libinput_event_get_device(event); + struct evdev_device *device = + libinput_device_get_user_data(libinput_device); + int handled = 1; + bool need_frame = false; + + switch (libinput_event_get_type(event)) { + case LIBINPUT_EVENT_KEYBOARD_KEY: + handle_keyboard_key(libinput_device, + libinput_event_get_keyboard_event(event)); + break; + case LIBINPUT_EVENT_POINTER_MOTION: + need_frame = handle_pointer_motion(libinput_device, + libinput_event_get_pointer_event(event)); + break; + case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: + need_frame = handle_pointer_motion_absolute( + libinput_device, + libinput_event_get_pointer_event(event)); + break; + case LIBINPUT_EVENT_POINTER_BUTTON: + need_frame = handle_pointer_button(libinput_device, + libinput_event_get_pointer_event(event)); + break; + case LIBINPUT_EVENT_POINTER_AXIS: + need_frame = handle_pointer_axis( + libinput_device, + libinput_event_get_pointer_event(event)); + break; + case LIBINPUT_EVENT_TOUCH_DOWN: + handle_touch_down(libinput_device, + libinput_event_get_touch_event(event)); + break; + case LIBINPUT_EVENT_TOUCH_MOTION: + handle_touch_motion(libinput_device, + libinput_event_get_touch_event(event)); + break; + case LIBINPUT_EVENT_TOUCH_UP: + handle_touch_up(libinput_device, + libinput_event_get_touch_event(event)); + break; + case LIBINPUT_EVENT_TOUCH_FRAME: + handle_touch_frame(libinput_device, + libinput_event_get_touch_event(event)); + break; + default: + handled = 0; + weston_log("unknown libinput event %d\n", + libinput_event_get_type(event)); + } + + if (need_frame) + notify_pointer_frame(device->seat); + + return handled; +} + +static void +notify_output_destroy(struct wl_listener *listener, void *data) +{ + struct evdev_device *device = + container_of(listener, + struct evdev_device, output_destroy_listener); + + evdev_device_set_output(device, NULL); +} + +/** + * The WL_CALIBRATION property requires a pixel-specific matrix to be + * applied after scaling device coordinates to screen coordinates. libinput + * can't do that, so we need to convert the calibration to the normalized + * format libinput expects. + */ +void +evdev_device_set_calibration(struct evdev_device *device) +{ + struct udev *udev; + struct udev_device *udev_device = NULL; + const char *sysname = libinput_device_get_sysname(device->device); + const char *calibration_values; + uint32_t width, height; + struct weston_touch_device_matrix calibration; + + if (!libinput_device_config_calibration_has_matrix(device->device)) + return; + + /* If LIBINPUT_CALIBRATION_MATRIX was set to non-identity, we will not + * override it with WL_CALIBRATION. It also means we don't need an + * output to load a calibration. */ + if (libinput_device_config_calibration_get_default_matrix( + device->device, + calibration.m) != 0) + return; + + /* touch_set_calibration() has updated the values, do not load old + * values from WL_CALIBRATION. + */ + if (device->override_wl_calibration) + return; + + if (!device->output) { + weston_log("input device %s has no enabled output associated " + "(%s named), skipping calibration for now.\n", + sysname, device->output_name ?: "none"); + return; + } + + width = device->output->width; + height = device->output->height; + if (width == 0 || height == 0) + return; + + udev = udev_new(); + if (!udev) + return; + + udev_device = udev_device_new_from_subsystem_sysname(udev, + "input", + sysname); + if (!udev_device) + goto out; + + calibration_values = + udev_device_get_property_value(udev_device, + "WL_CALIBRATION"); + + if (calibration_values) { + weston_log("Warning: input device %s has WL_CALIBRATION property set. " + "Support for it will be removed in the future. " + "Please use LIBINPUT_CALIBRATION_MATRIX instead.\n", + sysname); + } + + if (!calibration_values || sscanf(calibration_values, + "%f %f %f %f %f %f", + &calibration.m[0], + &calibration.m[1], + &calibration.m[2], + &calibration.m[3], + &calibration.m[4], + &calibration.m[5]) != 6) + goto out; + + /* normalize to a format libinput can use. There is a chance of + this being wrong if the width/height don't match the device + width/height but I'm not sure how to fix that */ + calibration.m[2] /= width; + calibration.m[5] /= height; + + do_set_calibration(device, &calibration); + + weston_log_continue(STAMP_SPACE " raw translation %f %f for output %s\n", + calibration.m[2] * width, + calibration.m[5] * height, + device->output->name); + +out: + if (udev_device) + udev_device_unref(udev_device); + udev_unref(udev); +} + +void +evdev_device_set_output(struct evdev_device *device, + struct weston_output *output) +{ + if (device->output == output) + return; + + if (device->output_destroy_listener.notify) { + wl_list_remove(&device->output_destroy_listener.link); + device->output_destroy_listener.notify = NULL; + } + + if (!output) { + weston_log("output for input device %s removed\n", + libinput_device_get_sysname(device->device)); + + device->output = NULL; + return; + } + + weston_log("associating input device %s with output %s " + "(%s by udev)\n", + libinput_device_get_sysname(device->device), + output->name, + device->output_name ?: "none"); + + device->output = output; + device->output_destroy_listener.notify = notify_output_destroy; + wl_signal_add(&output->destroy_signal, + &device->output_destroy_listener); + evdev_device_set_calibration(device); +} + +struct evdev_device * +evdev_device_create(struct libinput_device *libinput_device, + struct weston_seat *seat) +{ + struct evdev_device *device; + + device = zalloc(sizeof *device); + if (device == NULL) + return NULL; + + device->seat = seat; + wl_list_init(&device->link); + device->device = libinput_device; + + if (libinput_device_has_capability(libinput_device, + LIBINPUT_DEVICE_CAP_KEYBOARD)) { + weston_seat_init_keyboard(seat, NULL); + device->seat_caps |= EVDEV_SEAT_KEYBOARD; + } + if (libinput_device_has_capability(libinput_device, + LIBINPUT_DEVICE_CAP_POINTER)) { + weston_seat_init_pointer(seat); + device->seat_caps |= EVDEV_SEAT_POINTER; + } + if (libinput_device_has_capability(libinput_device, + LIBINPUT_DEVICE_CAP_TOUCH)) { + weston_seat_init_touch(seat); + device->seat_caps |= EVDEV_SEAT_TOUCH; + device->touch_device = create_touch_device(device); + } + + libinput_device_set_user_data(libinput_device, device); + libinput_device_ref(libinput_device); + + return device; +} + +void +evdev_device_destroy(struct evdev_device *device) +{ + 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_touch_device_destroy(device->touch_device); + weston_seat_release_touch(device->seat); + } + + if (device->output) + wl_list_remove(&device->output_destroy_listener.link); + wl_list_remove(&device->link); + libinput_device_unref(device->device); + free(device->output_name); + free(device); +} + +void +evdev_notify_keyboard_focus(struct weston_seat *seat, + struct wl_list *evdev_devices) +{ + struct wl_array keys; + + if (seat->keyboard_device_count == 0) + return; + + wl_array_init(&keys); + notify_keyboard_focus_in(seat, &keys, STATE_UPDATE_AUTOMATIC); + wl_array_release(&keys); +} diff --git a/libweston/libinput-device.h b/libweston/libinput-device.h new file mode 100644 index 0000000..d3fc645 --- /dev/null +++ b/libweston/libinput-device.h @@ -0,0 +1,82 @@ +/* + * Copyright © 2011, 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _LIBINPUT_DEVICE_H_ +#define _LIBINPUT_DEVICE_H_ + +#include "config.h" + +#include +#include +#include +#include + +#include + +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; + enum evdev_device_seat_capability seat_caps; + struct libinput_device *device; + struct weston_touch_device *touch_device; + struct wl_list link; + struct weston_output *output; + struct wl_listener output_destroy_listener; + char *output_name; + int fd; + bool override_wl_calibration; +}; + +void +evdev_led_update(struct evdev_device *device, enum weston_led leds); + +struct evdev_device * +evdev_device_create(struct libinput_device *libinput_device, + struct weston_seat *seat); + +int +evdev_device_process_event(struct libinput_event *event); + +void +evdev_device_set_output(struct evdev_device *device, + struct weston_output *output); +void +evdev_device_destroy(struct evdev_device *device); + +void +evdev_notify_keyboard_focus(struct weston_seat *seat, + struct wl_list *evdev_devices); +void +evdev_device_set_calibration(struct evdev_device *device); + +int +dispatch_libinput(struct libinput *libinput); + +#endif /* _LIBINPUT_DEVICE_H_ */ diff --git a/libweston/libinput-seat.c b/libweston/libinput-seat.c new file mode 100644 index 0000000..656f5c4 --- /dev/null +++ b/libweston/libinput-seat.c @@ -0,0 +1,490 @@ +/* + * Copyright © 2013 Intel Corporation + * Copyright © 2013 Jonas Ådahl + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "backend.h" +#include "libweston-internal.h" +// OHOS remove logger +//#include "weston-log-internal.h" +#include "launcher-util.h" +#include "libinput-seat.h" +#include "libinput-device.h" +#include "shared/helpers.h" + +static void +process_events(struct udev_input *input); +static struct udev_seat * +udev_seat_create(struct udev_input *input, const char *seat_name); +static void +udev_seat_destroy(struct udev_seat *seat); + +static struct udev_seat * +get_udev_seat(struct udev_input *input, struct libinput_device *device) +{ + struct libinput_seat *libinput_seat; + const char *seat_name; + + libinput_seat = libinput_device_get_seat(device); + seat_name = libinput_seat_get_logical_name(libinput_seat); + return udev_seat_get_named(input, seat_name); +} + +static struct weston_output * +output_find_by_head_name(struct weston_compositor *compositor, + const char *head_name) +{ + struct weston_output *output; + struct weston_head *head; + + if (!head_name) + return NULL; + + /* Only enabled outputs with connected heads. + * This means force-enabled outputs but with disconnected heads + * will be ignored; if the touchscreen doesn't have a video signal, + * touching it is meaningless. + */ + wl_list_for_each(output, &compositor->output_list, link) { + wl_list_for_each(head, &output->head_list, output_link) { + if (!weston_head_is_connected(head)) + continue; + + if (strcmp(head_name, head->name) == 0) + return output; + } + } + + return NULL; +} + +static void +device_added(struct udev_input *input, struct libinput_device *libinput_device) +{ + struct weston_compositor *c; + struct evdev_device *device; + struct weston_output *output; + const char *output_name; + struct weston_seat *seat; + struct udev_seat *udev_seat; + struct weston_pointer *pointer; + + c = input->compositor; + + udev_seat = get_udev_seat(input, libinput_device); + if (!udev_seat) + return; + + seat = &udev_seat->base; + device = evdev_device_create(libinput_device, seat); + if (device == NULL) + return; + + if (input->configure_device != NULL) + input->configure_device(c, device->device); + evdev_device_set_calibration(device); + udev_seat = (struct udev_seat *) seat; + wl_list_insert(udev_seat->devices_list.prev, &device->link); + + pointer = weston_seat_get_pointer(seat); + if (seat->output && pointer) + weston_pointer_clamp(pointer, + &pointer->x, + &pointer->y); + + output_name = libinput_device_get_output_name(libinput_device); + if (output_name) { + device->output_name = strdup(output_name); + output = output_find_by_head_name(c, output_name); + evdev_device_set_output(device, output); + } else if (!wl_list_empty(&c->output_list)) { + /* default assignment to an arbitrary output */ + output = container_of(c->output_list.next, + struct weston_output, link); + evdev_device_set_output(device, output); + } + + if (!input->suspended) + weston_seat_repick(seat); +} + +static void +device_removed(struct udev_input *input, struct libinput_device *libinput_device) +{ + struct evdev_device *device; + + device = libinput_device_get_user_data(libinput_device); + evdev_device_destroy(device); +} + +static void +udev_seat_remove_devices(struct udev_seat *seat) +{ + struct evdev_device *device, *next; + + wl_list_for_each_safe(device, next, &seat->devices_list, link) { + evdev_device_destroy(device); + } +} + +void +udev_input_disable(struct udev_input *input) +{ + if (input->suspended) + return; + + wl_event_source_remove(input->libinput_source); + input->libinput_source = NULL; + libinput_suspend(input->libinput); + process_events(input); + input->suspended = 1; +} + +static int +udev_input_process_event(struct libinput_event *event) +{ + struct libinput *libinput = libinput_event_get_context(event); + struct libinput_device *libinput_device = + libinput_event_get_device(event); + struct udev_input *input = libinput_get_user_data(libinput); + int handled = 1; + + switch (libinput_event_get_type(event)) { + case LIBINPUT_EVENT_DEVICE_ADDED: + device_added(input, libinput_device); + break; + case LIBINPUT_EVENT_DEVICE_REMOVED: + device_removed(input, libinput_device); + break; + default: + handled = 0; + } + + return handled; +} + +static void +process_event(struct libinput_event *event) +{ + if (udev_input_process_event(event)) + return; + if (evdev_device_process_event(event)) + return; +} + +static void +process_events(struct udev_input *input) +{ + struct libinput_event *event; + + while ((event = libinput_get_event(input->libinput))) { + process_event(event); + libinput_event_destroy(event); + } +} + +static int +udev_input_dispatch(struct udev_input *input) +{ + if (libinput_dispatch(input->libinput) != 0) + weston_log("libinput: Failed to dispatch libinput\n"); + + process_events(input); + + return 0; +} + +static int +libinput_source_dispatch(int fd, uint32_t mask, void *data) +{ + struct udev_input *input = data; + + return udev_input_dispatch(input) != 0; +} + +static int +open_restricted(const char *path, int flags, void *user_data) +{ + struct udev_input *input = user_data; + struct weston_launcher *launcher = input->compositor->launcher; + + return weston_launcher_open(launcher, path, flags); +} + +static void +close_restricted(int fd, void *user_data) +{ + struct udev_input *input = user_data; + struct weston_launcher *launcher = input->compositor->launcher; + + weston_launcher_close(launcher, fd); +} + +const struct libinput_interface libinput_interface = { + open_restricted, + close_restricted, +}; + +int +udev_input_enable(struct udev_input *input) +{ + struct wl_event_loop *loop; + struct weston_compositor *c = input->compositor; + int fd; + struct udev_seat *seat; + int devices_found = 0; + + loop = wl_display_get_event_loop(c->wl_display); + fd = libinput_get_fd(input->libinput); + input->libinput_source = + wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, + libinput_source_dispatch, input); + if (!input->libinput_source) { + return -1; + } + + if (input->suspended) { + if (libinput_resume(input->libinput) != 0) { + wl_event_source_remove(input->libinput_source); + input->libinput_source = NULL; + return -1; + } + input->suspended = 0; + process_events(input); + } + + 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 && !c->require_input) { + weston_log("warning: no input devices found, but none required " + "as per configuration.\n"); + return 0; + } + + 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 void +libinput_log_func(struct libinput *libinput, + enum libinput_log_priority priority, + const char *format, va_list args) +{ + weston_vlog(format, args); +} + +int +udev_input_init(struct udev_input *input, struct weston_compositor *c, + struct udev *udev, const char *seat_id, + udev_configure_device_t configure_device) +{ + enum libinput_log_priority priority = LIBINPUT_LOG_PRIORITY_INFO; + const char *log_priority = NULL; + + memset(input, 0, sizeof *input); + + input->compositor = c; + input->configure_device = configure_device; + + log_priority = getenv("WESTON_LIBINPUT_LOG_PRIORITY"); + + input->libinput = libinput_udev_create_context(&libinput_interface, + input, udev); + if (!input->libinput) { + return -1; + } + + libinput_log_set_handler(input->libinput, &libinput_log_func); + + if (log_priority) { + if (strcmp(log_priority, "debug") == 0) { + priority = LIBINPUT_LOG_PRIORITY_DEBUG; + } else if (strcmp(log_priority, "info") == 0) { + priority = LIBINPUT_LOG_PRIORITY_INFO; + } else if (strcmp(log_priority, "error") == 0) { + priority = LIBINPUT_LOG_PRIORITY_ERROR; + } + } + + libinput_log_set_priority(input->libinput, priority); + + if (libinput_udev_assign_seat(input->libinput, seat_id) != 0) { + libinput_unref(input->libinput); + return -1; + } + + process_events(input); + + return udev_input_enable(input); +} + +void +udev_input_destroy(struct udev_input *input) +{ + struct udev_seat *seat, *next; + + if (input->libinput_source) + wl_event_source_remove(input->libinput_source); + wl_list_for_each_safe(seat, next, &input->compositor->seat_list, base.link) + udev_seat_destroy(seat); + libinput_unref(input->libinput); +} + +static void +udev_seat_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 void +udev_seat_output_changed(struct udev_seat *seat, struct weston_output *output) +{ + struct evdev_device *device; + struct weston_output *found; + + wl_list_for_each(device, &seat->devices_list, link) { + /* If we find any input device without an associated output + * or an output name to associate with, just tie it with the + * output we got here - the default assignment. + */ + if (!device->output_name) { + if (!device->output) + evdev_device_set_output(device, output); + + continue; + } + + /* Update all devices' output associations, may they gain or + * lose it. + */ + found = output_find_by_head_name(output->compositor, + device->output_name); + evdev_device_set_output(device, found); + } +} + +static void +notify_output_create(struct wl_listener *listener, void *data) +{ + struct udev_seat *seat = container_of(listener, struct udev_seat, + output_create_listener); + struct weston_output *output = data; + + udev_seat_output_changed(seat, output); +} + +static void +notify_output_heads_changed(struct wl_listener *listener, void *data) +{ + struct udev_seat *seat = container_of(listener, struct udev_seat, + output_heads_listener); + struct weston_output *output = data; + + udev_seat_output_changed(seat, output); +} + +static struct udev_seat * +udev_seat_create(struct udev_input *input, const char *seat_name) +{ + struct weston_compositor *c = input->compositor; + struct udev_seat *seat; + + seat = zalloc(sizeof *seat); + if (!seat) + return NULL; + + weston_seat_init(&seat->base, c, seat_name); + seat->base.led_update = udev_seat_led_update; + + seat->output_create_listener.notify = notify_output_create; + wl_signal_add(&c->output_created_signal, + &seat->output_create_listener); + + seat->output_heads_listener.notify = notify_output_heads_changed; + wl_signal_add(&c->output_heads_changed_signal, + &seat->output_heads_listener); + + wl_list_init(&seat->devices_list); + + return seat; +} + +static void +udev_seat_destroy(struct udev_seat *seat) +{ + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(&seat->base); + + if (keyboard) + notify_keyboard_focus_out(&seat->base); + + udev_seat_remove_devices(seat); + weston_seat_release(&seat->base); + wl_list_remove(&seat->output_create_listener.link); + wl_list_remove(&seat->output_heads_listener.link); + free(seat); +} + +struct udev_seat * +udev_seat_get_named(struct udev_input *input, const char *seat_name) +{ + struct udev_seat *seat; + + wl_list_for_each(seat, &input->compositor->seat_list, base.link) { + if (strcmp(seat->base.seat_name, seat_name) == 0) + return seat; + } + + return udev_seat_create(input, seat_name); +} diff --git a/libweston/libinput-seat.h b/libweston/libinput-seat.h new file mode 100644 index 0000000..315980d --- /dev/null +++ b/libweston/libinput-seat.h @@ -0,0 +1,73 @@ +/* + * Copyright © 2013 Intel Corporation + * Copyright © 2013 Jonas Ådahl + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _LIBINPUT_SEAT_H_ +#define _LIBINPUT_SEAT_H_ + +#include "config.h" + +#include + +#include + +struct libinput_device; + +struct udev_seat { + struct weston_seat base; + struct wl_list devices_list; + struct wl_listener output_create_listener; + struct wl_listener output_heads_listener; +}; + +typedef void (*udev_configure_device_t)(struct weston_compositor *compositor, + struct libinput_device *device); + +struct udev_input { + struct libinput *libinput; + struct wl_event_source *libinput_source; + struct weston_compositor *compositor; + int suspended; + udev_configure_device_t configure_device; +}; + +int +udev_input_enable(struct udev_input *input); +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, + udev_configure_device_t configure_device); +void +udev_input_destroy(struct udev_input *input); + +struct udev_seat * +udev_seat_get_named(struct udev_input *u, + const char *seat_name); + +#endif diff --git a/libweston/libweston-internal.h b/libweston/libweston-internal.h new file mode 100755 index 0000000..66c1a70 --- /dev/null +++ b/libweston/libweston-internal.h @@ -0,0 +1,334 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2017, 2018 General Electric Company + * Copyright © 2012, 2017-2019 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef LIBWESTON_INTERNAL_H +#define LIBWESTON_INTERNAL_H + +/* + * This is the internal (private) part of libweston. All symbols found here + * are, and should be only (with a few exceptions) used within the internal + * parts of libweston. Notable exception(s) include a few files in tests/ that + * need access to these functions, screen-share file from compositor/ and those + * remoting/. Those will require some further fixing as to avoid including this + * private header. + * + * Eventually, these symbols should reside naturally into their own scope. New + * features should either provide their own (internal) header or use this one. + */ + + +/* weston_buffer */ + +void +weston_buffer_send_server_error(struct weston_buffer *buffer, + const char *msg); +void +weston_buffer_reference(struct weston_buffer_reference *ref, + struct weston_buffer *buffer); + +void +weston_buffer_release_move(struct weston_buffer_release_reference *dest, + struct weston_buffer_release_reference *src); + +void +weston_buffer_release_reference(struct weston_buffer_release_reference *ref, + struct weston_buffer_release *buf_release); + +/* weston_bindings */ +void +weston_binding_list_destroy_all(struct wl_list *list); + +/* weston_compositor */ + +void +touch_calibrator_mode_changed(struct weston_compositor *compositor); + +// OHOS remove noop +// int +// noop_renderer_init(struct weston_compositor *ec); + +void +weston_compositor_add_head(struct weston_compositor *compositor, + struct weston_head *head); +void +weston_compositor_add_pending_output(struct weston_output *output, + struct weston_compositor *compositor); +bool +weston_compositor_import_dmabuf(struct weston_compositor *compositor, + struct linux_dmabuf_buffer *buffer); +bool +weston_compositor_dmabuf_can_scanout(struct weston_compositor *compositor, + struct linux_dmabuf_buffer *buffer); +void +weston_compositor_offscreen(struct weston_compositor *compositor); + +// OHOS remove logger +// char * +// weston_compositor_print_scene_graph(struct weston_compositor *ec); + +void +weston_compositor_read_presentation_clock( + const struct weston_compositor *compositor, + struct timespec *ts); + +int +weston_compositor_run_axis_binding(struct weston_compositor *compositor, + struct weston_pointer *pointer, + const struct timespec *time, + struct weston_pointer_axis_event *event); +void +weston_compositor_run_button_binding(struct weston_compositor *compositor, + struct weston_pointer *pointer, + const struct timespec *time, + uint32_t button, + enum wl_pointer_button_state value); +int +weston_compositor_run_debug_binding(struct weston_compositor *compositor, + struct weston_keyboard *keyboard, + const struct timespec *time, + uint32_t key, + enum wl_keyboard_key_state state); +void +weston_compositor_run_key_binding(struct weston_compositor *compositor, + struct weston_keyboard *keyboard, + const struct timespec *time, + uint32_t key, + enum wl_keyboard_key_state state); +void +weston_compositor_run_modifier_binding(struct weston_compositor *compositor, + struct weston_keyboard *keyboard, + enum weston_keyboard_modifier modifier, + enum wl_keyboard_key_state state); +void +weston_compositor_run_touch_binding(struct weston_compositor *compositor, + struct weston_touch *touch, + const struct timespec *time, + int touch_type); +void +weston_compositor_stack_plane(struct weston_compositor *ec, + struct weston_plane *plane, + struct weston_plane *above); +void +weston_compositor_set_touch_mode_normal(struct weston_compositor *compositor); + +void +weston_compositor_set_touch_mode_calib(struct weston_compositor *compositor); + +int +weston_compositor_set_presentation_clock(struct weston_compositor *compositor, + clockid_t clk_id); +int +weston_compositor_set_presentation_clock_software( + struct weston_compositor *compositor); +void +weston_compositor_shutdown(struct weston_compositor *ec); + +void +weston_compositor_xkb_destroy(struct weston_compositor *ec); + +int +weston_input_init(struct weston_compositor *compositor); + +/* weston_output */ + +void +weston_output_disable_planes_incr(struct weston_output *output); + +void +weston_output_disable_planes_decr(struct weston_output *output); + +/* weston_plane */ + +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); + +/* weston_seat */ + +struct clipboard * +clipboard_create(struct weston_seat *seat); + +void +weston_seat_init(struct weston_seat *seat, struct weston_compositor *ec, + const char *seat_name); + +void +weston_seat_repick(struct weston_seat *seat); + +void +weston_seat_release(struct weston_seat *seat); + +void +weston_seat_send_selection(struct weston_seat *seat, struct wl_client *client); + +void +weston_seat_init_pointer(struct weston_seat *seat); + +int +weston_seat_init_keyboard(struct weston_seat *seat, struct xkb_keymap *keymap); + +void +weston_seat_init_touch(struct weston_seat *seat); + +void +weston_seat_release_keyboard(struct weston_seat *seat); + +void +weston_seat_release_pointer(struct weston_seat *seat); + +void +weston_seat_release_touch(struct weston_seat *seat); + +void +weston_seat_update_keymap(struct weston_seat *seat, struct xkb_keymap *keymap); + +void +wl_data_device_set_keyboard_focus(struct weston_seat *seat); + +/* weston_pointer */ + +void +weston_pointer_clamp(struct weston_pointer *pointer, + wl_fixed_t *fx, wl_fixed_t *fy); +void +weston_pointer_set_default_grab(struct weston_pointer *pointer, + const struct weston_pointer_grab_interface *interface); + +void +weston_pointer_constraint_destroy(struct weston_pointer_constraint *constraint); + +/* weston_keyboard */ +bool +weston_keyboard_has_focus_resource(struct weston_keyboard *keyboard); + +/* weston_touch */ + +struct weston_touch_device * +weston_touch_create_touch_device(struct weston_touch *touch, + const char *syspath, + void *backend_data, + const struct weston_touch_device_ops *ops); + +void +weston_touch_device_destroy(struct weston_touch_device *device); + +bool +weston_touch_has_focus_resource(struct weston_touch *touch); + +int +weston_touch_start_drag(struct weston_touch *touch, + struct weston_data_source *source, + struct weston_surface *icon, + struct wl_client *client); + + +/* weston_touch_device */ + +bool +weston_touch_device_can_calibrate(struct weston_touch_device *device); + +/* weston_surface */ +void +weston_surface_to_buffer_float(struct weston_surface *surface, + float x, float y, float *bx, float *by); +pixman_box32_t +weston_surface_to_buffer_rect(struct weston_surface *surface, + pixman_box32_t rect); + +void +weston_surface_to_buffer_region(struct weston_surface *surface, + pixman_region32_t *surface_region, + pixman_region32_t *buffer_region); +void +weston_surface_schedule_repaint(struct weston_surface *surface); + +/* weston_spring */ + +void +weston_spring_init(struct weston_spring *spring, + double k, double current, double target); +int +weston_spring_done(struct weston_spring *spring); + +void +weston_spring_update(struct weston_spring *spring, const struct timespec *time); + +/* weston_view */ + +void +weston_view_to_global_fixed(struct weston_view *view, + wl_fixed_t sx, wl_fixed_t sy, + wl_fixed_t *x, wl_fixed_t *y); +void +weston_view_from_global_float(struct weston_view *view, + float x, float y, float *vx, float *vy); +bool +weston_view_is_opaque(struct weston_view *ev, pixman_region32_t *region); + +bool +weston_view_has_valid_buffer(struct weston_view *ev); + +bool +weston_view_matches_output_entirely(struct weston_view *ev, + struct weston_output *output); +void +weston_view_move_to_plane(struct weston_view *view, + struct weston_plane *plane); + +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); +void +weston_transformed_region(int width, int height, + enum wl_output_transform transform, + int32_t scale, + pixman_region32_t *src, pixman_region32_t *dest); +void +weston_matrix_transform_region(pixman_region32_t *dest, + struct weston_matrix *matrix, + pixman_region32_t *src); + +/* protected_surface */ +void +weston_protected_surface_send_event(struct protected_surface *psurface, + enum weston_hdcp_protection protection); + +/* others */ +int +wl_data_device_manager_init(struct wl_display *display); + +#endif diff --git a/libweston/linux-dmabuf.c b/libweston/linux-dmabuf.c new file mode 100644 index 0000000..796e982 --- /dev/null +++ b/libweston/linux-dmabuf.c @@ -0,0 +1,591 @@ +/* + * Copyright © 2014, 2015 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include "linux-dmabuf.h" +#include "linux-dmabuf-unstable-v1-server-protocol.h" +#include "libweston-internal.h" + +static void +linux_dmabuf_buffer_destroy(struct linux_dmabuf_buffer *buffer) +{ + int i; + + for (i = 0; i < buffer->attributes.n_planes; i++) { + close(buffer->attributes.fd[i]); + buffer->attributes.fd[i] = -1; + } + + buffer->attributes.n_planes = 0; + free(buffer); +} + +static void +destroy_params(struct wl_resource *params_resource) +{ + struct linux_dmabuf_buffer *buffer; + + buffer = wl_resource_get_user_data(params_resource); + + if (!buffer) + return; + + linux_dmabuf_buffer_destroy(buffer); +} + +static void +params_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +params_add(struct wl_client *client, + struct wl_resource *params_resource, + int32_t name_fd, + uint32_t plane_idx, + uint32_t offset, + uint32_t stride, + uint32_t modifier_hi, + uint32_t modifier_lo) +{ + struct linux_dmabuf_buffer *buffer; + + buffer = wl_resource_get_user_data(params_resource); + if (!buffer) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, + "params was already used to create a wl_buffer"); + close(name_fd); + return; + } + + assert(buffer->params_resource == params_resource); + assert(!buffer->buffer_resource); + + if (plane_idx >= MAX_DMABUF_PLANES) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX, + "plane index %u is too high", plane_idx); + close(name_fd); + return; + } + + if (buffer->attributes.fd[plane_idx] != -1) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET, + "a dmabuf has already been added for plane %u", + plane_idx); + close(name_fd); + return; + } + + buffer->attributes.fd[plane_idx] = name_fd; + buffer->attributes.offset[plane_idx] = offset; + buffer->attributes.stride[plane_idx] = stride; + + if (wl_resource_get_version(params_resource) < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) + buffer->attributes.modifier[plane_idx] = DRM_FORMAT_MOD_INVALID; + else + buffer->attributes.modifier[plane_idx] = ((uint64_t)modifier_hi << 32) | + modifier_lo; + + buffer->attributes.n_planes++; +} + +static void +linux_dmabuf_wl_buffer_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct wl_buffer_interface linux_dmabuf_buffer_implementation = { + linux_dmabuf_wl_buffer_destroy +}; + +static void +destroy_linux_dmabuf_wl_buffer(struct wl_resource *resource) +{ + struct linux_dmabuf_buffer *buffer; + + buffer = wl_resource_get_user_data(resource); + assert(buffer->buffer_resource == resource); + assert(!buffer->params_resource); + + if (buffer->user_data_destroy_func) + buffer->user_data_destroy_func(buffer); + + linux_dmabuf_buffer_destroy(buffer); +} + +static void +params_create_common(struct wl_client *client, + struct wl_resource *params_resource, + uint32_t buffer_id, + int32_t width, + int32_t height, + uint32_t format, + uint32_t flags) +{ + struct linux_dmabuf_buffer *buffer; + int i; + + buffer = wl_resource_get_user_data(params_resource); + + if (!buffer) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, + "params was already used to create a wl_buffer"); + return; + } + + assert(buffer->params_resource == params_resource); + assert(!buffer->buffer_resource); + + /* Switch the linux_dmabuf_buffer object from params resource to + * eventually wl_buffer resource. + */ + wl_resource_set_user_data(buffer->params_resource, NULL); + buffer->params_resource = NULL; + + if (!buffer->attributes.n_planes) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, + "no dmabuf has been added to the params"); + goto err_out; + } + + /* Check for holes in the dmabufs set (e.g. [0, 1, 3]) */ + for (i = 0; i < buffer->attributes.n_planes; i++) { + if (buffer->attributes.fd[i] == -1) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, + "no dmabuf has been added for plane %i", i); + goto err_out; + } + } + + buffer->attributes.width = width; + buffer->attributes.height = height; + buffer->attributes.format = format; + buffer->attributes.flags = flags; + + if (width < 1 || height < 1) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS, + "invalid width %d or height %d", width, height); + goto err_out; + } + + for (i = 0; i < buffer->attributes.n_planes; i++) { + off_t size; + + if ((uint64_t) buffer->attributes.offset[i] + buffer->attributes.stride[i] > UINT32_MAX) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "size overflow for plane %i", i); + goto err_out; + } + + if (i == 0 && + (uint64_t) buffer->attributes.offset[i] + + (uint64_t) buffer->attributes.stride[i] * height > UINT32_MAX) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "size overflow for plane %i", i); + goto err_out; + } + + /* Don't report an error as it might be caused + * by the kernel not supporting seeking on dmabuf */ + size = lseek(buffer->attributes.fd[i], 0, SEEK_END); + if (size == -1) + continue; + + if (buffer->attributes.offset[i] >= size) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "invalid offset %i for plane %i", + buffer->attributes.offset[i], i); + goto err_out; + } + + if (buffer->attributes.offset[i] + buffer->attributes.stride[i] > size) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "invalid stride %i for plane %i", + buffer->attributes.stride[i], i); + goto err_out; + } + + /* Only valid for first plane as other planes might be + * sub-sampled according to fourcc format */ + if (i == 0 && + buffer->attributes.offset[i] + buffer->attributes.stride[i] * height > size) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "invalid buffer stride or height for plane %i", i); + goto err_out; + } + } + + if (buffer->direct_display) { + if (!weston_compositor_dmabuf_can_scanout(buffer->compositor, + buffer)) + goto err_failed; + + goto avoid_gpu_import; + } + + if (!weston_compositor_import_dmabuf(buffer->compositor, buffer)) + goto err_failed; + +avoid_gpu_import: + buffer->buffer_resource = wl_resource_create(client, + &wl_buffer_interface, + 1, buffer_id); + if (!buffer->buffer_resource) { + wl_resource_post_no_memory(params_resource); + goto err_buffer; + } + + wl_resource_set_implementation(buffer->buffer_resource, + &linux_dmabuf_buffer_implementation, + buffer, destroy_linux_dmabuf_wl_buffer); + + /* send 'created' event when the request is not for an immediate + * import, ie buffer_id is zero */ + if (buffer_id == 0) + zwp_linux_buffer_params_v1_send_created(params_resource, + buffer->buffer_resource); + + return; + +err_buffer: + if (buffer->user_data_destroy_func) + buffer->user_data_destroy_func(buffer); + +err_failed: + if (buffer_id == 0) + zwp_linux_buffer_params_v1_send_failed(params_resource); + else + /* since the behavior is left implementation defined by the + * protocol in case of create_immed failure due to an unknown cause, + * we choose to treat it as a fatal error and immediately kill the + * client instead of creating an invalid handle and waiting for it + * to be used. + */ + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER, + "importing the supplied dmabufs failed"); + +err_out: + linux_dmabuf_buffer_destroy(buffer); +} + +static void +params_create(struct wl_client *client, + struct wl_resource *params_resource, + int32_t width, + int32_t height, + uint32_t format, + uint32_t flags) +{ + params_create_common(client, params_resource, 0, width, height, format, + flags); +} + +static void +params_create_immed(struct wl_client *client, + struct wl_resource *params_resource, + uint32_t buffer_id, + int32_t width, + int32_t height, + uint32_t format, + uint32_t flags) +{ + params_create_common(client, params_resource, buffer_id, width, height, + format, flags); +} + +static const struct zwp_linux_buffer_params_v1_interface +zwp_linux_buffer_params_implementation = { + params_destroy, + params_add, + params_create, + params_create_immed +}; + +static void +linux_dmabuf_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +linux_dmabuf_create_params(struct wl_client *client, + struct wl_resource *linux_dmabuf_resource, + uint32_t params_id) +{ + struct weston_compositor *compositor; + struct linux_dmabuf_buffer *buffer; + uint32_t version; + int i; + + version = wl_resource_get_version(linux_dmabuf_resource); + compositor = wl_resource_get_user_data(linux_dmabuf_resource); + + buffer = zalloc(sizeof *buffer); + if (!buffer) + goto err_out; + + for (i = 0; i < MAX_DMABUF_PLANES; i++) + buffer->attributes.fd[i] = -1; + + buffer->compositor = compositor; + buffer->params_resource = + wl_resource_create(client, + &zwp_linux_buffer_params_v1_interface, + version, params_id); + buffer->direct_display = false; + if (!buffer->params_resource) + goto err_dealloc; + + wl_resource_set_implementation(buffer->params_resource, + &zwp_linux_buffer_params_implementation, + buffer, destroy_params); + + return; + +err_dealloc: + free(buffer); + +err_out: + wl_resource_post_no_memory(linux_dmabuf_resource); +} + +/** Get the linux_dmabuf_buffer from a wl_buffer resource + * + * If the given wl_buffer resource was created through the linux_dmabuf + * protocol interface, returns the linux_dmabuf_buffer object. This can + * be used as a type check for a wl_buffer. + * + * \param resource A wl_buffer resource. + * \return The linux_dmabuf_buffer if it exists, or NULL otherwise. + */ +WL_EXPORT struct linux_dmabuf_buffer * +linux_dmabuf_buffer_get(struct wl_resource *resource) +{ + struct linux_dmabuf_buffer *buffer; + + if (!resource) + return NULL; + + if (!wl_resource_instance_of(resource, &wl_buffer_interface, + &linux_dmabuf_buffer_implementation)) + return NULL; + + buffer = wl_resource_get_user_data(resource); + assert(buffer); + assert(!buffer->params_resource); + assert(buffer->buffer_resource == resource); + + return buffer; +} + +/** Set renderer-private data + * + * Set the user data for the linux_dmabuf_buffer. It is invalid to overwrite + * a non-NULL user data with a new non-NULL pointer. This is meant to + * protect against renderers fighting over linux_dmabuf_buffer user data + * ownership. + * + * The renderer-private data is usually set from the + * weston_renderer::import_dmabuf hook. + * + * \param buffer The linux_dmabuf_buffer object to set for. + * \param data The new renderer-private data pointer. + * \param func Destructor function to be called for the renderer-private + * data when the linux_dmabuf_buffer gets destroyed. + * + * \sa weston_compositor_import_dmabuf + */ +WL_EXPORT void +linux_dmabuf_buffer_set_user_data(struct linux_dmabuf_buffer *buffer, + void *data, + dmabuf_user_data_destroy_func func) +{ + assert(data == NULL || buffer->user_data == NULL); + + buffer->user_data = data; + buffer->user_data_destroy_func = func; +} + +/** Get renderer-private data + * + * Get the user data from the linux_dmabuf_buffer. + * + * \param buffer The linux_dmabuf_buffer to query. + * \return Renderer-private data pointer. + * + * \sa linux_dmabuf_buffer_set_user_data + */ +WL_EXPORT void * +linux_dmabuf_buffer_get_user_data(struct linux_dmabuf_buffer *buffer) +{ + return buffer->user_data; +} + +static const struct zwp_linux_dmabuf_v1_interface linux_dmabuf_implementation = { + linux_dmabuf_destroy, + linux_dmabuf_create_params +}; + +static void +bind_linux_dmabuf(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct weston_compositor *compositor = data; + struct wl_resource *resource; + int *formats = NULL; + uint64_t *modifiers = NULL; + int num_formats, num_modifiers; + uint64_t modifier_invalid = DRM_FORMAT_MOD_INVALID; + int i, j; + + resource = wl_resource_create(client, &zwp_linux_dmabuf_v1_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &linux_dmabuf_implementation, + compositor, NULL); + + /* + * Use EGL_EXT_image_dma_buf_import_modifiers to query and advertise + * format/modifier codes. + */ + compositor->renderer->query_dmabuf_formats(compositor, &formats, + &num_formats); + + for (i = 0; i < num_formats; i++) { + compositor->renderer->query_dmabuf_modifiers(compositor, + formats[i], + &modifiers, + &num_modifiers); + + /* send DRM_FORMAT_MOD_INVALID token when no modifiers are supported + * for this format */ + if (num_modifiers == 0) { + num_modifiers = 1; + modifiers = &modifier_invalid; + } + for (j = 0; j < num_modifiers; j++) { + if (version >= ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) { + uint32_t modifier_lo = modifiers[j] & 0xFFFFFFFF; + uint32_t modifier_hi = modifiers[j] >> 32; + zwp_linux_dmabuf_v1_send_modifier(resource, + formats[i], + modifier_hi, + modifier_lo); + } else if (modifiers[j] == DRM_FORMAT_MOD_LINEAR || + modifiers == &modifier_invalid) { + zwp_linux_dmabuf_v1_send_format(resource, + formats[i]); + } + } + if (modifiers != &modifier_invalid) + free(modifiers); + } + free(formats); +} + +/** Advertise linux_dmabuf support + * + * Calling this initializes the zwp_linux_dmabuf protocol support, so that + * the interface will be advertised to clients. Essentially it creates a + * global. Do not call this function multiple times in the compositor's + * lifetime. There is no way to deinit explicitly, globals will be reaped + * when the wl_display gets destroyed. + * + * \param compositor The compositor to init for. + * \return Zero on success, -1 on failure. + */ +WL_EXPORT int +linux_dmabuf_setup(struct weston_compositor *compositor) +{ + if (!wl_global_create(compositor->wl_display, + &zwp_linux_dmabuf_v1_interface, 3, + compositor, bind_linux_dmabuf)) + return -1; + + return 0; +} + +/** Resolve an internal compositor error by disconnecting the client. + * + * This function is used in cases when the dmabuf-based wl_buffer + * turns out unusable and there is no fallback path. This is used by + * renderers which are the fallback path in the first place. + * + * It is possible the fault is caused by a compositor bug, the underlying + * graphics stack bug or normal behaviour, or perhaps a client mistake. + * In any case, the options are to either composite garbage or nothing, + * or disconnect the client. This is a helper function for the latter. + * + * The error is sent as an INVALID_OBJECT error on the client's wl_display. + * + * \param buffer The linux_dmabuf_buffer that is unusable. + * \param msg A custom error message attached to the protocol error. + */ +WL_EXPORT void +linux_dmabuf_buffer_send_server_error(struct linux_dmabuf_buffer *buffer, + const char *msg) +{ + struct wl_client *client; + struct wl_resource *display_resource; + uint32_t id; + + assert(buffer->buffer_resource); + id = wl_resource_get_id(buffer->buffer_resource); + client = wl_resource_get_client(buffer->buffer_resource); + display_resource = wl_client_get_object(client, 1); + + assert(display_resource); + wl_resource_post_error(display_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "linux_dmabuf server error with " + "wl_buffer@%u: %s", id, msg); +} diff --git a/libweston/linux-dmabuf.h b/libweston/linux-dmabuf.h new file mode 100644 index 0000000..926dd9e --- /dev/null +++ b/libweston/linux-dmabuf.h @@ -0,0 +1,103 @@ +/* + * Copyright © 2014, 2015 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_LINUX_DMABUF_H +#define WESTON_LINUX_DMABUF_H + +#include + +#define MAX_DMABUF_PLANES 4 +#ifndef DRM_FORMAT_MOD_INVALID +#define DRM_FORMAT_MOD_INVALID ((1ULL<<56) - 1) +#endif +#ifndef DRM_FORMAT_MOD_LINEAR +#define DRM_FORMAT_MOD_LINEAR 0 +#endif + +struct linux_dmabuf_buffer; +typedef void (*dmabuf_user_data_destroy_func)( + struct linux_dmabuf_buffer *buffer); + +struct dmabuf_attributes { + int32_t width; + int32_t height; + uint32_t format; + uint32_t flags; /* enum zlinux_buffer_params_flags */ + int n_planes; + int fd[MAX_DMABUF_PLANES]; + uint32_t offset[MAX_DMABUF_PLANES]; + uint32_t stride[MAX_DMABUF_PLANES]; + uint64_t modifier[MAX_DMABUF_PLANES]; +}; + +struct linux_dmabuf_buffer { + struct wl_resource *buffer_resource; + struct wl_resource *params_resource; + struct weston_compositor *compositor; + struct dmabuf_attributes attributes; + + void *user_data; + dmabuf_user_data_destroy_func user_data_destroy_func; + + /* XXX: + * + * Add backend private data. This would be for the backend + * to do all additional imports it might ever use in advance. + * The basic principle, even if not implemented in drivers today, + * is that dmabufs are first attached, but the actual allocation + * is deferred to first use. This would allow the exporter and all + * attachers to agree on how to allocate. + * + * The DRM backend would use this to create drmFBs for each + * dmabuf_buffer, just in case at some point it would become + * feasible to scan it out directly. This would improve the + * possibilities to successfully scan out, avoiding compositing. + */ + + /**< marked as scan-out capable, avoids any composition */ + bool direct_display; +}; + +int +linux_dmabuf_setup(struct weston_compositor *compositor); + +int +weston_direct_display_setup(struct weston_compositor *compositor); + +struct linux_dmabuf_buffer * +linux_dmabuf_buffer_get(struct wl_resource *resource); + +void +linux_dmabuf_buffer_set_user_data(struct linux_dmabuf_buffer *buffer, + void *data, + dmabuf_user_data_destroy_func func); +void * +linux_dmabuf_buffer_get_user_data(struct linux_dmabuf_buffer *buffer); + +void +linux_dmabuf_buffer_send_server_error(struct linux_dmabuf_buffer *buffer, + const char *msg); + +#endif /* WESTON_LINUX_DMABUF_H */ diff --git a/libweston/linux-explicit-synchronization.c b/libweston/linux-explicit-synchronization.c new file mode 100644 index 0000000..4b47383 --- /dev/null +++ b/libweston/linux-explicit-synchronization.c @@ -0,0 +1,287 @@ +/* + * Copyright © 2018 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include +#include "linux-explicit-synchronization.h" +#include "linux-explicit-synchronization-unstable-v1-server-protocol.h" +#include "linux-sync-file.h" +#include "shared/fd-util.h" +#include "libweston-internal.h" + +static void +destroy_linux_buffer_release(struct wl_resource *resource) +{ + struct weston_buffer_release *buffer_release = + wl_resource_get_user_data(resource); + + fd_clear(&buffer_release->fence_fd); + free(buffer_release); +} + +static void +destroy_linux_surface_synchronization(struct wl_resource *resource) +{ + struct weston_surface *surface = + wl_resource_get_user_data(resource); + + if (surface) { + fd_clear(&surface->pending.acquire_fence_fd); + surface->synchronization_resource = NULL; + } +} + +static void +linux_surface_synchronization_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +linux_surface_synchronization_set_acquire_fence(struct wl_client *client, + struct wl_resource *resource, + int32_t fd) +{ + struct weston_surface *surface = wl_resource_get_user_data(resource); + + if (!surface) { + wl_resource_post_error( + resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_SURFACE, + "surface no longer exists"); + goto err; + } + + if (!linux_sync_file_is_valid(fd)) { + wl_resource_post_error( + resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_INVALID_FENCE, + "invalid fence fd"); + goto err; + } + + if (surface->pending.acquire_fence_fd != -1) { + wl_resource_post_error( + resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_DUPLICATE_FENCE, + "already have a fence fd"); + goto err; + } + + fd_update(&surface->pending.acquire_fence_fd, fd); + + return; + +err: + close(fd); +} + +static void +linux_surface_synchronization_get_release(struct wl_client *client, + struct wl_resource *resource, + uint32_t id) +{ + struct weston_surface *surface = + wl_resource_get_user_data(resource); + struct weston_buffer_release *buffer_release; + + if (!surface) { + wl_resource_post_error( + resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_SURFACE, + "surface no longer exists"); + return; + } + + if (surface->pending.buffer_release_ref.buffer_release) { + wl_resource_post_error( + resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_DUPLICATE_RELEASE, + "already has a buffer release"); + return; + } + + buffer_release = zalloc(sizeof *buffer_release); + if (buffer_release == NULL) + goto err_alloc; + + buffer_release->fence_fd = -1; + buffer_release->resource = + wl_resource_create(client, + &zwp_linux_buffer_release_v1_interface, + wl_resource_get_version(resource), id); + if (!buffer_release->resource) + goto err_create; + + wl_resource_set_implementation(buffer_release->resource, NULL, + buffer_release, + destroy_linux_buffer_release); + + weston_buffer_release_reference(&surface->pending.buffer_release_ref, + buffer_release); + + return; + +err_create: + free(buffer_release); + +err_alloc: + wl_client_post_no_memory(client); + +} + +const struct zwp_linux_surface_synchronization_v1_interface +linux_surface_synchronization_implementation = { + linux_surface_synchronization_destroy, + linux_surface_synchronization_set_acquire_fence, + linux_surface_synchronization_get_release, +}; + +static void +linux_explicit_synchronization_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +linux_explicit_synchronization_get_synchronization(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); + + if (surface->synchronization_resource) { + wl_resource_post_error( + resource, + ZWP_LINUX_EXPLICIT_SYNCHRONIZATION_V1_ERROR_SYNCHRONIZATION_EXISTS, + "wl_surface@%"PRIu32" already has a synchronization object", + wl_resource_get_id(surface_resource)); + return; + } + + surface->synchronization_resource = + wl_resource_create(client, + &zwp_linux_surface_synchronization_v1_interface, + wl_resource_get_version(resource), id); + if (!surface->synchronization_resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(surface->synchronization_resource, + &linux_surface_synchronization_implementation, + surface, + destroy_linux_surface_synchronization); +} + +static const struct zwp_linux_explicit_synchronization_v1_interface +linux_explicit_synchronization_implementation = { + linux_explicit_synchronization_destroy, + linux_explicit_synchronization_get_synchronization +}; + +static void +bind_linux_explicit_synchronization(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, + &zwp_linux_explicit_synchronization_v1_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, + &linux_explicit_synchronization_implementation, + compositor, NULL); +} + +/** Advertise linux_explicit_synchronization support + * + * Calling this initializes the zwp_linux_explicit_synchronization_v1 + * protocol support, so that the interface will be advertised to clients. + * Essentially it creates a global. Do not call this function multiple times + * in the compositor's lifetime. There is no way to deinit explicitly, globals + * will be reaped when the wl_display gets destroyed. + * + * \param compositor The compositor to init for. + * \return Zero on success, -1 on failure. + */ +WL_EXPORT int +linux_explicit_synchronization_setup(struct weston_compositor *compositor) +{ + if (!wl_global_create(compositor->wl_display, + &zwp_linux_explicit_synchronization_v1_interface, + 2, compositor, + bind_linux_explicit_synchronization)) + return -1; + + return 0; +} + +/** Resolve an internal compositor error by disconnecting the client. + * + * This function is used in cases when explicit synchronization + * turns out to be unusable and there is no fallback path. + * + * It is possible the fault is caused by a compositor bug, the underlying + * graphics stack bug or normal behaviour, or perhaps a client mistake. + * In any case, the options are to either composite garbage or nothing, + * or disconnect the client. This is a helper function for the latter. + * + * The error is sent as an INVALID_OBJECT error on the client's wl_display. + * + * \param resource The explicit synchronization related resource that is unusable. + * \param msg A custom error message attached to the protocol error. + */ +WL_EXPORT void +linux_explicit_synchronization_send_server_error(struct wl_resource *resource, + const char *msg) +{ + uint32_t id = wl_resource_get_id(resource); + const char *class = wl_resource_get_class(resource); + struct wl_client *client = wl_resource_get_client(resource); + struct wl_resource *display_resource = wl_client_get_object(client, 1); + + assert(display_resource); + wl_resource_post_error(display_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "linux_explicit_synchronization server error " + "with %s@%"PRIu32": %s", + class, id, msg); +} diff --git a/libweston/linux-explicit-synchronization.h b/libweston/linux-explicit-synchronization.h new file mode 100644 index 0000000..5502287 --- /dev/null +++ b/libweston/linux-explicit-synchronization.h @@ -0,0 +1,39 @@ +/* + * Copyright © 2018 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_LINUX_EXPLICIT_SYNCHRONIZATION_H +#define WESTON_LINUX_EXPLICIT_SYNCHRONIZATION_H + +struct weston_compositor; +struct wl_resource; + +int +linux_explicit_synchronization_setup(struct weston_compositor *compositor); + +void +linux_explicit_synchronization_send_server_error(struct wl_resource *resource, + const char *msg); + +#endif /* WESTON_LINUX_EXPLICIT_SYNCHRONIZATION */ diff --git a/libweston/linux-sync-file-uapi.h b/libweston/linux-sync-file-uapi.h new file mode 100644 index 0000000..cd30665 --- /dev/null +++ b/libweston/linux-sync-file-uapi.h @@ -0,0 +1,30 @@ +/* Sync file Linux kernel UAPI */ + +#ifndef WESTON_LINUX_SYNC_FILE_UAPI_H +#define WESTON_LINUX_SYNC_FILE_UAPI_H + +#include +#include + +struct sync_fence_info { + char obj_name[32]; + char driver_name[32]; + __s32 status; + __u32 flags; + __u64 timestamp_ns; +}; + +struct sync_file_info { + char name[32]; + __s32 status; + __u32 flags; + __u32 num_fences; + __u32 pad; + + __u64 sync_fence_info; +}; + +#define SYNC_IOC_MAGIC '>' +#define SYNC_IOC_FILE_INFO _IOWR(SYNC_IOC_MAGIC, 4, struct sync_file_info) + +#endif /* WESTON_LINUX_SYNC_FILE_UAPI_H */ diff --git a/libweston/linux-sync-file.c b/libweston/linux-sync-file.c new file mode 100644 index 0000000..9f5313c --- /dev/null +++ b/libweston/linux-sync-file.c @@ -0,0 +1,83 @@ +/* + * Copyright © 2018 Collabora, Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LINUX_SYNC_FILE_H +#include +#else +#include "linux-sync-file-uapi.h" +#endif + +#include "linux-sync-file.h" +#include "shared/timespec-util.h" + +/* Check that a file descriptor represents a valid sync file + * + * \param fd[in] a file descriptor + * \return true if fd is a valid sync file, false otherwise + */ +bool +linux_sync_file_is_valid(int fd) +{ + struct sync_file_info file_info = { { 0 } }; + + if (ioctl(fd, SYNC_IOC_FILE_INFO, &file_info) < 0) + return false; + + return file_info.num_fences > 0; +} + +/* Read the timestamp stored in a sync file + * + * \param fd[in] fd a file descriptor for a sync file + * \param ts[out] the timespec struct to fill with the timestamp + * \return 0 if a timestamp was read, -1 on error + */ +WL_EXPORT int +weston_linux_sync_file_read_timestamp(int fd, struct timespec *ts) +{ + struct sync_file_info file_info = { { 0 } }; + struct sync_fence_info fence_info = { { 0 } }; + + assert(ts != NULL); + + file_info.sync_fence_info = (uint64_t)(uintptr_t)&fence_info; + file_info.num_fences = 1; + + if (ioctl(fd, SYNC_IOC_FILE_INFO, &file_info) < 0) + return -1; + + timespec_from_nsec(ts, fence_info.timestamp_ns); + + return 0; +} diff --git a/libweston/linux-sync-file.h b/libweston/linux-sync-file.h new file mode 100644 index 0000000..9746d7b --- /dev/null +++ b/libweston/linux-sync-file.h @@ -0,0 +1,38 @@ +/* + * Copyright © 2018 Collabora, Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_LINUX_SYNC_FILE_H +#define WESTON_LINUX_SYNC_FILE_H + +#include +#include + +bool +linux_sync_file_is_valid(int fd); + +int +weston_linux_sync_file_read_timestamp(int fd, struct timespec *ts); + +#endif /* WESTON_LINUX_SYNC_FILE_H */ diff --git a/libweston/log.c b/libweston/log.c new file mode 100644 index 0000000..da57e03 --- /dev/null +++ b/libweston/log.c @@ -0,0 +1,157 @@ +/* + * Copyright © 2012 Martin Minarik + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "weston-log-internal.h" + +/** + * \defgroup wlog weston-logging + */ + +static int +default_log_handler(const char *fmt, va_list ap); + +/** Needs to be set, defaults to default_log_handler + * + * \ingroup wlog + */ +static log_func_t log_handler = default_log_handler; + +/** Needs to be set, defaults to default_log_handler + * + * \ingroup wlog + */ +static log_func_t log_continue_handler = default_log_handler; + +/** Sentinel log message handler + * + * This function is used as the default handler for log messages. It + * exists only to issue a noisy reminder to the user that a real handler + * must be installed prior to issuing logging calls. The process is + * immediately aborted after the reminder is printed. + * + * \param fmt The format string. Ignored. + * \param ap The variadic argument list. Ignored. + * + * \ingroup wlog + */ +static int +default_log_handler(const char *fmt, va_list ap) +{ + fprintf(stderr, "weston_log_set_handler() must be called before using of weston_log().\n"); + abort(); +} + +/** Install the log handler + * + * The given functions will be called to output text as passed to the + * \a weston_log and \a weston_log_continue functions. + * + * \param log The log function. This function will be called when + * \a weston_log is called, and should begin a new line, + * with user defined line headers, if any. + * \param cont The continue log function. This function will be called + * when \a weston_log_continue is called, and should append + * its output to the current line, without any header or + * other content in between. + * + * \ingroup wlog + */ +WL_EXPORT void +weston_log_set_handler(log_func_t log, log_func_t cont) +{ + log_handler = log; + log_continue_handler = cont; +} + +/** weston_vlog calls log_handler + * \ingroup wlog + */ +WL_EXPORT int +weston_vlog(const char *fmt, va_list ap) +{ + return log_handler(fmt, ap); +} + +/** printf() equivalent in weston compositor. + * + * \rststar + * .. note:: + * + * Needs :var:`log_handler` to be set-up! + * \endrststar + * + * \ingroup wlog + */ +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; +} + +/** weston_vlog_continue calls log_continue_handler + * + * \ingroup wlog + */ +WL_EXPORT int +weston_vlog_continue(const char *fmt, va_list argp) +{ + return log_continue_handler(fmt, argp); +} + +/** weston_log_continue + * + * \ingroup wlog + */ +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/libweston/meson.build b/libweston/meson.build new file mode 100644 index 0000000..08d23ec --- /dev/null +++ b/libweston/meson.build @@ -0,0 +1,242 @@ +deps_libweston = [ + dep_wayland_server, + dep_pixman, + dep_libm, + dep_libdl, + dep_libdrm_headers, + dep_xkbcommon, + dep_matrix_c +] +srcs_libweston = [ + git_version_h, + 'animation.c', + 'bindings.c', + 'clipboard.c', + 'compositor.c', + 'content-protection.c', + 'data-device.c', + 'input.c', + 'linux-dmabuf.c', + 'linux-explicit-synchronization.c', + 'linux-sync-file.c', + 'log.c', + 'noop-renderer.c', + 'pixel-formats.c', + 'pixman-renderer.c', + 'plugin-registry.c', + 'screenshooter.c', + 'timeline.c', + 'touch-calibration.c', + 'weston-log-wayland.c', + 'weston-log-file.c', + 'weston-log-flight-rec.c', + 'weston-log.c', + 'weston-direct-display.c', + 'zoom.c', + linux_dmabuf_unstable_v1_protocol_c, + linux_dmabuf_unstable_v1_server_protocol_h, + linux_explicit_synchronization_unstable_v1_protocol_c, + linux_explicit_synchronization_unstable_v1_server_protocol_h, + input_method_unstable_v1_protocol_c, + input_method_unstable_v1_server_protocol_h, + input_timestamps_unstable_v1_protocol_c, + input_timestamps_unstable_v1_server_protocol_h, + presentation_time_protocol_c, + presentation_time_server_protocol_h, + pointer_constraints_unstable_v1_protocol_c, + pointer_constraints_unstable_v1_server_protocol_h, + relative_pointer_unstable_v1_protocol_c, + relative_pointer_unstable_v1_server_protocol_h, + weston_screenshooter_protocol_c, + weston_screenshooter_server_protocol_h, + text_cursor_position_protocol_c, + text_cursor_position_server_protocol_h, + text_input_unstable_v1_protocol_c, + text_input_unstable_v1_server_protocol_h, + weston_touch_calibration_protocol_c, + weston_touch_calibration_server_protocol_h, + weston_content_protection_protocol_c, + weston_content_protection_server_protocol_h, + viewporter_protocol_c, + viewporter_server_protocol_h, + xdg_output_unstable_v1_protocol_c, + xdg_output_unstable_v1_server_protocol_h, + weston_debug_protocol_c, + weston_debug_server_protocol_h, + weston_direct_display_protocol_c, + weston_direct_display_server_protocol_h, +] + +if get_option('renderer-gl') + dep_egl = dependency('egl', required: false) + if not dep_egl.found() + error('libweston + gl-renderer requires egl which was not found. Or, you can use \'-Drenderer-gl=false\'.') + endif + deps_libweston += dep_egl +endif + +lib_weston = shared_library( + 'weston-@0@'.format(libweston_major), + srcs_libweston, + include_directories: common_inc, + install: true, + version: '0.0.@0@'.format(libweston_revision), + link_whole: lib_libshared, + dependencies: deps_libweston +) + +deps_for_libweston_users = [ + dep_wayland_server, + dep_pixman, + dep_xkbcommon, +] + +# For external users, like Weston. +dep_libweston_public = declare_dependency( + link_with: lib_weston, + include_directories: public_inc, + dependencies: deps_for_libweston_users +) + +# For internal users, like the backends. +dep_libweston_private = declare_dependency( + link_with: lib_weston, + include_directories: [ include_directories('.'), public_inc ], + dependencies: deps_for_libweston_users +) + +# XXX: We should be able to use dep_libweston_private.partial_dependency() instead +# of this, but a Meson bug makes it not work. It will be fixed with +# https://github.com/mesonbuild/meson/pull/5167 +# in hopefully Meson 0.51. +dep_libweston_private_h_deps = [] +foreach d : deps_for_libweston_users + dep_libweston_private_h_deps += d.partial_dependency(compile_args: true) +endforeach +dep_libweston_private_h = declare_dependency( + include_directories: [ include_directories('.'), public_inc ], + dependencies: dep_libweston_private_h_deps +) + +pkgconfig.generate( + lib_weston, + filebase: 'libweston-@0@'.format(libweston_major), + name: 'libweston API', + version: version_weston, + description: 'Header files for libweston compositors development', + requires_private: deps_for_libweston_users, + subdirs: dir_include_libweston +) + +pkgconfig.generate( + filebase: 'libweston-@0@-protocols'.format(libweston_major), + name: 'libWeston Protocols', + version: version_weston, + description: 'libWeston protocol files', + variables: [ + 'datarootdir=' + join_paths('${prefix}', get_option('datadir')), + 'pkgdatadir=' + join_paths('${pc_sysrootdir}${datarootdir}', dir_protocol_libweston) + ], + install_dir: dir_data_pc +) + +srcs_session_helper = [ + 'launcher-direct.c', + 'launcher-util.c', + 'launcher-weston-launch.c', +] +deps_session_helper = [ dep_libweston_private_h ] + +if get_option('backend-drm') + deps_session_helper += dep_libdrm +endif + +systemd_dep = dependency('', required: false) +if get_option('launcher-logind') + systemd_dep = dependency('libsystemd', version: '>= 209', required: false) + if systemd_dep.found() + config_h.set('HAVE_SYSTEMD_LOGIN_209', '1') + else + systemd_dep = dependency('libsystemd-login', version: '>= 198', required: false) + if not systemd_dep.found() + error('logind support requires libsystemd or libsystemd-login but neither was found. Or, you can use \'-Dlauncher-logind=false\'') + endif + endif + + dbus_dep = dependency('dbus-1', version: '>= 1.6', required: false) + if not dbus_dep.found() + error('logind support requires dbus-1 >= 1.6 which was not found. Or, you can use \'-Dlauncher-logind=false\'') + endif + + config_h.set('HAVE_DBUS', '1') + config_h.set('HAVE_SYSTEMD_LOGIN', '1') + + srcs_session_helper += [ + 'dbus.c', + 'launcher-logind.c', + ] + deps_session_helper += [ + dbus_dep, + systemd_dep, + ] +endif + +lib_session_helper = static_library( + 'session-helper', + srcs_session_helper, + include_directories: common_inc, + dependencies: deps_session_helper, + install: false +) +dep_session_helper = declare_dependency(link_with: lib_session_helper) + + +lib_libinput_backend = static_library( + 'libinput-backend', + [ + 'libinput-device.c', + 'libinput-seat.c' + ], + dependencies: [ + dep_libweston_private, + dep_libinput, + dependency('libudev', version: '>= 136') + ], + include_directories: common_inc, + install: false +) +dep_libinput_backend = declare_dependency( + link_with: lib_libinput_backend, + include_directories: include_directories('.') +) + +dep_vertex_clipping = declare_dependency( + sources: 'vertex-clipping.c', + include_directories: include_directories('.') +) + +if get_option('weston-launch') + dep_pam = cc.find_library('pam') + + if not cc.has_function('pam_open_session', dependencies: dep_pam) + error('pam_open_session not found for weston-launch') + endif + + executable( + 'weston-launch', + 'weston-launch.c', + dependencies: [dep_pam, systemd_dep, dep_libdrm], + include_directories: common_inc, + install: true + ) + + meson.add_install_script('echo', 'REMINDER: You are installing weston-launch, please make it setuid-root.') +endif + +subdir('renderer-gl') +subdir('backend-drm') +subdir('backend-fbdev') +subdir('backend-headless') +subdir('backend-rdp') +subdir('backend-wayland') +subdir('backend-x11') diff --git a/libweston/noop-renderer.c b/libweston/noop-renderer.c new file mode 100644 index 0000000..d86e7f0 --- /dev/null +++ b/libweston/noop-renderer.c @@ -0,0 +1,123 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include +#include "libweston-internal.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) +{ + struct wl_shm_buffer *shm_buffer; + uint8_t *data; + uint32_t size, i, width, height, stride; + volatile unsigned char unused = 0; /* volatile so it's not optimized out */ + + if (!buffer) + return; + + shm_buffer = wl_shm_buffer_get(buffer->resource); + + if (!shm_buffer) { + weston_log("No-op renderer supports only SHM buffers\n"); + return; + } + + data = wl_shm_buffer_get_data(shm_buffer); + stride = wl_shm_buffer_get_stride(shm_buffer); + width = wl_shm_buffer_get_width(shm_buffer); + height = wl_shm_buffer_get_height(shm_buffer); + size = stride * height; + + /* Access the buffer data to make sure the buffer's client gets killed + * if the buffer size is invalid. This makes the bad_buffer test pass. + * This can be removed if we start reading the buffer contents + * somewhere else, e.g. in repaint_output(). */ + wl_shm_buffer_begin_access(shm_buffer); + for (i = 0; i < size; i++) + unused ^= data[i]; + wl_shm_buffer_end_access(shm_buffer); + + buffer->shm_buffer = shm_buffer; + buffer->width = width; + buffer->height = height; +} + +static void +noop_renderer_surface_set_color(struct weston_surface *surface, + float red, float green, float blue, float alpha) +{ +} + +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 = zalloc(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->surface_set_color = noop_renderer_surface_set_color; + renderer->destroy = noop_renderer_destroy; + ec->renderer = renderer; + + return 0; +} diff --git a/libweston/pixel-formats.c b/libweston/pixel-formats.c new file mode 100755 index 0000000..79dc709 --- /dev/null +++ b/libweston/pixel-formats.c @@ -0,0 +1,516 @@ +/* + * Copyright © 2016, 2019 Collabora, Ltd. + * Copyright (c) 2018 DisplayLink (UK) Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Author: Daniel Stone + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "shared/helpers.h" +#include "wayland-util.h" +#include "pixel-formats.h" + +#if ENABLE_EGL +#include +#include +#include +#include +#define GL_FORMAT(fmt) .gl_format = (fmt) +#define GL_TYPE(type) .gl_type = (type) +#define SAMPLER_TYPE(type) .sampler_type = (type) +#else +#define GL_FORMAT(fmt) .gl_format = 0 +#define GL_TYPE(type) .gl_type = 0 +#define SAMPLER_TYPE(type) .sampler_type = 0 +#endif + +#define DRM_FORMAT(f) .format = DRM_FORMAT_ ## f, .drm_format_name = #f +#define BITS_RGBA_FIXED(r_, g_, b_, a_) \ + .bits.r = r_, \ + .bits.g = g_, \ + .bits.b = b_, \ + .bits.a = a_, \ + .component_type = PIXEL_COMPONENT_TYPE_FIXED + +#include "shared/weston-egl-ext.h" + +/** + * Table of DRM formats supported by Weston; RGB, ARGB and YUV formats are + * supported. Indexed/greyscale formats, and formats not containing complete + * colour channels, are not supported. + */ +static const struct pixel_format_info pixel_format_table[] = { + { + DRM_FORMAT(XRGB4444), + BITS_RGBA_FIXED(4, 4, 4, 0), + }, + { + DRM_FORMAT(ARGB4444), + BITS_RGBA_FIXED(4, 4, 4, 4), + .opaque_substitute = DRM_FORMAT_XRGB4444, + }, + { + DRM_FORMAT(XBGR4444), + BITS_RGBA_FIXED(4, 4, 4, 0), + }, + { + DRM_FORMAT(ABGR4444), + BITS_RGBA_FIXED(4, 4, 4, 4), + .opaque_substitute = DRM_FORMAT_XBGR4444, + }, + { + DRM_FORMAT(RGBX4444), + BITS_RGBA_FIXED(4, 4, 4, 0), +# if __BYTE_ORDER == __LITTLE_ENDIAN + GL_FORMAT(GL_RGBA), + GL_TYPE(GL_UNSIGNED_SHORT_4_4_4_4), +#endif + }, + { + DRM_FORMAT(RGBA4444), + BITS_RGBA_FIXED(4, 4, 4, 4), + .opaque_substitute = DRM_FORMAT_RGBX4444, +# if __BYTE_ORDER == __LITTLE_ENDIAN + GL_FORMAT(GL_RGBA), + GL_TYPE(GL_UNSIGNED_SHORT_4_4_4_4), +#endif + }, + { + DRM_FORMAT(BGRX4444), + BITS_RGBA_FIXED(4, 4, 4, 0), + }, + { + DRM_FORMAT(BGRA4444), + BITS_RGBA_FIXED(4, 4, 4, 4), + .opaque_substitute = DRM_FORMAT_BGRX4444, + }, + { + DRM_FORMAT(XRGB1555), + BITS_RGBA_FIXED(5, 5, 5, 0), + .depth = 15, + .bpp = 16, + }, + { + DRM_FORMAT(ARGB1555), + BITS_RGBA_FIXED(5, 5, 5, 1), + .opaque_substitute = DRM_FORMAT_XRGB1555, + }, + { + DRM_FORMAT(XBGR1555), + BITS_RGBA_FIXED(5, 5, 5, 0), + }, + { + DRM_FORMAT(ABGR1555), + BITS_RGBA_FIXED(5, 5, 5, 1), + .opaque_substitute = DRM_FORMAT_XBGR1555, + }, + { + DRM_FORMAT(RGBX5551), + BITS_RGBA_FIXED(5, 5, 5, 0), +# if __BYTE_ORDER == __LITTLE_ENDIAN + GL_FORMAT(GL_RGBA), + GL_TYPE(GL_UNSIGNED_SHORT_5_5_5_1), +#endif + }, + { + DRM_FORMAT(RGBA5551), + BITS_RGBA_FIXED(5, 5, 5, 1), + .opaque_substitute = DRM_FORMAT_RGBX5551, +# if __BYTE_ORDER == __LITTLE_ENDIAN + GL_FORMAT(GL_RGBA), + GL_TYPE(GL_UNSIGNED_SHORT_5_5_5_1), +#endif + }, + { + DRM_FORMAT(BGRX5551), + BITS_RGBA_FIXED(5, 5, 5, 0), + }, + { + DRM_FORMAT(BGRA5551), + BITS_RGBA_FIXED(5, 5, 5, 1), + .opaque_substitute = DRM_FORMAT_BGRX5551, + }, + { + DRM_FORMAT(RGB565), + BITS_RGBA_FIXED(5, 6, 5, 0), + .depth = 16, + .bpp = 16, +# if __BYTE_ORDER == __LITTLE_ENDIAN + GL_FORMAT(GL_RGB), + GL_TYPE(GL_UNSIGNED_SHORT_5_6_5), +#endif + }, + { + DRM_FORMAT(BGR565), + BITS_RGBA_FIXED(5, 6, 5, 0), + }, + { + DRM_FORMAT(RGB888), + BITS_RGBA_FIXED(8, 8, 8, 0), + }, + { + DRM_FORMAT(BGR888), + BITS_RGBA_FIXED(8, 8, 8, 0), + GL_FORMAT(GL_RGB), + GL_TYPE(GL_UNSIGNED_BYTE), + }, + { + DRM_FORMAT(XRGB8888), + BITS_RGBA_FIXED(8, 8, 8, 0), + .depth = 24, + .bpp = 32, + GL_FORMAT(GL_BGRA_EXT), + GL_TYPE(GL_UNSIGNED_BYTE), + }, + { + DRM_FORMAT(ARGB8888), + BITS_RGBA_FIXED(8, 8, 8, 8), + .opaque_substitute = DRM_FORMAT_XRGB8888, + .depth = 32, + .bpp = 32, + GL_FORMAT(GL_BGRA_EXT), + GL_TYPE(GL_UNSIGNED_BYTE), + }, + { + DRM_FORMAT(XBGR8888), + BITS_RGBA_FIXED(8, 8, 8, 0), + GL_FORMAT(GL_RGBA), + GL_TYPE(GL_UNSIGNED_BYTE), + }, + { + DRM_FORMAT(ABGR8888), + BITS_RGBA_FIXED(8, 8, 8, 8), + .opaque_substitute = DRM_FORMAT_XBGR8888, + GL_FORMAT(GL_RGBA), + GL_TYPE(GL_UNSIGNED_BYTE), + }, + { + DRM_FORMAT(RGBX8888), + BITS_RGBA_FIXED(8, 8, 8, 0), + }, + { + DRM_FORMAT(RGBA8888), + BITS_RGBA_FIXED(8, 8, 8, 8), + .opaque_substitute = DRM_FORMAT_RGBX8888, + }, + { + DRM_FORMAT(BGRX8888), + BITS_RGBA_FIXED(8, 8, 8, 0), + }, + { + DRM_FORMAT(BGRA8888), + BITS_RGBA_FIXED(8, 8, 8, 8), + .opaque_substitute = DRM_FORMAT_BGRX8888, + }, + { + DRM_FORMAT(XRGB2101010), + BITS_RGBA_FIXED(10, 10, 10, 0), + .depth = 30, + .bpp = 32, + }, + { + DRM_FORMAT(ARGB2101010), + BITS_RGBA_FIXED(10, 10, 10, 2), + .opaque_substitute = DRM_FORMAT_XRGB2101010, + }, + { + DRM_FORMAT(XBGR2101010), + BITS_RGBA_FIXED(10, 10, 10, 0), +# if __BYTE_ORDER == __LITTLE_ENDIAN + GL_FORMAT(GL_RGBA), + GL_TYPE(GL_UNSIGNED_INT_2_10_10_10_REV_EXT), +#endif + }, + { + DRM_FORMAT(ABGR2101010), + BITS_RGBA_FIXED(10, 10, 10, 2), + .opaque_substitute = DRM_FORMAT_XBGR2101010, +# if __BYTE_ORDER == __LITTLE_ENDIAN + GL_FORMAT(GL_RGBA), + GL_TYPE(GL_UNSIGNED_INT_2_10_10_10_REV_EXT), +#endif + }, + { + DRM_FORMAT(RGBX1010102), + BITS_RGBA_FIXED(10, 10, 10, 0), + }, + { + DRM_FORMAT(RGBA1010102), + BITS_RGBA_FIXED(10, 10, 10, 2), + .opaque_substitute = DRM_FORMAT_RGBX1010102, + }, + { + DRM_FORMAT(BGRX1010102), + BITS_RGBA_FIXED(10, 10, 10, 0), + }, + { + DRM_FORMAT(BGRA1010102), + BITS_RGBA_FIXED(10, 10, 10, 2), + .opaque_substitute = DRM_FORMAT_BGRX1010102, + }, + { + DRM_FORMAT(YUYV), + SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), + .num_planes = 1, + .hsub = 2, + }, + { + DRM_FORMAT(YVYU), + SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), + .num_planes = 1, + .chroma_order = ORDER_VU, + .hsub = 2, + }, + { + DRM_FORMAT(UYVY), + SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), + .num_planes = 1, + .luma_chroma_order = ORDER_CHROMA_LUMA, + .hsub = 2, + }, + { + DRM_FORMAT(VYUY), + SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), + .num_planes = 1, + .luma_chroma_order = ORDER_CHROMA_LUMA, + .chroma_order = ORDER_VU, + .hsub = 2, + }, + { + DRM_FORMAT(NV12), + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), + .num_planes = 2, + .hsub = 2, + .vsub = 2, + }, + { + DRM_FORMAT(NV21), + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), + .num_planes = 2, + .chroma_order = ORDER_VU, + .hsub = 2, + .vsub = 2, + }, + { + DRM_FORMAT(NV16), + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), + .num_planes = 2, + .hsub = 2, + .vsub = 1, + }, + { + DRM_FORMAT(NV61), + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), + .num_planes = 2, + .chroma_order = ORDER_VU, + .hsub = 2, + .vsub = 1, + }, + { + DRM_FORMAT(NV24), + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), + .num_planes = 2, + }, + { + DRM_FORMAT(NV42), + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), + .num_planes = 2, + .chroma_order = ORDER_VU, + }, + { + DRM_FORMAT(YUV410), + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .hsub = 4, + .vsub = 4, + }, + { + DRM_FORMAT(YVU410), + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .chroma_order = ORDER_VU, + .hsub = 4, + .vsub = 4, + }, + { + DRM_FORMAT(YUV411), + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .hsub = 4, + .vsub = 1, + }, + { + DRM_FORMAT(YVU411), + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .chroma_order = ORDER_VU, + .hsub = 4, + .vsub = 1, + }, + { + DRM_FORMAT(YUV420), + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .hsub = 2, + .vsub = 2, + }, + { + DRM_FORMAT(YVU420), + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .chroma_order = ORDER_VU, + .hsub = 2, + .vsub = 2, + }, + { + DRM_FORMAT(YUV422), + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .hsub = 2, + .vsub = 1, + }, + { + DRM_FORMAT(YVU422), + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .chroma_order = ORDER_VU, + .hsub = 2, + .vsub = 1, + }, + { + DRM_FORMAT(YUV444), + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + }, + { + DRM_FORMAT(YVU444), + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .chroma_order = ORDER_VU, + }, +}; + +WL_EXPORT const struct pixel_format_info * +pixel_format_get_info_shm(uint32_t format) +{ + if (format == WL_SHM_FORMAT_XRGB8888) + return pixel_format_get_info(DRM_FORMAT_XRGB8888); + else if (format == WL_SHM_FORMAT_ARGB8888) + return pixel_format_get_info(DRM_FORMAT_ARGB8888); + else + return pixel_format_get_info(format); +} + +WL_EXPORT const struct pixel_format_info * +pixel_format_get_info(uint32_t format) +{ + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(pixel_format_table); i++) { + if (pixel_format_table[i].format == format) + return &pixel_format_table[i]; + } + + return NULL; +} + +WL_EXPORT const struct pixel_format_info * +pixel_format_get_info_by_drm_name(const char *drm_format_name) +{ + const struct pixel_format_info *info; + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(pixel_format_table); i++) { + info = &pixel_format_table[i]; + if (strcasecmp(info->drm_format_name, drm_format_name) == 0) + return info; + } + + return NULL; +} + +WL_EXPORT unsigned int +pixel_format_get_plane_count(const struct pixel_format_info *info) +{ + return info->num_planes ? info->num_planes : 1; +} + +WL_EXPORT bool +pixel_format_is_opaque(const struct pixel_format_info *info) +{ + return !info->opaque_substitute; +} + +WL_EXPORT const struct pixel_format_info * +pixel_format_get_opaque_substitute(const struct pixel_format_info *info) +{ + if (!info->opaque_substitute) + return info; + else + return pixel_format_get_info(info->opaque_substitute); +} + +WL_EXPORT const struct pixel_format_info * +pixel_format_get_info_by_opaque_substitute(uint32_t format) +{ + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(pixel_format_table); i++) { + if (pixel_format_table[i].opaque_substitute == format) + return &pixel_format_table[i]; + } + + return NULL; +} + +WL_EXPORT unsigned int +pixel_format_width_for_plane(const struct pixel_format_info *info, + unsigned int plane, + unsigned int width) +{ + /* We don't support any formats where the first plane is subsampled. */ + if (plane == 0 || !info->hsub) + return width; + + return width / info->hsub; +} + +WL_EXPORT unsigned int +pixel_format_height_for_plane(const struct pixel_format_info *info, + unsigned int plane, + unsigned int height) +{ + /* We don't support any formats where the first plane is subsampled. */ + if (plane == 0 || !info->vsub) + return height; + + return height / info->vsub; +} diff --git a/libweston/pixel-formats.h b/libweston/pixel-formats.h new file mode 100644 index 0000000..3e12526 --- /dev/null +++ b/libweston/pixel-formats.h @@ -0,0 +1,262 @@ +/* + * Copyright © 2016, 2019 Collabora, Ltd. + * Copyright (c) 2018 DisplayLink (UK) Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Author: Daniel Stone + */ + +#include +#include + +/** + * Contains information about pixel formats, mapping format codes from + * wl_shm and drm_fourcc.h (which are deliberately identical, but for the + * special cases of WL_SHM_ARGB8888 and WL_SHM_XRGB8888) into various + * sets of information. Helper functions are provided for dealing with these + * raw structures. + */ +struct pixel_format_info { + /** DRM/wl_shm format code */ + uint32_t format; + + /** The DRM format name without the DRM_FORMAT_ prefix. */ + const char *drm_format_name; + + /** If non-zero, number of planes in base (non-modified) format. */ + int num_planes; + + /** If format contains alpha channel, opaque equivalent of format, + * i.e. alpha channel replaced with X. */ + uint32_t opaque_substitute; + + /** How the format should be sampled, expressed in terms of tokens + * from the EGL_WL_bind_wayland_display extension. If not set, + * assumed to be either RGB or RGBA, depending on whether or not + * the format contains an alpha channel. The samplers may still + * return alpha even for opaque formats; users must manually set + * the alpha channel to 1.0 (or ignore it) if the format is + * opaque. */ + uint32_t sampler_type; + + /** GL format, if data can be natively/directly uploaded. Note that + * whilst DRM formats are little-endian unless explicitly specified, + * (i.e. DRM_FORMAT_ARGB8888 is stored BGRA as sequential bytes in + * memory), GL uses the sequential byte order, so that format maps to + * GL_BGRA_EXT plus GL_UNSIGNED_BYTE. To add to the confusion, the + * explicitly-sized types (e.g. GL_UNSIGNED_SHORT_5_5_5_1) read in + * machine-endian order, so for these types, the correspondence + * depends on endianness. */ + int gl_format; + + /** GL data type, if data can be natively/directly uploaded. */ + int gl_type; + + /** If set, this format can be used with the legacy drmModeAddFB() + * function (not AddFB2), using this and the bpp member. */ + int depth; + + /** See 'depth' member above. */ + int bpp; + + /** Horizontal subsampling; if non-zero, divide the width by this + * member to obtain the number of columns in the source buffer for + * secondary planes only. Stride is not affected by horizontal + * subsampling. */ + int hsub; + + /** Vertical subsampling; if non-zero, divide the height by this + * member to obtain the number of rows in the source buffer for + * secondary planes only. */ + int vsub; + + /* Ordering of chroma components. */ + enum { + ORDER_UV = 0, + ORDER_VU, + } chroma_order; + + /* If packed YUV (num_planes == 1), ordering of luma/chroma + * components. */ + enum { + ORDER_LUMA_CHROMA = 0, + ORDER_CHROMA_LUMA, + } luma_chroma_order; + + /** How many significant bits each channel has, or zero if N/A. */ + struct { + int r; + int g; + int b; + int a; + } bits; + + /** How channel bits are interpreted, fixed (uint) or floating-point */ + enum { + PIXEL_COMPONENT_TYPE_FIXED = 0, + PIXEL_COMPONENT_TYPE_FLOAT, + } component_type; +}; + +/** + * Get pixel format information for a DRM format code + * + * Given a DRM format code, return a pixel format info structure describing + * the properties of that format. + * + * @param format DRM format code to get info for + * @returns A pixel format structure (must not be freed), or NULL if the + * format could not be found + */ +const struct pixel_format_info * +pixel_format_get_info(uint32_t format); + +/** + * Get pixel format information for a SHM format code + * + * Given a SHM format code, return a DRM pixel format info structure describing + * the properties of that format. + * + * @param format SHM format code to get info for. + * @returns A pixel format structure (must not be freed), or NULL if the + * format could not be found. + */ +const struct pixel_format_info * +pixel_format_get_info_shm(uint32_t format); + +/** + * Get pixel format information for a named DRM format + * + * Given a DRM format name, return a pixel format info structure describing + * the properties of that format. + * + * The DRM format name is the preprocessor token name from drm_fourcc.h + * without the DRM_FORMAT_ prefix. The search is also case-insensitive. + * Both "xrgb8888" and "XRGB8888" searches will find DRM_FORMAT_XRGB8888 + * for example. + * + * @param drm_format_name DRM format name to get info for (not NULL) + * @returns A pixel format structure (must not be freed), or NULL if the + * name could not be found + */ +const struct pixel_format_info * +pixel_format_get_info_by_drm_name(const char *drm_format_name); + +/** + * Get number of planes used by a pixel format + * + * Given a pixel format info structure, return the number of planes + * required for a buffer. Note that this is not necessarily identical to + * the number of samplers required to be bound, as two views into a single + * plane are sometimes required. + * + * @param format Pixel format info structure + * @returns Number of planes required for the format + */ +unsigned int +pixel_format_get_plane_count(const struct pixel_format_info *format); + +/** + * Determine if a pixel format is opaque or contains alpha + * + * Returns whether or not the pixel format is opaque, or contains a + * significant alpha channel. Note that the suggested EGL sampler type may + * still sample undefined data into the alpha channel; users must consider + * alpha as 1.0 if the format is opaque, and not rely on the sampler to + * return this when sampling from the alpha channel. + * + * @param format Pixel format info structure + * @returns True if the format is opaque, or false if it has significant alpha + */ +bool +pixel_format_is_opaque(const struct pixel_format_info *format); + +/** + * Get compatible opaque equivalent for a format + * + * Given a pixel format info structure, return a format which is wholly + * compatible with the input format, but opaque, ignoring the alpha channel. + * If an alpha format is provided, but the content is known to all be opaque, + * then this can be used as a substitute to avoid blending. + * + * If the input format is opaque, this function will return the input format. + * + * @param format Pixel format info structure + * @returns A pixel format info structure for the compatible opaque substitute + */ +const struct pixel_format_info * +pixel_format_get_opaque_substitute(const struct pixel_format_info *format); + +/** + * For an opaque format, get the equivalent format with alpha instead of an + * ignored channel + * + * This is the opposite lookup from pixel_format_get_opaque_substitute(). + * Finds the format whose opaque substitute is the given format. + * + * If the input format is not opaque or does not have ignored (X) bits, then + * the search cannot find a match. + * + * @param format DRM format code to search for + * @returns A pixel format info structure for the pixel format whose opaque + * substitute is the argument, or NULL if no match. + */ +const struct pixel_format_info * +pixel_format_get_info_by_opaque_substitute(uint32_t format); + +/** + * Return the effective sampling width for a given plane + * + * When horizontal subsampling is effective, a sampler bound to a secondary + * plane must bind the sampler with a smaller effective width. This function + * returns the effective width to use for the sampler, i.e. dividing by hsub. + * + * If horizontal subsampling is not in effect, this will be equal to the + * width. + * + * @param format Pixel format info structure + * @param plane Zero-indexed plane number + * @param width Width of the buffer + * @returns Effective width for sampling + */ +unsigned int +pixel_format_width_for_plane(const struct pixel_format_info *format, + unsigned int plane, + unsigned int width); + +/** + * Return the effective sampling height for a given plane + * + * When vertical subsampling is in effect, a sampler bound to a secondary + * plane must bind the sampler with a smaller effective height. This function + * returns the effective height to use for the sampler, i.e. dividing by vsub. + * + * If vertical subsampling is not in effect, this will be equal to the height. + * + * @param format Pixel format info structure + * @param plane Zero-indexed plane number + * @param height Height of the buffer + * @returns Effective width for sampling + */ +unsigned int +pixel_format_height_for_plane(const struct pixel_format_info *format, + unsigned int plane, + unsigned int height); diff --git a/libweston/pixman-renderer-protected.h b/libweston/pixman-renderer-protected.h new file mode 100644 index 0000000..d8ac85f --- /dev/null +++ b/libweston/pixman-renderer-protected.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "pixman-renderer.h" + +#ifndef LIBWESTON_PIXMAN_RENDERER_PROTECTED_H +#define LIBWESTON_PIXMAN_RENDERER_PROTECTED_H + +struct pixman_output_state { + void *shadow_buffer; + pixman_image_t *shadow_image; + pixman_image_t *hw_buffer; + pixman_region32_t *hw_extra_damage; + struct tde_output_state_t *tde; +}; + +struct pixman_surface_state { + struct weston_surface *surface; + + pixman_image_t *image; + struct weston_buffer_reference buffer_ref; + struct weston_buffer_release_reference buffer_release_ref; + + struct wl_listener buffer_destroy_listener; + struct wl_listener surface_destroy_listener; + struct wl_listener renderer_destroy_listener; + struct tde_surface_state_t *tde; +}; + +struct pixman_renderer { + struct weston_renderer base; + + int repaint_debug; + pixman_image_t *debug_color; + struct weston_binding *debug_binding; + + struct wl_signal destroy_signal; + struct tde_renderer_t *tde; +}; + +struct pixman_surface_state * get_surface_state(struct weston_surface *surface); +struct pixman_renderer * get_renderer(struct weston_compositor *ec); +struct pixman_output_state * get_output_state(struct weston_output *output); + +#endif // LIBWESTON_PIXMAN_RENDERER_PROTECTED_H diff --git a/libweston/pixman-renderer.c b/libweston/pixman-renderer.c new file mode 100755 index 0000000..fbdce23 --- /dev/null +++ b/libweston/pixman-renderer.c @@ -0,0 +1,998 @@ +/* + * Copyright © 2012 Intel Corporation + * Copyright © 2013 Vasily Khoruzhick + * Copyright © 2015 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "pixman-renderer.h" +#include "pixman-renderer-protected.h" +#include "shared/helpers.h" + +#include + +#include "tde-render-part.h" + +static int +pixman_renderer_create_surface(struct weston_surface *surface); + +struct pixman_output_state * +get_output_state(struct weston_output *output) +{ + return (struct pixman_output_state *)output->renderer_state; +} + +static int +pixman_renderer_create_surface(struct weston_surface *surface); + +struct pixman_surface_state * +get_surface_state(struct weston_surface *surface) +{ + if (!surface->renderer_state) + pixman_renderer_create_surface(surface); + + return (struct pixman_surface_state *)surface->renderer_state; +} + +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_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); + + pixman_image_composite32(PIXMAN_OP_SRC, + po->hw_buffer, /* src */ + NULL /* mask */, + out_buf, /* dest */ + x, y, /* 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_unref(out_buf); + + return 0; +} + +static void +region_global_to_output(struct weston_output *output, pixman_region32_t *region) +{ + if (output->zoom.active) { + weston_matrix_transform_region(region, &output->matrix, region); + } else { + pixman_region32_translate(region, -output->x, -output->y); + weston_transformed_region(output->width, output->height, + output->transform, + output->current_scale, + region, region); + } +} + +#define D2F(v) pixman_double_to_fixed((double)v) + +static void +weston_matrix_to_pixman_transform(pixman_transform_t *pt, + const struct weston_matrix *wm) +{ + /* Pixman supports only 2D transform matrix, but Weston uses 3D, * + * so we're omitting Z coordinate here. */ + pt->matrix[0][0] = pixman_double_to_fixed(wm->d[0]); + pt->matrix[0][1] = pixman_double_to_fixed(wm->d[4]); + pt->matrix[0][2] = pixman_double_to_fixed(wm->d[12]); + pt->matrix[1][0] = pixman_double_to_fixed(wm->d[1]); + pt->matrix[1][1] = pixman_double_to_fixed(wm->d[5]); + pt->matrix[1][2] = pixman_double_to_fixed(wm->d[13]); + pt->matrix[2][0] = pixman_double_to_fixed(wm->d[3]); + pt->matrix[2][1] = pixman_double_to_fixed(wm->d[7]); + pt->matrix[2][2] = pixman_double_to_fixed(wm->d[15]); +} + +static void +pixman_renderer_compute_transform(pixman_transform_t *transform_out, + struct weston_view *ev, + struct weston_output *output) +{ + struct weston_matrix matrix; + + /* Set up the source transformation based on the surface + position, the output position/transform/scale and the client + specified buffer transform/scale */ + matrix = output->inverse_matrix; + + if (ev->transform.enabled) { + weston_matrix_multiply(&matrix, &ev->transform.inverse); + } else { + weston_matrix_translate(&matrix, + -ev->geometry.x, -ev->geometry.y, 0); + } + + weston_matrix_multiply(&matrix, &ev->surface->surface_to_buffer_matrix); + + weston_matrix_to_pixman_transform(transform_out, &matrix); +} + +static bool +view_transformation_is_translation(struct weston_view *view) +{ + if (!view->transform.enabled) + return true; + + if (view->transform.matrix.type <= WESTON_MATRIX_TRANSFORM_TRANSLATE) + return true; + + return false; +} + +static void +region_intersect_only_translation(pixman_region32_t *result_global, + pixman_region32_t *global, + pixman_region32_t *surf, + struct weston_view *view) +{ + float view_x, view_y; + + assert(view_transformation_is_translation(view)); + + /* Convert from surface to global coordinates */ + pixman_region32_copy(result_global, surf); + weston_view_to_global_float(view, 0, 0, &view_x, &view_y); + pixman_region32_translate(result_global, (int)view_x, (int)view_y); + + pixman_region32_intersect(result_global, result_global, global); +} + +static void +composite_whole(pixman_op_t op, + pixman_image_t *src, + pixman_image_t *mask, + pixman_image_t *dest, + const pixman_transform_t *transform, + pixman_filter_t filter) +{ + int32_t dest_width; + int32_t dest_height; + + dest_width = pixman_image_get_width(dest); + dest_height = pixman_image_get_height(dest); + + pixman_image_set_transform(src, transform); + pixman_image_set_filter(src, filter, NULL, 0); + + /* bilinear filtering needs the equivalent of OpenGL CLAMP_TO_EDGE */ + if (filter == PIXMAN_FILTER_NEAREST) + pixman_image_set_repeat(src, PIXMAN_REPEAT_NONE); + else + pixman_image_set_repeat(src, PIXMAN_REPEAT_PAD); + + pixman_image_composite32(op, src, mask, dest, + 0, 0, /* src_x, src_y */ + 0, 0, /* mask_x, mask_y */ + 0, 0, /* dest_x, dest_y */ + dest_width, dest_height); +} + +static void +composite_clipped(pixman_image_t *src, + pixman_image_t *mask, + pixman_image_t *dest, + const pixman_transform_t *transform, + pixman_filter_t filter, + pixman_region32_t *src_clip) +{ + int n_box; + pixman_box32_t *boxes; + int32_t dest_width; + int32_t dest_height; + int src_stride; + int bitspp; + pixman_format_code_t src_format; + void *src_data; + int i; + + /* + * Hardcoded to use PIXMAN_OP_OVER, because sampling outside of + * a Pixman image produces (0,0,0,0) instead of discarding the + * fragment. + * + * Also repeat mode must be PIXMAN_REPEAT_NONE (the default) to + * actually sample (0,0,0,0). This may cause issues for clients that + * expect OpenGL CLAMP_TO_EDGE sampling behavior on their buffer. + * Using temporary 'boximg' it is not possible to apply CLAMP_TO_EDGE + * correctly with bilinear filter. Maybe trapezoid rendering could be + * the answer instead of source clip? + */ + + dest_width = pixman_image_get_width(dest); + dest_height = pixman_image_get_height(dest); + src_format = pixman_image_get_format(src); + src_stride = pixman_image_get_stride(src); + bitspp = PIXMAN_FORMAT_BPP(src_format); + src_data = pixman_image_get_data(src); + + assert(src_format); + + /* This would be massive overdraw, except when n_box is 1. */ + boxes = pixman_region32_rectangles(src_clip, &n_box); + for (i = 0; i < n_box; i++) { + uint8_t *ptr = src_data; + pixman_image_t *boximg; + pixman_transform_t adj = *transform; + + ptr += boxes[i].y1 * src_stride; + ptr += boxes[i].x1 * bitspp / 8; + boximg = pixman_image_create_bits_no_clear(src_format, + boxes[i].x2 - boxes[i].x1, + boxes[i].y2 - boxes[i].y1, + (uint32_t *)ptr, src_stride); + + pixman_transform_translate(&adj, NULL, + pixman_int_to_fixed(-boxes[i].x1), + pixman_int_to_fixed(-boxes[i].y1)); + pixman_image_set_transform(boximg, &adj); + + pixman_image_set_filter(boximg, filter, NULL, 0); + pixman_image_composite32(PIXMAN_OP_OVER, boximg, mask, dest, + 0, 0, /* src_x, src_y */ + 0, 0, /* mask_x, mask_y */ + 0, 0, /* dest_x, dest_y */ + dest_width, dest_height); + + pixman_image_unref(boximg); + } + + if (n_box > 1) { + static bool warned = false; + + if (!warned) + weston_log("Pixman-renderer warning: %dx overdraw\n", + n_box); + warned = true; + } +} + +/** Paint an intersected region + * + * \param ev The view to be painted. + * \param output The output being painted. + * \param repaint_output The region to be painted in output coordinates. + * \param source_clip The region of the source image to use, in source image + * coordinates. If NULL, use the whole source image. + * \param pixman_op Compositing operator, either SRC or OVER. + */ +static void +repaint_region(struct weston_view *ev, struct weston_output *output, + pixman_region32_t *repaint_output, + pixman_region32_t *source_clip, + pixman_op_t pixman_op) +{ + struct pixman_renderer *pr = + (struct pixman_renderer *) output->compositor->renderer; + struct pixman_surface_state *ps = get_surface_state(ev->surface); + struct pixman_output_state *po = get_output_state(output); + struct weston_buffer_viewport *vp = &ev->surface->buffer_viewport; + pixman_image_t *target_image; + pixman_transform_t transform; + pixman_filter_t filter; + pixman_image_t *mask_image; + pixman_color_t mask = { 0, }; + + if (po->shadow_image) + target_image = po->shadow_image; + else + target_image = po->hw_buffer; + + /* Clip rendering to the damaged output region */ + pixman_image_set_clip_region32(target_image, repaint_output); + + pixman_renderer_compute_transform(&transform, ev, output); + + if (ev->transform.enabled || output->current_scale != vp->buffer.scale) + filter = PIXMAN_FILTER_BILINEAR; + else + filter = PIXMAN_FILTER_NEAREST; + + if (ps->buffer_ref.buffer && ps->buffer_ref.buffer->shm_buffer) + wl_shm_buffer_begin_access(ps->buffer_ref.buffer->shm_buffer); + + if (ev->alpha < 1.0) { + mask.alpha = 0xffff * ev->alpha; + mask_image = pixman_image_create_solid_fill(&mask); + } else { + mask_image = NULL; + } + + if (source_clip) + composite_clipped(ps->image, mask_image, target_image, + &transform, filter, source_clip); + else + composite_whole(pixman_op, ps->image, mask_image, + target_image, &transform, filter); + + if (mask_image) + pixman_image_unref(mask_image); + + if (ps->buffer_ref.buffer && ps->buffer_ref.buffer->shm_buffer) + wl_shm_buffer_end_access(ps->buffer_ref.buffer->shm_buffer); + + if (pr->repaint_debug) + pixman_image_composite32(PIXMAN_OP_OVER, + pr->debug_color, /* src */ + NULL /* mask */, + target_image, /* dest */ + 0, 0, /* src_x, src_y */ + 0, 0, /* mask_x, mask_y */ + 0, 0, /* dest_x, dest_y */ + pixman_image_get_width (target_image), /* width */ + pixman_image_get_height (target_image) /* height */); + + pixman_image_set_clip_region32(target_image, NULL); +} + +static void +draw_view_translated(struct weston_view *view, struct weston_output *output, + pixman_region32_t *repaint_global) +{ + struct weston_surface *surface = view->surface; + /* non-opaque region in surface coordinates: */ + pixman_region32_t surface_blend; + /* region to be painted in output coordinates: */ + pixman_region32_t repaint_output; + + pixman_region32_init(&repaint_output); + + /* Blended region is whole surface minus opaque region, + * unless surface alpha forces us to blend all. + */ + pixman_region32_init_rect(&surface_blend, 0, 0, + surface->width, surface->height); + + if (!(view->alpha < 1.0)) { + pixman_region32_subtract(&surface_blend, &surface_blend, + &surface->opaque); + + if (pixman_region32_not_empty(&surface->opaque)) { + region_intersect_only_translation(&repaint_output, + repaint_global, + &surface->opaque, + view); + region_global_to_output(output, &repaint_output); + + repaint_region(view, output, &repaint_output, NULL, + PIXMAN_OP_SRC); + } + } + + if (pixman_region32_not_empty(&surface_blend)) { + region_intersect_only_translation(&repaint_output, + repaint_global, + &surface_blend, view); + region_global_to_output(output, &repaint_output); + + // OHOS TDE + if (tde_repaint_region_hook(view, output, &surface_blend, &repaint_output) != 0) { + repaint_region(view, output, &repaint_output, NULL, + PIXMAN_OP_OVER); + } + } + + pixman_region32_fini(&surface_blend); + pixman_region32_fini(&repaint_output); +} + +static void +draw_view_source_clipped(struct weston_view *view, + struct weston_output *output, + pixman_region32_t *repaint_global) +{ + struct weston_surface *surface = view->surface; + pixman_region32_t surf_region; + pixman_region32_t buffer_region; + pixman_region32_t repaint_output; + + /* Do not bother separating the opaque region from non-opaque. + * Source clipping requires PIXMAN_OP_OVER in all cases, so painting + * opaque separately has no benefit. + */ + + pixman_region32_init_rect(&surf_region, 0, 0, + surface->width, surface->height); + if (view->geometry.scissor_enabled) + pixman_region32_intersect(&surf_region, &surf_region, + &view->geometry.scissor); + + pixman_region32_init(&buffer_region); + weston_surface_to_buffer_region(surface, &surf_region, &buffer_region); + + pixman_region32_init(&repaint_output); + pixman_region32_copy(&repaint_output, repaint_global); + region_global_to_output(output, &repaint_output); + + // OHOS TDE + if (tde_repaint_region_hook(view, output, &buffer_region, &repaint_output) != 0) { + repaint_region(view, output, &repaint_output, &buffer_region, + PIXMAN_OP_OVER); + } + + pixman_region32_fini(&repaint_output); + pixman_region32_fini(&buffer_region); + pixman_region32_fini(&surf_region); +} + +static void +draw_view(struct weston_view *ev, struct weston_output *output, + pixman_region32_t *damage) /* in global coordinates */ +{ + struct pixman_surface_state *ps = get_surface_state(ev->surface); + /* repaint bounding region in global coordinates: */ + pixman_region32_t repaint; + + /* No buffer attached */ + if (!ps->image) + return; + + pixman_region32_init(&repaint); + pixman_region32_intersect(&repaint, + &ev->transform.boundingbox, damage); + pixman_region32_subtract(&repaint, &repaint, &ev->clip); + + if (!pixman_region32_not_empty(&repaint)) + goto out; + + if (view_transformation_is_translation(ev)) { + /* The simple case: The surface regions opaque, non-opaque, + * etc. are convertible to global coordinate space. + * There is no need to use a source clip region. + * It is possible to paint opaque region as PIXMAN_OP_SRC. + * Also the boundingbox is accurate rather than an + * approximation. + */ + draw_view_translated(ev, output, &repaint); + } else { + /* The complex case: the view transformation does not allow + * converting opaque etc. regions into global coordinate space. + * Therefore we need source clipping to avoid sampling from + * unwanted source image areas, unless the source image is + * to be used whole. Source clipping does not work with + * PIXMAN_OP_SRC. + */ + draw_view_source_clipped(ev, output, &repaint); + } + +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_view *view; + + wl_list_for_each_reverse(view, &compositor->view_list, link) + if (view->plane == &compositor->primary_plane) + draw_view(view, 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_region32_fini(&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); + pixman_region32_t hw_damage; + + if (!po->hw_buffer) { + po->hw_extra_damage = NULL; + return; + } + + pixman_region32_init(&hw_damage); + if (po->hw_extra_damage) { + pixman_region32_union(&hw_damage, + po->hw_extra_damage, output_damage); + po->hw_extra_damage = NULL; + } else { + pixman_region32_copy(&hw_damage, output_damage); + } + + if (po->shadow_image) { + repaint_surfaces(output, output_damage); + copy_to_hw_buffer(output, &hw_damage); + } else { + repaint_surfaces(output, &hw_damage); + } + pixman_region32_fini(&hw_damage); + + wl_signal_emit(&output->frame_signal, output_damage); + + /* Actual flip should be done by caller */ +} + +static void +pixman_renderer_flush_damage(struct weston_surface *surface) +{ + /* No-op for pixman renderer */ +} + +static void +buffer_state_handle_buffer_destroy(struct wl_listener *listener, void *data) +{ + struct pixman_surface_state *ps; + + ps = container_of(listener, struct pixman_surface_state, + buffer_destroy_listener); + + if (ps->image) { + tde_unref_image_hook(ps->image); + pixman_image_unref(ps->image); + ps->image = NULL; + } + + ps->buffer_destroy_listener.notify = NULL; +} + +static void +pixman_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) +{ + // OHOS TDE + if (tde_render_attach_hook(es, buffer) == 0) { + return; + } + + 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); + weston_buffer_release_reference(&ps->buffer_release_ref, + es->buffer_release_ref.buffer_release); + + if (ps->buffer_destroy_listener.notify) { + wl_list_remove(&ps->buffer_destroy_listener.link); + ps->buffer_destroy_listener.notify = NULL; + } + + 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); + weston_buffer_release_reference(&ps->buffer_release_ref, NULL); + return; + } + + switch (wl_shm_buffer_get_format(shm_buffer)) { + case WL_SHM_FORMAT_XRGB8888: + pixman_format = PIXMAN_x8r8g8b8; + es->is_opaque = true; + break; + case WL_SHM_FORMAT_ARGB8888: + pixman_format = PIXMAN_a8r8g8b8; + es->is_opaque = false; + break; + case WL_SHM_FORMAT_RGB565: + pixman_format = PIXMAN_r5g6b5; + es->is_opaque = true; + break; + default: + weston_log("Unsupported SHM buffer format 0x%x\n", + wl_shm_buffer_get_format(shm_buffer)); + weston_buffer_reference(&ps->buffer_ref, NULL); + weston_buffer_release_reference(&ps->buffer_release_ref, NULL); + weston_buffer_send_server_error(buffer, + "disconnecting due to unhandled buffer type"); + 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)); + + ps->buffer_destroy_listener.notify = + buffer_state_handle_buffer_destroy; + wl_signal_add(&buffer->destroy_signal, + &ps->buffer_destroy_listener); +} + +static void +pixman_renderer_surface_state_destroy(struct pixman_surface_state *ps) +{ + wl_list_remove(&ps->surface_destroy_listener.link); + wl_list_remove(&ps->renderer_destroy_listener.link); + if (ps->buffer_destroy_listener.notify) { + wl_list_remove(&ps->buffer_destroy_listener.link); + ps->buffer_destroy_listener.notify = NULL; + } + + ps->surface->renderer_state = NULL; + + if (ps->image) { + tde_unref_image_hook(ps->image); + pixman_image_unref(ps->image); + ps->image = NULL; + } + weston_buffer_reference(&ps->buffer_ref, NULL); + weston_buffer_release_reference(&ps->buffer_release_ref, NULL); + tde_surface_state_free_hook(ps); + free(ps); +} + +static void +surface_state_handle_surface_destroy(struct wl_listener *listener, void *data) +{ + struct pixman_surface_state *ps; + + ps = container_of(listener, struct pixman_surface_state, + surface_destroy_listener); + + pixman_renderer_surface_state_destroy(ps); +} + +static void +surface_state_handle_renderer_destroy(struct wl_listener *listener, void *data) +{ + struct pixman_surface_state *ps; + + ps = container_of(listener, struct pixman_surface_state, + renderer_destroy_listener); + + pixman_renderer_surface_state_destroy(ps); +} + +int +pixman_renderer_create_surface(struct weston_surface *surface) +{ + struct pixman_surface_state *ps; + struct pixman_renderer *pr = get_renderer(surface->compositor); + + ps = zalloc(sizeof *ps); + if (ps == NULL) + return -1; + + tde_surface_state_alloc_hook(ps); + + surface->renderer_state = ps; + + ps->surface = surface; + + ps->surface_destroy_listener.notify = + surface_state_handle_surface_destroy; + wl_signal_add(&surface->destroy_signal, + &ps->surface_destroy_listener); + + ps->renderer_destroy_listener.notify = + surface_state_handle_renderer_destroy; + wl_signal_add(&pr->destroy_signal, + &ps->renderer_destroy_listener); + + 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) { + tde_unref_image_hook(ps->image); + pixman_image_unref(ps->image); + ps->image = NULL; + } + + ps->image = pixman_image_create_solid_fill(&color); +} + +static void +pixman_renderer_destroy(struct weston_compositor *ec) +{ + struct pixman_renderer *pr = get_renderer(ec); + + wl_signal_emit(&pr->destroy_signal, pr); + + // OHOS remove debugger + if (pr->debug_binding != NULL) + weston_binding_destroy(pr->debug_binding); + + // OHOS TDE + tde_renderer_free_hook(pr); + free(pr); + + ec->renderer = NULL; +} + +static void +pixman_renderer_surface_get_content_size(struct weston_surface *surface, + int *width, int *height) +{ + struct pixman_surface_state *ps = get_surface_state(surface); + + if (ps->image) { + *width = pixman_image_get_width(ps->image); + *height = pixman_image_get_height(ps->image); + } else { + *width = 0; + *height = 0; + } +} + +static int +pixman_renderer_surface_copy_content(struct weston_surface *surface, + void *target, size_t size, + int src_x, int src_y, + int width, int height) +{ + const pixman_format_code_t format = PIXMAN_a8b8g8r8; + const size_t bytespp = 4; /* PIXMAN_a8b8g8r8 */ + struct pixman_surface_state *ps = get_surface_state(surface); + pixman_image_t *out_buf; + + if (!ps->image) + return -1; + + out_buf = pixman_image_create_bits(format, width, height, + target, width * bytespp); + + pixman_image_set_transform(ps->image, NULL); + pixman_image_composite32(PIXMAN_OP_SRC, + ps->image, /* src */ + NULL, /* mask */ + out_buf, /* dest */ + src_x, src_y, /* src_x, src_y */ + 0, 0, /* mask_x, mask_y */ + 0, 0, /* dest_x, dest_y */ + width, height); + + pixman_image_unref(out_buf); + + return 0; +} + +// OHOS debugger +//static void +//debug_binding(struct weston_keyboard *keyboard, const struct timespec *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); +// } +//} + +static bool pixman_renderer_import_dmabuf(struct weston_compositor *ec, struct linux_dmabuf_buffer *dmabuf); +static void pixman_renderer_query_dmabuf_formats(struct weston_compositor *wc, int **formats, int *num_formats); +static void pixman_renderer_query_dmabuf_modifiers(struct weston_compositor *wc, int format, uint64_t **modifiers, int *num_modifiers); + +WL_EXPORT int +pixman_renderer_init(struct weston_compositor *ec) +{ + struct pixman_renderer *renderer; + + renderer = zalloc(sizeof *renderer); + if (renderer == NULL) + return -1; + + // OHOS TDE + tde_renderer_alloc_hook(renderer); + + 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.surface_set_color = pixman_renderer_surface_set_color; + renderer->base.destroy = pixman_renderer_destroy; + renderer->base.surface_get_content_size = + pixman_renderer_surface_get_content_size; + renderer->base.surface_copy_content = + pixman_renderer_surface_copy_content; + ec->renderer = &renderer->base; + ec->capabilities |= WESTON_CAP_ROTATION_ANY; + ec->capabilities |= WESTON_CAP_VIEW_CLIP_MASK; + + // OHOS remove debugger + //renderer->debug_binding = + // weston_compositor_add_debug_binding(ec, KEY_R, + // debug_binding, ec); + + wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_RGB565); + + wl_signal_init(&renderer->destroy_signal); + + 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 void +pixman_renderer_output_set_hw_extra_damage(struct weston_output *output, + pixman_region32_t *extra_damage) +{ + struct pixman_output_state *po = get_output_state(output); + + po->hw_extra_damage = extra_damage; +} + +WL_EXPORT int +pixman_renderer_output_create(struct weston_output *output, + const struct pixman_renderer_output_options *options) +{ + struct pixman_output_state *po; + int w, h; + + po = zalloc(sizeof *po); + if (po == NULL) + return -1; + + // OHOS TDE + tde_output_state_alloc_hook(po); + + if (options->use_shadow) { + /* 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) { + tde_output_state_free_hook(po); + 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); + tde_output_state_free_hook(po); + 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); + + if (po->shadow_image) + pixman_image_unref(po->shadow_image); + + if (po->hw_buffer) + pixman_image_unref(po->hw_buffer); + + free(po->shadow_buffer); + + po->shadow_buffer = NULL; + po->shadow_image = NULL; + po->hw_buffer = NULL; + + tde_output_state_free_hook(po); + free(po); +} diff --git a/libweston/pixman-renderer.h b/libweston/pixman-renderer.h new file mode 100644 index 0000000..c0ebc83 --- /dev/null +++ b/libweston/pixman-renderer.h @@ -0,0 +1,58 @@ +/* + * Copyright © 2013 Vasily Khoruzhick + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include "backend.h" +#include "libweston-internal.h" + +#ifndef LIBWESTON_PIXMAN_RENDERER_H +#define LIBWESTON_PIXMAN_RENDERER_H + +int +pixman_renderer_init(struct weston_compositor *ec); + +struct pixman_renderer_output_options { + /** Composite into a shadow buffer, copying to the hardware buffer */ + bool use_shadow; +}; + +int +pixman_renderer_output_create(struct weston_output *output, + const struct pixman_renderer_output_options *options); + +void +pixman_renderer_output_set_buffer(struct weston_output *output, + pixman_image_t *buffer); + +void +pixman_renderer_output_set_hw_extra_damage(struct weston_output *output, + pixman_region32_t *extra_damage); + +void +pixman_renderer_output_destroy(struct weston_output *output); + +#endif // LIBWESTON_PIXMAN_RENDERER_H diff --git a/libweston/plugin-registry.c b/libweston/plugin-registry.c new file mode 100644 index 0000000..f5ec6fa --- /dev/null +++ b/libweston/plugin-registry.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2016 DENSO CORPORATION + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +struct weston_plugin_api { + struct wl_list link; /**< in weston_compositor::plugin_api_list */ + + char *api_name; /**< The search key */ + const void *vtable; /**< The function table */ + size_t vtable_size; /**< Size of the function table in bytes */ +}; + +/** Register an implementation of an API + * + * \param compositor The compositor instance. + * \param api_name The API name which other plugins use to find the + * implementation. + * \param vtable Pointer to the function table of the API. + * \param vtable_size Size of the function table in bytes. + * \return 0 on success, -1 on error, -2 if api_name already registered + * + * This call makes the given vtable to be reachable via + * weston_plugin_api_get(). Calls through the vtable may start happening + * as soon as the caller returns after success. Argument vtable must not be + * NULL. Argument api_name must be non-NULL and non-zero length. + * + * You can increase the function table size without breaking the ABI. + * To cater for ABI breaks, it is recommended to have api_name include a + * version number. + * + * A registered API cannot be unregistered. However, calls through a + * registered API must not be made from the compositor destroy signal handlers. + */ +WL_EXPORT int +weston_plugin_api_register(struct weston_compositor *compositor, + const char *api_name, + const void *vtable, + size_t vtable_size) +{ + struct weston_plugin_api *wpa; + + assert(api_name); + assert(strlen(api_name) > 0); + assert(vtable); + + if (!api_name || !vtable || strlen(api_name) == 0) + return -1; + + wl_list_for_each(wpa, &compositor->plugin_api_list, link) + if (strcmp(wpa->api_name, api_name) == 0) + return -2; + + wpa = zalloc(sizeof(*wpa)); + if (!wpa) + return -1; + + wpa->api_name = strdup(api_name); + wpa->vtable = vtable; + wpa->vtable_size = vtable_size; + + if (!wpa->api_name) { + free(wpa); + + return -1; + } + + wl_list_insert(&compositor->plugin_api_list, &wpa->link); + weston_log("Registered plugin API '%s' of size %zd\n", + wpa->api_name, wpa->vtable_size); + + return 0; +} + +/** Internal function to free registered APIs + * + * \param compositor The compositor instance. + */ +void +weston_plugin_api_destroy_list(struct weston_compositor *compositor) +{ + struct weston_plugin_api *wpa, *tmp; + + wl_list_for_each_safe(wpa, tmp, &compositor->plugin_api_list, link) { + free(wpa->api_name); + wl_list_remove(&wpa->link); + free(wpa); + } +} + +/** Fetch the implementation of an API + * + * \param compositor The compositor instance. + * \param api_name The name of the API to search for. + * \param vtable_size The expected function table size in bytes. + * \return Pointer to the function table, or NULL on error. + * + * Find the function table corresponding to api_name. The vtable_size here + * must be less or equal to the vtable_size given in the corresponding + * weston_plugin_api_register() call made by the implementing plugin. + * + * Calls can be made through the function table immediately. However, calls + * must not be made from or after the compositor destroy signal handler. + */ +WL_EXPORT const void * +weston_plugin_api_get(struct weston_compositor *compositor, + const char *api_name, + size_t vtable_size) +{ + struct weston_plugin_api *wpa; + + assert(api_name); + if (!api_name) + return NULL; + + wl_list_for_each(wpa, &compositor->plugin_api_list, link) { + if (strcmp(wpa->api_name, api_name) != 0) + continue; + + if (vtable_size <= wpa->vtable_size) + return wpa->vtable; + + return NULL; + } + + return NULL; +} diff --git a/libweston/renderer-gl/egl-glue.c b/libweston/renderer-gl/egl-glue.c new file mode 100644 index 0000000..28be4ff --- /dev/null +++ b/libweston/renderer-gl/egl-glue.c @@ -0,0 +1,693 @@ +/* + * Copyright © 2012 Intel Corporation + * Copyright © 2015, 2019 Collabora, Ltd. + * Copyright © 2016 NVIDIA Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include + +#include "shared/helpers.h" +#include "shared/platform.h" + +#include "gl-renderer.h" +#include "gl-renderer-internal.h" +#include "pixel-formats.h" +#include "shared/weston-egl-ext.h" + +#include + +struct egl_config_print_info { + const EGLint *attrs; + unsigned attrs_count; + const char *prefix; + const char *separator; + int field_width; +}; + +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 +} + +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); +} + +static void +print_egl_surface_type_bits(FILE *fp, EGLint egl_surface_type) +{ + const char *sep = ""; + unsigned i; + + static const struct { + EGLint bit; + const char *str; + } egl_surf_bits[] = { + { EGL_WINDOW_BIT, "win" }, + { EGL_PIXMAP_BIT, "pix" }, + { EGL_PBUFFER_BIT, "pbf" }, + { EGL_MULTISAMPLE_RESOLVE_BOX_BIT, "ms_resolve_box" }, + { EGL_SWAP_BEHAVIOR_PRESERVED_BIT, "swap_preserved" }, + }; + + for (i = 0; i < ARRAY_LENGTH(egl_surf_bits); i++) { + if (egl_surface_type & egl_surf_bits[i].bit) { + fprintf(fp, "%s%s", sep, egl_surf_bits[i].str); + sep = "|"; + } + } +} + +static const struct egl_config_print_info config_info_ints[] = { +#define ARRAY(...) ((const EGLint[]) { __VA_ARGS__ }) + + { ARRAY(EGL_CONFIG_ID), 1, "id: ", "", 3 }, + { ARRAY(EGL_RED_SIZE, EGL_GREEN_SIZE, EGL_BLUE_SIZE, EGL_ALPHA_SIZE), 4, + "rgba: ", " ", 1 }, + { ARRAY(EGL_BUFFER_SIZE), 1, "buf: ", "", 2 }, + { ARRAY(EGL_DEPTH_SIZE), 1, "dep: ", "", 2 }, + { ARRAY(EGL_STENCIL_SIZE), 1, "stcl: ", "", 1 }, + { ARRAY(EGL_MIN_SWAP_INTERVAL, EGL_MAX_SWAP_INTERVAL), 2, + "int: ", "-", 1 }, + +#undef ARRAY +}; + +static void +print_egl_config_ints(FILE *fp, EGLDisplay egldpy, EGLConfig eglconfig) +{ + unsigned i; + + for (i = 0; i < ARRAY_LENGTH(config_info_ints); i++) { + const struct egl_config_print_info *info = &config_info_ints[i]; + unsigned j; + const char *sep = ""; + + fputs(info->prefix, fp); + for (j = 0; j < info->attrs_count; j++) { + EGLint value; + + if (eglGetConfigAttrib(egldpy, eglconfig, + info->attrs[j], &value)) { + fprintf(fp, "%s%*d", + sep, info->field_width, value); + } else { + fprintf(fp, "%s!", sep); + } + sep = info->separator; + } + + fputs(" ", fp); + } +} + +static void +print_egl_config_info(FILE *fp, EGLDisplay egldpy, EGLConfig eglconfig) +{ + EGLint value; + + print_egl_config_ints(fp, egldpy, eglconfig); + + fputs("type: ", fp); + if (eglGetConfigAttrib(egldpy, eglconfig, EGL_SURFACE_TYPE, &value)) + print_egl_surface_type_bits(fp, value); + else + fputs("-", fp); + + fputs(" vis_id: ", fp); + if (eglGetConfigAttrib(egldpy, eglconfig, EGL_NATIVE_VISUAL_ID, &value)) { + if (value != 0) { + const struct pixel_format_info *p; + + p = pixel_format_get_info(value); + if (p) { + fprintf(fp, "%s (0x%x)", + p->drm_format_name, (unsigned)value); + } else { + fprintf(fp, "0x%x", (unsigned)value); + } + } else { + fputs("0", fp); + } + } else { + fputs("-", fp); + } +} + +static void +log_all_egl_configs(EGLDisplay egldpy) +{ + EGLint count = 0; + EGLConfig *configs; + int i; + char *strbuf = NULL; + size_t strsize = 0; + FILE *fp; + + weston_log("All available EGLConfigs:\n"); + + if (!eglGetConfigs(egldpy, NULL, 0, &count) || count < 1) + return; + + configs = calloc(count, sizeof *configs); + if (!configs) + return; + + if (!eglGetConfigs(egldpy, configs, count, &count)) + return; + + fp = open_memstream(&strbuf, &strsize); + if (!fp) + goto out; + + for (i = 0; i < count; i++) { + print_egl_config_info(fp, egldpy, configs[i]); + fputc(0, fp); + fflush(fp); + weston_log_continue(STAMP_SPACE "%s\n", strbuf); + rewind(fp); + } + + fclose(fp); + free(strbuf); + +out: + free(configs); +} + +void +log_egl_config_info(EGLDisplay egldpy, EGLConfig eglconfig) +{ + char *strbuf = NULL; + size_t strsize = 0; + FILE *fp; + + fp = open_memstream(&strbuf, &strsize); + if (fp) { + print_egl_config_info(fp, egldpy, eglconfig); + fclose(fp); + } + + weston_log("Chosen EGL config details: %s\n", strbuf ? strbuf : "?"); + free(strbuf); +} + +static bool +egl_config_pixel_format_matches(struct gl_renderer *gr, + EGLConfig config, + const struct pixel_format_info *pinfo) +{ + static const EGLint attribs[4] = { + EGL_ALPHA_SIZE, EGL_RED_SIZE, EGL_GREEN_SIZE, EGL_BLUE_SIZE + }; + const int *argb[4] = { + &pinfo->bits.a, &pinfo->bits.r, &pinfo->bits.g, &pinfo->bits.b + }; + unsigned i; + EGLint value; + + if (gr->platform == EGL_PLATFORM_GBM_KHR) { + if (!eglGetConfigAttrib(gr->egl_display, config, + EGL_NATIVE_VISUAL_ID, &value)) + return false; + + return ((uint32_t)value) == pinfo->format; + } + + for (i = 0; i < 4; i++) { + if (!eglGetConfigAttrib(gr->egl_display, config, + attribs[i], &value)) + return false; + if (value != *argb[i]) + return false; + } + + return true; +} + +static int +egl_choose_config(struct gl_renderer *gr, + const EGLint *attribs, + const struct pixel_format_info *const *pinfo, + unsigned pinfo_count, + EGLConfig *config_out) +{ + EGLint count = 0; + EGLint matched = 0; + EGLConfig *configs; + unsigned i; + EGLint j; + int config_index = -1; + + if (!eglGetConfigs(gr->egl_display, NULL, 0, &count) || count < 1) { + weston_log("No EGL configs to choose from.\n"); + return -1; + } + configs = calloc(count, sizeof *configs); + if (!configs) + return -1; + + if (!eglChooseConfig(gr->egl_display, attribs, configs, + count, &matched) || !matched) { + weston_log("No EGL configs with appropriate attributes.\n"); + goto out; + } + + if (pinfo_count == 0) + config_index = 0; + + for (i = 0; config_index == -1 && i < pinfo_count; i++) + for (j = 0; config_index == -1 && j < matched; j++) + if (egl_config_pixel_format_matches(gr, configs[j], + pinfo[i])) + config_index = j; + + if (config_index != -1) + *config_out = configs[config_index]; + +out: + free(configs); + if (config_index == -1) + return -1; + + if (i > 1) + weston_log("Unable to use first choice EGL config with" + " %s, succeeded with alternate %s.\n", + pinfo[0]->drm_format_name, + pinfo[i - 1]->drm_format_name); + return 0; +} + +static bool +egl_config_is_compatible(struct gl_renderer *gr, + EGLConfig config, + EGLint egl_surface_type, + const struct pixel_format_info *const *pinfo, + unsigned pinfo_count) +{ + EGLint value; + unsigned i; + + if (config == EGL_NO_CONFIG_KHR) + return false; + + if (!eglGetConfigAttrib(gr->egl_display, config, + EGL_SURFACE_TYPE, &value)) + return false; + if ((value & egl_surface_type) != egl_surface_type) + return false; + + for (i = 0; i < pinfo_count; i++) { + if (egl_config_pixel_format_matches(gr, config, pinfo[i])) + return true; + } + return false; +} + +/* The caller must free() the string */ +static char * +explain_egl_config_criteria(EGLint egl_surface_type, + const struct pixel_format_info *const *pinfo, + unsigned pinfo_count) +{ + FILE *fp; + char *str = NULL; + size_t size = 0; + const char *sep; + unsigned i; + + fp = open_memstream(&str, &size); + if (!fp) + return NULL; + + fputs("{ ", fp); + + print_egl_surface_type_bits(fp, egl_surface_type); + fputs("; ", fp); + + sep = ""; + for (i = 0; i < pinfo_count; i++) { + fprintf(fp, "%s%s", sep, pinfo[i]->drm_format_name); + sep = ", "; + } + + fputs(" }", fp); + + fclose(fp); + + return str; +} + +EGLConfig +gl_renderer_get_egl_config(struct gl_renderer *gr, + EGLint egl_surface_type, + const uint32_t *drm_formats, + unsigned drm_formats_count) +{ + EGLConfig egl_config; + const struct pixel_format_info *pinfo[16]; + unsigned pinfo_count; + unsigned i; + char *what; + EGLint config_attribs[] = { + EGL_SURFACE_TYPE, egl_surface_type, + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + assert(drm_formats_count < ARRAY_LENGTH(pinfo)); + drm_formats_count = MIN(drm_formats_count, ARRAY_LENGTH(pinfo)); + + for (pinfo_count = 0, i = 0; i < drm_formats_count; i++) { + pinfo[pinfo_count] = pixel_format_get_info(drm_formats[i]); + if (!pinfo[pinfo_count]) { + weston_log("Bad/unknown DRM format code 0x%08x.\n", + drm_formats[i]); + continue; + } + pinfo_count++; + } + + if (egl_config_is_compatible(gr, gr->egl_config, egl_surface_type, + pinfo, pinfo_count)) + return gr->egl_config; + + if (egl_choose_config(gr, config_attribs, pinfo, pinfo_count, + &egl_config) < 0) { + what = explain_egl_config_criteria(egl_surface_type, + pinfo, pinfo_count); + weston_log("No EGLConfig matches %s.\n", what); + free(what); + log_all_egl_configs(gr->egl_display); + return EGL_NO_CONFIG_KHR; + } + + /* + * If we do not have configless context support, all EGLConfigs must + * be the one and the same, because we use just one GL context for + * everything. + */ + if (gr->egl_config != EGL_NO_CONFIG_KHR && + egl_config != gr->egl_config) { + what = explain_egl_config_criteria(egl_surface_type, + pinfo, pinfo_count); + weston_log("Found an EGLConfig matching %s but it is not usable" + " because neither EGL_KHR_no_config_context nor " + "EGL_MESA_configless_context are supported by EGL.\n", + what); + free(what); + return EGL_NO_CONFIG_KHR; + } + + return egl_config; +} + +int +gl_renderer_setup_egl_display(struct gl_renderer *gr, + void *native_display) +{ + gr->egl_display = NULL; + + /* extension_suffix is supported */ + if (gr->has_platform_base) + gr->egl_display = gr->get_platform_display(gr->platform, + native_display, + NULL); + + if (!gr->egl_display) { + weston_log("warning: either no EGL_EXT_platform_base " + "support or specific platform support; " + "falling back to eglGetDisplay.\n"); + gr->egl_display = eglGetDisplay(native_display); + } + + if (!gr->egl_display) { + weston_log("failed to create display\n"); + return -1; + } + + if (!eglInitialize(gr->egl_display, NULL, NULL)) { + weston_log("failed to initialize display\n"); + goto fail; + } + + return 0; + +fail: + gl_renderer_print_egl_error_state(); + return -1; +} + +static const char * +platform_to_extension(EGLenum platform) +{ + switch (platform) { + case EGL_PLATFORM_GBM_KHR: + return "gbm"; + case EGL_PLATFORM_WAYLAND_KHR: + return "wayland"; + case EGL_PLATFORM_X11_KHR: + return "x11"; + case EGL_PLATFORM_SURFACELESS_MESA: + return "surfaceless"; + default: + assert(0 && "bad EGL platform enum"); + } +} + +/** Checks for EGL client extensions (i.e. independent of EGL display), + * loads the function pointers, and checks if the platform is supported. + * + * \param gr The OpenGL renderer + * \return 0 for success, -1 if platform is unsupported + * + * This function checks whether a specific platform_* extension is supported + * by EGL by checking in order "EGL_KHR_platform_foo", "EGL_EXT_platform_foo", + * and "EGL_MESA_platform_foo" in order, for some platform "foo". + */ +int +gl_renderer_setup_egl_client_extensions(struct gl_renderer *gr) +{ + const char *extensions; + const char *extension_suffix = platform_to_extension(gr->platform); + char s[64]; + + extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (!extensions) { + weston_log("Retrieving EGL client extension string failed.\n"); + return 0; + } + + gl_renderer_log_extensions("EGL client extensions", + extensions); + + if (weston_check_egl_extension(extensions, "EGL_EXT_platform_base")) { + gr->get_platform_display = + (void *) eglGetProcAddress("eglGetPlatformDisplayEXT"); + gr->create_platform_window = + (void *) eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"); + gr->has_platform_base = true; + } else { + weston_log("warning: EGL_EXT_platform_base not supported.\n"); + + /* Surfaceless is unusable without platform_base extension */ + if (gr->platform == EGL_PLATFORM_SURFACELESS_MESA) + return -1; + + return 0; + } + + snprintf(s, sizeof s, "EGL_KHR_platform_%s", extension_suffix); + if (weston_check_egl_extension(extensions, s)) + return 0; + + snprintf(s, sizeof s, "EGL_EXT_platform_%s", extension_suffix); + if (weston_check_egl_extension(extensions, s)) + return 0; + + snprintf(s, sizeof s, "EGL_MESA_platform_%s", extension_suffix); + if (weston_check_egl_extension(extensions, s)) + return 0; + + /* at this point we definitely have some platform extensions but + * haven't found the supplied platform, so chances are it's + * not supported. */ + + return -1; +} + +int +gl_renderer_setup_egl_extensions(struct weston_compositor *ec) +{ + static const struct { + char *extension, *entrypoint; + } swap_damage_ext_to_entrypoint[] = { + { + .extension = "EGL_EXT_swap_buffers_with_damage", + .entrypoint = "eglSwapBuffersWithDamageEXT", + }, + { + .extension = "EGL_KHR_swap_buffers_with_damage", + .entrypoint = "eglSwapBuffersWithDamageKHR", + }, + }; + struct gl_renderer *gr = get_renderer(ec); + const char *extensions; + EGLBoolean ret; + unsigned i; + + 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"); + gr->set_damage_region = + (void *) eglGetProcAddress("eglSetDamageRegionKHR"); + + extensions = + (const char *) eglQueryString(gr->egl_display, EGL_EXTENSIONS); + if (!extensions) { + weston_log("Retrieving EGL extension string failed.\n"); + return -1; + } + + if (weston_check_egl_extension(extensions, "EGL_IMG_context_priority")) + gr->has_context_priority = true; + + if (weston_check_egl_extension(extensions, "EGL_WL_bind_wayland_display")) + gr->has_bind_display = true; + if (gr->has_bind_display) { + assert(gr->bind_display); + assert(gr->unbind_display); + assert(gr->query_buffer); + ret = gr->bind_display(gr->egl_display, ec->wl_display); + if (!ret) + gr->has_bind_display = false; + } + + if (weston_check_egl_extension(extensions, "EGL_EXT_buffer_age")) + gr->has_egl_buffer_age = true; + + if (weston_check_egl_extension(extensions, "EGL_KHR_partial_update")) { + assert(gr->set_damage_region); + gr->has_egl_partial_update = true; + } + + for (i = 0; i < ARRAY_LENGTH(swap_damage_ext_to_entrypoint); i++) { + if (weston_check_egl_extension(extensions, + swap_damage_ext_to_entrypoint[i].extension)) { + gr->swap_buffers_with_damage = + (void *) eglGetProcAddress( + swap_damage_ext_to_entrypoint[i].entrypoint); + assert(gr->swap_buffers_with_damage); + break; + } + } + + if (weston_check_egl_extension(extensions, "EGL_KHR_no_config_context") || + weston_check_egl_extension(extensions, "EGL_MESA_configless_context")) + gr->has_configless_context = true; + + if (weston_check_egl_extension(extensions, "EGL_KHR_surfaceless_context")) + gr->has_surfaceless_context = true; + + if (weston_check_egl_extension(extensions, "EGL_EXT_image_dma_buf_import")) + gr->has_dmabuf_import = true; + + if (weston_check_egl_extension(extensions, + "EGL_EXT_image_dma_buf_import_modifiers")) { + gr->query_dmabuf_formats = + (void *) eglGetProcAddress("eglQueryDmaBufFormatsEXT"); + gr->query_dmabuf_modifiers = + (void *) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); + assert(gr->query_dmabuf_formats); + assert(gr->query_dmabuf_modifiers); + gr->has_dmabuf_import_modifiers = true; + } + + if (weston_check_egl_extension(extensions, "EGL_KHR_fence_sync") && + weston_check_egl_extension(extensions, "EGL_ANDROID_native_fence_sync")) { + gr->create_sync = + (void *) eglGetProcAddress("eglCreateSyncKHR"); + gr->destroy_sync = + (void *) eglGetProcAddress("eglDestroySyncKHR"); + gr->dup_native_fence_fd = + (void *) eglGetProcAddress("eglDupNativeFenceFDANDROID"); + assert(gr->create_sync); + assert(gr->destroy_sync); + assert(gr->dup_native_fence_fd); + gr->has_native_fence_sync = true; + } else { + weston_log("warning: Disabling render GPU timeline and explicit " + "synchronization due to missing " + "EGL_ANDROID_native_fence_sync extension\n"); + } + + if (weston_check_egl_extension(extensions, "EGL_KHR_wait_sync")) { + gr->wait_sync = (void *) eglGetProcAddress("eglWaitSyncKHR"); + assert(gr->wait_sync); + gr->has_wait_sync = true; + } else { + weston_log("warning: Disabling explicit synchronization due" + "to missing EGL_KHR_wait_sync extension\n"); + } + + return 0; +} diff --git a/libweston/renderer-gl/gl-renderer-internal.h b/libweston/renderer-gl/gl-renderer-internal.h new file mode 100644 index 0000000..529cb2f --- /dev/null +++ b/libweston/renderer-gl/gl-renderer-internal.h @@ -0,0 +1,154 @@ +/* + * Copyright © 2019 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef GL_RENDERER_INTERNAL_H +#define GL_RENDERER_INTERNAL_H + +#include +#include +#include "shared/weston-egl-ext.h" /* for PFN* stuff */ + +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; +}; + +struct gl_renderer { + struct weston_renderer base; + bool fragment_shader_debug; + bool fan_debug; + struct weston_binding *fragment_binding; + struct weston_binding *fan_binding; + + EGLenum platform; + EGLDisplay egl_display; + EGLContext egl_context; + EGLConfig egl_config; + + EGLSurface dummy_surface; + + uint32_t gl_version; + + struct wl_array vertices; + struct wl_array vtxcnt; + + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d; + PFNEGLCREATEIMAGEKHRPROC create_image; + PFNEGLDESTROYIMAGEKHRPROC destroy_image; + PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage; + + PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display; + PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC create_platform_window; + bool has_platform_base; + + bool has_unpack_subimage; + + PFNEGLBINDWAYLANDDISPLAYWL bind_display; + PFNEGLUNBINDWAYLANDDISPLAYWL unbind_display; + PFNEGLQUERYWAYLANDBUFFERWL query_buffer; + bool has_bind_display; + + bool has_context_priority; + + bool has_egl_image_external; + + bool has_egl_buffer_age; + bool has_egl_partial_update; + PFNEGLSETDAMAGEREGIONKHRPROC set_damage_region; + + bool has_configless_context; + + bool has_surfaceless_context; + + bool has_dmabuf_import; + struct wl_list dmabuf_images; + struct wl_list dmabuf_formats; + + bool has_gl_texture_rg; + + 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 texture_shader_xyuv; + struct gl_shader invert_color_shader; + struct gl_shader solid_shader; + struct gl_shader *current_shader; + + struct wl_signal destroy_signal; + + struct wl_listener output_destroy_listener; + + bool has_dmabuf_import_modifiers; + PFNEGLQUERYDMABUFFORMATSEXTPROC query_dmabuf_formats; + PFNEGLQUERYDMABUFMODIFIERSEXTPROC query_dmabuf_modifiers; + + bool has_native_fence_sync; + PFNEGLCREATESYNCKHRPROC create_sync; + PFNEGLDESTROYSYNCKHRPROC destroy_sync; + PFNEGLDUPNATIVEFENCEFDANDROIDPROC dup_native_fence_fd; + + bool has_wait_sync; + PFNEGLWAITSYNCKHRPROC wait_sync; +}; + +static inline struct gl_renderer * +get_renderer(struct weston_compositor *ec) +{ + return (struct gl_renderer *)ec->renderer; +} + +void +gl_renderer_print_egl_error_state(void); + +void +gl_renderer_log_extensions(const char *name, const char *extensions); + +void +log_egl_config_info(EGLDisplay egldpy, EGLConfig eglconfig); + +EGLConfig +gl_renderer_get_egl_config(struct gl_renderer *gr, + EGLint egl_surface_type, + const uint32_t *drm_formats, + unsigned drm_formats_count); + +int +gl_renderer_setup_egl_display(struct gl_renderer *gr, void *native_display); + +int +gl_renderer_setup_egl_client_extensions(struct gl_renderer *gr); + +int +gl_renderer_setup_egl_extensions(struct weston_compositor *ec); + +#endif /* GL_RENDERER_INTERNAL_H */ diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c new file mode 100755 index 0000000..78e94a2 --- /dev/null +++ b/libweston/renderer-gl/gl-renderer.c @@ -0,0 +1,3820 @@ +/* + * Copyright © 2012 Intel Corporation + * Copyright © 2015,2019 Collabora, Ltd. + * Copyright © 2016 NVIDIA Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "linux-sync-file.h" +#include "timeline.h" + +#include "gl-renderer.h" +#include "gl-renderer-internal.h" +#include "vertex-clipping.h" +#include "linux-dmabuf.h" +#include "linux-dmabuf-unstable-v1-server-protocol.h" +#include "linux-explicit-synchronization.h" +#include "pixel-formats.h" + +#include "shared/fd-util.h" +#include "shared/helpers.h" +#include "shared/platform.h" +#include "shared/timespec-util.h" +#include "shared/weston-egl-ext.h" + +#define GR_GL_VERSION(major, minor) \ + (((uint32_t)(major) << 16) | (uint32_t)(minor)) + +#define GR_GL_VERSION_INVALID \ + GR_GL_VERSION(0, 0) + +#define BUFFER_DAMAGE_COUNT 2 + +enum gl_border_status { + BORDER_STATUS_CLEAN = 0, + BORDER_TOP_DIRTY = 1 << GL_RENDERER_BORDER_TOP, + BORDER_LEFT_DIRTY = 1 << GL_RENDERER_BORDER_LEFT, + BORDER_RIGHT_DIRTY = 1 << GL_RENDERER_BORDER_RIGHT, + BORDER_BOTTOM_DIRTY = 1 << GL_RENDERER_BORDER_BOTTOM, + BORDER_ALL_DIRTY = 0xf, + BORDER_SIZE_CHANGED = 0x10 +}; + +struct gl_border_image { + GLuint tex; + int32_t width, height; + int32_t tex_width; + void *data; +}; + +struct gl_output_state { + EGLSurface egl_surface; + pixman_region32_t buffer_damage[BUFFER_DAMAGE_COUNT]; + int buffer_damage_index; + enum gl_border_status border_damage[BUFFER_DAMAGE_COUNT]; + struct gl_border_image borders[4]; + enum gl_border_status border_status; + + struct weston_matrix output_matrix; + + EGLSyncKHR begin_render_sync, end_render_sync; + + /* struct timeline_render_point::link */ + struct wl_list timeline_render_point_list; +}; + +enum buffer_type { + BUFFER_TYPE_NULL, + BUFFER_TYPE_SOLID, /* internal solid color surfaces without a buffer */ + BUFFER_TYPE_SHM, + BUFFER_TYPE_EGL +}; + +struct gl_renderer; + +struct egl_image { + struct gl_renderer *renderer; + EGLImageKHR image; + int refcount; +}; + +enum import_type { + IMPORT_TYPE_INVALID, + IMPORT_TYPE_DIRECT, + IMPORT_TYPE_GL_CONVERSION +}; + +struct dmabuf_image { + struct linux_dmabuf_buffer *dmabuf; + int num_images; + struct egl_image *images[3]; + struct wl_list link; + + enum import_type import_type; + GLenum target; + struct gl_shader *shader; +}; + +struct dmabuf_format { + uint32_t format; + struct wl_list link; + + uint64_t *modifiers; + unsigned *external_only; + int num_modifiers; +}; + +struct yuv_plane_descriptor { + int width_divisor; + int height_divisor; + uint32_t format; + int plane_index; +}; + +enum texture_type { + TEXTURE_Y_XUXV_WL, + TEXTURE_Y_UV_WL, + TEXTURE_Y_U_V_WL, + TEXTURE_XYUV_WL +}; + +struct yuv_format_descriptor { + uint32_t format; + int input_planes; + int output_planes; + enum texture_type texture_type; + struct yuv_plane_descriptor plane[4]; +}; + +struct gl_surface_state { + GLfloat color[4]; + struct gl_shader *shader; + + GLuint textures[3]; + int num_textures; + bool needs_full_upload; + pixman_region32_t texture_damage; + + /* These are only used by SHM surfaces to detect when we need + * to do a full upload to specify a new internal texture + * format */ + GLenum gl_format[3]; + GLenum gl_pixel_type; + + struct egl_image* images[3]; + GLenum target; + int num_images; + + struct weston_buffer_reference buffer_ref; + struct weston_buffer_release_reference buffer_release_ref; + enum buffer_type buffer_type; + int pitch; /* in pixels */ + int height; /* in pixels */ + bool y_inverted; + bool direct_display; + + /* Extension needed for SHM YUV texture */ + int offset[3]; /* offset per plane */ + int hsub[3]; /* horizontal subsampling per plane */ + int vsub[3]; /* vertical subsampling per plane */ + + struct weston_surface *surface; + + /* Whether this surface was used in the current output repaint. + Used only in the context of a gl_renderer_repaint_output call. */ + bool used_in_output_repaint; + + struct wl_listener surface_destroy_listener; + struct wl_listener renderer_destroy_listener; +}; + +enum timeline_render_point_type { + TIMELINE_RENDER_POINT_TYPE_BEGIN, + TIMELINE_RENDER_POINT_TYPE_END +}; + +struct timeline_render_point { + struct wl_list link; /* gl_output_state::timeline_render_point_list */ + + enum timeline_render_point_type type; + int fd; + struct weston_output *output; + struct wl_event_source *event_source; +}; + +static inline const char * +dump_format(uint32_t format, char out[4]) +{ +#if BYTE_ORDER == BIG_ENDIAN + format = __builtin_bswap32(format); +#endif + memcpy(out, &format, 4); + return out; +} + +static inline struct gl_output_state * +get_output_state(struct weston_output *output) +{ + return (struct gl_output_state *)output->renderer_state; +} + +static int +gl_renderer_create_surface(struct weston_surface *surface); + +static inline struct gl_surface_state * +get_surface_state(struct weston_surface *surface) +{ + if (!surface->renderer_state) + gl_renderer_create_surface(surface); + + return (struct gl_surface_state *)surface->renderer_state; +} + +static void +timeline_render_point_destroy(struct timeline_render_point *trp) +{ + wl_list_remove(&trp->link); + wl_event_source_remove(trp->event_source); + close(trp->fd); + free(trp); +} + +static int +timeline_render_point_handler(int fd, uint32_t mask, void *data) +{ + struct timeline_render_point *trp = data; + const char *tp_name = trp->type == TIMELINE_RENDER_POINT_TYPE_BEGIN ? + "renderer_gpu_begin" : "renderer_gpu_end"; + + if (mask & WL_EVENT_READABLE) { + struct timespec tspec = { 0 }; + + if (weston_linux_sync_file_read_timestamp(trp->fd, + &tspec) == 0) { + TL_POINT(trp->output->compositor, tp_name, TLP_GPU(&tspec), + TLP_OUTPUT(trp->output), TLP_END); + } + } + + timeline_render_point_destroy(trp); + + return 0; +} + +static EGLSyncKHR +create_render_sync(struct gl_renderer *gr) +{ + static const EGLint attribs[] = { EGL_NONE }; + + if (!gr->has_native_fence_sync) + return EGL_NO_SYNC_KHR; + + return gr->create_sync(gr->egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID, + attribs); +} + +static void +timeline_submit_render_sync(struct gl_renderer *gr, + struct weston_compositor *ec, + struct weston_output *output, + EGLSyncKHR sync, + enum timeline_render_point_type type) +{ + struct gl_output_state *go; + struct wl_event_loop *loop; + int fd; + struct timeline_render_point *trp; + +// OHOS remove timeline +// if (!weston_log_scope_is_enabled(ec->timeline) || + if (true || + !gr->has_native_fence_sync || + sync == EGL_NO_SYNC_KHR) + return; + + go = get_output_state(output); + loop = wl_display_get_event_loop(ec->wl_display); + + fd = gr->dup_native_fence_fd(gr->egl_display, sync); + if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) + return; + + trp = zalloc(sizeof *trp); + if (trp == NULL) { + close(fd); + return; + } + + trp->type = type; + trp->fd = fd; + trp->output = output; + trp->event_source = wl_event_loop_add_fd(loop, fd, + WL_EVENT_READABLE, + timeline_render_point_handler, + trp); + + wl_list_insert(&go->timeline_render_point_list, &trp->link); +} + +static struct egl_image* +egl_image_create(struct gl_renderer *gr, EGLenum target, + EGLClientBuffer buffer, const EGLint *attribs) +{ + struct egl_image *img; + + img = zalloc(sizeof *img); + img->renderer = gr; + img->refcount = 1; + img->image = gr->create_image(gr->egl_display, EGL_NO_CONTEXT, + target, buffer, attribs); + + if (img->image == EGL_NO_IMAGE_KHR) { + free(img); + return NULL; + } + + return img; +} + +static struct egl_image* +egl_image_ref(struct egl_image *image) +{ + image->refcount++; + + return image; +} + +static int +egl_image_unref(struct egl_image *image) +{ + struct gl_renderer *gr = image->renderer; + + assert(image->refcount > 0); + + image->refcount--; + if (image->refcount > 0) + return image->refcount; + + gr->destroy_image(gr->egl_display, image->image); + free(image); + + return 0; +} + +static struct dmabuf_image* +dmabuf_image_create(void) +{ + struct dmabuf_image *img; + + img = zalloc(sizeof *img); + wl_list_init(&img->link); + + return img; +} + +static void +dmabuf_image_destroy(struct dmabuf_image *image) +{ + int i; + + for (i = 0; i < image->num_images; ++i) + egl_image_unref(image->images[i]); + + if (image->dmabuf) + linux_dmabuf_buffer_set_user_data(image->dmabuf, NULL, NULL); + + wl_list_remove(&image->link); + free(image); +} + +#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_view *ev, 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_view_to_global_float(ev, 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 (!ev->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 bool +merge_down(pixman_box32_t *a, pixman_box32_t *b, pixman_box32_t *merge) +{ + if (a->x1 == b->x1 && a->x2 == b->x2 && a->y1 == b->y2) { + merge->x1 = a->x1; + merge->x2 = a->x2; + merge->y1 = b->y1; + merge->y2 = a->y2; + return true; + } + return false; +} + +static int +compress_bands(pixman_box32_t *inrects, int nrects, + pixman_box32_t **outrects) +{ + bool merged = false; + pixman_box32_t *out, merge_rect; + int i, j, nout; + + if (!nrects) { + *outrects = NULL; + return 0; + } + + /* nrects is an upper bound - we're not too worried about + * allocating a little extra + */ + out = malloc(sizeof(pixman_box32_t) * nrects); + out[0] = inrects[0]; + nout = 1; + for (i = 1; i < nrects; i++) { + for (j = 0; j < nout; j++) { + merged = merge_down(&inrects[i], &out[j], &merge_rect); + if (merged) { + out[j] = merge_rect; + break; + } + } + if (!merged) { + out[nout] = inrects[i]; + nout++; + } + } + *outrects = out; + return nout; +} + +static int +texture_region(struct weston_view *ev, pixman_region32_t *region, + pixman_region32_t *surf_region) +{ + struct gl_surface_state *gs = get_surface_state(ev->surface); + struct weston_compositor *ec = ev->surface->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; + pixman_box32_t *raw_rects; + int i, j, k, nrects, nsurf, raw_nrects; + bool used_band_compression; + raw_rects = pixman_region32_rectangles(region, &raw_nrects); + surf_rects = pixman_region32_rectangles(surf_region, &nsurf); + + if (raw_nrects < 4) { + used_band_compression = false; + nrects = raw_nrects; + rects = raw_rects; + } else { + nrects = compress_bands(raw_rects, raw_nrects, &rects); + used_band_compression = true; + } + /* 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(ev, rect, surf_rect, ex, ey); + if (n < 3) + continue; + + /* emit edge points: */ + for (k = 0; k < n; k++) { + weston_view_from_global_float(ev, ex[k], ey[k], + &sx, &sy); + /* position: */ + *(v++) = ex[k]; + *(v++) = ey[k]; + /* texcoord: */ + weston_surface_to_buffer_float(ev->surface, + 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; + } + } + + if (used_band_compression) + free(rects); + return nvtx; +} + +static void +triangle_fan_debug(struct weston_view *view, int first, int count) +{ + struct weston_compositor *compositor = view->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_view *ev, pixman_region32_t *region, + pixman_region32_t *surf_region) +{ + struct weston_compositor *ec = ev->surface->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 vertices, actually). + */ + nfans = texture_region(ev, 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(ev, 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_view *view, + struct weston_output *output) +{ + int i; + struct gl_surface_state *gs = get_surface_state(view->surface); + struct gl_output_state *go = get_output_state(output); + + glUniformMatrix4fv(shader->proj_uniform, + 1, GL_FALSE, go->output_matrix.d); + glUniform4fv(shader->color_uniform, 1, gs->color); + glUniform1f(shader->alpha_uniform, view->alpha); + + for (i = 0; i < gs->num_textures; i++) + glUniform1i(shader->tex_uniforms[i], i); +} + +static int +ensure_surface_buffer_is_ready(struct gl_renderer *gr, + struct gl_surface_state *gs) +{ + EGLint attribs[] = { + EGL_SYNC_NATIVE_FENCE_FD_ANDROID, + -1, + EGL_NONE + }; + struct weston_surface *surface = gs->surface; + struct weston_buffer *buffer = gs->buffer_ref.buffer; + EGLSyncKHR sync; + EGLint wait_ret; + EGLint destroy_ret; + + if (!buffer) + return 0; + + if (surface->acquire_fence_fd < 0) + return 0; + + /* We should only get a fence if we support EGLSyncKHR, since + * we don't advertise the explicit sync protocol otherwise. */ + assert(gr->has_native_fence_sync); + /* We should only get a fence for non-SHM buffers, since surface + * commit would have failed otherwise. */ + assert(wl_shm_buffer_get(buffer->resource) == NULL); + + attribs[1] = dup(surface->acquire_fence_fd); + if (attribs[1] == -1) { + linux_explicit_synchronization_send_server_error( + gs->surface->synchronization_resource, + "Failed to dup acquire fence"); + return -1; + } + + sync = gr->create_sync(gr->egl_display, + EGL_SYNC_NATIVE_FENCE_ANDROID, + attribs); + if (sync == EGL_NO_SYNC_KHR) { + linux_explicit_synchronization_send_server_error( + gs->surface->synchronization_resource, + "Failed to create EGLSyncKHR object"); + close(attribs[1]); + return -1; + } + + wait_ret = gr->wait_sync(gr->egl_display, sync, 0); + if (wait_ret == EGL_FALSE) { + linux_explicit_synchronization_send_server_error( + gs->surface->synchronization_resource, + "Failed to wait on EGLSyncKHR object"); + /* Continue to try to destroy the sync object. */ + } + + + destroy_ret = gr->destroy_sync(gr->egl_display, sync); + if (destroy_ret == EGL_FALSE) { + linux_explicit_synchronization_send_server_error( + gs->surface->synchronization_resource, + "Failed to destroy on EGLSyncKHR object"); + } + + return (wait_ret == EGL_TRUE && destroy_ret == EGL_TRUE) ? 0 : -1; +} + + + /* Checks if a view needs to be censored on an output + * Checks for 2 types of censor requirements + * - recording_censor: Censor protected view when a + * protected view is captured. + * - unprotected_censor: Censor regions of protected views + * when displayed on an output which has lower protection capability. + * Returns the originally stored gl_shader if content censoring is required, + * NULL otherwise. + */ +static struct gl_shader * +setup_censor_overrides(struct weston_output *output, + struct weston_view *ev) +{ + struct gl_shader *replaced_shader = NULL; + struct weston_compositor *ec = ev->surface->compositor; + struct gl_renderer *gr = get_renderer(ec); + struct gl_surface_state *gs = get_surface_state(ev->surface); + bool recording_censor = + (output->disable_planes > 0) && + (ev->surface->desired_protection > WESTON_HDCP_DISABLE); + + bool unprotected_censor = + (ev->surface->desired_protection > output->current_protection); + + if (gs->direct_display) { + gs->color[0] = 0.40; + gs->color[1] = 0.0; + gs->color[2] = 0.0; + gs->color[3] = 1.0; + gs->shader = &gr->solid_shader; + return gs->shader; + } + + /* When not in enforced mode, the client is notified of the protection */ + /* change, so content censoring is not required */ + if (ev->surface->protection_mode != + WESTON_SURFACE_PROTECTION_MODE_ENFORCED) + return NULL; + + if (recording_censor || unprotected_censor) { + replaced_shader = gs->shader; + gs->color[0] = 0.40; + gs->color[1] = 0.0; + gs->color[2] = 0.0; + gs->color[3] = 1.0; + gs->shader = &gr->solid_shader; + } + + return replaced_shader; +} + +static void +draw_view(struct weston_view *ev, struct weston_output *output, + pixman_region32_t *damage) /* in global coordinates */ +{ + struct weston_compositor *ec = ev->surface->compositor; + struct gl_renderer *gr = get_renderer(ec); + struct gl_surface_state *gs = get_surface_state(ev->surface); + /* repaint bounding region in global coordinates: */ + pixman_region32_t repaint; + /* opaque region in surface coordinates: */ + pixman_region32_t surface_opaque; + /* non-opaque region in surface coordinates: */ + pixman_region32_t surface_blend; + GLint filter; + int i; + struct gl_shader *replaced_shader = NULL; + + /* In case of a runtime switch of renderers, we may not have received + * an attach for this surface since the switch. In that case we don't + * have a valid buffer or a proper shader set up so skip rendering. */ + if (!gs->shader && !gs->direct_display) + return; + + pixman_region32_init(&repaint); + pixman_region32_intersect(&repaint, + &ev->transform.boundingbox, damage); + pixman_region32_subtract(&repaint, &repaint, &ev->clip); + + if (!pixman_region32_not_empty(&repaint)) + goto out; + + if (ensure_surface_buffer_is_ready(gr, gs) < 0) + goto out; + + replaced_shader = setup_censor_overrides(output, ev); + + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + if (gr->fan_debug) { + use_shader(gr, &gr->solid_shader); + shader_uniforms(&gr->solid_shader, ev, output); + } + + use_shader(gr, gs->shader); + shader_uniforms(gs->shader, ev, output); + + if (ev->transform.enabled || output->zoom.active || + output->current_scale != ev->surface->buffer_viewport.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, + ev->surface->width, ev->surface->height); + if (ev->geometry.scissor_enabled) + pixman_region32_intersect(&surface_blend, &surface_blend, + &ev->geometry.scissor); + pixman_region32_subtract(&surface_blend, &surface_blend, + &ev->surface->opaque); + + /* XXX: Should we be using ev->transform.opaque here? */ + pixman_region32_init(&surface_opaque); + if (ev->geometry.scissor_enabled) + pixman_region32_intersect(&surface_opaque, + &ev->surface->opaque, + &ev->geometry.scissor); + else + pixman_region32_copy(&surface_opaque, &ev->surface->opaque); + + if (pixman_region32_not_empty(&surface_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, ev, output); + } + + if (ev->alpha < 1.0) + glEnable(GL_BLEND); + else + glDisable(GL_BLEND); + + repaint_region(ev, &repaint, &surface_opaque); + gs->used_in_output_repaint = true; + } + + if (pixman_region32_not_empty(&surface_blend)) { + use_shader(gr, gs->shader); + glEnable(GL_BLEND); + repaint_region(ev, &repaint, &surface_blend); + gs->used_in_output_repaint = true; + } + + pixman_region32_fini(&surface_blend); + pixman_region32_fini(&surface_opaque); + +out: + pixman_region32_fini(&repaint); + + if (replaced_shader) + gs->shader = replaced_shader; +} + +static void +repaint_views(struct weston_output *output, pixman_region32_t *damage) +{ + struct weston_compositor *compositor = output->compositor; + struct weston_view *view; + + wl_list_for_each_reverse(view, &compositor->view_list, link) + if (view->plane == &compositor->primary_plane) + draw_view(view, output, damage); +} + +static int +gl_renderer_create_fence_fd(struct weston_output *output); + +/* Updates the release fences of surfaces that were used in the current output + * repaint. Should only be used from gl_renderer_repaint_output, so that the + * information in gl_surface_state.used_in_output_repaint is accurate. + */ +static void +update_buffer_release_fences(struct weston_compositor *compositor, + struct weston_output *output) +{ + struct weston_view *view; + + wl_list_for_each_reverse(view, &compositor->view_list, link) { + struct gl_surface_state *gs; + struct weston_buffer_release *buffer_release; + int fence_fd; + + if (view->plane != &compositor->primary_plane) + continue; + + gs = get_surface_state(view->surface); + buffer_release = gs->buffer_release_ref.buffer_release; + + if (!gs->used_in_output_repaint || !buffer_release) + continue; + + fence_fd = gl_renderer_create_fence_fd(output); + + /* If we have a buffer_release then it means we support fences, + * and we should be able to create the release fence. If we + * can't, something has gone horribly wrong, so disconnect the + * client. + */ + if (fence_fd == -1) { + linux_explicit_synchronization_send_server_error( + buffer_release->resource, + "Failed to create release fence"); + fd_clear(&buffer_release->fence_fd); + continue; + } + + /* At the moment it is safe to just replace the fence_fd, + * discarding the previous one: + * + * 1. If the previous fence fd represents a sync fence from + * a previous repaint cycle, that fence fd is now not + * sufficient to provide the release guarantee and should + * be replaced. + * + * 2. If the fence fd represents a sync fence from another + * output in the same repaint cycle, it's fine to replace + * it since we are rendering to all outputs using the same + * EGL context, so a fence issued for a later output rendering + * is guaranteed to signal after fences for previous output + * renderings. + * + * Note that the above is only valid if the buffer_release + * fences only originate from the GL renderer, which guarantees + * a total order of operations and fences. If we introduce + * fences from other sources (e.g., plane out-fences), we will + * need to merge fences instead. + */ + fd_update(&buffer_release->fence_fd, fence_fd); + } +} + +static void +draw_output_border_texture(struct gl_output_state *go, + enum gl_renderer_border_side side, + int32_t x, int32_t y, + int32_t width, int32_t height) +{ + struct gl_border_image *img = &go->borders[side]; + static GLushort indices [] = { 0, 1, 3, 3, 1, 2 }; + + if (!img->data) { + if (img->tex) { + glDeleteTextures(1, &img->tex); + img->tex = 0; + } + + return; + } + + if (!img->tex) { + glGenTextures(1, &img->tex); + glBindTexture(GL_TEXTURE_2D, img->tex); + + 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); + } else { + glBindTexture(GL_TEXTURE_2D, img->tex); + } + + if (go->border_status & (1 << side)) { + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); + glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA_EXT, + img->tex_width, img->height, 0, + GL_BGRA_EXT, GL_UNSIGNED_BYTE, img->data); + } + + GLfloat texcoord[] = { + 0.0f, 0.0f, + (GLfloat)img->width / (GLfloat)img->tex_width, 0.0f, + (GLfloat)img->width / (GLfloat)img->tex_width, 1.0f, + 0.0f, 1.0f, + }; + + GLfloat verts[] = { + x, y, + x + width, y, + x + width, y + height, + x, y + height + }; + + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, verts); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, texcoord); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices); + + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(0); +} + +static int +output_has_borders(struct weston_output *output) +{ + struct gl_output_state *go = get_output_state(output); + + return go->borders[GL_RENDERER_BORDER_TOP].data || + go->borders[GL_RENDERER_BORDER_RIGHT].data || + go->borders[GL_RENDERER_BORDER_BOTTOM].data || + go->borders[GL_RENDERER_BORDER_LEFT].data; +} + +static void +draw_output_borders(struct weston_output *output, + enum gl_border_status border_status) +{ + struct gl_output_state *go = get_output_state(output); + struct gl_renderer *gr = get_renderer(output->compositor); + struct gl_shader *shader = &gr->texture_shader_rgba; + struct gl_border_image *top, *bottom, *left, *right; + struct weston_matrix matrix; + int full_width, full_height; + + if (border_status == BORDER_STATUS_CLEAN) + return; /* Clean. Nothing to do. */ + + top = &go->borders[GL_RENDERER_BORDER_TOP]; + bottom = &go->borders[GL_RENDERER_BORDER_BOTTOM]; + left = &go->borders[GL_RENDERER_BORDER_LEFT]; + right = &go->borders[GL_RENDERER_BORDER_RIGHT]; + + full_width = output->current_mode->width + left->width + right->width; + full_height = output->current_mode->height + top->height + bottom->height; + + glDisable(GL_BLEND); + use_shader(gr, shader); + + glViewport(0, 0, full_width, full_height); + + weston_matrix_init(&matrix); + weston_matrix_translate(&matrix, -full_width/2.0, -full_height/2.0, 0); + weston_matrix_scale(&matrix, 2.0/full_width, -2.0/full_height, 1); + glUniformMatrix4fv(shader->proj_uniform, 1, GL_FALSE, matrix.d); + + glUniform1i(shader->tex_uniforms[0], 0); + glUniform1f(shader->alpha_uniform, 1); + glActiveTexture(GL_TEXTURE0); + + if (border_status & BORDER_TOP_DIRTY) + draw_output_border_texture(go, GL_RENDERER_BORDER_TOP, + 0, 0, + full_width, top->height); + if (border_status & BORDER_LEFT_DIRTY) + draw_output_border_texture(go, GL_RENDERER_BORDER_LEFT, + 0, top->height, + left->width, output->current_mode->height); + if (border_status & BORDER_RIGHT_DIRTY) + draw_output_border_texture(go, GL_RENDERER_BORDER_RIGHT, + full_width - right->width, top->height, + right->width, output->current_mode->height); + if (border_status & BORDER_BOTTOM_DIRTY) + draw_output_border_texture(go, GL_RENDERER_BORDER_BOTTOM, + 0, full_height - bottom->height, + full_width, bottom->height); +} + +static void +output_get_border_damage(struct weston_output *output, + enum gl_border_status border_status, + pixman_region32_t *damage) +{ + struct gl_output_state *go = get_output_state(output); + struct gl_border_image *top, *bottom, *left, *right; + int full_width, full_height; + + if (border_status == BORDER_STATUS_CLEAN) + return; /* Clean. Nothing to do. */ + + top = &go->borders[GL_RENDERER_BORDER_TOP]; + bottom = &go->borders[GL_RENDERER_BORDER_BOTTOM]; + left = &go->borders[GL_RENDERER_BORDER_LEFT]; + right = &go->borders[GL_RENDERER_BORDER_RIGHT]; + + full_width = output->current_mode->width + left->width + right->width; + full_height = output->current_mode->height + top->height + bottom->height; + if (border_status & BORDER_TOP_DIRTY) + pixman_region32_union_rect(damage, damage, + 0, 0, + full_width, top->height); + if (border_status & BORDER_LEFT_DIRTY) + pixman_region32_union_rect(damage, damage, + 0, top->height, + left->width, output->current_mode->height); + if (border_status & BORDER_RIGHT_DIRTY) + pixman_region32_union_rect(damage, damage, + full_width - right->width, top->height, + right->width, output->current_mode->height); + if (border_status & BORDER_BOTTOM_DIRTY) + pixman_region32_union_rect(damage, damage, + 0, full_height - bottom->height, + full_width, bottom->height); +} + +static void +output_get_damage(struct weston_output *output, + pixman_region32_t *buffer_damage, uint32_t *border_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); + *border_damage = BORDER_ALL_DIRTY; + } else { + for (i = 0; i < buffer_age - 1; i++) + *border_damage |= go->border_damage[(go->buffer_damage_index + i) % BUFFER_DAMAGE_COUNT]; + + if (*border_damage & BORDER_SIZE_CHANGED) { + /* If we've had a resize, we have to do a full + * repaint. */ + *border_damage |= BORDER_ALL_DIRTY; + 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[(go->buffer_damage_index + i) % BUFFER_DAMAGE_COUNT]); + } + } +} + +static void +output_rotate_damage(struct weston_output *output, + pixman_region32_t *output_damage, + enum gl_border_status border_status) +{ + struct gl_output_state *go = get_output_state(output); + struct gl_renderer *gr = get_renderer(output->compositor); + + if (!gr->has_egl_buffer_age) + return; + + go->buffer_damage_index += BUFFER_DAMAGE_COUNT - 1; + go->buffer_damage_index %= BUFFER_DAMAGE_COUNT; + + pixman_region32_copy(&go->buffer_damage[go->buffer_damage_index], output_damage); + go->border_damage[go->buffer_damage_index] = border_status; +} + +/** + * Given a region in Weston's (top-left-origin) global co-ordinate space, + * translate it to the co-ordinate space used by GL for our output + * rendering. This requires shifting it into output co-ordinate space: + * translating for output offset within the global co-ordinate space, + * multiplying by output scale to get buffer rather than logical size. + * + * Finally, if borders are drawn around the output, we translate the area + * to account for the border region around the outside, and add any + * damage if the borders have been redrawn. + * + * @param output The output whose co-ordinate space we are after + * @param global_region The affected region in global co-ordinate space + * @param[out] rects Y-inverted quads in {x,y,w,h} order; caller must free + * @param[out] nrects Number of quads (4x number of co-ordinates) + */ +static void +pixman_region_to_egl_y_invert(struct weston_output *output, + struct pixman_region32 *global_region, + EGLint **rects, + EGLint *nrects) +{ + struct gl_output_state *go = get_output_state(output); + pixman_region32_t transformed; + struct pixman_box32 *box; + int buffer_height; + EGLint *d; + int i; + + /* Translate from global to output co-ordinate space. */ + pixman_region32_init(&transformed); + pixman_region32_copy(&transformed, global_region); + pixman_region32_translate(&transformed, -output->x, -output->y); + weston_transformed_region(output->width, output->height, + output->transform, + output->current_scale, + &transformed, &transformed); + + /* If we have borders drawn around the output, shift our output damage + * to account for borders being drawn around the outside, adding any + * damage resulting from borders being redrawn. */ + if (output_has_borders(output)) { + pixman_region32_translate(&transformed, + go->borders[GL_RENDERER_BORDER_LEFT].width, + go->borders[GL_RENDERER_BORDER_TOP].height); + output_get_border_damage(output, go->border_status, + &transformed); + } + + /* Convert from a Pixman region into {x,y,w,h} quads, flipping in the + * Y axis to account for GL's lower-left-origin co-ordinate space. */ + box = pixman_region32_rectangles(&transformed, nrects); + *rects = malloc(*nrects * 4 * sizeof(EGLint)); + + buffer_height = go->borders[GL_RENDERER_BORDER_TOP].height + + output->current_mode->height + + go->borders[GL_RENDERER_BORDER_BOTTOM].height; + + d = *rects; + for (i = 0; i < *nrects; ++i) { + *d++ = box[i].x1; + *d++ = buffer_height - box[i].y2; + *d++ = box[i].x2 - box[i].x1; + *d++ = box[i].y2 - box[i].y1; + } + + pixman_region32_fini(&transformed); +} + +/* NOTE: We now allow falling back to ARGB gl visuals when XRGB is + * unavailable, so we're assuming the background has no transparency + * and that everything with a blend, like drop shadows, will have something + * opaque (like the background) drawn underneath it. + * + * Depending on the underlying hardware, violating that assumption could + * result in seeing through to another display plane. + */ +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; + /* areas we've damaged since we last used this buffer */ + pixman_region32_t previous_damage; + /* total area we need to repaint this time */ + pixman_region32_t total_damage; + enum gl_border_status border_status = BORDER_STATUS_CLEAN; + struct weston_view *view; + + if (use_output(output) < 0) + return; + + /* Clear the used_in_output_repaint flag, so that we can properly track + * which surfaces were used in this output repaint. */ + wl_list_for_each_reverse(view, &compositor->view_list, link) { + if (view->plane == &compositor->primary_plane) { + struct gl_surface_state *gs = + get_surface_state(view->surface); + gs->used_in_output_repaint = false; + } + } + + if (go->begin_render_sync != EGL_NO_SYNC_KHR) + gr->destroy_sync(gr->egl_display, go->begin_render_sync); + if (go->end_render_sync != EGL_NO_SYNC_KHR) + gr->destroy_sync(gr->egl_display, go->end_render_sync); + + go->begin_render_sync = create_render_sync(gr); + + /* Calculate the viewport */ + glViewport(go->borders[GL_RENDERER_BORDER_LEFT].width, + go->borders[GL_RENDERER_BORDER_BOTTOM].height, + output->current_mode->width, + output->current_mode->height); + + /* Calculate the global GL matrix */ + go->output_matrix = output->matrix; + weston_matrix_translate(&go->output_matrix, + -(output->current_mode->width / 2.0), + -(output->current_mode->height / 2.0), 0); + weston_matrix_scale(&go->output_matrix, + 2.0 / output->current_mode->width, + -2.0 / output->current_mode->height, 1); + + /* In fan debug mode, redraw everything to make sure that we clear any + * fans left over from previous draws on this buffer. + * This precludes the use of EGL_EXT_swap_buffers_with_damage and + * EGL_KHR_partial_update, since we damage the whole area. */ + if (gr->fan_debug) { + pixman_region32_t undamaged; + pixman_region32_init(&undamaged); + pixman_region32_subtract(&undamaged, &output->region, + output_damage); + gr->fan_debug = false; + repaint_views(output, &undamaged); + gr->fan_debug = true; + pixman_region32_fini(&undamaged); + } + + /* previous_damage covers regions damaged in previous paints since we + * last used this buffer */ + pixman_region32_init(&previous_damage); + pixman_region32_init(&total_damage); /* total area to redraw */ + + /* Update previous_damage using buffer_age (if available), and store + * current damaged region for future use. */ + output_get_damage(output, &previous_damage, &border_status); + output_rotate_damage(output, output_damage, go->border_status); + + /* Redraw both areas which have changed since we last used this buffer, + * as well as the areas we now want to repaint, to make sure the + * buffer is up to date. */ + pixman_region32_union(&total_damage, &previous_damage, output_damage); + border_status |= go->border_status; + + if (gr->has_egl_partial_update && !gr->fan_debug) { + int n_egl_rects; + EGLint *egl_rects; + + /* For partial_update, we need to pass the region which has + * changed since we last rendered into this specific buffer; + * this is total_damage. */ + pixman_region_to_egl_y_invert(output, &total_damage, + &egl_rects, &n_egl_rects); + gr->set_damage_region(gr->egl_display, go->egl_surface, + egl_rects, n_egl_rects); + free(egl_rects); + } + + repaint_views(output, &total_damage); + + pixman_region32_fini(&total_damage); + pixman_region32_fini(&previous_damage); + + draw_output_borders(output, border_status); + + wl_signal_emit(&output->frame_signal, output_damage); + + go->end_render_sync = create_render_sync(gr); + + if (gr->swap_buffers_with_damage && !gr->fan_debug) { + int n_egl_rects; + EGLint *egl_rects; + + /* For swap_buffers_with_damage, we need to pass the region + * which has changed since the previous SwapBuffers on this + * surface - this is output_damage. */ + pixman_region_to_egl_y_invert(output, output_damage, + &egl_rects, &n_egl_rects); + ret = gr->swap_buffers_with_damage(gr->egl_display, + go->egl_surface, + egl_rects, n_egl_rects); + free(egl_rects); + } else { + 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(); + } + + go->border_status = BORDER_STATUS_CLEAN; + + /* We have to submit the render sync objects after swap buffers, since + * the objects get assigned a valid sync file fd only after a gl flush. + */ + timeline_submit_render_sync(gr, compositor, output, + go->begin_render_sync, + TIMELINE_RENDER_POINT_TYPE_BEGIN); + timeline_submit_render_sync(gr, compositor, output, go->end_render_sync, + TIMELINE_RENDER_POINT_TYPE_END); + + update_buffer_release_fences(compositor, output); +} + +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; + struct gl_output_state *go = get_output_state(output); + + x += go->borders[GL_RENDERER_BORDER_LEFT].width; + y += go->borders[GL_RENDERER_BORDER_BOTTOM].height; + + 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 GLenum gl_format_from_internal(GLenum internal_format) +{ + switch (internal_format) { + case GL_R8_EXT: + return GL_RED_EXT; + case GL_RG8_EXT: + return GL_RG_EXT; + default: + return internal_format; + } +} + +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; + struct weston_view *view; + bool texture_used; + pixman_box32_t *rectangles; + uint8_t *data; + int i, j, n; + + 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. + */ + texture_used = false; + wl_list_for_each(view, &surface->views, surface_link) { + if (view->plane == &surface->compositor->primary_plane) { + texture_used = true; + break; + } + } + if (!texture_used) + return; + + if (!pixman_region32_not_empty(&gs->texture_damage) && + !gs->needs_full_upload) + goto done; + + data = wl_shm_buffer_get_data(buffer->shm_buffer); + + if (!gr->has_unpack_subimage) { + wl_shm_buffer_begin_access(buffer->shm_buffer); + for (j = 0; j < gs->num_textures; j++) { + glBindTexture(GL_TEXTURE_2D, gs->textures[j]); + glTexImage2D(GL_TEXTURE_2D, 0, + gs->gl_format[j], + gs->pitch / gs->hsub[j], + buffer->height / gs->vsub[j], + 0, + gl_format_from_internal(gs->gl_format[j]), + gs->gl_pixel_type, + data + gs->offset[j]); + } + wl_shm_buffer_end_access(buffer->shm_buffer); + + goto done; + } + + if (gs->needs_full_upload) { + glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0); + wl_shm_buffer_begin_access(buffer->shm_buffer); + for (j = 0; j < gs->num_textures; j++) { + glBindTexture(GL_TEXTURE_2D, gs->textures[j]); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, + gs->pitch / gs->hsub[j]); + glTexImage2D(GL_TEXTURE_2D, 0, + gs->gl_format[j], + gs->pitch / gs->hsub[j], + buffer->height / gs->vsub[j], + 0, + gl_format_from_internal(gs->gl_format[j]), + gs->gl_pixel_type, + data + gs->offset[j]); + } + wl_shm_buffer_end_access(buffer->shm_buffer); + goto done; + } + + rectangles = pixman_region32_rectangles(&gs->texture_damage, &n); + wl_shm_buffer_begin_access(buffer->shm_buffer); + for (i = 0; i < n; i++) { + pixman_box32_t r; + + r = weston_surface_to_buffer_rect(surface, rectangles[i]); + + for (j = 0; j < gs->num_textures; j++) { + glBindTexture(GL_TEXTURE_2D, gs->textures[j]); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, + gs->pitch / gs->hsub[j]); + glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, + r.x1 / gs->hsub[j]); + glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, + r.y1 / gs->hsub[j]); + glTexSubImage2D(GL_TEXTURE_2D, 0, + r.x1 / gs->hsub[j], + r.y1 / gs->vsub[j], + (r.x2 - r.x1) / gs->hsub[j], + (r.y2 - r.y1) / gs->vsub[j], + gl_format_from_internal(gs->gl_format[j]), + gs->gl_pixel_type, + data + gs->offset[j]); + } + } + wl_shm_buffer_end_access(buffer->shm_buffer); + +done: + pixman_region32_fini(&gs->texture_damage); + pixman_region32_init(&gs->texture_damage); + gs->needs_full_upload = false; + + weston_buffer_reference(&gs->buffer_ref, NULL); + weston_buffer_release_reference(&gs->buffer_release_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); + GLenum gl_format[3] = {0, 0, 0}; + GLenum gl_pixel_type; + int pitch; + int num_planes; + + buffer->shm_buffer = shm_buffer; + buffer->width = wl_shm_buffer_get_width(shm_buffer); + buffer->height = wl_shm_buffer_get_height(shm_buffer); + + num_planes = 1; + gs->offset[0] = 0; + gs->hsub[0] = 1; + gs->vsub[0] = 1; + + 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; + gl_format[0] = GL_BGRA_EXT; + gl_pixel_type = GL_UNSIGNED_BYTE; + es->is_opaque = true; + break; + case WL_SHM_FORMAT_ARGB8888: + gs->shader = &gr->texture_shader_rgba; + pitch = wl_shm_buffer_get_stride(shm_buffer) / 4; + gl_format[0] = GL_BGRA_EXT; + gl_pixel_type = GL_UNSIGNED_BYTE; + es->is_opaque = false; + break; + case WL_SHM_FORMAT_RGB565: + gs->shader = &gr->texture_shader_rgbx; + pitch = wl_shm_buffer_get_stride(shm_buffer) / 2; + gl_format[0] = GL_RGB; + gl_pixel_type = GL_UNSIGNED_SHORT_5_6_5; + es->is_opaque = true; + break; + case WL_SHM_FORMAT_YUV420: + gs->shader = &gr->texture_shader_y_u_v; + pitch = wl_shm_buffer_get_stride(shm_buffer); + gl_pixel_type = GL_UNSIGNED_BYTE; + num_planes = 3; + gs->offset[1] = gs->offset[0] + (pitch / gs->hsub[0]) * + (buffer->height / gs->vsub[0]); + gs->hsub[1] = 2; + gs->vsub[1] = 2; + gs->offset[2] = gs->offset[1] + (pitch / gs->hsub[1]) * + (buffer->height / gs->vsub[1]); + gs->hsub[2] = 2; + gs->vsub[2] = 2; + if (gr->has_gl_texture_rg) { + gl_format[0] = GL_R8_EXT; + gl_format[1] = GL_R8_EXT; + gl_format[2] = GL_R8_EXT; + } else { + gl_format[0] = GL_LUMINANCE; + gl_format[1] = GL_LUMINANCE; + gl_format[2] = GL_LUMINANCE; + } + es->is_opaque = true; + break; + case WL_SHM_FORMAT_NV12: + pitch = wl_shm_buffer_get_stride(shm_buffer); + gl_pixel_type = GL_UNSIGNED_BYTE; + num_planes = 2; + gs->offset[1] = gs->offset[0] + (pitch / gs->hsub[0]) * + (buffer->height / gs->vsub[0]); + gs->hsub[1] = 2; + gs->vsub[1] = 2; + if (gr->has_gl_texture_rg) { + gs->shader = &gr->texture_shader_y_uv; + gl_format[0] = GL_R8_EXT; + gl_format[1] = GL_RG8_EXT; + } else { + gs->shader = &gr->texture_shader_y_xuxv; + gl_format[0] = GL_LUMINANCE; + gl_format[1] = GL_LUMINANCE_ALPHA; + } + es->is_opaque = true; + break; + case WL_SHM_FORMAT_YUYV: + gs->shader = &gr->texture_shader_y_xuxv; + pitch = wl_shm_buffer_get_stride(shm_buffer) / 2; + gl_pixel_type = GL_UNSIGNED_BYTE; + num_planes = 2; + gs->offset[1] = 0; + gs->hsub[1] = 2; + gs->vsub[1] = 1; + if (gr->has_gl_texture_rg) + gl_format[0] = GL_RG8_EXT; + else + gl_format[0] = GL_LUMINANCE_ALPHA; + gl_format[1] = GL_BGRA_EXT; + es->is_opaque = true; + break; + default: + weston_log("warning: unknown shm buffer format: %08x\n", + wl_shm_buffer_get_format(shm_buffer)); + return; + } + + /* 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 || + gl_format[0] != gs->gl_format[0] || + gl_format[1] != gs->gl_format[1] || + gl_format[2] != gs->gl_format[2] || + gl_pixel_type != gs->gl_pixel_type || + gs->buffer_type != BUFFER_TYPE_SHM) { + gs->pitch = pitch; + gs->height = buffer->height; + gs->target = GL_TEXTURE_2D; + gs->gl_format[0] = gl_format[0]; + gs->gl_format[1] = gl_format[1]; + gs->gl_format[2] = gl_format[2]; + gs->gl_pixel_type = gl_pixel_type; + gs->buffer_type = BUFFER_TYPE_SHM; + gs->needs_full_upload = true; + gs->y_inverted = true; + gs->direct_display = false; + + gs->surface = es; + + ensure_textures(gs, num_planes); + } +} + +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++) { + egl_image_unref(gs->images[i]); + gs->images[i] = NULL; + } + gs->num_images = 0; + gs->target = GL_TEXTURE_2D; + es->is_opaque = false; + switch (format) { + case EGL_TEXTURE_RGB: + es->is_opaque = true; + /* fallthrough */ + 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; + es->is_opaque = true; + break; + case EGL_TEXTURE_Y_U_V_WL: + num_planes = 3; + gs->shader = &gr->texture_shader_y_u_v; + es->is_opaque = true; + break; + case EGL_TEXTURE_Y_XUXV_WL: + num_planes = 2; + gs->shader = &gr->texture_shader_y_xuxv; + es->is_opaque = true; + 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] = egl_image_create(gr, + 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]->image); + } + + gs->pitch = buffer->width; + gs->height = buffer->height; + gs->buffer_type = BUFFER_TYPE_EGL; + gs->y_inverted = buffer->y_inverted; +} + +static void +gl_renderer_destroy_dmabuf(struct linux_dmabuf_buffer *dmabuf) +{ + struct dmabuf_image *image = linux_dmabuf_buffer_get_user_data(dmabuf); + + dmabuf_image_destroy(image); +} + +static struct egl_image * +import_simple_dmabuf(struct gl_renderer *gr, + struct dmabuf_attributes *attributes) +{ + struct egl_image *image; + EGLint attribs[50]; + int atti = 0; + bool has_modifier; + + /* This requires the Mesa commit in + * Mesa 10.3 (08264e5dad4df448e7718e782ad9077902089a07) or + * Mesa 10.2.7 (55d28925e6109a4afd61f109e845a8a51bd17652). + * Otherwise Mesa closes the fd behind our back and re-importing + * will fail. + * https://bugs.freedesktop.org/show_bug.cgi?id=76188 + */ + + attribs[atti++] = EGL_WIDTH; + attribs[atti++] = attributes->width; + attribs[atti++] = EGL_HEIGHT; + attribs[atti++] = attributes->height; + attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; + attribs[atti++] = attributes->format; + + if (attributes->modifier[0] != DRM_FORMAT_MOD_INVALID) { + if (!gr->has_dmabuf_import_modifiers) + return NULL; + has_modifier = true; + } else { + has_modifier = false; + } + + if (attributes->n_planes > 0) { + attribs[atti++] = EGL_DMA_BUF_PLANE0_FD_EXT; + attribs[atti++] = attributes->fd[0]; + attribs[atti++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT; + attribs[atti++] = attributes->offset[0]; + attribs[atti++] = EGL_DMA_BUF_PLANE0_PITCH_EXT; + attribs[atti++] = attributes->stride[0]; + if (has_modifier) { + attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT; + attribs[atti++] = attributes->modifier[0] & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT; + attribs[atti++] = attributes->modifier[0] >> 32; + } + } + + if (attributes->n_planes > 1) { + attribs[atti++] = EGL_DMA_BUF_PLANE1_FD_EXT; + attribs[atti++] = attributes->fd[1]; + attribs[atti++] = EGL_DMA_BUF_PLANE1_OFFSET_EXT; + attribs[atti++] = attributes->offset[1]; + attribs[atti++] = EGL_DMA_BUF_PLANE1_PITCH_EXT; + attribs[atti++] = attributes->stride[1]; + if (has_modifier) { + attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT; + attribs[atti++] = attributes->modifier[1] & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT; + attribs[atti++] = attributes->modifier[1] >> 32; + } + } + + if (attributes->n_planes > 2) { + attribs[atti++] = EGL_DMA_BUF_PLANE2_FD_EXT; + attribs[atti++] = attributes->fd[2]; + attribs[atti++] = EGL_DMA_BUF_PLANE2_OFFSET_EXT; + attribs[atti++] = attributes->offset[2]; + attribs[atti++] = EGL_DMA_BUF_PLANE2_PITCH_EXT; + attribs[atti++] = attributes->stride[2]; + if (has_modifier) { + attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT; + attribs[atti++] = attributes->modifier[2] & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT; + attribs[atti++] = attributes->modifier[2] >> 32; + } + } + + if (gr->has_dmabuf_import_modifiers) { + if (attributes->n_planes > 3) { + attribs[atti++] = EGL_DMA_BUF_PLANE3_FD_EXT; + attribs[atti++] = attributes->fd[3]; + attribs[atti++] = EGL_DMA_BUF_PLANE3_OFFSET_EXT; + attribs[atti++] = attributes->offset[3]; + attribs[atti++] = EGL_DMA_BUF_PLANE3_PITCH_EXT; + attribs[atti++] = attributes->stride[3]; + attribs[atti++] = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT; + attribs[atti++] = attributes->modifier[3] & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT; + attribs[atti++] = attributes->modifier[3] >> 32; + } + } + + attribs[atti++] = EGL_NONE; + + image = egl_image_create(gr, EGL_LINUX_DMA_BUF_EXT, NULL, + attribs); + + return image; +} + +/* The kernel header drm_fourcc.h defines the DRM formats below. We duplicate + * some of the definitions here so that building Weston won't require + * bleeding-edge kernel headers. + */ +#ifndef DRM_FORMAT_R8 +#define DRM_FORMAT_R8 fourcc_code('R', '8', ' ', ' ') /* [7:0] R */ +#endif + +#ifndef DRM_FORMAT_GR88 +#define DRM_FORMAT_GR88 fourcc_code('G', 'R', '8', '8') /* [15:0] G:R 8:8 little endian */ +#endif + +#ifndef DRM_FORMAT_XYUV8888 +#define DRM_FORMAT_XYUV8888 fourcc_code('X', 'Y', 'U', 'V') /* [31:0] X:Y:Cb:Cr 8:8:8:8 little endian */ +#endif + +struct yuv_format_descriptor yuv_formats[] = { + { + .format = DRM_FORMAT_YUYV, + .input_planes = 1, + .output_planes = 2, + .texture_type = TEXTURE_Y_XUXV_WL, + {{ + .width_divisor = 1, + .height_divisor = 1, + .format = DRM_FORMAT_GR88, + .plane_index = 0 + }, { + .width_divisor = 2, + .height_divisor = 1, + .format = DRM_FORMAT_ARGB8888, + .plane_index = 0 + }} + }, { + .format = DRM_FORMAT_NV12, + .input_planes = 2, + .output_planes = 2, + .texture_type = TEXTURE_Y_UV_WL, + {{ + .width_divisor = 1, + .height_divisor = 1, + .format = DRM_FORMAT_R8, + .plane_index = 0 + }, { + .width_divisor = 2, + .height_divisor = 2, + .format = DRM_FORMAT_GR88, + .plane_index = 1 + }} + }, { + .format = DRM_FORMAT_YUV420, + .input_planes = 3, + .output_planes = 3, + .texture_type = TEXTURE_Y_U_V_WL, + {{ + .width_divisor = 1, + .height_divisor = 1, + .format = DRM_FORMAT_R8, + .plane_index = 0 + }, { + .width_divisor = 2, + .height_divisor = 2, + .format = DRM_FORMAT_R8, + .plane_index = 1 + }, { + .width_divisor = 2, + .height_divisor = 2, + .format = DRM_FORMAT_R8, + .plane_index = 2 + }} + }, { + .format = DRM_FORMAT_YUV444, + .input_planes = 3, + .output_planes = 3, + .texture_type = TEXTURE_Y_U_V_WL, + {{ + .width_divisor = 1, + .height_divisor = 1, + .format = DRM_FORMAT_R8, + .plane_index = 0 + }, { + .width_divisor = 1, + .height_divisor = 1, + .format = DRM_FORMAT_R8, + .plane_index = 1 + }, { + .width_divisor = 1, + .height_divisor = 1, + .format = DRM_FORMAT_R8, + .plane_index = 2 + }} + }, { + .format = DRM_FORMAT_XYUV8888, + .input_planes = 1, + .output_planes = 1, + .texture_type = TEXTURE_XYUV_WL, + {{ + .width_divisor = 1, + .height_divisor = 1, + .format = DRM_FORMAT_XBGR8888, + .plane_index = 0 + }} + } +}; + +static struct egl_image * +import_dmabuf_single_plane(struct gl_renderer *gr, + const struct dmabuf_attributes *attributes, + struct yuv_plane_descriptor *descriptor) +{ + struct dmabuf_attributes plane; + struct egl_image *image; + char fmt[4]; + + plane.width = attributes->width / descriptor->width_divisor; + plane.height = attributes->height / descriptor->height_divisor; + plane.format = descriptor->format; + plane.n_planes = 1; + plane.fd[0] = attributes->fd[descriptor->plane_index]; + plane.offset[0] = attributes->offset[descriptor->plane_index]; + plane.stride[0] = attributes->stride[descriptor->plane_index]; + plane.modifier[0] = attributes->modifier[descriptor->plane_index]; + + image = import_simple_dmabuf(gr, &plane); + if (!image) { + weston_log("Failed to import plane %d as %.4s\n", + descriptor->plane_index, + dump_format(descriptor->format, fmt)); + return NULL; + } + + return image; +} + +static bool +import_yuv_dmabuf(struct gl_renderer *gr, + struct dmabuf_image *image) +{ + unsigned i; + int j; + int ret; + struct yuv_format_descriptor *format = NULL; + struct dmabuf_attributes *attributes = &image->dmabuf->attributes; + char fmt[4]; + + for (i = 0; i < ARRAY_LENGTH(yuv_formats); ++i) { + if (yuv_formats[i].format == attributes->format) { + format = &yuv_formats[i]; + break; + } + } + + if (!format) { + weston_log("Error during import, and no known conversion for format " + "%.4s in the renderer\n", + dump_format(attributes->format, fmt)); + return false; + } + + if (attributes->n_planes != format->input_planes) { + weston_log("%.4s dmabuf must contain %d plane%s (%d provided)\n", + dump_format(format->format, fmt), + format->input_planes, + (format->input_planes > 1) ? "s" : "", + attributes->n_planes); + return false; + } + + for (j = 0; j < format->output_planes; ++j) { + image->images[j] = import_dmabuf_single_plane(gr, attributes, + &format->plane[j]); + if (!image->images[j]) { + while (j) { + ret = egl_image_unref(image->images[--j]); + assert(ret == 0); + } + return false; + } + } + + image->num_images = format->output_planes; + + switch (format->texture_type) { + case TEXTURE_Y_XUXV_WL: + image->shader = &gr->texture_shader_y_xuxv; + break; + case TEXTURE_Y_UV_WL: + image->shader = &gr->texture_shader_y_uv; + break; + case TEXTURE_Y_U_V_WL: + image->shader = &gr->texture_shader_y_u_v; + break; + case TEXTURE_XYUV_WL: + image->shader = &gr->texture_shader_xyuv; + break; + default: + assert(false); + } + + return true; +} + +static void +gl_renderer_query_dmabuf_modifiers_full(struct gl_renderer *gr, int format, + uint64_t **modifiers, + unsigned **external_only, + int *num_modifiers); + +static struct dmabuf_format* +dmabuf_format_create(struct gl_renderer *gr, uint32_t format) +{ + struct dmabuf_format *dmabuf_format; + + dmabuf_format = calloc(1, sizeof(struct dmabuf_format)); + if (!dmabuf_format) + return NULL; + + dmabuf_format->format = format; + + gl_renderer_query_dmabuf_modifiers_full(gr, format, + &dmabuf_format->modifiers, + &dmabuf_format->external_only, + &dmabuf_format->num_modifiers); + + if (dmabuf_format->num_modifiers == 0) { + free(dmabuf_format); + return NULL; + } + + wl_list_insert(&gr->dmabuf_formats, &dmabuf_format->link); + return dmabuf_format; +} + +static void +dmabuf_format_destroy(struct dmabuf_format *format) +{ + free(format->modifiers); + free(format->external_only); + wl_list_remove(&format->link); + free(format); +} + +static GLenum +choose_texture_target(struct gl_renderer *gr, + struct dmabuf_attributes *attributes) +{ + struct dmabuf_format *tmp, *format = NULL; + + wl_list_for_each(tmp, &gr->dmabuf_formats, link) { + if (tmp->format == attributes->format) { + format = tmp; + break; + } + } + + if (!format) + format = dmabuf_format_create(gr, attributes->format); + + if (format) { + int i; + + for (i = 0; i < format->num_modifiers; ++i) { + if (format->modifiers[i] == attributes->modifier[0]) { + if(format->external_only[i]) + return GL_TEXTURE_EXTERNAL_OES; + else + return GL_TEXTURE_2D; + } + } + } + + if (attributes->n_planes > 1) + return GL_TEXTURE_EXTERNAL_OES; + + switch (attributes->format & ~DRM_FORMAT_BIG_ENDIAN) { + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVYU: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VYUY: + case DRM_FORMAT_AYUV: + case DRM_FORMAT_XYUV8888: + return GL_TEXTURE_EXTERNAL_OES; + default: + return GL_TEXTURE_2D; + } +} + +static struct dmabuf_image * +import_dmabuf(struct gl_renderer *gr, + struct linux_dmabuf_buffer *dmabuf) +{ + struct egl_image *egl_image; + struct dmabuf_image *image; + + image = dmabuf_image_create(); + image->dmabuf = dmabuf; + + egl_image = import_simple_dmabuf(gr, &dmabuf->attributes); + if (egl_image) { + image->num_images = 1; + image->images[0] = egl_image; + image->import_type = IMPORT_TYPE_DIRECT; + image->target = choose_texture_target(gr, &dmabuf->attributes); + + switch (image->target) { + case GL_TEXTURE_2D: + image->shader = &gr->texture_shader_rgba; + break; + default: + image->shader = &gr->texture_shader_egl_external; + } + } else { + if (!import_yuv_dmabuf(gr, image)) { + dmabuf_image_destroy(image); + return NULL; + } + image->import_type = IMPORT_TYPE_GL_CONVERSION; + image->target = GL_TEXTURE_2D; + } + + return image; +} + +static void +gl_renderer_query_dmabuf_formats(struct weston_compositor *wc, + int **formats, int *num_formats) +{ + struct gl_renderer *gr = get_renderer(wc); + static const int fallback_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_YUYV, + DRM_FORMAT_NV12, + DRM_FORMAT_YUV420, + DRM_FORMAT_YUV444, + DRM_FORMAT_XYUV8888, + }; + bool fallback = false; + EGLint num; + + assert(gr->has_dmabuf_import); + + if (!gr->has_dmabuf_import_modifiers || + !gr->query_dmabuf_formats(gr->egl_display, 0, NULL, &num)) { + num = gr->has_gl_texture_rg ? ARRAY_LENGTH(fallback_formats) : 2; + fallback = true; + } + + *formats = calloc(num, sizeof(int)); + if (*formats == NULL) { + *num_formats = 0; + return; + } + + if (fallback) { + memcpy(*formats, fallback_formats, num * sizeof(int)); + *num_formats = num; + return; + } + + if (!gr->query_dmabuf_formats(gr->egl_display, num, *formats, &num)) { + *num_formats = 0; + free(*formats); + return; + } + + *num_formats = num; +} + +static void +gl_renderer_query_dmabuf_modifiers_full(struct gl_renderer *gr, int format, + uint64_t **modifiers, + unsigned **external_only, + int *num_modifiers) +{ + int num; + + assert(gr->has_dmabuf_import); + + if (!gr->has_dmabuf_import_modifiers || + !gr->query_dmabuf_modifiers(gr->egl_display, format, 0, NULL, + NULL, &num) || + num == 0) { + *num_modifiers = 0; + return; + } + + *modifiers = calloc(num, sizeof(uint64_t)); + if (*modifiers == NULL) { + *num_modifiers = 0; + return; + } + if (external_only) { + *external_only = calloc(num, sizeof(unsigned)); + if (*external_only == NULL) { + *num_modifiers = 0; + free(*modifiers); + return; + } + } + if (!gr->query_dmabuf_modifiers(gr->egl_display, format, + num, *modifiers, external_only ? + *external_only : NULL, &num)) { + *num_modifiers = 0; + free(*modifiers); + if (external_only) + free(*external_only); + return; + } + + *num_modifiers = num; +} + +static void +gl_renderer_query_dmabuf_modifiers(struct weston_compositor *wc, int format, + uint64_t **modifiers, + int *num_modifiers) +{ + struct gl_renderer *gr = get_renderer(wc); + + gl_renderer_query_dmabuf_modifiers_full(gr, format, modifiers, NULL, + num_modifiers); +} + +static bool +gl_renderer_import_dmabuf(struct weston_compositor *ec, + struct linux_dmabuf_buffer *dmabuf) +{ + struct gl_renderer *gr = get_renderer(ec); + struct dmabuf_image *image; + int i; + + assert(gr->has_dmabuf_import); + + for (i = 0; i < dmabuf->attributes.n_planes; i++) { + /* return if EGL doesn't support import modifiers */ + if (dmabuf->attributes.modifier[i] != DRM_FORMAT_MOD_INVALID) + if (!gr->has_dmabuf_import_modifiers) + return false; + + /* return if modifiers passed are unequal */ + if (dmabuf->attributes.modifier[i] != + dmabuf->attributes.modifier[0]) + return false; + } + + /* reject all flags we do not recognize or handle */ + if (dmabuf->attributes.flags & ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT) + return false; + + image = import_dmabuf(gr, dmabuf); + if (!image) + return false; + + wl_list_insert(&gr->dmabuf_images, &image->link); + linux_dmabuf_buffer_set_user_data(dmabuf, image, + gl_renderer_destroy_dmabuf); + + return true; +} + +static bool +import_known_dmabuf(struct gl_renderer *gr, + struct dmabuf_image *image) +{ + switch (image->import_type) { + case IMPORT_TYPE_DIRECT: + image->images[0] = import_simple_dmabuf(gr, &image->dmabuf->attributes); + if (!image->images[0]) + return false; + image->num_images = 1; + break; + + case IMPORT_TYPE_GL_CONVERSION: + if (!import_yuv_dmabuf(gr, image)) + return false; + break; + + default: + weston_log("Invalid import type for dmabuf\n"); + return false; + } + + return true; +} + +static bool +dmabuf_is_opaque(struct linux_dmabuf_buffer *dmabuf) +{ + const struct pixel_format_info *info; + + info = pixel_format_get_info(dmabuf->attributes.format & + ~DRM_FORMAT_BIG_ENDIAN); + if (!info) + return false; + + return pixel_format_is_opaque(info); +} + +static void +gl_renderer_attach_dmabuf(struct weston_surface *surface, + struct weston_buffer *buffer, + struct linux_dmabuf_buffer *dmabuf) +{ + struct gl_renderer *gr = get_renderer(surface->compositor); + struct gl_surface_state *gs = get_surface_state(surface); + struct dmabuf_image *image; + int i; + + if (!gr->has_dmabuf_import) { + linux_dmabuf_buffer_send_server_error(dmabuf, + "EGL dmabuf import not supported"); + return; + } + + buffer->width = dmabuf->attributes.width; + buffer->height = dmabuf->attributes.height; + + /* + * GL-renderer uses the OpenGL convention of texture coordinates, where + * the origin is at bottom-left. Because dmabuf buffers have the origin + * at top-left, we must invert the Y_INVERT flag to get the image right. + */ + buffer->y_inverted = + !(dmabuf->attributes.flags & ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT); + + for (i = 0; i < gs->num_images; i++) + egl_image_unref(gs->images[i]); + gs->num_images = 0; + + gs->pitch = buffer->width; + gs->height = buffer->height; + gs->buffer_type = BUFFER_TYPE_EGL; + gs->y_inverted = buffer->y_inverted; + gs->direct_display = dmabuf->direct_display; + surface->is_opaque = dmabuf_is_opaque(dmabuf); + + /* + * We try to always hold an imported EGLImage from the dmabuf + * to prevent the client from preventing re-imports. But, we also + * need to re-import every time the contents may change because + * GL driver's caching may need flushing. + * + * Here we release the cache reference which has to be final. + */ + if (dmabuf->direct_display) + return; + + image = linux_dmabuf_buffer_get_user_data(dmabuf); + + /* The dmabuf_image should have been created during the import */ + assert(image != NULL); + + for (i = 0; i < image->num_images; ++i) + egl_image_unref(image->images[i]); + + if (!import_known_dmabuf(gr, image)) { + linux_dmabuf_buffer_send_server_error(dmabuf, "EGL dmabuf import failed"); + return; + } + + gs->num_images = image->num_images; + for (i = 0; i < gs->num_images; ++i) + gs->images[i] = egl_image_ref(image->images[i]); + + gs->target = image->target; + ensure_textures(gs, gs->num_images); + for (i = 0; i < gs->num_images; ++i) { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(gs->target, gs->textures[i]); + gr->image_target_texture_2d(gs->target, gs->images[i]->image); + } + + gs->shader = image->shader; +} + +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; + struct linux_dmabuf_buffer *dmabuf; + EGLint format; + int i; + + weston_buffer_reference(&gs->buffer_ref, buffer); + weston_buffer_release_reference(&gs->buffer_release_ref, + es->buffer_release_ref.buffer_release); + + if (!buffer) { + for (i = 0; i < gs->num_images; i++) { + egl_image_unref(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 = true; + gs->direct_display = false; + es->is_opaque = false; + return; + } + + shm_buffer = wl_shm_buffer_get(buffer->resource); + + if (shm_buffer) + gl_renderer_attach_shm(es, buffer, shm_buffer); + else if (gr->has_bind_display && + gr->query_buffer(gr->egl_display, (void *)buffer->resource, + EGL_TEXTURE_FORMAT, &format)) + gl_renderer_attach_egl(es, buffer, format); + else if ((dmabuf = linux_dmabuf_buffer_get(buffer->resource))) + gl_renderer_attach_dmabuf(es, buffer, dmabuf); + else { + weston_log("unhandled buffer type!\n"); + if (gr->has_bind_display) { + weston_log("eglQueryWaylandBufferWL failed\n"); + gl_renderer_print_egl_error_state(); + } + weston_buffer_reference(&gs->buffer_ref, NULL); + weston_buffer_release_reference(&gs->buffer_release_ref, NULL); + gs->buffer_type = BUFFER_TYPE_NULL; + gs->y_inverted = true; + es->is_opaque = false; + weston_buffer_send_server_error(buffer, + "disconnecting due to unhandled buffer type"); + } +} + +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->buffer_type = BUFFER_TYPE_SOLID; + gs->pitch = 1; + gs->height = 1; + + gs->shader = &gr->solid_shader; +} + +static void +gl_renderer_surface_get_content_size(struct weston_surface *surface, + int *width, int *height) +{ + struct gl_surface_state *gs = get_surface_state(surface); + + if (gs->buffer_type == BUFFER_TYPE_NULL) { + *width = 0; + *height = 0; + } else { + *width = gs->pitch; + *height = gs->height; + } +} + +static uint32_t +pack_color(pixman_format_code_t format, float *c) +{ + uint8_t r = round(c[0] * 255.0f); + uint8_t g = round(c[1] * 255.0f); + uint8_t b = round(c[2] * 255.0f); + uint8_t a = round(c[3] * 255.0f); + + switch (format) { + case PIXMAN_a8b8g8r8: + return (a << 24) | (b << 16) | (g << 8) | r; + default: + assert(0); + return 0; + } +} + +static int +gl_renderer_surface_copy_content(struct weston_surface *surface, + void *target, size_t size, + int src_x, int src_y, + int width, int height) +{ + static const GLfloat verts[4 * 2] = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + }; + static const GLfloat projmat_normal[16] = { /* transpose */ + 2.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 2.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 0.0f, 1.0f + }; + static const GLfloat projmat_yinvert[16] = { /* transpose */ + 2.0f, 0.0f, 0.0f, 0.0f, + 0.0f, -2.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 1.0f + }; + const pixman_format_code_t format = PIXMAN_a8b8g8r8; + const size_t bytespp = 4; /* PIXMAN_a8b8g8r8 */ + const GLenum gl_format = GL_RGBA; /* PIXMAN_a8b8g8r8 little-endian */ + struct gl_renderer *gr = get_renderer(surface->compositor); + struct gl_surface_state *gs = get_surface_state(surface); + int cw, ch; + GLuint fbo; + GLuint tex; + GLenum status; + const GLfloat *proj; + int i; + + gl_renderer_surface_get_content_size(surface, &cw, &ch); + + switch (gs->buffer_type) { + case BUFFER_TYPE_NULL: + return -1; + case BUFFER_TYPE_SOLID: + *(uint32_t *)target = pack_color(format, gs->color); + return 0; + case BUFFER_TYPE_SHM: + gl_renderer_flush_damage(surface); + /* fall through */ + case BUFFER_TYPE_EGL: + break; + } + + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cw, ch, + 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, tex, 0); + + status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + weston_log("%s: fbo error: %#x\n", __func__, status); + glDeleteFramebuffers(1, &fbo); + glDeleteTextures(1, &tex); + return -1; + } + + glViewport(0, 0, cw, ch); + glDisable(GL_BLEND); + use_shader(gr, gs->shader); + if (gs->y_inverted) + proj = projmat_normal; + else + proj = projmat_yinvert; + + glUniformMatrix4fv(gs->shader->proj_uniform, 1, GL_FALSE, proj); + glUniform1f(gs->shader->alpha_uniform, 1.0f); + + for (i = 0; i < gs->num_textures; i++) { + glUniform1i(gs->shader->tex_uniforms[i], i); + + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(gs->target, gs->textures[i]); + glTexParameteri(gs->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(gs->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + + /* position: */ + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, verts); + glEnableVertexAttribArray(0); + + /* texcoord: */ + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, verts); + glEnableVertexAttribArray(1); + + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(0); + + glPixelStorei(GL_PACK_ALIGNMENT, bytespp); + glReadPixels(src_x, src_y, width, height, gl_format, + GL_UNSIGNED_BYTE, target); + + glDeleteFramebuffers(1, &fbo); + glDeleteTextures(1, &tex); + + return 0; +} + +static void +surface_state_destroy(struct gl_surface_state *gs, struct gl_renderer *gr) +{ + int i; + + wl_list_remove(&gs->surface_destroy_listener.link); + wl_list_remove(&gs->renderer_destroy_listener.link); + + gs->surface->renderer_state = NULL; + + glDeleteTextures(gs->num_textures, gs->textures); + + for (i = 0; i < gs->num_images; i++) + egl_image_unref(gs->images[i]); + + weston_buffer_reference(&gs->buffer_ref, NULL); + weston_buffer_release_reference(&gs->buffer_release_ref, NULL); + pixman_region32_fini(&gs->texture_damage); + free(gs); +} + +static void +surface_state_handle_surface_destroy(struct wl_listener *listener, void *data) +{ + struct gl_surface_state *gs; + struct gl_renderer *gr; + + gs = container_of(listener, struct gl_surface_state, + surface_destroy_listener); + + gr = get_renderer(gs->surface->compositor); + + surface_state_destroy(gs, gr); +} + +static void +surface_state_handle_renderer_destroy(struct wl_listener *listener, void *data) +{ + struct gl_surface_state *gs; + struct gl_renderer *gr; + + gr = data; + + gs = container_of(listener, struct gl_surface_state, + renderer_destroy_listener); + + surface_state_destroy(gs, gr); +} + +static int +gl_renderer_create_surface(struct weston_surface *surface) +{ + struct gl_surface_state *gs; + struct gl_renderer *gr = get_renderer(surface->compositor); + + gs = zalloc(sizeof *gs); + if (gs == NULL) + 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 = true; + gs->direct_display = false; + + gs->surface = surface; + + pixman_region32_init(&gs->texture_damage); + surface->renderer_state = gs; + + gs->surface_destroy_listener.notify = + surface_state_handle_surface_destroy; + wl_signal_add(&surface->destroy_signal, + &gs->surface_destroy_listener); + + gs->renderer_destroy_listener.notify = + surface_state_handle_renderer_destroy; + wl_signal_add(&gr->destroy_signal, + &gs->renderer_destroy_listener); + + if (surface->buffer_ref.buffer) { + gl_renderer_attach(surface, surface->buffer_ref.buffer); + gl_renderer_flush_damage(surface); + } + + return 0; +} + +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 texture_fragment_shader_xyuv[] = + "precision mediump float;\n" + "uniform sampler2D tex;\n" + "varying vec2 v_texcoord;\n" + "uniform float alpha;\n" + "void main() {\n" + " float y = 1.16438356 * (texture2D(tex, v_texcoord).b - 0.0625);\n" + " float u = texture2D(tex, v_texcoord).g - 0.5;\n" + " float v = texture2D(tex, v_texcoord).r - 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 (shader->vertex_shader == GL_NONE) + return -1; + + 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); + if (shader->fragment_shader == GL_NONE) + return -1; + + 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; +} + +void +gl_renderer_log_extensions(const char *name, const char *extensions) +{ +// OHOS +// 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"); + weston_log("%s:", name); + weston_log("%s:", extensions); +} + +static void +log_egl_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); + gl_renderer_log_extensions("EGL extensions", str ? str : "(null)"); +} + +static void +log_gl_info(void) +{ + const char *str; + + 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); + gl_renderer_log_extensions("GL extensions", str ? str : "(null)"); +} + +static void +gl_renderer_output_set_border(struct weston_output *output, + enum gl_renderer_border_side side, + int32_t width, int32_t height, + int32_t tex_width, unsigned char *data) +{ + struct gl_output_state *go = get_output_state(output); + + if (go->borders[side].width != width || + go->borders[side].height != height) + /* In this case, we have to blow everything and do a full + * repaint. */ + go->border_status |= BORDER_SIZE_CHANGED | BORDER_ALL_DIRTY; + + if (data == NULL) { + width = 0; + height = 0; + } + + go->borders[side].width = width; + go->borders[side].height = height; + go->borders[side].tex_width = tex_width; + go->borders[side].data = data; + go->border_status |= 1 << side; +} + +static int +gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface); + +static EGLSurface +gl_renderer_create_window_surface(struct gl_renderer *gr, + EGLNativeWindowType window_for_legacy, + void *window_for_platform, + const uint32_t *drm_formats, + unsigned drm_formats_count) +{ + EGLSurface egl_surface = EGL_NO_SURFACE; + EGLConfig egl_config; + + egl_config = gl_renderer_get_egl_config(gr, EGL_WINDOW_BIT, + drm_formats, drm_formats_count); + if (egl_config == EGL_NO_CONFIG_KHR) + return EGL_NO_SURFACE; + + log_egl_config_info(gr->egl_display, egl_config); + + if (gr->create_platform_window) + egl_surface = gr->create_platform_window(gr->egl_display, + egl_config, + window_for_platform, + NULL); + else + egl_surface = eglCreateWindowSurface(gr->egl_display, + egl_config, + window_for_legacy, NULL); + + return egl_surface; +} + +static int +gl_renderer_output_create(struct weston_output *output, + EGLSurface surface) +{ + struct gl_output_state *go; + int i; + + go = zalloc(sizeof *go); + if (go == NULL) + return -1; + + go->egl_surface = surface; + + for (i = 0; i < BUFFER_DAMAGE_COUNT; i++) + pixman_region32_init(&go->buffer_damage[i]); + + wl_list_init(&go->timeline_render_point_list); + + go->begin_render_sync = EGL_NO_SYNC_KHR; + go->end_render_sync = EGL_NO_SYNC_KHR; + + output->renderer_state = go; + + return 0; +} + +static int +gl_renderer_output_window_create(struct weston_output *output, + const struct gl_renderer_output_options *options) +{ + struct weston_compositor *ec = output->compositor; + struct gl_renderer *gr = get_renderer(ec); + EGLSurface egl_surface = EGL_NO_SURFACE; + int ret = 0; + + egl_surface = gl_renderer_create_window_surface(gr, + options->window_for_legacy, + options->window_for_platform, + options->drm_formats, + options->drm_formats_count); + if (egl_surface == EGL_NO_SURFACE) { + weston_log("failed to create egl surface\n"); + return -1; + } + + ret = gl_renderer_output_create(output, egl_surface); + if (ret < 0) + weston_platform_destroy_egl_surface(gr->egl_display, egl_surface); + + return ret; +} + +static int +gl_renderer_output_pbuffer_create(struct weston_output *output, + const struct gl_renderer_pbuffer_options *options) +{ + struct gl_renderer *gr = get_renderer(output->compositor); + EGLConfig pbuffer_config; + EGLSurface egl_surface; + int ret; + EGLint pbuffer_attribs[] = { + EGL_WIDTH, options->width, + EGL_HEIGHT, options->height, + EGL_NONE + }; + + pbuffer_config = gl_renderer_get_egl_config(gr, EGL_PBUFFER_BIT, + options->drm_formats, + options->drm_formats_count); + if (pbuffer_config == EGL_NO_CONFIG_KHR) { + weston_log("failed to choose EGL config for PbufferSurface\n"); + return -1; + } + + log_egl_config_info(gr->egl_display, pbuffer_config); + + egl_surface = eglCreatePbufferSurface(gr->egl_display, pbuffer_config, + pbuffer_attribs); + if (egl_surface == EGL_NO_SURFACE) { + weston_log("failed to create egl surface\n"); + gl_renderer_print_egl_error_state(); + return -1; + } + + ret = gl_renderer_output_create(output, egl_surface); + if (ret < 0) + eglDestroySurface(gr->egl_display, egl_surface); + + return ret; +} + +static 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); + struct timeline_render_point *trp, *tmp; + int i; + + for (i = 0; i < 2; i++) + pixman_region32_fini(&go->buffer_damage[i]); + + eglMakeCurrent(gr->egl_display, + EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + + weston_platform_destroy_egl_surface(gr->egl_display, go->egl_surface); + + if (!wl_list_empty(&go->timeline_render_point_list)) + weston_log("warning: discarding pending timeline render" + "objects at output destruction"); + + wl_list_for_each_safe(trp, tmp, &go->timeline_render_point_list, link) + timeline_render_point_destroy(trp); + + if (go->begin_render_sync != EGL_NO_SYNC_KHR) + gr->destroy_sync(gr->egl_display, go->begin_render_sync); + if (go->end_render_sync != EGL_NO_SYNC_KHR) + gr->destroy_sync(gr->egl_display, go->end_render_sync); + + free(go); +} + +static int +gl_renderer_create_fence_fd(struct weston_output *output) +{ + struct gl_output_state *go = get_output_state(output); + struct gl_renderer *gr = get_renderer(output->compositor); + int fd; + + if (go->end_render_sync == EGL_NO_SYNC_KHR) + return -1; + + fd = gr->dup_native_fence_fd(gr->egl_display, go->end_render_sync); + if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) + return -1; + + return fd; +} + +static void +gl_renderer_destroy(struct weston_compositor *ec) +{ + struct gl_renderer *gr = get_renderer(ec); + struct dmabuf_image *image, *next; + struct dmabuf_format *format, *next_format; + + wl_signal_emit(&gr->destroy_signal, gr); + + 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); + + + wl_list_for_each_safe(image, next, &gr->dmabuf_images, link) + dmabuf_image_destroy(image); + + wl_list_for_each_safe(format, next_format, &gr->dmabuf_formats, link) + dmabuf_format_destroy(format); + + if (gr->dummy_surface != EGL_NO_SURFACE) + weston_platform_destroy_egl_surface(gr->egl_display, + gr->dummy_surface); + + eglTerminate(gr->egl_display); + eglReleaseThread(); + + wl_list_remove(&gr->output_destroy_listener.link); + + wl_array_release(&gr->vertices); + wl_array_release(&gr->vtxcnt); + + if (gr->fragment_binding) + weston_binding_destroy(gr->fragment_binding); + if (gr->fan_binding) + weston_binding_destroy(gr->fan_binding); + + free(gr); +} + +static void +output_handle_destroy(struct wl_listener *listener, void *data) +{ + struct gl_renderer *gr; + struct weston_output *output = data; + + gr = container_of(listener, struct gl_renderer, + output_destroy_listener); + + if (wl_list_empty(&output->compositor->output_list)) + eglMakeCurrent(gr->egl_display, gr->dummy_surface, + gr->dummy_surface, gr->egl_context); +} + +static int +gl_renderer_create_pbuffer_surface(struct gl_renderer *gr) { + EGLConfig pbuffer_config; + static const EGLint pbuffer_attribs[] = { + EGL_WIDTH, 10, + EGL_HEIGHT, 10, + EGL_NONE + }; + + pbuffer_config = gr->egl_config; + if (pbuffer_config == EGL_NO_CONFIG_KHR) { + pbuffer_config = + gl_renderer_get_egl_config(gr, EGL_PBUFFER_BIT, + NULL, 0); + } + if (pbuffer_config == EGL_NO_CONFIG_KHR) { + weston_log("failed to choose EGL config for PbufferSurface\n"); + return -1; + } + + gr->dummy_surface = eglCreatePbufferSurface(gr->egl_display, + pbuffer_config, + pbuffer_attribs); + + if (gr->dummy_surface == EGL_NO_SURFACE) { + weston_log("failed to create PbufferSurface\n"); + return -1; + } + + return 0; +} + +static int +gl_renderer_display_create(struct weston_compositor *ec, + const struct gl_renderer_display_options *options) +{ + struct gl_renderer *gr; + + gr = zalloc(sizeof *gr); + if (gr == NULL) + return -1; + + gr->platform = options->egl_platform; + + if (gl_renderer_setup_egl_client_extensions(gr) < 0) + goto fail; + + 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.surface_set_color = gl_renderer_surface_set_color; + gr->base.destroy = gl_renderer_destroy; + gr->base.surface_get_content_size = + gl_renderer_surface_get_content_size; + gr->base.surface_copy_content = gl_renderer_surface_copy_content; + + if (gl_renderer_setup_egl_display(gr, options->egl_native_display) < 0) + goto fail; + + log_egl_info(gr->egl_display); + + ec->renderer = &gr->base; + + if (gl_renderer_setup_egl_extensions(ec) < 0) + goto fail_with_error; + + if (!gr->has_configless_context) { + EGLint egl_surface_type = options->egl_surface_type; + + if (!gr->has_surfaceless_context) + egl_surface_type |= EGL_PBUFFER_BIT; + + gr->egl_config = + gl_renderer_get_egl_config(gr, + egl_surface_type, + options->drm_formats, + options->drm_formats_count); + if (gr->egl_config == EGL_NO_CONFIG_KHR) { + weston_log("failed to choose EGL config\n"); + goto fail_terminate; + } + } + + ec->capabilities |= WESTON_CAP_ROTATION_ANY; + ec->capabilities |= WESTON_CAP_CAPTURE_YFLIP; + ec->capabilities |= WESTON_CAP_VIEW_CLIP_MASK; + if (gr->has_native_fence_sync && gr->has_wait_sync) + ec->capabilities |= WESTON_CAP_EXPLICIT_SYNC; + + wl_list_init(&gr->dmabuf_images); + if (gr->has_dmabuf_import) { + gr->base.import_dmabuf = gl_renderer_import_dmabuf; + gr->base.query_dmabuf_formats = + gl_renderer_query_dmabuf_formats; + gr->base.query_dmabuf_modifiers = + gl_renderer_query_dmabuf_modifiers; + } + wl_list_init(&gr->dmabuf_formats); + + if (gr->has_surfaceless_context) { + weston_log("EGL_KHR_surfaceless_context available\n"); + gr->dummy_surface = EGL_NO_SURFACE; + } else { + weston_log("EGL_KHR_surfaceless_context unavailable. " + "Trying PbufferSurface\n"); + + if (gl_renderer_create_pbuffer_surface(gr) < 0) + goto fail_with_error; + } + + wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_RGB565); + wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_YUV420); + wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_NV12); + wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_YUYV); + + wl_signal_init(&gr->destroy_signal); + + if (gl_renderer_setup(ec, gr->dummy_surface) < 0) { + if (gr->dummy_surface != EGL_NO_SURFACE) + weston_platform_destroy_egl_surface(gr->egl_display, + gr->dummy_surface); + goto fail_with_error; + } + + return 0; + +fail_with_error: + gl_renderer_print_egl_error_state(); +fail_terminate: + eglTerminate(gr->egl_display); +fail: + free(gr); + ec->renderer = NULL; + return -1; +} + +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_xuxv.vertex_source = vertex_shader; + gr->texture_shader_y_xuxv.fragment_source = + texture_fragment_shader_y_xuxv; + + gr->texture_shader_xyuv.vertex_source = vertex_shader; + gr->texture_shader_xyuv.fragment_source = texture_fragment_shader_xyuv; + + gr->solid_shader.vertex_source = vertex_shader; + gr->solid_shader.fragment_source = solid_fragment_shader; + + return 0; +} + +static void +fragment_debug_binding(struct weston_keyboard *keyboard, + const struct timespec *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 = !gr->fragment_shader_debug; + + 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->texture_shader_xyuv); + 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_keyboard *keyboard, + const struct timespec *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 uint32_t +get_gl_version(void) +{ + const char *version; + int major, minor; + + version = (const char *) glGetString(GL_VERSION); + if (version && + (sscanf(version, "%d.%d", &major, &minor) == 2 || + sscanf(version, "OpenGL ES %d.%d", &major, &minor) == 2)) { + return GR_GL_VERSION(major, minor); + } + + return GR_GL_VERSION_INVALID; +} + +static int +gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) +{ + struct gl_renderer *gr = get_renderer(ec); + const char *extensions; + EGLBoolean ret; + + EGLint context_attribs[16] = { + EGL_CONTEXT_CLIENT_VERSION, 0, + }; + unsigned int nattr = 2; + + if (!eglBindAPI(EGL_OPENGL_ES_API)) { + weston_log("failed to bind EGL_OPENGL_ES_API\n"); + gl_renderer_print_egl_error_state(); + return -1; + } + + /* + * Being the compositor we require minimum output latency, + * so request a high priority context for ourselves - that should + * reschedule all of our rendering and its dependencies to be completed + * first. If the driver doesn't permit us to create a high priority + * context, it will fallback to the default priority (MEDIUM). + */ + if (gr->has_context_priority) { + context_attribs[nattr++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG; + context_attribs[nattr++] = EGL_CONTEXT_PRIORITY_HIGH_IMG; + } + + assert(nattr < ARRAY_LENGTH(context_attribs)); + context_attribs[nattr] = EGL_NONE; + + /* try to create an OpenGLES 3 context first */ + context_attribs[1] = 3; + gr->egl_context = eglCreateContext(gr->egl_display, gr->egl_config, + EGL_NO_CONTEXT, context_attribs); + if (gr->egl_context == NULL) { + /* and then fallback to OpenGLES 2 */ + context_attribs[1] = 2; + 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; + } + } + + if (gr->has_context_priority) { + EGLint value = EGL_CONTEXT_PRIORITY_MEDIUM_IMG; + + eglQueryContext(gr->egl_display, gr->egl_context, + EGL_CONTEXT_PRIORITY_LEVEL_IMG, &value); + + if (value != EGL_CONTEXT_PRIORITY_HIGH_IMG) { + weston_log("Failed to obtain a high priority context.\n"); + /* Not an error, continue on as normal */ + } + } + + 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; + } + + gr->gl_version = get_gl_version(); + if (gr->gl_version == GR_GL_VERSION_INVALID) { + weston_log("warning: failed to detect GLES version, " + "defaulting to 2.0.\n"); + gr->gl_version = GR_GL_VERSION(2, 0); + } + + log_gl_info(); + + gr->image_target_texture_2d = + (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES"); + + extensions = (const char *) glGetString(GL_EXTENSIONS); + if (!extensions) { + weston_log("Retrieving GL extension string failed.\n"); + return -1; + } + + if (!weston_check_egl_extension(extensions, "GL_EXT_texture_format_BGRA8888")) { + weston_log("GL_EXT_texture_format_BGRA8888 not available\n"); + return -1; + } + + if (weston_check_egl_extension(extensions, "GL_EXT_read_format_bgra")) + ec->read_format = PIXMAN_a8r8g8b8; + else + ec->read_format = PIXMAN_a8b8g8r8; + + if (gr->gl_version >= GR_GL_VERSION(3, 0) || + weston_check_egl_extension(extensions, "GL_EXT_unpack_subimage")) + gr->has_unpack_subimage = true; + + if (gr->gl_version >= GR_GL_VERSION(3, 0) || + weston_check_egl_extension(extensions, "GL_EXT_texture_rg")) + gr->has_gl_texture_rg = true; + + if (weston_check_egl_extension(extensions, "GL_OES_EGL_image_external")) + gr->has_egl_image_external = true; + + glActiveTexture(GL_TEXTURE0); + + if (compile_shaders(ec)) + return -1; + +// OHOS remove debugger +// gr->fragment_binding = +// weston_compositor_add_debug_binding(ec, KEY_S, +// fragment_debug_binding, +// ec); +// gr->fan_binding = +// weston_compositor_add_debug_binding(ec, KEY_F, +// fan_debug_repaint_binding, +// ec); + + gr->output_destroy_listener.notify = output_handle_destroy; + wl_signal_add(&ec->output_destroyed_signal, + &gr->output_destroy_listener); + + 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; +} + +WL_EXPORT struct gl_renderer_interface gl_renderer_interface = { + .display_create = gl_renderer_display_create, + .output_window_create = gl_renderer_output_window_create, + .output_pbuffer_create = gl_renderer_output_pbuffer_create, + .output_destroy = gl_renderer_output_destroy, + .output_set_border = gl_renderer_output_set_border, + .create_fence_fd = gl_renderer_create_fence_fd, +}; diff --git a/libweston/renderer-gl/gl-renderer.h b/libweston/renderer-gl/gl-renderer.h new file mode 100755 index 0000000..2bf938e --- /dev/null +++ b/libweston/renderer-gl/gl-renderer.h @@ -0,0 +1,228 @@ +/* + * Copyright © 2012 John Kåre Alsaker + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include + +#include +#include "backend.h" +#include "libweston-internal.h" +#include // OHOS hilog + +// OHOS hilog +#ifndef weston_log +#define weston_log(fmt, ...) (HILOG_INFO(LOG_CORE, fmt, ##__VA_ARGS__)) +#define weston_log_continue(fmt, ...) (HILOG_DEBUG(LOG_CORE, fmt, ##__VA_ARGS__)) +#endif + +#ifdef ENABLE_EGL + +#include +#include + +#else + +typedef int EGLint; +typedef int EGLenum; +typedef void *EGLDisplay; +typedef void *EGLSurface; +typedef void *EGLConfig; +typedef intptr_t EGLNativeDisplayType; +typedef intptr_t EGLNativeWindowType; +#define EGL_DEFAULT_DISPLAY ((EGLNativeDisplayType)0) +#define EGL_PBUFFER_BIT 0x0001 +#define EGL_WINDOW_BIT 0x0004 + +#endif /* ENABLE_EGL */ + +enum gl_renderer_border_side { + GL_RENDERER_BORDER_TOP = 0, + GL_RENDERER_BORDER_LEFT = 1, + GL_RENDERER_BORDER_RIGHT = 2, + GL_RENDERER_BORDER_BOTTOM = 3, +}; + +/** + * Options passed to the \c display_create method of the GL renderer interface. + * + * \see struct gl_renderer_interface + */ +struct gl_renderer_display_options { + /** The EGL platform identifier */ + EGLenum egl_platform; + /** The native display corresponding to the given EGL platform */ + void *egl_native_display; + /** EGL_SURFACE_TYPE bits for the base EGLConfig */ + EGLint egl_surface_type; + /** Array of DRM pixel formats acceptable for the base EGLConfig */ + const uint32_t *drm_formats; + /** The \c drm_formats array length */ + unsigned drm_formats_count; +}; + +struct gl_renderer_output_options { + /** Native window handle for \c eglCreateWindowSurface */ + EGLNativeWindowType window_for_legacy; + /** Native window handle for \c eglCreatePlatformWindowSurface */ + void *window_for_platform; + /** Array of DRM pixel formats acceptable for the window */ + const uint32_t *drm_formats; + /** The \c drm_formats array length */ + unsigned drm_formats_count; +}; + +struct gl_renderer_pbuffer_options { + /** Width of the rendering surface in pixels */ + int width; + /** Height of the rendering surface in pixels */ + int height; + /** Array of DRM pixel formats acceptable for the pbuffer */ + const uint32_t *drm_formats; + /** The \c drm_formats array length */ + unsigned drm_formats_count; +}; + +struct gl_renderer_interface { + /** + * Initialize GL-renderer with the given EGL platform and native display + * + * \param ec The weston_compositor where to initialize. + * \param options The options struct describing display configuration + * \return 0 on success, -1 on failure. + * + * This function creates an EGLDisplay and initializes it. It also + * creates the GL ES context and sets it up. It attempts GL ES 3.0 + * and falls back to GL ES 2.0 if 3.0 is not supported. + * + * If \c platform is zero or EGL_EXT_platform_base is not supported, + * choosing the platform is left for the EGL implementation. Otherwise + * the given platform is used explicitly if the EGL implementation + * advertises it. Without the advertisement this function fails. + * + * If neither EGL_KHR_no_config_context or EGL_MESA_configless_context + * are supported, the arguments egl_surface_type, drm_formats, and + * drm_formats_count are used to find a so called base EGLConfig. The + * GL context is created with the base EGLConfig, and outputs will be + * required to use the same config as well. If one or both of the + * extensions are supported, these arguments are unused, and each + * output can use a different EGLConfig (pixel format). + * + * The first format in drm_formats that matches any EGLConfig + * determines which EGLConfig is chosen. On EGL GBM platform, the + * pixel format must match exactly. On other platforms, it is enough + * that each R, G, B, A channel has the same number of bits as in the + * DRM format. + */ + int (*display_create)(struct weston_compositor *ec, + const struct gl_renderer_display_options *options); + + /** + * Attach GL-renderer to the output with a native window + * + * \param output The output to create a rendering surface for. + * \param options The options struct describing output configuration + * \return 0 on success, -1 on failure. + * + * This function creates the renderer data structures needed to repaint + * the output. The repaint results will be directed to the given native + * window. + * + * If EGL_EXT_platform_base is supported then \c window_for_platform is + * used, otherwise \c window_for_legacy is used. This is because the + * handle on X11 platform is different between the two. + * + * The first format in drm_formats that matches any EGLConfig + * determines which EGLConfig is chosen. See \c display_create about + * how the matching works and the possible limitations. + * + * This function should be used only if \c display_create was called + * with \c EGL_WINDOW_BIT in \c egl_surface_type. + */ + int (*output_window_create)(struct weston_output *output, + const struct gl_renderer_output_options *options); + + /** + * Attach GL-renderer to the output with internal pixel storage + * + * \param output The output to create a rendering surface for. + * \param options The options struct describing the pbuffer + * \return 0 on success, -1 on failure. + * + * This function creates the renderer data structures needed to repaint + * the output. The repaint results will be kept internal and can only + * be accessed through e.g. screen capture. + * + * The first format in drm_formats that matches any EGLConfig + * determines which EGLConfig is chosen. See \c display_create about + * how the matching works and the possible limitations. + * + * This function should be used only if \c display_create was called + * with \c EGL_PBUFFER_BIT in \c egl_surface_type. + */ + int (*output_pbuffer_create)(struct weston_output *output, + const struct gl_renderer_pbuffer_options *options); + + void (*output_destroy)(struct weston_output *output); + + /* Sets the output border. + * + * The side specifies the side for which we are setting the border. + * The width and height are the width and height of the border. + * The tex_width patemeter specifies the width of the actual + * texture; this may be larger than width if the data is not + * tightly packed. + * + * The top and bottom textures will extend over the sides to the + * full width of the bordered window. The right and left edges, + * however, will extend only to the top and bottom of the + * compositor surface. This is demonstrated by the picture below: + * + * +-----------------------+ + * | TOP | + * +-+-------------------+-+ + * | | | | + * |L| |R| + * |E| |I| + * |F| |G| + * |T| |H| + * | | |T| + * | | | | + * +-+-------------------+-+ + * | BOTTOM | + * +-----------------------+ + */ + void (*output_set_border)(struct weston_output *output, + enum gl_renderer_border_side side, + int32_t width, int32_t height, + int32_t tex_width, unsigned char *data); + + /* Create fence sync FD to wait for GPU rendering. + * + * Return FD on success, -1 on failure or unsupported + * EGL_ANDROID_native_fence_sync extension. + */ + int (*create_fence_fd)(struct weston_output *output); +}; diff --git a/libweston/renderer-gl/meson.build b/libweston/renderer-gl/meson.build new file mode 100644 index 0000000..374e65b --- /dev/null +++ b/libweston/renderer-gl/meson.build @@ -0,0 +1,39 @@ +if not get_option('renderer-gl') + subdir_done() +endif + +config_h.set('ENABLE_EGL', '1') + +srcs_renderer_gl = [ + 'egl-glue.c', + 'gl-renderer.c', + linux_dmabuf_unstable_v1_protocol_c, + linux_dmabuf_unstable_v1_server_protocol_h, +] + +deps_renderer_gl = [ + dep_libm, + dep_pixman, + dep_libweston_private, + dep_libdrm_headers, + dep_vertex_clipping +] + +foreach name : [ 'egl', 'glesv2' ] + d = dependency(name, required: false) + if not d.found() + error('gl-renderer requires @0@ which was not found. Or, you can use \'-Drenderer-gl=false\'.'.format(name)) + endif + deps_renderer_gl += d +endforeach + +plugin_gl = shared_library( + 'gl-renderer', + srcs_renderer_gl, + include_directories: common_inc, + dependencies: deps_renderer_gl, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'gl-renderer.so=@0@;'.format(plugin_gl.full_path()) diff --git a/libweston/screenshooter.c b/libweston/screenshooter.c new file mode 100644 index 0000000..4ea519b --- /dev/null +++ b/libweston/screenshooter.c @@ -0,0 +1,494 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "shared/helpers.h" +#include "shared/timespec-util.h" +#include "backend.h" +#include "libweston-internal.h" + +#include "wcap/wcap-decode.h" + +struct screenshooter_frame_listener { + struct wl_listener listener; + struct weston_buffer *buffer; + struct weston_output *output; + weston_screenshooter_done_func_t done; + void *data; +}; + +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 = l->output; + struct weston_compositor *compositor = output->compositor; + int32_t stride; + uint8_t *pixels, *d, *s; + + weston_output_disable_planes_decr(output); + 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) { + l->done(l->data, WESTON_SCREENSHOOTER_NO_MEMORY); + 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); + + wl_shm_buffer_begin_access(l->buffer->shm_buffer); + + 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; + } + + wl_shm_buffer_end_access(l->buffer->shm_buffer); + + l->done(l->data, WESTON_SCREENSHOOTER_SUCCESS); + free(pixels); + free(l); +} + +WL_EXPORT int +weston_screenshooter_shoot(struct weston_output *output, + struct weston_buffer *buffer, + weston_screenshooter_done_func_t done, void *data) +{ + struct screenshooter_frame_listener *l; + + if (!wl_shm_buffer_get(buffer->resource)) { + done(data, WESTON_SCREENSHOOTER_BAD_BUFFER); + return -1; + } + + 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) { + done(data, WESTON_SCREENSHOOTER_BAD_BUFFER); + return -1; + } + + l = malloc(sizeof *l); + if (l == NULL) { + done(data, WESTON_SCREENSHOOTER_NO_MEMORY); + return -1; + } + + l->buffer = buffer; + l->output = output; + l->done = done; + l->data = data; + l->listener.notify = screenshooter_frame_notify; + wl_signal_add(&output->frame_signal, &l->listener); + weston_output_disable_planes_incr(output); + weston_output_damage(output); + + return 0; +} + +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, destroying; +}; + +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 +weston_recorder_destroy(struct weston_recorder *recorder); + +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 = recorder->output; + struct weston_compositor *compositor = output->compositor; + uint32_t msecs = timespec_to_msec(&output->frame_time); + pixman_box32_t *r; + pixman_region32_t damage, transformed_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_init(&transformed_damage); + pixman_region32_intersect(&damage, &output->region, data); + pixman_region32_translate(&damage, -output->x, -output->y); + weston_transformed_region(output->width, output->height, + output->transform, output->current_scale, + &damage, &transformed_damage); + pixman_region32_fini(&damage); + + r = pixman_region32_rectangles(&transformed_damage, &n); + if (n == 0) { + pixman_region32_fini(&transformed_damage); + return; + } + + 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); + + p = outbuf; + run = prev = 0; /* quiet gcc */ + for (j = 0; j < height; j++) { + if (do_yflip) + s = recorder->rect + width * j; + else + s = recorder->rect + width * (height - j - 1); + y_orig = r[i].y2 - j - 1; + 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(&transformed_damage); + recorder->count++; + + if (recorder->destroying) + weston_recorder_destroy(recorder); +} + +static void +weston_recorder_free(struct weston_recorder *recorder) +{ + if (recorder == NULL) + return; + + free(recorder->tmpbuf); + free(recorder->rect); + free(recorder->frame); + free(recorder); +} + +static struct weston_recorder * +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 = zalloc(sizeof *recorder); + if (recorder == NULL) { + weston_log("%s: out of memory\n", __func__); + return NULL; + } + + stride = output->current_mode->width; + size = stride * 4 * output->current_mode->height; + recorder->frame = zalloc(size); + recorder->rect = malloc(size); + recorder->output = output; + + if ((recorder->frame == NULL) || (recorder->rect == NULL)) { + weston_log("%s: out of memory\n", __func__); + goto err_recorder; + } + + if (!do_yflip) { + recorder->tmpbuf = malloc(size); + if (recorder->tmpbuf == NULL) { + weston_log("%s: out of memory\n", __func__); + goto err_recorder; + } + } + + 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"); + goto err_recorder; + } + + recorder->fd = open(filename, + O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); + + if (recorder->fd < 0) { + weston_log("problem opening output file %s: %s\n", filename, + strerror(errno)); + goto err_recorder; + } + + 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); + weston_output_disable_planes_incr(output); + weston_output_damage(output); + + return recorder; + +err_recorder: + weston_recorder_free(recorder); + return NULL; +} + +static void +weston_recorder_destroy(struct weston_recorder *recorder) +{ + wl_list_remove(&recorder->frame_listener.link); + close(recorder->fd); + weston_output_disable_planes_decr(recorder->output); + weston_recorder_free(recorder); +} + +WL_EXPORT struct weston_recorder * +weston_recorder_start(struct weston_output *output, const char *filename) +{ + struct wl_listener *listener; + + listener = wl_signal_get(&output->frame_signal, + weston_recorder_frame_notify); + if (listener) { + weston_log("a recorder on output %s is already running\n", + output->name); + return NULL; + } + + weston_log("starting recorder for output %s, file %s\n", + output->name, filename); + return weston_recorder_create(output, filename); +} + +WL_EXPORT void +weston_recorder_stop(struct weston_recorder *recorder) +{ + weston_log("stopping recorder, total file size %dM, %d frames\n", + recorder->total / (1024 * 1024), recorder->count); + + recorder->destroying = 1; + weston_output_schedule_repaint(recorder->output); +} diff --git a/libweston/spring-tool.c b/libweston/spring-tool.c new file mode 100644 index 0000000..b032d73 --- /dev/null +++ b/libweston/spring-tool.c @@ -0,0 +1,79 @@ +/* + * Copyright © 2011 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include + +#include "config.h" + +#include +#include "shared/timespec-util.h" + +WL_EXPORT void +weston_view_geometry_dirty(struct weston_view *view) +{ +} + +WL_EXPORT int +weston_log(const char *fmt, ...) +{ + return 0; +} + +WL_EXPORT void +weston_view_schedule_repaint(struct weston_view *view) +{ +} + +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; + struct timespec time = { 0 }; + + weston_spring_init(&spring, k, current, target); + spring.friction = friction; + spring.previous = 0.48; + spring.timestamp = (struct timespec) { 0 }; + + while (!weston_spring_done(&spring)) { + printf("\t%" PRId64 "\t%f\n", + timespec_to_msec(&time), spring.current); + weston_spring_update(&spring, &time); + timespec_add_msec(&time, &time, 16); + } + + return 0; +} diff --git a/libweston/tde-render-part.c b/libweston/tde-render-part.c new file mode 100644 index 0000000..1f3da8e --- /dev/null +++ b/libweston/tde-render-part.c @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tde-render-part.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libweston/weston-log.h" +#include "pixman-renderer-protected.h" + +struct tde_image_t { + int32_t width; + int32_t height; + uint32_t format; + int n_planes; // 默认为1,引入plane是为了保证方案的通用性 + __u64 phyaddr; // 物理地址 + int fd[MAX_DMABUF_PLANES]; + uint32_t offset[MAX_DMABUF_PLANES]; + uint32_t stride[MAX_DMABUF_PLANES]; +}; + +struct tde_output_state_t { + struct tde_image_t image; +}; + +struct tde_surface_state_t { + struct tde_image_t image; +}; + +struct tde_renderer_t { + GfxFuncs *gfx_funcs; + int use_tde; +}; + +struct drm_hisilicon_phy_addr { + uint64_t phyaddr; // return the phy addr + int fd; // dmabuf file descriptor +}; + +#define DRM_HISILICON_GEM_FD_TO_PHYADDR (0x1) +#define DRM_IOCTL_HISILICON_GEM_FD_TO_PHYADDR \ + (DRM_IOWR(DRM_COMMAND_BASE + DRM_HISILICON_GEM_FD_TO_PHYADDR, \ + struct drm_hisilicon_phy_addr)) + +static uint64_t drm_fd_phyaddr(struct weston_compositor *compositor, int fd) +{ + struct drm_backend *backend = to_drm_backend(compositor); + struct drm_hisilicon_phy_addr args = { .fd = fd }; + + int ret = ioctl(backend->drm.fd, + DRM_IOCTL_HISILICON_GEM_FD_TO_PHYADDR, &args); + return args.phyaddr; +} + +static void drm_close_handle(struct weston_compositor *compositor, int fd) +{ + struct drm_backend *backend = to_drm_backend(compositor); + uint32_t gem_handle; + int ret = drmPrimeFDToHandle(backend->drm.fd , fd, &gem_handle); + if (ret) { + weston_log("Failed to PrimeFDToHandle gem handle"); + return; + } + + struct drm_gem_close gem_close = { .handle = gem_handle }; + ret = drmIoctl(backend->drm.fd, DRM_IOCTL_GEM_CLOSE, &gem_close); + if (ret) { + weston_log("Failed to close gem handle"); + return; + } +} + +static uint64_t dst_image_phyaddr(struct weston_output *wo) +{ + struct drm_output *output = to_drm_output(wo); + struct drm_backend *backend = to_drm_backend(wo->compositor); + + int prime_fd; + int ret = drmPrimeHandleToFD(backend->drm.fd, + output->dumb[output->current_image]->handles[0], + DRM_CLOEXEC, &prime_fd); + + uint64_t phyaddr = drm_fd_phyaddr(wo->compositor, prime_fd); + close(prime_fd); + return phyaddr; +} + +static void src_surface_init(ISurface *surface, struct tde_image_t buffer) +{ + surface->width = buffer.width; + surface->height = buffer.height; + surface->phyAddr = buffer.phyaddr; + surface->stride = buffer.stride[0]; + surface->enColorFmt = PIXEL_FMT_RGBA_8888; + surface->bAlphaExt1555 = true; + surface->bAlphaMax255 = true; + surface->alpha0 = 0XFF; + surface->alpha1 = 0XFF; +} + +static void dst_surface_init(ISurface *surface, pixman_image_t *target_image, + struct weston_output *output) +{ + surface->width = pixman_image_get_width(target_image); + surface->height = pixman_image_get_height(target_image); + surface->phyAddr = dst_image_phyaddr(output); + surface->enColorFmt = PIXEL_FMT_BGRA_8888; + surface->stride = pixman_image_get_stride(target_image); + surface->bAlphaExt1555 = true; + surface->bAlphaMax255 = true; + surface->alpha0 = 0XFF; + surface->alpha1 = 0XFF; +} + +static int tde_repaint_region(struct weston_view *ev, + struct weston_output *output, + pixman_region32_t *buffer_region, + pixman_region32_t *repaint_output) +{ + struct pixman_renderer *renderer = output->compositor->renderer; + struct pixman_surface_state *surface = get_surface_state(ev->surface); + struct pixman_output_state *output_state = get_output_state(output); + + int ret = 0; + ret = renderer->tde->gfx_funcs->InitGfx(); + if (ret) { + return -1; + } + + pixman_image_t *target_image = output_state->hw_buffer; + if (output_state->shadow_image) { + target_image = output_state->shadow_image; + } + + ISurface dstSurface = {}; + dst_surface_init(&dstSurface, target_image, output); + + pixman_box32_t dstRect = *pixman_region32_extents(repaint_output); + IRect IdstRect = {dstRect.x1, dstRect.y1, + dstRect.x2 - dstRect.x1, dstRect.y2 - dstRect.y1}; + + if (ev->surface->type == 2) { + GfxOpt opt = { + .blendType = BLEND_SRCOVER, + .enableScale = true, + .enPixelAlpha = true, + }; + renderer->tde->gfx_funcs->FillRect(&dstSurface, &IdstRect, + 0x00000000, &opt); + } else { + GfxOpt opt = { + .blendType = BLEND_SRC, + .rotateType = ROTATE_90, + .enableScale = true, + .enPixelAlpha = true, + }; + pixman_box32_t srcRect = *pixman_region32_extents(buffer_region); + IRect IsrcRect = {srcRect.x1, srcRect.y1, + srcRect.x2 - srcRect.x1 ,srcRect.y2 - srcRect.y1}; + ISurface srcSurface = {}; + src_surface_init(&srcSurface, surface->tde->image); + + ret = renderer->tde->gfx_funcs->Blit(&srcSurface, &IsrcRect, &dstSurface, &IdstRect, &opt); + } + + ret = renderer->tde->gfx_funcs->DeinitGfx(); + return 0; +} + +static bool import_dmabuf(struct weston_compositor *ec, + struct linux_dmabuf_buffer *dmabuf) +{ + return true; +} + +static void query_dmabuf_formats(struct weston_compositor *wc, + int **formats, int *num_formats) +{ + static const int fallback_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_YUYV, + DRM_FORMAT_NV12, + DRM_FORMAT_YUV420, + DRM_FORMAT_YUV444, + }; + int num = ARRAY_LENGTH(fallback_formats); + if (num > 0) { + *formats = calloc(num, sizeof(int)); + } + + memcpy_s(*formats, num * sizeof(int), fallback_formats, sizeof(fallback_formats)); + *num_formats = num; +} + +static void query_dmabuf_modifiers(struct weston_compositor *wc, int format, + uint64_t **modifiers, int *num_modifiers) +{ + *modifiers = NULL; + *num_modifiers = 0; +} + +int tde_renderer_alloc_hook(struct pixman_renderer *renderer) +{ + renderer->tde = zalloc(sizeof(*renderer->tde)); + if (renderer->tde == NULL) { + return -1; + } + + if (GfxInitialize(&renderer->tde->gfx_funcs) == 0) { + renderer->tde->use_tde = 1; + } + + renderer->base.import_dmabuf = import_dmabuf; + renderer->base.query_dmabuf_formats = query_dmabuf_formats; + renderer->base.query_dmabuf_modifiers = query_dmabuf_modifiers; + return 0; +} + +int tde_renderer_free_hook(struct pixman_renderer *renderer) +{ + GfxUninitialize(&renderer->tde->gfx_funcs); + free(renderer->tde); + return 0; +} + +int tde_output_state_alloc_hook(struct pixman_output_state *state) +{ + state->tde = zalloc(sizeof(*state->tde)); + return 0; +} + +int tde_output_state_free_hook(struct pixman_output_state *state) +{ + free(state->tde); + return 0; +} + +int tde_surface_state_alloc_hook(struct pixman_surface_state *state) +{ + state->tde = zalloc(sizeof(*state->tde)); + return 0; +} + +int tde_surface_state_free_hook(struct pixman_surface_state *state) +{ + free(state->tde); + return 0; +} + +static void buffer_state_handle_buffer_destroy(struct wl_listener *listener, + void *data) +{ + struct pixman_surface_state *ps = container_of(listener, + struct pixman_surface_state, buffer_destroy_listener); + if (ps->image) { + tde_unref_image_hook(ps->image); + + pixman_image_unref(ps->image); + ps->image = NULL; + } + + ps->buffer_destroy_listener.notify = NULL; +} + +int tde_render_attach_hook(struct weston_surface *es, struct weston_buffer *buffer) +{ + if (!buffer) { + return -1; + } + + struct linux_dmabuf_buffer *dmabuf = linux_dmabuf_buffer_get(buffer->resource); + if (!dmabuf) { + return -1; + } + + struct pixman_surface_state *ps = get_surface_state(es); + weston_buffer_reference(&ps->buffer_ref, buffer); + weston_buffer_release_reference(&ps->buffer_release_ref, + es->buffer_release_ref.buffer_release); + + if (ps->buffer_destroy_listener.notify) { + wl_list_remove(&ps->buffer_destroy_listener.link); + ps->buffer_destroy_listener.notify = NULL; + } + + if (ps->image) { + tde_unref_image_hook(ps->image); + + pixman_image_unref(ps->image); + ps->image = NULL; + } + + pixman_format_code_t pixman_format = PIXMAN_a8r8g8b8; + buffer->legacy_buffer = NULL; + buffer->width = dmabuf->attributes.width; + buffer->height = dmabuf->attributes.height; + int stride = dmabuf->attributes.stride[0]; + int fd = dmabuf->attributes.fd[0]; + + ps->tde->image.width = dmabuf->attributes.width; + ps->tde->image.height = dmabuf->attributes.height; + ps->tde->image.stride[0] = dmabuf->attributes.stride[0]; + ps->tde->image.format = dmabuf->attributes.format; + ps->tde->image.fd[0] = fd; + + uint32_t* ptr = (uint32_t*)mmap(NULL, stride * buffer->height, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + + ps->tde->image.phyaddr = drm_fd_phyaddr(es->compositor, fd); + drm_close_handle(es->compositor, fd); + if (ps->tde->image.phyaddr == 0) { + if (ptr) { + munmap(ptr, stride * buffer->height); + } + return 0; + } + + ps->image = pixman_image_create_bits(pixman_format, buffer->width, buffer->height, ptr, stride); + + ps->buffer_destroy_listener.notify = buffer_state_handle_buffer_destroy; + wl_signal_add(&buffer->destroy_signal, &ps->buffer_destroy_listener); + return 0; +} + +int tde_repaint_region_hook(struct weston_view *ev, struct weston_output *output, + pixman_region32_t *buffer_region, pixman_region32_t *repaint_output) +{ + struct pixman_renderer *renderer = output->compositor->renderer; + if (!renderer->tde->use_tde) { + return -1; + } + + return tde_repaint_region(ev, output, buffer_region, repaint_output); +} + +int tde_unref_image_hook(pixman_image_t *image) +{ + if (image == NULL) { + return 0; + } + + int height = pixman_image_get_height(image); + int stride = pixman_image_get_stride(image); + void *ptr = pixman_image_get_data(image); + if (ptr) { + munmap(ptr, height * stride); + } + + return 0; +} diff --git a/libweston/tde-render-part.h b/libweston/tde-render-part.h new file mode 100644 index 0000000..26b0262 --- /dev/null +++ b/libweston/tde-render-part.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "pixman-renderer-protected.h" + +#ifndef LIBWESTON_TDE_RENDER_PART_H +#define LIBWESTON_TDE_RENDER_PART_H + +// hook return 0: success, other: failure +int tde_renderer_alloc_hook(struct pixman_renderer *renderer); +int tde_renderer_free_hook(struct pixman_renderer *renderer); + +int tde_output_state_alloc_hook(struct pixman_output_state *state); +int tde_output_state_free_hook(struct pixman_output_state *state); + +int tde_surface_state_alloc_hook(struct pixman_surface_state *state); +int tde_surface_state_free_hook(struct pixman_surface_state *state); + +int tde_render_attach_hook(struct weston_surface *es, struct weston_buffer *buffer); + +int tde_repaint_region_hook(struct weston_view *ev, struct weston_output *output, + pixman_region32_t *buffer_region, + pixman_region32_t *repaint_output); + +int tde_unref_image_hook(pixman_image_t *image); + +#endif // LIBWESTON_TDE_RENDER_PART_H diff --git a/libweston/timeline.c b/libweston/timeline.c new file mode 100644 index 0000000..d5738f4 --- /dev/null +++ b/libweston/timeline.c @@ -0,0 +1,461 @@ +/* + * Copyright © 2014 Pekka Paalanen + * Copyright © 2014, 2019 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include +#include "timeline.h" +#include "weston-log-internal.h" + +/** + * Timeline itself is not a subscriber but a scope (a producer of data), and it + * re-routes the data it produces to all the subscriptions (and implicitly + * to the subscribers) using a subscription iteration to go through all of them. + * + * Public API: + * * weston_timeline_refresh_subscription_objects() - allows outside parts of + * libweston notify/signal timeline code about the fact that underlying object + * has suffered some modifications and needs to re-emit the object ID. + * * weston_log_timeline_point() - which will disseminate data to all + * subscriptions + * + * Do note that only weston_timeline_refresh_subscription_objects() + * is exported in libweston. + * + * Destruction of the objects assigned to each underlying objects happens in + * two places: one in the logging framework callback of the log scope + * ('destroy_subscription'), and secondly, when the object itself gets + * destroyed. + * + * timeline_emit_context - For each subscription this object will be created to + * store a buffer when the object itself will be written and a subscription, + * which will be used to force the object ID if there is a need to do so (the + * underlying object has been refreshed, or better said has suffered some + * modification). Data written to a subscription will be flushed before the + * data written to the FILE *. + * + * @param cur a FILE * + * @param subscription a pointer to an already created subscription + * + * @ingroup internal-log + * @sa weston_timeline_point + */ +struct timeline_emit_context { + FILE *cur; + struct weston_log_subscription *subscription; +}; + +/** Create a timeline subscription and hang it off the subscription + * + * Called when the subscription is created. + * + * @ingroup internal-log + */ +void +weston_timeline_create_subscription(struct weston_log_subscription *sub, + void *user_data) +{ + struct weston_timeline_subscription *tl_sub = zalloc(sizeof(*tl_sub)); + if (!tl_sub) + return; + + wl_list_init(&tl_sub->objects); + + /* attach this timeline_subscription to it */ + weston_log_subscription_set_data(sub, tl_sub); +} + +static void +weston_timeline_destroy_subscription_object(struct weston_timeline_subscription_object *sub_obj) +{ + /* remove the notify listener */ + wl_list_remove(&sub_obj->destroy_listener.link); + sub_obj->destroy_listener.notify = NULL; + + wl_list_remove(&sub_obj->subscription_link); + free(sub_obj); +} + +/** Destroy the timeline subscription and all timeline subscription objects + * associated with it. + * + * Called when (before) the subscription is destroyed. + * + * @ingroup internal-log + */ +void +weston_timeline_destroy_subscription(struct weston_log_subscription *sub, + void *user_data) +{ + struct weston_timeline_subscription *tl_sub = + weston_log_subscription_get_data(sub); + struct weston_timeline_subscription_object *sub_obj, *tmp_sub_obj; + + if (!tl_sub) + return; + + wl_list_for_each_safe(sub_obj, tmp_sub_obj, + &tl_sub->objects, subscription_link) + weston_timeline_destroy_subscription_object(sub_obj); + + free(tl_sub); +} + +static bool +weston_timeline_check_object_refresh(struct weston_timeline_subscription_object *obj) +{ + if (obj->force_refresh == true) { + obj->force_refresh = false; + return true; + } + return false; +} + +static struct weston_timeline_subscription_object * +weston_timeline_subscription_search(struct weston_timeline_subscription *tl_sub, + void *object) +{ + struct weston_timeline_subscription_object *sub_obj; + + wl_list_for_each(sub_obj, &tl_sub->objects, subscription_link) + if (sub_obj->object == object) + return sub_obj; + + return NULL; +} + +static struct weston_timeline_subscription_object * +weston_timeline_subscription_object_create(void *object, + struct weston_timeline_subscription *tm_sub) +{ + struct weston_timeline_subscription_object *sub_obj; + + sub_obj = zalloc(sizeof(*sub_obj)); + sub_obj->id = ++tm_sub->next_id; + sub_obj->object = object; + + /* when the object is created so that it has the chance to display the + * object ID, we set the refresh status; it will only be re-freshed by + * the backend (or part parts) when the underlying objects has suffered + * modifications */ + sub_obj->force_refresh = true; + + wl_list_insert(&tm_sub->objects, &sub_obj->subscription_link); + + return sub_obj; +} + +static void +weston_timeline_destroy_subscription_object_notify(struct wl_listener *listener, void *data) +{ + struct weston_timeline_subscription_object *sub_obj; + + sub_obj = wl_container_of(listener, sub_obj, destroy_listener); + weston_timeline_destroy_subscription_object(sub_obj); +} + +static struct weston_timeline_subscription_object * +weston_timeline_subscription_output_ensure(struct weston_timeline_subscription *tl_sub, + struct weston_output *output) +{ + struct weston_timeline_subscription_object *sub_obj; + + sub_obj = weston_timeline_subscription_search(tl_sub, output); + if (!sub_obj) { + sub_obj = weston_timeline_subscription_object_create(output, tl_sub); + + sub_obj->destroy_listener.notify = + weston_timeline_destroy_subscription_object_notify; + wl_signal_add(&output->destroy_signal, + &sub_obj->destroy_listener); + } + return sub_obj; +} + +static struct weston_timeline_subscription_object * +weston_timeline_subscription_surface_ensure(struct weston_timeline_subscription *tl_sub, + struct weston_surface *surface) +{ + struct weston_timeline_subscription_object *sub_obj; + + sub_obj = weston_timeline_subscription_search(tl_sub, surface); + if (!sub_obj) { + sub_obj = weston_timeline_subscription_object_create(surface, tl_sub); + + sub_obj->destroy_listener.notify = + weston_timeline_destroy_subscription_object_notify; + wl_signal_add(&surface->destroy_signal, + &sub_obj->destroy_listener); + } + + return sub_obj; +} + +static void +fprint_quoted_string(struct weston_log_subscription *sub, const char *str) +{ + if (!str) { + weston_log_subscription_printf(sub, "null"); + return; + } + + weston_log_subscription_printf(sub, "\"%s\"", str); +} + +static void +emit_weston_output_print_id(struct weston_log_subscription *sub, + struct weston_timeline_subscription_object *sub_obj, + const char *name) +{ + if (!weston_timeline_check_object_refresh(sub_obj)) + return; + + weston_log_subscription_printf(sub, "{ \"id\":%u, " + "\"type\":\"weston_output\", \"name\":", sub_obj->id); + fprint_quoted_string(sub, name); + weston_log_subscription_printf(sub, " }\n"); +} + +static int +emit_weston_output(struct timeline_emit_context *ctx, void *obj) +{ + struct weston_log_subscription *sub = ctx->subscription; + struct weston_output *output = obj; + struct weston_timeline_subscription_object *sub_obj; + struct weston_timeline_subscription *tl_sub; + + tl_sub = weston_log_subscription_get_data(sub); + sub_obj = weston_timeline_subscription_output_ensure(tl_sub, output); + emit_weston_output_print_id(sub, sub_obj, output->name); + + assert(sub_obj->id != 0); + fprintf(ctx->cur, "\"wo\":%u", sub_obj->id); + + return 1; +} + + +static void +check_weston_surface_description(struct weston_log_subscription *sub, + struct weston_surface *s, + struct weston_timeline_subscription *tm_sub, + struct weston_timeline_subscription_object *sub_obj) +{ + struct weston_surface *mains; + char d[512]; + char mainstr[32]; + + if (!weston_timeline_check_object_refresh(sub_obj)) + return; + + mains = weston_surface_get_main_surface(s); + if (mains != s) { + struct weston_timeline_subscription_object *new_sub_obj; + + new_sub_obj = weston_timeline_subscription_surface_ensure(tm_sub, mains); + check_weston_surface_description(sub, mains, tm_sub, new_sub_obj); + if (snprintf(mainstr, sizeof(mainstr), ", \"main_surface\":%u", + new_sub_obj->id) < 0) + mainstr[0] = '\0'; + } else { + mainstr[0] = '\0'; + } + + if (!s->get_label || s->get_label(s, d, sizeof(d)) < 0) + d[0] = '\0'; + + weston_log_subscription_printf(sub, "{ \"id\":%u, " + "\"type\":\"weston_surface\", \"desc\":", + sub_obj->id); + fprint_quoted_string(sub, d[0] ? d : NULL); + weston_log_subscription_printf(sub, "%s }\n", mainstr); +} + +static int +emit_weston_surface(struct timeline_emit_context *ctx, void *obj) +{ + struct weston_log_subscription *sub = ctx->subscription; + struct weston_surface *surface = obj; + struct weston_timeline_subscription_object *sub_obj; + struct weston_timeline_subscription *tl_sub; + + tl_sub = weston_log_subscription_get_data(sub); + sub_obj = weston_timeline_subscription_surface_ensure(tl_sub, surface); + check_weston_surface_description(sub, surface, tl_sub, sub_obj); + + assert(sub_obj->id != 0); + fprintf(ctx->cur, "\"ws\":%u", sub_obj->id); + + return 1; +} + +static int +emit_vblank_timestamp(struct timeline_emit_context *ctx, void *obj) +{ + struct timespec *ts = obj; + + fprintf(ctx->cur, "\"vblank_monotonic\":[%" PRId64 ", %ld]", + (int64_t)ts->tv_sec, ts->tv_nsec); + + return 1; +} + +static int +emit_gpu_timestamp(struct timeline_emit_context *ctx, void *obj) +{ + struct timespec *ts = obj; + + fprintf(ctx->cur, "\"gpu\":[%" PRId64 ", %ld]", + (int64_t)ts->tv_sec, ts->tv_nsec); + + return 1; +} + +static struct weston_timeline_subscription_object * +weston_timeline_get_subscription_object(struct weston_log_subscription *sub, + void *object) +{ + struct weston_timeline_subscription *tl_sub; + + tl_sub = weston_log_subscription_get_data(sub); + if (!tl_sub) + return NULL; + + return weston_timeline_subscription_search(tl_sub, object); +} + +/** Sets (on) the timeline subscription object refresh status. + * + * This function 'notifies' timeline to print the object ID. The timeline code + * will reset it back, so there's no need for users to do anything about it. + * + * Can be used from outside libweston. + * + * @param wc a weston_compositor instance + * @param object the underyling object + * + * @ingroup log + */ +WL_EXPORT void +weston_timeline_refresh_subscription_objects(struct weston_compositor *wc, + void *object) +{ + struct weston_log_subscription *sub = NULL; + + while ((sub = weston_log_subscription_iterate(wc->timeline, sub))) { + struct weston_timeline_subscription_object *sub_obj; + + sub_obj = weston_timeline_get_subscription_object(sub, object); + if (sub_obj) + sub_obj->force_refresh = true; + } +} + +typedef int (*type_func)(struct timeline_emit_context *ctx, void *obj); + +static const type_func type_dispatch[] = { + [TLT_OUTPUT] = emit_weston_output, + [TLT_SURFACE] = emit_weston_surface, + [TLT_VBLANK] = emit_vblank_timestamp, + [TLT_GPU] = emit_gpu_timestamp, +}; + +/** Disseminates the message to all subscriptions of the scope \c + * timeline_scope + * + * The TL_POINT() is a wrapper over this function, but it uses the weston_compositor + * instance to pass the timeline scope. + * + * @param timeline_scope the timeline scope + * @param name the name of the timeline point. Interpretable by the tool reading + * the output (wesgr). + * + * @ingroup log + */ +WL_EXPORT void +weston_timeline_point(struct weston_log_scope *timeline_scope, + const char *name, ...) +{ + struct timespec ts; + enum timeline_type otype; + void *obj; + char buf[512]; + struct weston_log_subscription *sub = NULL; + + if (!weston_log_scope_is_enabled(timeline_scope)) + return; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + while ((sub = weston_log_subscription_iterate(timeline_scope, sub))) { + va_list argp; + struct timeline_emit_context ctx = {}; + + memset(buf, 0, sizeof(buf)); + ctx.cur = fmemopen(buf, sizeof(buf), "w"); + ctx.subscription = sub; + + if (!ctx.cur) { + weston_log("Timeline error in fmemopen, closing.\n"); + return; + } + + fprintf(ctx.cur, "{ \"T\":[%" PRId64 ", %ld], \"N\":\"%s\"", + (int64_t)ts.tv_sec, ts.tv_nsec, name); + + va_start(argp, name); + while (1) { + otype = va_arg(argp, enum timeline_type); + if (otype == TLT_END) + break; + + obj = va_arg(argp, void *); + if (type_dispatch[otype]) { + fprintf(ctx.cur, ", "); + type_dispatch[otype](&ctx, obj); + } + } + va_end(argp); + + fprintf(ctx.cur, " }\n"); + fflush(ctx.cur); + if (ferror(ctx.cur)) { + weston_log("Timeline error in constructing entry, closing.\n"); + } else { + weston_log_subscription_printf(ctx.subscription, "%s", buf); + } + + fclose(ctx.cur); + + } +} diff --git a/libweston/timeline.h b/libweston/timeline.h new file mode 100644 index 0000000..aaed743 --- /dev/null +++ b/libweston/timeline.h @@ -0,0 +1,103 @@ +/* + * Copyright © 2014 Pekka Paalanen + * Copyright © 2014, 2019 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_TIMELINE_H +#define WESTON_TIMELINE_H + +#include +#include + +#include +#include + +enum timeline_type { + TLT_END = 0, + TLT_OUTPUT, + TLT_SURFACE, + TLT_VBLANK, + TLT_GPU, +}; + +/** Timeline subscription created for each subscription + * + * Created automatically by weston_log_scope::new_subscription and + * destroyed by weston_log_scope::destroy_subscription. + * + * @ingroup internal-log + */ +struct weston_timeline_subscription { + unsigned int next_id; + struct wl_list objects; /**< weston_timeline_subscription_object::subscription_link */ +}; + +/** + * Created when object is first seen for a particular timeline subscription + * Destroyed when the subscription got destroyed or object was destroyed + * + * @ingroup internal-log + */ +struct weston_timeline_subscription_object { + void *object; /**< points to the object */ + unsigned int id; + bool force_refresh; + struct wl_list subscription_link; /**< weston_timeline_subscription::objects */ + struct wl_listener destroy_listener; +}; + +#define TYPEVERIFY(type, arg) ({ \ + typeof(arg) tmp___ = (arg); \ + (void)((type)0 == tmp___); \ + tmp___; }) + +/** + * Should be used as the last argument when using TL_POINT macro + * + * @ingroup log + */ +#define TLP_END TLT_END, NULL + +#define TLP_OUTPUT(o) TLT_OUTPUT, TYPEVERIFY(struct weston_output *, (o)) +#define TLP_SURFACE(s) TLT_SURFACE, TYPEVERIFY(struct weston_surface *, (s)) +#define TLP_VBLANK(t) TLT_VBLANK, TYPEVERIFY(const struct timespec *, (t)) +#define TLP_GPU(t) TLT_GPU, TYPEVERIFY(const struct timespec *, (t)) + +/** This macro is used to add timeline points. + * + * Use TLP_END when done for the vargs. + * + * @param ec weston_compositor instance + * + * @ingroup log + */ +#define TL_POINT(ec, ...) do { \ + weston_timeline_point(ec->timeline, __VA_ARGS__); \ +} while (0) + +void +weston_timeline_point(struct weston_log_scope *timeline_scope, + const char *name, ...); + +#endif /* WESTON_TIMELINE_H */ diff --git a/libweston/touch-calibration.c b/libweston/touch-calibration.c new file mode 100644 index 0000000..9dd99bb --- /dev/null +++ b/libweston/touch-calibration.c @@ -0,0 +1,716 @@ +/* + * Copyright 2017-2018 Collabora, Ltd. + * Copyright 2017-2018 General Electric Company + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include "shared/helpers.h" +#include "shared/string-helpers.h" +#include +#include "shared/timespec-util.h" +#include +#include "libweston-internal.h" +#include "backend.h" + +#include "weston-touch-calibration-server-protocol.h" + +struct weston_touch_calibrator { + struct wl_resource *resource; + + struct weston_compositor *compositor; + + struct weston_surface *surface; + struct wl_listener surface_destroy_listener; + struct wl_listener surface_commit_listener; + + struct weston_touch_device *device; + struct wl_listener device_destroy_listener; + + struct weston_output *output; + struct wl_listener output_destroy_listener; + + struct weston_view *view; + + /** The calibration procedure has been cancelled. */ + bool calibration_cancelled; + + /** The current touch sequence has been cancelled. */ + bool touch_cancelled; +}; + +static struct weston_touch_calibrator * +calibrator_from_device(struct weston_touch_device *device) +{ + return device->aggregate->seat->compositor->touch_calibrator; +} + +static uint32_t +wire_uint_from_double(double c) +{ + assert(c >= 0.0); + assert(c <= 1.0); + + return round(c * 0xffffffff); +} + +static bool +normalized_is_valid(const struct weston_point2d_device_normalized *p) +{ + return p->x >= 0.0 && p->x <= 1.0 && + p->y >= 0.0 && p->y <= 1.0; +} + +WL_EXPORT void +notify_touch_calibrator(struct weston_touch_device *device, + const struct timespec *time, int32_t slot, + const struct weston_point2d_device_normalized *norm, + int touch_type) +{ + struct weston_touch_calibrator *calibrator; + struct wl_resource *res; + uint32_t msecs; + uint32_t x = 0; + uint32_t y = 0; + + calibrator = calibrator_from_device(device); + if (!calibrator) + return; + + res = calibrator->resource; + + /* Ignore any touch events coming from another device */ + if (device != calibrator->device) { + if (touch_type == WL_TOUCH_DOWN) + weston_touch_calibrator_send_invalid_touch(res); + return; + } + + /* Ignore all events if we have sent 'cancel' event until all + * touches (on the seat) are up. + */ + if (calibrator->touch_cancelled) { + if (calibrator->device->aggregate->num_tp == 0) { + assert(touch_type == WL_TOUCH_UP); + calibrator->touch_cancelled = false; + } + return; + } + + msecs = timespec_to_msec(time); + if (touch_type != WL_TOUCH_UP) { + if (normalized_is_valid(norm)) { + x = wire_uint_from_double(norm->x); + y = wire_uint_from_double(norm->y); + } else { + /* Coordinates are out of bounds */ + if (touch_type == WL_TOUCH_MOTION) { + weston_touch_calibrator_send_cancel(res); + calibrator->touch_cancelled = true; + } + weston_touch_calibrator_send_invalid_touch(res); + return; + } + } + + switch (touch_type) { + case WL_TOUCH_UP: + weston_touch_calibrator_send_up(res, msecs, slot); + break; + case WL_TOUCH_DOWN: + weston_touch_calibrator_send_down(res, msecs, slot, x, y); + break; + case WL_TOUCH_MOTION: + weston_touch_calibrator_send_motion(res, msecs, slot, x, y); + break; + default: + return; + } +} + +WL_EXPORT void +notify_touch_calibrator_frame(struct weston_touch_device *device) +{ + struct weston_touch_calibrator *calibrator; + + calibrator = calibrator_from_device(device); + if (!calibrator) + return; + + weston_touch_calibrator_send_frame(calibrator->resource); +} + +WL_EXPORT void +notify_touch_calibrator_cancel(struct weston_touch_device *device) +{ + struct weston_touch_calibrator *calibrator; + + calibrator = calibrator_from_device(device); + if (!calibrator) + return; + + weston_touch_calibrator_send_cancel(calibrator->resource); +} + +static void +map_calibrator(struct weston_touch_calibrator *calibrator) +{ + struct weston_compositor *c = calibrator->compositor; + struct weston_touch_device *device = calibrator->device; + static const struct weston_touch_device_matrix identity = { + .m = { 1, 0, 0, 0, 1, 0} + }; + + assert(!calibrator->view); + assert(calibrator->output); + assert(calibrator->surface); + assert(calibrator->surface->resource); + + calibrator->view = weston_view_create(calibrator->surface); + if (!calibrator->view) { + wl_resource_post_no_memory(calibrator->surface->resource); + return; + } + + weston_layer_entry_insert(&c->calibrator_layer.view_list, + &calibrator->view->layer_link); + + weston_view_set_position(calibrator->view, + calibrator->output->x, + calibrator->output->y); + calibrator->view->output = calibrator->surface->output; + calibrator->view->is_mapped = true; + + calibrator->surface->output = calibrator->output; + calibrator->surface->is_mapped = true; + + weston_output_schedule_repaint(calibrator->output); + + device->ops->get_calibration(device, &device->saved_calibration); + device->ops->set_calibration(device, &identity); +} + +static void +unmap_calibrator(struct weston_touch_calibrator *calibrator) +{ + struct weston_touch_device *device = calibrator->device; + + wl_list_remove(&calibrator->surface_commit_listener.link); + wl_list_init(&calibrator->surface_commit_listener.link); + + if (!calibrator->view) + return; + + weston_view_destroy(calibrator->view); + calibrator->view = NULL; + weston_surface_unmap(calibrator->surface); + + /* Reload saved calibration */ + if (device) + device->ops->set_calibration(device, + &device->saved_calibration); +} + +void +touch_calibrator_mode_changed(struct weston_compositor *compositor) +{ + struct weston_touch_calibrator *calibrator; + + calibrator = compositor->touch_calibrator; + if (!calibrator) + return; + + if (calibrator->calibration_cancelled) + return; + + if (compositor->touch_mode == WESTON_TOUCH_MODE_CALIB) + map_calibrator(calibrator); +} + +static void +touch_calibrator_surface_committed(struct wl_listener *listener, void *data) +{ + struct weston_touch_calibrator *calibrator = + container_of(listener, struct weston_touch_calibrator, + surface_commit_listener); + struct weston_surface *surface = calibrator->surface; + + wl_list_remove(&calibrator->surface_commit_listener.link); + wl_list_init(&calibrator->surface_commit_listener.link); + + if (surface->width != calibrator->output->width || + surface->height != calibrator->output->height) { + wl_resource_post_error(calibrator->resource, + WESTON_TOUCH_CALIBRATOR_ERROR_BAD_SIZE, + "calibrator surface size does not match"); + return; + } + + weston_compositor_set_touch_mode_calib(calibrator->compositor); + /* results in call to touch_calibrator_mode_changed() */ +} + +static void +touch_calibrator_convert(struct wl_client *client, + struct wl_resource *resource, + int32_t x, + int32_t y, + uint32_t coordinate_id) +{ + struct weston_touch_calibrator *calibrator; + struct wl_resource *coordinate_resource; + struct weston_output *output; + struct weston_surface *surface; + uint32_t version; + struct weston_vector p = { { 0.0, 0.0, 0.0, 1.0 } }; + struct weston_point2d_device_normalized norm; + + version = wl_resource_get_version(resource); + calibrator = wl_resource_get_user_data(resource); + surface = calibrator->surface; + output = calibrator->output; + + coordinate_resource = + wl_resource_create(client, &weston_touch_coordinate_interface, + version, coordinate_id); + if (!coordinate_resource) { + wl_client_post_no_memory(client); + return; + } + + if (calibrator->calibration_cancelled) { + weston_touch_coordinate_send_result(coordinate_resource, 0, 0); + wl_resource_destroy(coordinate_resource); + return; + } + + if (!surface || !surface->is_mapped) { + wl_resource_post_error(resource, + WESTON_TOUCH_CALIBRATOR_ERROR_NOT_MAPPED, + "calibrator surface is not mapped"); + return; + } + assert(calibrator->view); + assert(output); + + if (x < 0 || y < 0 || x >= surface->width || y >= surface->height) { + wl_resource_post_error(resource, + WESTON_TOUCH_CALIBRATOR_ERROR_BAD_COORDINATES, + "convert(%d, %d) input is out of bounds", + x, y); + return; + } + + /* Convert from surface-local coordinates into global, from global + * into output-raw, do perspective division and normalize. + */ + weston_view_to_global_float(calibrator->view, x, y, &p.f[0], &p.f[1]); + weston_matrix_transform(&output->matrix, &p); + norm.x = p.f[0] / (p.f[3] * output->current_mode->width); + norm.y = p.f[1] / (p.f[3] * output->current_mode->height); + + if (!normalized_is_valid(&norm)) { + wl_resource_post_error(resource, + WESTON_TOUCH_CALIBRATOR_ERROR_BAD_COORDINATES, + "convert(%d, %d) output is out of bounds", + x, y); + return; + } + + weston_touch_coordinate_send_result(coordinate_resource, + wire_uint_from_double(norm.x), + wire_uint_from_double(norm.y)); + wl_resource_destroy(coordinate_resource); +} + +static void +destroy_touch_calibrator(struct wl_resource *resource) +{ + struct weston_touch_calibrator *calibrator; + + calibrator = wl_resource_get_user_data(resource); + + calibrator->compositor->touch_calibrator = NULL; + + weston_compositor_set_touch_mode_normal(calibrator->compositor); + + if (calibrator->surface) { + unmap_calibrator(calibrator); + wl_list_remove(&calibrator->surface_destroy_listener.link); + wl_list_remove(&calibrator->surface_commit_listener.link); + } + + if (calibrator->device) + wl_list_remove(&calibrator->device_destroy_listener.link); + + if (calibrator->output) + wl_list_remove(&calibrator->output_destroy_listener.link); + + free(calibrator); +} + +static void +touch_calibrator_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct weston_touch_calibrator_interface +touch_calibrator_implementation = { + touch_calibrator_destroy, + touch_calibrator_convert +}; + +static void +touch_calibrator_cancel_calibration(struct weston_touch_calibrator *calibrator) +{ + weston_touch_calibrator_send_cancel_calibration(calibrator->resource); + calibrator->calibration_cancelled = true; + + if (calibrator->surface) + unmap_calibrator(calibrator); +} + +static void +touch_calibrator_output_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_touch_calibrator *calibrator = + container_of(listener, struct weston_touch_calibrator, + output_destroy_listener); + + assert(calibrator->output == data); + calibrator->output = NULL; + + touch_calibrator_cancel_calibration(calibrator); +} + +static void +touch_calibrator_device_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_touch_calibrator *calibrator = + container_of(listener, struct weston_touch_calibrator, + device_destroy_listener); + + assert(calibrator->device == data); + calibrator->device = NULL; + + touch_calibrator_cancel_calibration(calibrator); +} + +static void +touch_calibrator_surface_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_touch_calibrator *calibrator = + container_of(listener, struct weston_touch_calibrator, + surface_destroy_listener); + + assert(calibrator->surface->resource == data); + + unmap_calibrator(calibrator); + calibrator->surface = NULL; +} + +static void +touch_calibration_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static struct weston_touch_device * +weston_compositor_find_touch_device_by_syspath( + struct weston_compositor *compositor, + const char *syspath) +{ + struct weston_seat *seat; + struct weston_touch *touch; + struct weston_touch_device *device; + + if (!syspath) + return NULL; + + wl_list_for_each(seat, &compositor->seat_list, link) { + touch = weston_seat_get_touch(seat); + if (!touch) + continue; + + wl_list_for_each(device, &touch->device_list, link) { + if (strcmp(device->syspath, syspath) == 0) + return device; + } + } + + return NULL; +} + +static void +touch_calibration_create_calibrator( + struct wl_client *client, + struct wl_resource *touch_calibration_resource, + struct wl_resource *surface_resource, + const char *syspath, + uint32_t calibrator_id) +{ + struct weston_compositor *compositor; + struct weston_touch_calibrator *calibrator; + struct weston_touch_device *device; + struct weston_output *output = NULL; + struct weston_surface *surface; + uint32_t version; + int ret; + + version = wl_resource_get_version(touch_calibration_resource); + compositor = wl_resource_get_user_data(touch_calibration_resource); + + if (compositor->touch_calibrator != NULL) { + wl_resource_post_error(touch_calibration_resource, + WESTON_TOUCH_CALIBRATION_ERROR_ALREADY_EXISTS, + "a calibrator has already been created"); + return; + } + + calibrator = zalloc(sizeof *calibrator); + if (!calibrator) { + wl_client_post_no_memory(client); + return; + } + + calibrator->compositor = compositor; + calibrator->resource = wl_resource_create(client, + &weston_touch_calibrator_interface, + version, calibrator_id); + if (!calibrator->resource) { + wl_client_post_no_memory(client); + goto err_dealloc; + } + + surface = wl_resource_get_user_data(surface_resource); + assert(surface); + ret = weston_surface_set_role(surface, "weston_touch_calibrator", + touch_calibration_resource, + WESTON_TOUCH_CALIBRATION_ERROR_INVALID_SURFACE); + if (ret < 0) + goto err_destroy_resource; + + calibrator->surface_destroy_listener.notify = + touch_calibrator_surface_destroyed; + wl_resource_add_destroy_listener(surface->resource, + &calibrator->surface_destroy_listener); + calibrator->surface = surface; + + calibrator->surface_commit_listener.notify = + touch_calibrator_surface_committed; + wl_signal_add(&surface->commit_signal, + &calibrator->surface_commit_listener); + + device = weston_compositor_find_touch_device_by_syspath(compositor, + syspath); + if (device) { + output = device->ops->get_output(device); + if (weston_touch_device_can_calibrate(device) && output) + calibrator->device = device; + } + + if (!calibrator->device) { + wl_resource_post_error(touch_calibration_resource, + WESTON_TOUCH_CALIBRATION_ERROR_INVALID_DEVICE, + "the given touch device '%s' is not valid", + syspath ?: ""); + goto err_unlink_surface; + } + + calibrator->device_destroy_listener.notify = + touch_calibrator_device_destroyed; + wl_signal_add(&calibrator->device->destroy_signal, + &calibrator->device_destroy_listener); + + wl_resource_set_implementation(calibrator->resource, + &touch_calibrator_implementation, + calibrator, destroy_touch_calibrator); + + assert(output); + calibrator->output_destroy_listener.notify = + touch_calibrator_output_destroyed; + wl_signal_add(&output->destroy_signal, + &calibrator->output_destroy_listener); + calibrator->output = output; + + weston_touch_calibrator_send_configure(calibrator->resource, + output->width, + output->height); + + compositor->touch_calibrator = calibrator; + + return; + +err_unlink_surface: + wl_list_remove(&calibrator->surface_commit_listener.link); + wl_list_remove(&calibrator->surface_destroy_listener.link); + +err_destroy_resource: + wl_resource_destroy(calibrator->resource); + +err_dealloc: + free(calibrator); +} + +static void +touch_calibration_save(struct wl_client *client, + struct wl_resource *touch_calibration_resource, + const char *device_name, + struct wl_array *matrix_data) +{ + struct weston_touch_device *device; + struct weston_compositor *compositor; + struct weston_touch_device_matrix calibration; + struct weston_touch_calibrator *calibrator; + int i = 0; + float *c; + + compositor = wl_resource_get_user_data(touch_calibration_resource); + + device = weston_compositor_find_touch_device_by_syspath(compositor, + device_name); + if (!device || !weston_touch_device_can_calibrate(device)) { + wl_resource_post_error(touch_calibration_resource, + WESTON_TOUCH_CALIBRATION_ERROR_INVALID_DEVICE, + "the given device is not valid"); + return; + } + + wl_array_for_each(c, matrix_data) { + calibration.m[i++] = *c; + } + + /* If calibration can't be saved, don't set it as current */ + if (compositor->touch_calibration_save && + compositor->touch_calibration_save(compositor, device, + &calibration) < 0) + return; + + /* If calibrator is still mapped, the compositor will use + * saved_calibration when going back to normal touch handling. + * Continuing calibrating after save request is undefined. */ + calibrator = compositor->touch_calibrator; + if (calibrator && + calibrator->surface && + weston_surface_is_mapped(calibrator->surface)) + device->saved_calibration = calibration; + else + device->ops->set_calibration(device, &calibration); +} + +static const struct weston_touch_calibration_interface +touch_calibration_implementation = { + touch_calibration_destroy, + touch_calibration_create_calibrator, + touch_calibration_save +}; + +static void +bind_touch_calibration(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct weston_compositor *compositor = data; + struct wl_resource *resource; + struct weston_touch_device *device; + struct weston_seat *seat; + struct weston_touch *touch; + const char *name; + + resource = wl_resource_create(client, + &weston_touch_calibration_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, + &touch_calibration_implementation, + compositor, NULL); + + wl_list_for_each(seat, &compositor->seat_list, link) { + touch = weston_seat_get_touch(seat); + if (!touch) + continue; + + wl_list_for_each(device, &touch->device_list, link) { + if (!weston_touch_device_can_calibrate(device)) + continue; + + name = device->ops->get_calibration_head_name(device); + if (!name) + continue; + + weston_touch_calibration_send_touch_device(resource, + device->syspath, name); + } + } +} + +/** Advertise touch_calibration support + * + * \param compositor The compositor to init for. + * \param save The callback function for saving a new calibration, or NULL. + * \return Zero on success, -1 on failure or if already enabled. + * + * Calling this initializes the weston_touch_calibration protocol support, + * so that the interface will be advertised to clients. It is recommended + * to use some mechanism, e.g. wl_display_set_global_filter(), to restrict + * access to the interface. + * + * There is no way to disable this once enabled. + * + * If the save callback is NULL, a new calibration provided by a client will + * always be accepted. If the save callback is not NULL, it must return + * success for the new calibration to be accepted. + */ +WL_EXPORT int +weston_compositor_enable_touch_calibrator(struct weston_compositor *compositor, + weston_touch_calibration_save_func save) +{ + if (compositor->touch_calibration) + return -1; + + compositor->touch_calibration = wl_global_create(compositor->wl_display, + &weston_touch_calibration_interface, 1, + compositor, bind_touch_calibration); + if (!compositor->touch_calibration) + return -1; + + compositor->touch_calibration_save = save; + weston_layer_init(&compositor->calibrator_layer, compositor); + + /* needs to be stacked above everything except lock screen and cursor, + * otherwise the position value is arbitrary */ + weston_layer_set_position(&compositor->calibrator_layer, + WESTON_LAYER_POSITION_TOP_UI + 120); + + return 0; +} diff --git a/libweston/version.h b/libweston/version.h new file mode 100644 index 0000000..0176f69 --- /dev/null +++ b/libweston/version.h @@ -0,0 +1,50 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_VERSION_H +#define WESTON_VERSION_H + +#define WESTON_VERSION_MAJOR 5 +#define WESTON_VERSION_MINOR 0 +#define WESTON_VERSION_MICRO 0 +#define WESTON_VERSION "5.0.0" + +/* This macro may not do what you expect. Weston doesn't guarantee + * a stable API between 1.X and 1.Y, and thus this macro will return + * FALSE on any WESTON_VERSION_AT_LEAST(1,X,0) if the actual version + * is 1.Y.0 and X != Y). In particular, it fails if X < Y, that is, + * 1.3.0 is considered to not be "at least" 1.4.0. + * + * If you want to test for the version number being 1.3.0 or above or + * maybe in a range (eg 1.2.0 to 1.4.0), just use the WESTON_VERSION_* + * defines above directly. + */ + +#define WESTON_VERSION_AT_LEAST(major, minor, micro) \ + (WESTON_VERSION_MAJOR == (major) && \ + WESTON_VERSION_MINOR == (minor) && \ + WESTON_VERSION_MICRO >= (micro)) + +#endif diff --git a/libweston/vertex-clipping.c b/libweston/vertex-clipping.c new file mode 100644 index 0000000..a71e733 --- /dev/null +++ b/libweston/vertex-clipping.c @@ -0,0 +1,330 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include + +#include "vertex-clipping.h" + +float +float_difference(float a, float b) +{ + /* http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/ */ + static const float max_diff = 4.0f * FLT_MIN; + static const float max_rel_diff = 4.0e-5; + float diff = a - b; + float 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 float +clip_intersect_y(float p1x, float p1y, float p2x, float p2y, + float x_arg) +{ + float a; + float 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 float +clip_intersect_x(float p1x, float p1y, float p2x, float p2y, + float y_arg) +{ + float a; + float 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, float x, float y) +{ + *ctx->vertices.x++ = x; + *ctx->vertices.y++ = y; +} + +static enum path_transition +path_transition_left_edge(struct clip_context *ctx, float x, float 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, float x, float 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, float x, float 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, float x, float 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, + float x, float y, float clip_x) +{ + float 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, + float x, float y, float clip_y) +{ + float 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, + float *dst_x, float *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, + float *dst_x, float *dst_y) +{ + enum path_transition trans; + int i; + + if (src->n < 2) + return 0; + + 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, + float *dst_x, float *dst_y) +{ + enum path_transition trans; + int i; + + if (src->n < 2) + return 0; + + 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, + float *dst_x, float *dst_y) +{ + enum path_transition trans; + int i; + + if (src->n < 2) + return 0; + + 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, + float *dst_x, float *dst_y) +{ + enum path_transition trans; + int i; + + if (src->n < 2) + return 0; + + 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, + float *ex, + float *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, + float *ex, + float *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/libweston/vertex-clipping.h b/libweston/vertex-clipping.h new file mode 100644 index 0000000..0c69902 --- /dev/null +++ b/libweston/vertex-clipping.h @@ -0,0 +1,66 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _WESTON_VERTEX_CLIPPING_H +#define _WESTON_VERTEX_CLIPPING_H + +struct polygon8 { + float x[8]; + float y[8]; + int n; +}; + +struct clip_context { + struct { + float x; + float y; + } prev; + + struct { + float x1, y1; + float x2, y2; + } clip; + + struct { + float *x; + float *y; + } vertices; +}; + +float +float_difference(float a, float b); + +int +clip_simple(struct clip_context *ctx, + struct polygon8 *surf, + float *ex, + float *ey); + +int +clip_transformed(struct clip_context *ctx, + struct polygon8 *surf, + float *ex, + float *ey);\ + +#endif diff --git a/libweston/weston-direct-display.c b/libweston/weston-direct-display.c new file mode 100644 index 0000000..bf25b5a --- /dev/null +++ b/libweston/weston-direct-display.c @@ -0,0 +1,92 @@ +/* + * Copyright © 2019 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include "linux-dmabuf.h" +#include "weston-direct-display-server-protocol.h" +#include "libweston-internal.h" + +static void +direct_display_enable(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *dmabuf_res) +{ + struct linux_dmabuf_buffer *dmabuf; + + dmabuf = wl_resource_get_user_data(dmabuf_res); + assert(dmabuf); + dmabuf->direct_display = true; +} + +static void +direct_display_destroy(struct wl_client *client, + struct wl_resource *global_resource) +{ + wl_resource_destroy(global_resource); +} + +static const struct weston_direct_display_v1_interface + weston_direct_display_interface_v1 = { + direct_display_enable, + direct_display_destroy, +}; + +static void +bind_direct_display(struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct wl_resource *resource; + struct weston_compositor *ec = data; + + resource = wl_resource_create(client, + &weston_direct_display_v1_interface, + version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, + &weston_direct_display_interface_v1, + ec, NULL); +} + +WL_EXPORT int +weston_direct_display_setup(struct weston_compositor *ec) +{ + if (!wl_global_create(ec->wl_display, + &weston_direct_display_v1_interface, 1, + ec, bind_direct_display)) + return -1; + + return 0; +} diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c new file mode 100644 index 0000000..521cb2c --- /dev/null +++ b/libweston/weston-launch.c @@ -0,0 +1,916 @@ +/* + * Copyright © 2012 Benjamin Franzke + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "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 + +#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 + +#ifdef BUILD_DRM_COMPOSITOR + +#include + +#else + +static inline int +drmDropMaster(int drm_fd) +{ + return 0; +} + +static inline int +drmSetMaster(int drm_fd) +{ + return 0; +} + +#endif + +/* major()/minor() */ +#ifdef MAJOR_IN_MKDEV +# include +#endif +#ifdef MAJOR_IN_SYSMACROS +# include +#endif + +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(int *ngroups) +{ + int n; + gid_t *groups; + + n = getgroups(0, NULL); + + if (n < 0) { + fprintf(stderr, "Unable to retrieve groups: %s\n", + strerror(errno)); + return NULL; + } + + groups = malloc(n * sizeof(gid_t)); + if (!groups) + return NULL; + + if (getgroups(n, groups) < 0) { + fprintf(stderr, "Unable to retrieve groups: %s\n", + strerror(errno)); + free(groups); + return NULL; + } + + *ngroups = n; + return groups; +} + +static bool +weston_launch_allowed(struct weston_launch *wl) +{ + struct group *gr; + gid_t *groups; + int ngroups; +#ifdef HAVE_SYSTEMD_LOGIN + char *session, *seat; + int err; +#endif + + if (getuid() == 0) + return true; + + gr = getgrnam("weston-launch"); + if (gr) { + groups = read_groups(&ngroups); + if (groups && ngroups > 0) { + while (ngroups--) { + if (groups[ngroups] == gr->gr_gid) { + free(groups); + return true; + } + } + 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 true; + } + free(session); + } +#endif + + return false; +} + +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) { + fprintf(stderr, "weston: socketpair failed: %s\n", + strerror(errno)); + return -1; + } + + if (fcntl(wl->sock[0], F_SETFD, FD_CLOEXEC) < 0) { + fprintf(stderr, "weston: fcntl failed: %s\n", + strerror(errno)); + return -1; + } + + 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 +open_tty_by_number(int ttynr) +{ + int ret; + char filename[16]; + + ret = snprintf(filename, sizeof filename, "/dev/tty%d", ttynr); + if (ret < 0) + return -1; + + return open(filename, O_RDWR | O_NOCTTY); +} + +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: %s\n", + message->path, strerror(errno)); + 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 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_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; + case WESTON_LAUNCHER_DEACTIVATE_DONE: + close_input_fds(wl); + drmDropMaster(wl->drm_fd); + ioctl(wl->tty, VT_RELDISP, 1); + break; + } + + return ret; +} + +static void +quit(struct weston_launch *wl, int status) +{ + struct vt_mode mode = { 0 }; + int err; + int oldtty; + + 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); + } + + /* + * Get a fresh handle to the tty as the previous one is in + * hang-up state since weston (the controlling process for + * the tty) exit at this point. Reopen before closing the + * file descriptor to avoid a potential race condition. + * + * A similar fix exists in logind, see: + * https://github.com/systemd/systemd/pull/990 + */ + oldtty = wl->tty; + wl->tty = open_tty_by_number(wl->ttynr); + close(oldtty); + + if (ioctl(wl->tty, KDSKBMUTE, 0) && + ioctl(wl->tty, KDSKBMODE, wl->kb_mode)) + fprintf(stderr, "failed to restore keyboard mode: %s\n", + strerror(errno)); + + if (ioctl(wl->tty, KDSETMODE, KD_TEXT)) + fprintf(stderr, "failed to set KD_TEXT mode on tty: %s\n", + strerror(errno)); + + /* 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"); + + if (wl->tty != STDIN_FILENO) + close(wl->tty); + + exit(status); +} + +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) { + fprintf(stderr, "weston: reading signalfd failed: %s\n", + strerror(errno)); + 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) + break; + + if (wl->verbose) + fprintf(stderr, "weston-launch: sending %s to pid %d\n", + strsignal(sig.ssi_signo), wl->child); + + kill(wl->child, sig.ssi_signo); + break; + case SIGUSR1: + send_reply(wl, WESTON_LAUNCHER_DEACTIVATE); + 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); + + if (tty0 < 0) { + fprintf(stderr, "weston: could not open tty0: %s\n", + strerror(errno)); + return -1; + } + + if (ioctl(tty0, VT_OPENQRY, &wl->ttynr) < 0 || wl->ttynr == -1) + { + fprintf(stderr, "weston: failed to find non-opened console: %s\n", + strerror(errno)); + return -1; + } + + wl->tty = open_tty_by_number(wl->ttynr); + close(tty0); + } + + if (wl->tty < 0) { + fprintf(stderr, "weston: failed to open tty: %s\n", + strerror(errno)); + return -1; + } + + if (fstat(wl->tty, &buf) == -1 || + major(buf.st_rdev) != TTY_MAJOR || minor(buf.st_rdev) == 0) { + fprintf(stderr, "weston: weston-launch must be run from a virtual terminal\n"); + return -1; + } + + if (!wl->new_user || tty) { + if (fstat(wl->tty, &buf) < 0) { + fprintf(stderr, "weston: stat %s failed: %s\n", tty, + strerror(errno)); + return -1; + } + + if (major(buf.st_rdev) != TTY_MAJOR) { + fprintf(stderr, + "weston: invalid tty device: %s\n", tty); + return -1; + } + + wl->ttynr = minor(buf.st_rdev); + } + + if (ioctl(wl->tty, VT_ACTIVATE, wl->ttynr) < 0) { + fprintf(stderr, + "weston: failed to activate VT: %s\n", + strerror(errno)); + return -1; + } + + if (ioctl(wl->tty, VT_WAITACTIVE, wl->ttynr) < 0) { + fprintf(stderr, + "weston: failed to wait for VT to be active: %s\n", + strerror(errno)); + return -1; + } + + if (ioctl(wl->tty, KDGKBMODE, &wl->kb_mode)) { + fprintf(stderr, + "weston: failed to get current keyboard mode: %s\n", + strerror(errno)); + return -1; + } + + if (ioctl(wl->tty, KDSKBMUTE, 1) && + ioctl(wl->tty, KDSKBMODE, K_OFF)) { + fprintf(stderr, + "weston: failed to set K_OFF keyboard mode: %s\n", + strerror(errno)); + return -1; + } + + if (ioctl(wl->tty, KDSETMODE, KD_GRAPHICS)) { + fprintf(stderr, + "weston: failed to set KD_GRAPHICS mode on tty: %s\n", + strerror(errno)); + return -1; + } + + mode.mode = VT_PROCESS; + mode.relsig = SIGUSR1; + mode.acqsig = SIGUSR2; + if (ioctl(wl->tty, VT_SETMODE, &mode) < 0) { + fprintf(stderr, + "weston: failed to take control of vt handling %s\n", + strerror(errno)); + return -1; + } + + return 0; +} + +static int +setup_session(struct weston_launch *wl, char **child_argv) +{ + char **env; + char *term; + int i; + + if (wl->tty != STDIN_FILENO) { + if (setsid() < 0) { + fprintf(stderr, "weston: setsid failed %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + if (ioctl(wl->tty, TIOCSCTTY, 0) < 0) { + fprintf(stderr, "TIOCSCTTY failed - tty is in use %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + } + + 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) + fprintf(stderr, "putenv %s failed\n", env[i]); + } + free(env); + } + + /* + * We open a new session, so it makes sense + * to run a new login shell + */ + child_argv[0] = "/bin/sh"; + child_argv[1] = "-l"; + child_argv[2] = "-c"; + child_argv[3] = "exec " BINDIR "/weston \"$@\""; + child_argv[4] = "weston"; + return 5; +} + +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) { + fprintf(stderr, "weston: dropping privileges failed %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } +} + +static void +launch_compositor(struct weston_launch *wl, int argc, char *argv[]) +{ + char *child_argv[MAX_ARGV_SIZE]; + sigset_t mask; + int o, i; + + if (wl->verbose) + printf("weston-launch: spawned weston with pid: %d\n", getpid()); + if (wl->new_user) { + o = setup_session(wl, child_argv); + } else { + child_argv[0] = BINDIR "/weston"; + o = 1; + } + for (i = 0; i < argc; ++i) + child_argv[o + i] = argv[i]; + child_argv[o + i] = NULL; + + 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); + + + execv(child_argv[0], child_argv); + fprintf(stderr, "weston: exec failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); +} + +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" + " e.g. -u joe, requires root.\n"); + fprintf(stderr, " -t, --tty Start session on alternative tty,\n" + " e.g. -t /dev/tty4, requires -u option.\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) { + fprintf(stderr, "weston: Permission denied. -u allowed for root only\n"); + exit(EXIT_FAILURE); + } + break; + case 't': + tty = optarg; + break; + case 'v': + wl.verbose = 1; + break; + case 'h': + help("weston-launch"); + exit(EXIT_FAILURE); + default: + exit(EXIT_FAILURE); + } + } + + if ((argc - optind) > (MAX_ARGV_SIZE - 6)) { + fprintf(stderr, + "weston: Too many arguments to pass to weston: %s\n", + strerror(E2BIG)); + exit(EXIT_FAILURE); + } + + if (tty && !wl.new_user) { + fprintf(stderr, "weston: -t/--tty option requires -u/--user option as well\n"); + exit(EXIT_FAILURE); + } + + if (wl.new_user) + wl.pw = getpwnam(wl.new_user); + else + wl.pw = getpwuid(getuid()); + if (wl.pw == NULL) { + fprintf(stderr, "weston: failed to get username: %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + + if (!weston_launch_allowed(&wl)) { + fprintf(stderr, "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.\n"); + exit(EXIT_FAILURE); + } + + 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) { + fprintf(stderr, "weston: fork failed %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + if (wl.child == 0) + launch_compositor(&wl, argc - optind, argv + optind); + + close(wl.sock[1]); + + 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) { + fprintf(stderr, "poll failed: %s\n", strerror(errno)); + return -1; + } + if (fds[0].revents & POLLIN) + handle_socket_msg(&wl); + if (fds[1].revents) + handle_signal(&wl); + } + + return 0; +} diff --git a/libweston/weston-launch.h b/libweston/weston-launch.h new file mode 100644 index 0000000..575d2cf --- /dev/null +++ b/libweston/weston-launch.h @@ -0,0 +1,50 @@ +/* + * Copyright © 2012 Benjamin Franzke + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _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, + WESTON_LAUNCHER_DEACTIVATE_DONE, +}; + +struct weston_launcher_message { + int opcode; +}; + +struct weston_launcher_open { + struct weston_launcher_message header; + int flags; + char path[0]; +}; + +#endif diff --git a/libweston/weston-log-file.c b/libweston/weston-log-file.c new file mode 100644 index 0000000..25e36b4 --- /dev/null +++ b/libweston/weston-log-file.c @@ -0,0 +1,98 @@ +/* + * Copyright © 2019 Collabora Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include "shared/helpers.h" +#include + +#include "weston-log-internal.h" + +#include + +/** File type of stream + */ +struct weston_debug_log_file { + struct weston_log_subscriber base; + FILE *file; +}; + +static struct weston_debug_log_file * +to_weston_debug_log_file(struct weston_log_subscriber *sub) +{ + return container_of(sub, struct weston_debug_log_file, base); +} + +static void +weston_log_file_write(struct weston_log_subscriber *sub, + const char *data, size_t len) +{ + struct weston_debug_log_file *stream = to_weston_debug_log_file(sub); + fwrite(data, len, 1, stream->file); +} + +static void +weston_log_subscriber_destroy_log(struct weston_log_subscriber *subscriber) +{ + struct weston_debug_log_file *file = to_weston_debug_log_file(subscriber); + + weston_log_subscriber_release(subscriber); + free(file); +} + +/** Creates a file type of subscriber + * + * Should be destroyed using weston_log_subscriber_destroy() + * + * @param dump_to if specified, used for writing data to + * @returns a weston_log_subscriber object or NULL in case of failure + * + * @sa weston_log_subscriber_destroy + * + */ +WL_EXPORT struct weston_log_subscriber * +weston_log_subscriber_create_log(FILE *dump_to) +{ + struct weston_debug_log_file *file = zalloc(sizeof(*file)); + + if (!file) + return NULL; + + if (dump_to) + file->file = dump_to; + else + file->file = stderr; + + + file->base.write = weston_log_file_write; + file->base.destroy = weston_log_subscriber_destroy_log; + file->base.destroy_subscription = NULL; + file->base.complete = NULL; + + wl_list_init(&file->base.subscription_list); + + return &file->base; +} diff --git a/libweston/weston-log-flight-rec.c b/libweston/weston-log-flight-rec.c new file mode 100644 index 0000000..7364c81 --- /dev/null +++ b/libweston/weston-log-flight-rec.c @@ -0,0 +1,296 @@ +/* + * Copyright © 2019 Collabora Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include "shared/helpers.h" +#include + +#include "weston-log-internal.h" + +#include +#include +#include +#include +#include +#include + +struct weston_ring_buffer { + uint32_t append_pos; /**< where in the buffer we are */ + uint32_t size; /**< max length of the ring buffer */ + char *buf; /**< the buffer itself */ + FILE *file; /**< where to write in case we need to dump the buf */ + bool overlap; /**< in case buff overlaps, hint from where to print buf contents */ +}; + +/** allows easy access to the ring buffer in case of a core dump + */ +WL_EXPORT struct weston_ring_buffer *weston_primary_flight_recorder_ring_buffer = NULL; + +/** A black box type of stream, used to aggregate data continuously, and + * when needed, to dump its contents for inspection. + */ +struct weston_debug_log_flight_recorder { + struct weston_log_subscriber base; + struct weston_ring_buffer rb; +}; + +static void +weston_ring_buffer_init(struct weston_ring_buffer *rb, size_t size, char *buf) +{ + rb->append_pos = 0; + rb->size = size - 1; + rb->buf = buf; + rb->overlap = false; + rb->file = stderr; +} + +static struct weston_debug_log_flight_recorder * +to_flight_recorder(struct weston_log_subscriber *sub) +{ + return container_of(sub, struct weston_debug_log_flight_recorder, base); +} + +static void +weston_log_flight_recorder_adjust_end(struct weston_ring_buffer *rb, + size_t bytes_to_advance) +{ + if (rb->append_pos == rb->size - bytes_to_advance) + rb->append_pos = 0; + else + rb->append_pos += bytes_to_advance; +} + +static void +weston_log_flight_recorder_write_chunks(struct weston_ring_buffer *rb, + const char *data, size_t len) +{ + /* no of chunks that matches our buffer size */ + size_t nr_chunks = len / rb->size; + + /* bytes left that do not fill entire buffer */ + size_t bytes_left_last_chunk = len % rb->size; + const char *c_data = data; + + /* might overlap multiple times, memcpy is redundant, + * that's why we don't even modify append_pos */ + while (nr_chunks-- > 0) { + memcpy(&rb->buf[rb->append_pos], c_data, rb->size); + c_data += rb->size; + } + + if (bytes_left_last_chunk) + memcpy(&rb->buf[rb->append_pos], c_data, bytes_left_last_chunk); + + /* adjust append_pos */ + weston_log_flight_recorder_adjust_end(rb, bytes_left_last_chunk); +} + +static void +weston_log_flight_recorder_write_chunks_overlap(struct weston_ring_buffer *rb, + const char *data, size_t len) +{ + size_t transfer_remains = + (rb->append_pos + len) - rb->size; + size_t transfer_to_end = len - transfer_remains; + const char *c_data = data; + + /* transfer what remains until the end of the buffer */ + memcpy(&rb->buf[rb->append_pos], c_data, transfer_to_end); + c_data += transfer_to_end; + + /* reset append_pos as we filled up the buffer */ + rb->append_pos = 0; + + /* transfer what remains */ + weston_log_flight_recorder_write_chunks(rb, c_data, transfer_remains); + rb->overlap = true; +} + +static void +weston_log_flight_recorder_write_data(struct weston_ring_buffer *rb, + const char *data, size_t len) +{ + /* + * If append_pos is at the beginning of the buffer, we determine if we + * should do it in chunks, and if there are any bytes left we transfer + * those as well. + * + * If the append_pos is somewhere inside the buffer we determine how + * many bytes we need to transfer between we reach the end and overlap, + * then we proceed as in the first step. + */ + if (rb->append_pos == 0) + weston_log_flight_recorder_write_chunks(rb, data, len); + else + weston_log_flight_recorder_write_chunks_overlap(rb, data, len); +} + +static void +weston_log_flight_recorder_write(struct weston_log_subscriber *sub, + const char *data, size_t len) +{ + struct weston_debug_log_flight_recorder *flight_rec = + to_flight_recorder(sub); + struct weston_ring_buffer *rb = &flight_rec->rb; + + /* in case the data is bigger than the size of the buf */ + if (rb->size < len) { + weston_log_flight_recorder_write_data(rb, data, len); + } else { + /* if we can transfer it without wrapping it do it at once */ + if (rb->append_pos <= rb->size - len) { + memcpy(&rb->buf[rb->append_pos], data, len); + + /* + * adjust append_pos, take care of the situation were + * to fill up the entire buf + */ + weston_log_flight_recorder_adjust_end(rb, len); + } else { + weston_log_flight_recorder_write_data(rb, data, len); + } + } + +} + +static void +weston_log_flight_recorder_map_memory(struct weston_debug_log_flight_recorder *flight_rec) +{ + size_t i = 0; + + for (i = 0; i < flight_rec->rb.size; i++) + flight_rec->rb.buf[i] = 0xff; +} + +static void +weston_log_subscriber_display_flight_rec_data(struct weston_ring_buffer *rb, + FILE *file) +{ + FILE *file_d = stderr; + if (file) + file_d = file; + + if (!rb->overlap) { + if (rb->append_pos) + fwrite(rb->buf, sizeof(char), rb->append_pos, file_d); + else + fwrite(rb->buf, sizeof(char), rb->size, file_d); + } else { + /* from append_pos to size */ + fwrite(&rb->buf[rb->append_pos], sizeof(char), + rb->size - rb->append_pos, file_d); + /* from 0 to append_pos */ + fwrite(rb->buf, sizeof(char), rb->append_pos, file_d); + } +} + +WL_EXPORT void +weston_log_subscriber_display_flight_rec(struct weston_log_subscriber *sub) +{ + struct weston_debug_log_flight_recorder *flight_rec = + to_flight_recorder(sub); + struct weston_ring_buffer *rb = &flight_rec->rb; + + weston_log_subscriber_display_flight_rec_data(rb, rb->file); +} + +static void +weston_log_subscriber_destroy_flight_rec(struct weston_log_subscriber *sub) +{ + struct weston_debug_log_flight_recorder *flight_rec = to_flight_recorder(sub); + + /* Resets weston_primary_flight_recorder_ring_buffer to NULL if it + * is the destroyed subscriber */ + if (weston_primary_flight_recorder_ring_buffer == &flight_rec->rb) + weston_primary_flight_recorder_ring_buffer = NULL; + + weston_log_subscriber_release(sub); + free(flight_rec->rb.buf); + free(flight_rec); +} + +/** Create a flight recorder type of subscriber + * + * Allocates both the flight recorder and the underlying ring buffer. Use + * weston_log_subscriber_destroy() to clean-up. + * + * @param size specify the maximum size (in bytes) of the backing storage + * for the flight recorder + * @returns a weston_log_subscriber object or NULL in case of failure + */ +WL_EXPORT struct weston_log_subscriber * +weston_log_subscriber_create_flight_rec(size_t size) +{ + struct weston_debug_log_flight_recorder *flight_rec; + char *weston_rb; + + assert("Can't create more than one flight recorder." && + !weston_primary_flight_recorder_ring_buffer); + + flight_rec = zalloc(sizeof(*flight_rec)); + if (!flight_rec) + return NULL; + + flight_rec->base.write = weston_log_flight_recorder_write; + flight_rec->base.destroy = weston_log_subscriber_destroy_flight_rec; + flight_rec->base.destroy_subscription = NULL; + flight_rec->base.complete = NULL; + wl_list_init(&flight_rec->base.subscription_list); + + weston_rb = zalloc(sizeof(char) * size); + if (!weston_rb) { + free(flight_rec); + return NULL; + } + + weston_ring_buffer_init(&flight_rec->rb, size, weston_rb); + weston_primary_flight_recorder_ring_buffer = &flight_rec->rb; + + /* write some data to the rb such that the memory gets mapped */ + weston_log_flight_recorder_map_memory(flight_rec); + + return &flight_rec->base; +} + +/** Retrieve flight recorder ring buffer contents, could be useful when + * implementing an assert()-like wrapper. + * + * @param file a FILE type already opened. Can also pass stderr/stdout under gdb + * if the program is loaded into memory. + * + * Uses the global exposed weston_primary_flight_recorder_ring_buffer. + * + */ +WL_EXPORT void +weston_log_flight_recorder_display_buffer(FILE *file) +{ + if (!weston_primary_flight_recorder_ring_buffer) + return; + + weston_log_subscriber_display_flight_rec_data(weston_primary_flight_recorder_ring_buffer, + file); +} diff --git a/libweston/weston-log-internal.h b/libweston/weston-log-internal.h new file mode 100644 index 0000000..f1bfa6a --- /dev/null +++ b/libweston/weston-log-internal.h @@ -0,0 +1,117 @@ +/* + * Copyright © 2019 Collabora Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef WESTON_LOG_INTERNAL_H +#define WESTON_LOG_INTERNAL_H + +#include "wayland-util.h" + +struct weston_log_subscription; + +/** Subscriber allows each type of stream to customize or to provide its own + * methods to manipulate the underlying storage. It follows also an + * object-oriented approach, contains the ops callbacks and a list of + * subcriptions of type weston_log_subscription. Each subscription created will + * be both added to this subscription list and that of the weston_log_scope. + * + * A kind of stream can inherit the subscriber class and provide its own callbacks: + * @code + * struct weston_log_data_stream { + * struct weston_log_subscriber base; + * struct weston_data_stream opaque; + * }; + * @endcode + * + * Passing the base class will require container retrieval type of methods + * to be allowed to reach the opaque type (i.e., container_of()). + * + * @ingroup internal-log + * + */ +struct weston_log_subscriber { + /** write the data pointed by @param data */ + void (*write)(struct weston_log_subscriber *sub, const char *data, size_t len); + /** For destroying the subscriber */ + void (*destroy)(struct weston_log_subscriber *sub); + /** For the type of streams that required additional destroy operation + * for destroying the stream */ + void (*destroy_subscription)(struct weston_log_subscriber *sub); + /** For the type of streams that can inform the 'consumer' part that + * write operation has been terminated/finished and should close the + * stream. + */ + void (*complete)(struct weston_log_subscriber *sub); + struct wl_list subscription_list; /**< weston_log_subscription::owner_link */ +}; + +void +weston_log_subscription_create(struct weston_log_subscriber *owner, + struct weston_log_scope *scope); + +void +weston_log_subscription_destroy(struct weston_log_subscription *sub); + +void +weston_log_subscription_add(struct weston_log_scope *scope, + struct weston_log_subscription *sub); +void +weston_log_subscription_remove(struct weston_log_subscription *sub); + +void +weston_log_subscriber_release(struct weston_log_subscriber *subscriber); + +void +weston_log_bind_weston_debug(struct wl_client *client, + void *data, uint32_t version, uint32_t id); + +struct weston_log_scope * +weston_log_get_scope(struct weston_log_context *log_ctx, const char *name); + +void +weston_log_run_cb_new_subscription(struct weston_log_subscription *sub); + +void +weston_debug_protocol_advertise_scopes(struct weston_log_context *log_ctx, + struct wl_resource *res); + +int +weston_vlog(const char *fmt, va_list ap); +int +weston_vlog_continue(const char *fmt, va_list ap); + +void * +weston_log_subscription_get_data(struct weston_log_subscription *sub); + +void +weston_log_subscription_set_data(struct weston_log_subscription *sub, void *data); + +void +weston_timeline_create_subscription(struct weston_log_subscription *sub, + void *user_data); + +void +weston_timeline_destroy_subscription(struct weston_log_subscription *sub, + void *user_data); + +#endif /* WESTON_LOG_INTERNAL_H */ diff --git a/libweston/weston-log-wayland.c b/libweston/weston-log-wayland.c new file mode 100644 index 0000000..0add772 --- /dev/null +++ b/libweston/weston-log-wayland.c @@ -0,0 +1,290 @@ +/* + * Copyright © 2019 Collabora Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include "shared/helpers.h" +#include + +#include "weston-log-internal.h" +#include "weston-debug-server-protocol.h" + +#include +#include +#include +#include +#include +#include + +/** A debug stream created by a client + * + * A client provides a file descriptor for the server to write debug messages + * into. A weston_log_debug_wayland is associated to one weston_log_scope via the + * scope name, and the scope provides the messages. There can be several + * streams for the same scope, all streams getting the same messages. + * + * The following is specific to weston-debug protocol. + * Subscription/unsubscription takes place in the stream_create(), respectively + * in stream_destroy(). + */ +struct weston_log_debug_wayland { + struct weston_log_subscriber base; + int fd; /**< client provided fd */ + struct wl_resource *resource; /**< weston_debug_stream_v1 object */ +}; + +static struct weston_log_debug_wayland * +to_weston_log_debug_wayland(struct weston_log_subscriber *sub) +{ + return container_of(sub, struct weston_log_debug_wayland, base); +} + +static void +stream_close_unlink(struct weston_log_debug_wayland *stream) +{ + if (stream->fd != -1) + close(stream->fd); + stream->fd = -1; +} + +static void WL_PRINTF(2, 3) +stream_close_on_failure(struct weston_log_debug_wayland *stream, + const char *fmt, ...) +{ + char *msg; + va_list ap; + int ret; + + stream_close_unlink(stream); + + va_start(ap, fmt); + ret = vasprintf(&msg, fmt, ap); + va_end(ap); + + if (ret > 0) { + weston_debug_stream_v1_send_failure(stream->resource, msg); + free(msg); + } else { + weston_debug_stream_v1_send_failure(stream->resource, + "MEMFAIL"); + } +} + +/** Write data into a specific debug stream + * + * \param sub The subscriber's stream to write into; must not be NULL. + * \param[in] data Pointer to the data to write. + * \param len Number of bytes to write. + * + * Writes the given data (binary verbatim) into the debug stream. + * If \c len is zero or negative, the write is silently dropped. + * + * Writing is continued until all data has been written or + * a write fails. If the write fails due to a signal, it is re-tried. + * Otherwise on failure, the stream is closed and + * \c weston_debug_stream_v1.failure event is sent to the client. + * + * \memberof weston_log_debug_wayland + */ +static void +weston_log_debug_wayland_write(struct weston_log_subscriber *sub, + const char *data, size_t len) +{ + ssize_t len_ = len; + ssize_t ret; + int e; + struct weston_log_debug_wayland *stream = to_weston_log_debug_wayland(sub); + + if (stream->fd == -1) + return; + + while (len_ > 0) { + ret = write(stream->fd, data, len_); + e = errno; + if (ret < 0) { + if (e == EINTR) + continue; + + stream_close_on_failure(stream, + "Error writing %zd bytes: %s (%d)", + len_, strerror(e), e); + break; + } + + len_ -= ret; + data += ret; + } +} + +/** Close the debug stream and send success event + * + * \param sub Subscriber's stream to close. + * + * Closes the debug stream and sends \c weston_debug_stream_v1.complete + * event to the client. This tells the client the debug information dump + * is complete. + * + * \memberof weston_log_debug_wayland + */ +static void +weston_log_debug_wayland_complete(struct weston_log_subscriber *sub) +{ + struct weston_log_debug_wayland *stream = to_weston_log_debug_wayland(sub); + + stream_close_unlink(stream); + weston_debug_stream_v1_send_complete(stream->resource); +} + +static void +weston_log_debug_wayland_to_destroy(struct weston_log_subscriber *sub) +{ + struct weston_log_debug_wayland *stream = to_weston_log_debug_wayland(sub); + + if (stream->fd != -1) + stream_close_on_failure(stream, "debug name removed"); +} + +static struct weston_log_debug_wayland * +stream_create(struct weston_log_context *log_ctx, const char *name, + int32_t streamfd, struct wl_resource *stream_resource) +{ + struct weston_log_debug_wayland *stream; + struct weston_log_scope *scope; + + stream = zalloc(sizeof *stream); + if (!stream) + return NULL; + + stream->fd = streamfd; + stream->resource = stream_resource; + + stream->base.write = weston_log_debug_wayland_write; + stream->base.destroy = NULL; + stream->base.destroy_subscription = weston_log_debug_wayland_to_destroy; + stream->base.complete = weston_log_debug_wayland_complete; + wl_list_init(&stream->base.subscription_list); + + scope = weston_log_get_scope(log_ctx, name); + if (scope) { + weston_log_subscription_create(&stream->base, scope); + } else { + stream_close_on_failure(stream, + "Debug stream name '%s' is unknown.", + name); + } + + return stream; +} + +static void +stream_destroy(struct wl_resource *stream_resource) +{ + struct weston_log_debug_wayland *stream; + stream = wl_resource_get_user_data(stream_resource); + + stream_close_unlink(stream); + weston_log_subscriber_release(&stream->base); + free(stream); +} + +static void +weston_debug_stream_destroy(struct wl_client *client, + struct wl_resource *stream_resource) +{ + wl_resource_destroy(stream_resource); +} + +static const struct weston_debug_stream_v1_interface + weston_debug_stream_impl = { + weston_debug_stream_destroy +}; + +static void +weston_debug_destroy(struct wl_client *client, + struct wl_resource *global_resource) +{ + wl_resource_destroy(global_resource); +} + +static void +weston_debug_subscribe(struct wl_client *client, + struct wl_resource *global_resource, + const char *name, + int32_t streamfd, + uint32_t new_stream_id) +{ + struct weston_log_context *log_ctx; + struct wl_resource *stream_resource; + uint32_t version; + struct weston_log_debug_wayland *stream; + + log_ctx = wl_resource_get_user_data(global_resource); + version = wl_resource_get_version(global_resource); + + stream_resource = wl_resource_create(client, + &weston_debug_stream_v1_interface, + version, new_stream_id); + if (!stream_resource) + goto fail; + + stream = stream_create(log_ctx, name, streamfd, stream_resource); + if (!stream) + goto fail; + + wl_resource_set_implementation(stream_resource, + &weston_debug_stream_impl, + stream, stream_destroy); + return; + +fail: + close(streamfd); + wl_client_post_no_memory(client); +} + +static const struct weston_debug_v1_interface weston_debug_impl = { + weston_debug_destroy, + weston_debug_subscribe +}; + +void +weston_log_bind_weston_debug(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct weston_log_context *log_ctx = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, + &weston_debug_v1_interface, + version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &weston_debug_impl, + log_ctx, NULL); + + weston_debug_protocol_advertise_scopes(log_ctx, resource); +} diff --git a/libweston/weston-log.c b/libweston/weston-log.c new file mode 100644 index 0000000..b6f46c3 --- /dev/null +++ b/libweston/weston-log.c @@ -0,0 +1,1011 @@ +/* + * Copyright © 2017 Pekka Paalanen + * Copyright © 2018 Zodiac Inflight Innovations + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include "shared/helpers.h" +#include + +#include "weston-log-internal.h" +#include "weston-debug-server-protocol.h" + +#include +#include +#include +#include +#include +#include + +/** + * @defgroup log Public Logging/Debugging API + * @defgroup internal-log Private/Internal Logging/Debugging API + * @defgroup debug-protocol weston-debug protocol specific + */ + +/** Main weston-log context + * + * One per weston_compositor. Stores list of scopes created and a list pending + * subscriptions. + * + * A pending subscription is a subscription to a scope which hasn't been + * created. When the scope is finally created the pending subscription will be + * removed from the pending subscription list, but not before was added in the + * scope's subscription list and that of the subscriber list. + * + * Pending subscriptions only make sense for other types of streams, other than + * those created by weston-debug protocol. In the case of the weston-debug + * protocol, the subscription processes is done automatically whenever a client + * connects and subscribes to a scope which was previously advertised by the + * compositor. + * + * @ingroup internal-log + */ +struct weston_log_context { + struct wl_global *global; + struct wl_listener compositor_destroy_listener; + struct wl_list scope_list; /**< weston_log_scope::compositor_link */ + struct wl_list pending_subscription_list; /**< weston_log_subscription::source_link */ +}; + +/** weston-log message scope + * + * This is used for scoping logging/debugging messages. Clients can subscribe + * to only the scopes they are interested in. A scope is identified by its name + * (also referred to as debug stream name). + * + * @ingroup log + */ +struct weston_log_scope { + char *name; + char *desc; + weston_log_scope_cb new_subscription; + weston_log_scope_cb destroy_subscription; + void *user_data; + struct wl_list compositor_link; + struct wl_list subscription_list; /**< weston_log_subscription::source_link */ +}; + +/** Ties a subscriber to a scope + * + * A subscription is created each time we'd want to subscribe to a scope. From + * the stream type we can retrieve the subscriber and from the subscriber we + * reach each of the streams callbacks. See also weston_log_subscriber object. + * + * When a subscription has been created we store it in the scope's subscription + * list and in the subscriber's subscription list. The subscription might be a + * pending subscription until the scope for which there's was a subscribe has + * been created. The scope creation will take of looking through the pending + * subscription list. + * + * A subscription can reached from a subscriber subscription list by using the + * streams base class. + * + * @ingroup internal-log + */ +struct weston_log_subscription { + struct weston_log_subscriber *owner; + struct wl_list owner_link; /**< weston_log_subscriber::subscription_list */ + + char *scope_name; + struct weston_log_scope *source; + struct wl_list source_link; /**< weston_log_scope::subscription_list or + weston_log_context::pending_subscription_list */ + + void *data; +}; + +static struct weston_log_subscription * +find_pending_subscription(struct weston_log_context *log_ctx, + const char *scope_name) +{ + struct weston_log_subscription *sub; + + wl_list_for_each(sub, &log_ctx->pending_subscription_list, source_link) + if (!strcmp(sub->scope_name, scope_name)) + return sub; + + return NULL; +} + +/** Create a pending subscription and add it the list of pending subscriptions + * + * @param owner a subscriber represented by weston_log_subscriber object + * @param scope_name the name of the scope (which we don't have in the list of scopes) + * @param log_ctx the logging context used to add the pending subscription + * + * @memberof weston_log_subscription + */ +static void +weston_log_subscription_create_pending(struct weston_log_subscriber *owner, + const char *scope_name, + struct weston_log_context *log_ctx) +{ + assert(owner); + assert(scope_name); + struct weston_log_subscription *sub = zalloc(sizeof(*sub)); + + if (!sub) + return; + + sub->scope_name = strdup(scope_name); + sub->owner = owner; + + wl_list_insert(&log_ctx->pending_subscription_list, &sub->source_link); +} + +/** Destroys the pending subscription created previously with + * weston_log_subscription_create_pending() + * + * @param sub the weston_log_subscription object to remove from the list + * of subscriptions and to destroy the subscription + * + * @memberof weston_log_subscription + */ +static void +weston_log_subscription_destroy_pending(struct weston_log_subscription *sub) +{ + assert(sub); + /* pending subsriptions do not have a source */ + wl_list_remove(&sub->source_link); + free(sub->scope_name); + free(sub); +} + +/** Write to the stream's subscription + * + * @memberof weston_log_subscription + */ +static void +weston_log_subscription_write(struct weston_log_subscription *sub, + const char *data, size_t len) +{ + if (sub->owner && sub->owner->write) + sub->owner->write(sub->owner, data, len); +} + +/** Write a formatted string to the stream's subscription + * + * @memberof weston_log_subscription + */ +static void +weston_log_subscription_vprintf(struct weston_log_subscription *sub, + const char *fmt, va_list ap) +{ + static const char oom[] = "Out of memory"; + char *str; + int len; + + if (!weston_log_scope_is_enabled(sub->source)) + return; + + len = vasprintf(&str, fmt, ap); + if (len >= 0) { + weston_log_subscription_write(sub, str, len); + free(str); + } else { + weston_log_subscription_write(sub, oom, sizeof oom - 1); + } +} + +void +weston_log_subscription_set_data(struct weston_log_subscription *sub, void *data) +{ + /* don't allow data to already be set */ + assert(!sub->data); + sub->data = data; +} + +void * +weston_log_subscription_get_data(struct weston_log_subscription *sub) +{ + return sub->data; +} + +/** Creates a new subscription using the subscriber by \c owner. + * + * The subscription created is added to the \c owner subscription list. + * Destroying the subscription using weston_log_subscription_destroy() will + * remove the link from the subscription list and free storage alloc'ed. + * + * Note that this adds the subscription to the scope's subscription list + * hence the \c scope required argument. + * + * @param owner the subscriber owner, must be created before creating a + * subscription + * @param scope the scope in order to add the subscription to the scope's + * subscription list + * @returns a weston_log_subscription object in case of success, or NULL + * otherwise + * + * @sa weston_log_subscription_destroy, weston_log_subscription_remove, + * weston_log_subscription_add + * @memberof weston_log_subscription + */ +void +weston_log_subscription_create(struct weston_log_subscriber *owner, + struct weston_log_scope *scope) +{ + struct weston_log_subscription *sub; + assert(owner); + assert(scope); + assert(scope->name); + + sub = zalloc(sizeof(*sub)); + if (!sub) + return; + + sub->owner = owner; + sub->scope_name = strdup(scope->name); + + wl_list_insert(&sub->owner->subscription_list, &sub->owner_link); + + weston_log_subscription_add(scope, sub); + weston_log_run_cb_new_subscription(sub); +} + +/** Destroys the subscription + * + * Removes the subscription from the scopes subscription list and from + * subscriber's subscription list. It destroys the subscription afterwads. + * + * @memberof weston_log_subscription + */ +void +weston_log_subscription_destroy(struct weston_log_subscription *sub) +{ + assert(sub); + + if (sub->owner->destroy_subscription) + sub->owner->destroy_subscription(sub->owner); + + if (sub->source->destroy_subscription) + sub->source->destroy_subscription(sub, sub->source->user_data); + + if (sub->owner) + wl_list_remove(&sub->owner_link); + + weston_log_subscription_remove(sub); + free(sub->scope_name); + free(sub); +} + +/** Adds the subscription \c sub to the subscription list of the + * scope. + * + * This should used when the scope has been created, and the subscription \c + * sub has be created before calling this function. + * + * @param scope the scope + * @param sub the subscription, it must be created before, see + * weston_log_subscription_create() + * + * @memberof weston_log_subscription + */ +void +weston_log_subscription_add(struct weston_log_scope *scope, + struct weston_log_subscription *sub) +{ + assert(scope); + assert(sub); + /* don't allow subscriptions to have a source already! */ + assert(!sub->source); + + sub->source = scope; + wl_list_insert(&scope->subscription_list, &sub->source_link); +} + +/** Removes the subscription from the scope's subscription list + * + * @memberof weston_log_subscription + */ +void +weston_log_subscription_remove(struct weston_log_subscription *sub) +{ + assert(sub); + if (sub->source) + wl_list_remove(&sub->source_link); + sub->source = NULL; +} + +/** Look-up the scope from the scope list stored in the log context, by + * matching against the \c name. + * + * @param log_ctx + * @param name the scope name, see weston_log_ctx_add_log_scope() and + * weston_compositor_add_log_scope() + * @returns NULL if none found, or a pointer to a weston_log_scope + * + * @ingroup internal-log + */ +struct weston_log_scope * +weston_log_get_scope(struct weston_log_context *log_ctx, const char *name) +{ + struct weston_log_scope *scope; + wl_list_for_each(scope, &log_ctx->scope_list, compositor_link) + if (strcmp(name, scope->name) == 0) + return scope; + return NULL; +} + +/** Wrapper to invoke the weston_log_scope_cb. Allows to call the cb + * new_subscription of a log scope. + * + * @ingroup internal-log + */ +void +weston_log_run_cb_new_subscription(struct weston_log_subscription *sub) +{ + if (sub->source->new_subscription) + sub->source->new_subscription(sub, sub->source->user_data); +} + +/** Advertise the log scope name and the log scope description + * + * This is only used by the weston-debug protocol! + * + * @ingroup internal-log + */ +void +weston_debug_protocol_advertise_scopes(struct weston_log_context *log_ctx, + struct wl_resource *res) +{ + struct weston_log_scope *scope; + wl_list_for_each(scope, &log_ctx->scope_list, compositor_link) + weston_debug_v1_send_available(res, scope->name, scope->desc); +} + +/** Disable debug-protocol + * + * @param log_ctx The log context where the debug-protocol is linked + * + * @ingroup internal-log + */ +static void +weston_log_ctx_disable_debug_protocol(struct weston_log_context *log_ctx) +{ + if (!log_ctx->global) + return; + + wl_global_destroy(log_ctx->global); + log_ctx->global = NULL; +} + +/** Creates weston_log_context structure + * + * \return NULL in case of failure, or a weston_log_context object in case of + * success + * + * weston_log_context is a singleton for each weston_compositor. + * @ingroup log + * + */ +WL_EXPORT struct weston_log_context * +weston_log_ctx_create(void) +{ + struct weston_log_context *log_ctx; + + log_ctx = zalloc(sizeof *log_ctx); + if (!log_ctx) + return NULL; + + wl_list_init(&log_ctx->scope_list); + wl_list_init(&log_ctx->pending_subscription_list); + wl_list_init(&log_ctx->compositor_destroy_listener.link); + + return log_ctx; +} + +/** Destroy weston_log_context structure + * + * \param log_ctx The log context to destroy. + * + * @ingroup log + * + */ +WL_EXPORT void +weston_log_ctx_destroy(struct weston_log_context *log_ctx) +{ + struct weston_log_scope *scope; + struct weston_log_subscription *pending_sub, *pending_sub_tmp; + + /* We can't destroy the log context if there's still a compositor + * that depends on it. This is an user error */ + assert(wl_list_empty(&log_ctx->compositor_destroy_listener.link)); + + weston_log_ctx_disable_debug_protocol(log_ctx); + + wl_list_for_each(scope, &log_ctx->scope_list, compositor_link) + fprintf(stderr, "Internal warning: debug scope '%s' has not been destroyed.\n", + scope->name); + + /* Remove head to not crash if scope removed later. */ + wl_list_remove(&log_ctx->scope_list); + + /* Remove any pending subscription(s) which nobody subscribed to */ + wl_list_for_each_safe(pending_sub, pending_sub_tmp, + &log_ctx->pending_subscription_list, source_link) { + weston_log_subscription_destroy_pending(pending_sub); + } + + /* pending_subscription_list should be empty at this point */ + + free(log_ctx); +} + +static void +compositor_destroy_listener(struct wl_listener *listener, void *data) +{ + struct weston_log_context *log_ctx = + wl_container_of(listener, log_ctx, compositor_destroy_listener); + + /* We have to keep this list initalized as weston_log_ctx_destroy() has + * to check if there's any compositor destroy listener registered */ + wl_list_remove(&log_ctx->compositor_destroy_listener.link); + wl_list_init(&log_ctx->compositor_destroy_listener.link); + + weston_log_ctx_disable_debug_protocol(log_ctx); +} + +/** Enable weston-debug protocol extension + * + * \param compositor The libweston compositor where to enable. + * + * This enables the weston_debug_v1 Wayland protocol extension which any client + * can use to get debug messages from the compositor. + * + * WARNING: This feature should not be used in production. If a client + * provides a file descriptor that blocks writes, it will block the whole + * compositor indefinitely. + * + * There is no control on which client is allowed to subscribe to debug + * messages. Any and all clients are allowed. + * + * The debug extension is disabled by default, and once enabled, cannot be + * disabled again. + * + * @ingroup debug-protocol + */ +WL_EXPORT void +weston_compositor_enable_debug_protocol(struct weston_compositor *compositor) +{ + struct weston_log_context *log_ctx = compositor->weston_log_ctx; + assert(log_ctx); + if (log_ctx->global) + return; + + log_ctx->global = wl_global_create(compositor->wl_display, + &weston_debug_v1_interface, 1, + log_ctx, weston_log_bind_weston_debug); + if (!log_ctx->global) + return; + + log_ctx->compositor_destroy_listener.notify = compositor_destroy_listener; + wl_signal_add(&compositor->destroy_signal, &log_ctx->compositor_destroy_listener); + + fprintf(stderr, "WARNING: debug protocol has been enabled. " + "This is a potential denial-of-service attack vector and " + "information leak.\n"); +} + +/** Determine if the debug protocol has been enabled + * + * \param wc The libweston compositor to verify if debug protocol has been + * enabled + * + * @ingroup debug-protocol + */ +WL_EXPORT bool +weston_compositor_is_debug_protocol_enabled(struct weston_compositor *wc) +{ + return wc->weston_log_ctx->global != NULL; +} + +/** Register a new stream name, creating a log scope. + * + * @param log_ctx The weston_log_context where to add. + * @param name The debug stream/scope name; must not be NULL. + * @param description The log scope description for humans; must not be NULL. + * @param new_subscription Optional callback when a client subscribes to this + * scope. + * @param destroy_subscription Optional callback when a client destroys the + * subscription. + * @param user_data Optional user data pointer for the callback. + * @returns A valid pointer on success, NULL on failure. + * + * This function is used to create a log scope. All debug message printing + * happens for a scope, which allows clients to subscribe to the kind of debug + * messages they want by \c name. For the weston-debug protocol, + * subscription for the scope will happen automatically but for other types of + * streams, weston_log_subscribe() should be called as to create a subscription + * and tie it to the scope created by this function. + * + * \p name must be unique in the weston_compositor instance. \p name + * and \p description must both be provided. In case of the weston-debug + * protocol, the description is printed when a client asks for a list of + * supported log scopes. + * + * \p new_subscription, if not NULL, is called when a client subscribes to the log + * scope creating a debug stream. This is for log scopes that need to print + * messages as a response to a client appearing, e.g. printing a list of + * windows on demand or a static preamble. The argument \p user_data is + * passed in to the callback and is otherwise unused. + * + * For one-shot debug streams, \c new_subscription should finally call + * weston_log_subscription_complete() to close the stream and tell the client + * the printing is complete. Otherwise the client expects more data to be + * written. The complete callback in weston_log_subscriber should be installed + * to trigger it and it is set-up automatically for the weston-debug protocol. + * + * As subscription can take place before creating the scope, any pending + * subscriptions to scope added by weston_log_subscribe(), will be checked + * against the scope being created and if found will be added to the scope's + * subscription list. + * + * The log scope must be destroyed using weston_log_scope_destroy() + * before destroying the weston_compositor. + * + * @memberof weston_log_scope + * @sa weston_log_scope_cb, weston_log_subscribe + */ +WL_EXPORT struct weston_log_scope * +weston_log_ctx_add_log_scope(struct weston_log_context *log_ctx, + const char *name, + const char *description, + weston_log_scope_cb new_subscription, + weston_log_scope_cb destroy_subscription, + void *user_data) +{ + struct weston_log_scope *scope; + struct weston_log_subscription *pending_sub = NULL; + + if (!name || !description) { + fprintf(stderr, "Error: cannot add a debug scope without name or description.\n"); + return NULL; + } + + if (!log_ctx) { + fprintf(stderr, "Error: cannot add debug scope '%s', infra not initialized.\n", + name); + return NULL; + } + + if (weston_log_get_scope(log_ctx, name)){ + fprintf(stderr, "Error: debug scope named '%s' is already registered.\n", + name); + return NULL; + } + + scope = zalloc(sizeof *scope); + if (!scope) { + fprintf(stderr, "Error adding debug scope '%s': out of memory.\n", + name); + return NULL; + } + + scope->name = strdup(name); + scope->desc = strdup(description); + scope->new_subscription = new_subscription; + scope->destroy_subscription = destroy_subscription; + scope->user_data = user_data; + wl_list_init(&scope->subscription_list); + + if (!scope->name || !scope->desc) { + fprintf(stderr, "Error adding debug scope '%s': out of memory.\n", + name); + free(scope->name); + free(scope->desc); + free(scope); + return NULL; + } + + wl_list_insert(log_ctx->scope_list.prev, &scope->compositor_link); + + /* check if there are any pending subscriptions to this scope */ + while ((pending_sub = find_pending_subscription(log_ctx, scope->name)) != NULL) { + weston_log_subscription_create(pending_sub->owner, scope); + + /* remove it from pending */ + weston_log_subscription_destroy_pending(pending_sub); + } + + + return scope; +} + +/** Register a new stream name, creating a log scope. + * + * @param compositor The compositor that contains the log context where the log + * scope will be linked. + * @param name The debug stream/scope name; must not be NULL. + * @param description The log scope description for humans; must not be NULL. + * @param new_subscription Optional callback when a client subscribes to this + * scope. + * @param destroy_subscription Optional callback when a client destroys the + * subscription. + * @param user_data Optional user data pointer for the callback. + * @returns A valid pointer on success, NULL on failure. + * + * This function works like weston_log_ctx_add_log_scope(), but the log scope + * created is linked to the log context of \c compositor. + * + * @memberof weston_compositor + * @sa weston_log_ctx_add_log_scope + */ +WL_EXPORT struct weston_log_scope * +weston_compositor_add_log_scope(struct weston_compositor *compositor, + const char *name, + const char *description, + weston_log_scope_cb new_subscription, + weston_log_scope_cb destroy_subscription, + void *user_data) +{ + struct weston_log_scope *scope; + scope = weston_log_ctx_add_log_scope(compositor->weston_log_ctx, + name, description, + new_subscription, + destroy_subscription, + user_data); + return scope; +} + +/** Destroy a log scope + * + * @param scope The log scope to destroy; may be NULL. + * + * Destroys the log scope, calling each stream's destroy callback if one was + * installed/created. + * + * @memberof weston_log_scope + */ +WL_EXPORT void +weston_log_scope_destroy(struct weston_log_scope *scope) +{ + struct weston_log_subscription *sub, *sub_tmp; + + if (!scope) + return; + + wl_list_for_each_safe(sub, sub_tmp, &scope->subscription_list, source_link) + weston_log_subscription_destroy(sub); + + wl_list_remove(&scope->compositor_link); + free(scope->name); + free(scope->desc); + free(scope); +} + +/** Are there any active subscriptions to the scope? + * + * \param scope The log scope to check; may be NULL. + * \return True if any streams are open for this scope, false otherwise. + * + * As printing some debugging messages may be relatively expensive, one + * can use this function to determine if there is a need to gather the + * debugging information at all. If this function returns false, all + * printing for this scope is dropped, so gathering the information is + * pointless. + * + * The return value of this function should not be stored, as new clients + * may subscribe to the debug scope later. + * + * If the given scope is NULL, this function will always return false, + * making it safe to use in teardown or destroy code, provided the + * scope is initialized to NULL before creation and set to NULL after + * destruction. + * + * \memberof weston_log_scope + */ +WL_EXPORT bool +weston_log_scope_is_enabled(struct weston_log_scope *scope) +{ + if (!scope) + return false; + + return !wl_list_empty(&scope->subscription_list); +} + +/** Close the stream's complete callback if one was installed/created. + * + * @ingroup log + */ +WL_EXPORT void +weston_log_subscription_complete(struct weston_log_subscription *sub) +{ + if (sub->owner && sub->owner->complete) + sub->owner->complete(sub->owner); +} + +/** Close the log scope. + * + * @param scope The log scope to complete; may be NULL. + * + * Complete the log scope, calling each stream's complete callback if one was + * installed/created. This can be useful to signal the reading end that the + * data has been transmited and should no longer expect that written over the + * stream. Particularly useful for the weston-debug protocol. + * + * @memberof weston_log_scope + * @sa weston_log_ctx_add_log_scope, weston_compositor_add_log_scope, + * weston_log_scope_destroy + */ +WL_EXPORT void +weston_log_scope_complete(struct weston_log_scope *scope) +{ + struct weston_log_subscription *sub; + + if (!scope) + return; + + wl_list_for_each(sub, &scope->subscription_list, source_link) + weston_log_subscription_complete(sub); +} + +/** Write log data for a scope + * + * \param scope The debug scope to write for; may be NULL, in which case + * nothing will be written. + * \param[in] data Pointer to the data to write. + * \param len Number of bytes to write. + * + * Writes the given data to all subscribed clients' streams. + * + * \memberof weston_log_scope + */ +WL_EXPORT void +weston_log_scope_write(struct weston_log_scope *scope, + const char *data, size_t len) +{ + struct weston_log_subscription *sub; + + if (!scope) + return; + + wl_list_for_each(sub, &scope->subscription_list, source_link) + weston_log_subscription_write(sub, data, len); +} + +/** Write a formatted string for a scope (varargs) + * + * \param scope The log scope to write for; may be NULL, in which case + * nothing will be written. + * \param fmt Printf-style format string. + * \param ap Formatting arguments. + * + * Writes to formatted string to all subscribed clients' streams. + * + * The behavioral details for each stream are the same as for + * weston_debug_stream_write(). + * + * \memberof weston_log_scope + */ +WL_EXPORT int +weston_log_scope_vprintf(struct weston_log_scope *scope, + const char *fmt, va_list ap) +{ + static const char oom[] = "Out of memory"; + char *str; + int len = 0; + + if (!weston_log_scope_is_enabled(scope)) + return len; + + len = vasprintf(&str, fmt, ap); + if (len >= 0) { + weston_log_scope_write(scope, str, len); + free(str); + } else { + weston_log_scope_write(scope, oom, sizeof oom - 1); + } + + return len; +} + +/** Write a formatted string for a scope + * + * \param scope The log scope to write for; may be NULL, in which case + * nothing will be written. + * \param fmt Printf-style format string and arguments. + * + * Writes to formatted string to all subscribed clients' streams. + * + * The behavioral details for each stream are the same as for + * weston_debug_stream_write(). + * + * \memberof weston_log_scope + */ +WL_EXPORT int +weston_log_scope_printf(struct weston_log_scope *scope, + const char *fmt, ...) +{ + va_list ap; + int len; + + va_start(ap, fmt); + len = weston_log_scope_vprintf(scope, fmt, ap); + va_end(ap); + + return len; +} + +/** Write a formatted string for a subscription + * + * \param sub The subscription to write for; may be NULL, in which case + * nothing will be written. + * \param fmt Printf-style format string and arguments. + * + * Writes to formatted string to the stream that created the subscription. + * + * @ingroup log + */ +WL_EXPORT void +weston_log_subscription_printf(struct weston_log_subscription *sub, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + weston_log_subscription_vprintf(sub, fmt, ap); + va_end(ap); +} + +/** Write debug scope name and current time into string + * + * \param[in] scope debug scope; may be NULL + * \param[out] buf Buffer to store the string. + * \param len Available size in the buffer in bytes. + * \return \c buf + * + * Reads the current local wall-clock time and formats it into a string. + * and append the debug scope name to it, if a scope is available. + * The string is NUL-terminated, even if truncated. + * + * @memberof weston_log_scope + */ +WL_EXPORT char * +weston_log_scope_timestamp(struct weston_log_scope *scope, + char *buf, size_t len) +{ + struct timeval tv; + struct tm *bdt; + char string[128]; + size_t ret = 0; + + gettimeofday(&tv, NULL); + + bdt = localtime(&tv.tv_sec); + if (bdt) + ret = strftime(string, sizeof string, + "%Y-%m-%d %H:%M:%S", bdt); + + if (ret > 0) { + snprintf(buf, len, "[%s.%03ld][%s]", string, + tv.tv_usec / 1000, + (scope) ? scope->name : "no scope"); + } else { + snprintf(buf, len, "[?][%s]", + (scope) ? scope->name : "no scope"); + } + + return buf; +} + +void +weston_log_subscriber_release(struct weston_log_subscriber *subscriber) +{ + struct weston_log_subscription *sub, *sub_tmp; + + wl_list_for_each_safe(sub, sub_tmp, &subscriber->subscription_list, owner_link) + weston_log_subscription_destroy(sub); +} + +/** Destroy a file type or a flight-rec type subscriber. + * + * They are created, respectively, with weston_log_subscriber_create_log() + * and weston_log_subscriber_create_flight_rec() + * + * @param subscriber the weston_log_subscriber object to destroy + * + * @ingroup log + */ +WL_EXPORT void +weston_log_subscriber_destroy(struct weston_log_subscriber *subscriber) +{ + subscriber->destroy(subscriber); +} + +/** Subscribe to a scope + * + * Creates a subscription which is used to subscribe the \p subscriber + * to the scope \c scope_name. + * + * If \c scope_name has already been created (using + * weston_log_ctx_add_log_scope or weston_compositor_add_log_scope) the + * subscription will take place immediately, otherwise we store the + * subscription into a pending list. See also weston_log_ctx_add_log_scope() + * and weston_compositor_add_log_scope() + * + * @param log_ctx the log context, used for accessing pending list + * @param subscriber the subscriber, which has to be created before + * @param scope_name the scope name. In case the scope is not created + * we temporarily store the subscription in the pending list. + * + * @ingroup log + */ +WL_EXPORT void +weston_log_subscribe(struct weston_log_context *log_ctx, + struct weston_log_subscriber *subscriber, + const char *scope_name) +{ + assert(log_ctx); + assert(subscriber); + assert(scope_name); + + struct weston_log_scope *scope; + + scope = weston_log_get_scope(log_ctx, scope_name); + if (scope) + weston_log_subscription_create(subscriber, scope); + else + /* + * if we don't have already as scope for it, add it to pending + * subscription list + */ + weston_log_subscription_create_pending(subscriber, scope_name, log_ctx); +} + +/** Iterate over all subscriptions in a scope + * + * @param scope the scope for which you want to iterate + * @param sub_iter the iterator, use NULL to start from the 'head' + * @returns the next subscription from the log scope + * + * This is (quite) useful when 'log_scope' and 'log_subscription' are opaque. Do note + * that \c sub_iter needs to be NULL-initialized before calling this function. + * + */ +WL_EXPORT struct weston_log_subscription * +weston_log_subscription_iterate(struct weston_log_scope *scope, + struct weston_log_subscription *sub_iter) +{ + struct wl_list *list = &scope->subscription_list; + struct wl_list *node; + + /* go to the next item in the list or if not set starts with the head */ + if (sub_iter) + node = sub_iter->source_link.next; + else + node = list->next; + + assert(node); + assert(!sub_iter || node != &sub_iter->source_link); + + /* if we're at the end */ + if (node == list) + return NULL; + + return container_of(node, struct weston_log_subscription, source_link); +} diff --git a/libweston/zoom.c b/libweston/zoom.c new file mode 100644 index 0000000..064d6a8 --- /dev/null +++ b/libweston/zoom.c @@ -0,0 +1,184 @@ +/* + * Copyright © 2012 Scott Moreau + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include "backend.h" +#include "libweston-internal.h" +#include "text-cursor-position-server-protocol.h" +#include "shared/helpers.h" + +static void +weston_zoom_frame_z(struct weston_animation *animation, + struct weston_output *output, + const struct timespec *time) +{ + if (animation->frame_counter <= 1) + output->zoom.spring_z.timestamp = *time; + + weston_spring_update(&output->zoom.spring_z, time); + + 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 = false; + output->zoom.seat = NULL; + weston_output_disable_planes_decr(output); + wl_list_remove(&output->zoom.motion_listener.link); + } + 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 void +zoom_area_center_from_point(struct weston_output *output, + double *x, double *y) +{ + float level = output->zoom.spring_z.current; + + *x = (*x - output->x) * level + output->width / 2.; + *y = (*y - output->y) * level + output->height / 2.; +} + +static void +weston_output_update_zoom_transform(struct weston_output *output) +{ + double x = output->zoom.current.x; /* global pointer coords */ + double y = output->zoom.current.y; + float level; + + level = output->zoom.spring_z.current; + + if (!output->zoom.active || level > output->zoom.max_level || + level == 0.0f) + return; + + zoom_area_center_from_point(output, &x, &y); + + output->zoom.trans_x = x - output->width / 2; + output->zoom.trans_y = y - output->height / 2; + + if (output->zoom.trans_x < 0) + output->zoom.trans_x = 0; + if (output->zoom.trans_y < 0) + output->zoom.trans_y = 0; + if (output->zoom.trans_x > level * output->width) + output->zoom.trans_x = level * output->width; + if (output->zoom.trans_y > level * output->height) + output->zoom.trans_y = level * output->height; +} + +static void +weston_zoom_transition(struct weston_output *output) +{ + 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) +{ + struct weston_seat *seat = output->zoom.seat; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + if (!pointer) + return; + + assert(output->zoom.active); + + output->zoom.current.x = wl_fixed_to_double(pointer->x); + output->zoom.current.y = wl_fixed_to_double(pointer->y); + + weston_zoom_transition(output); + weston_output_update_zoom_transform(output); +} + +static void +motion(struct wl_listener *listener, void *data) +{ + struct weston_output_zoom *zoom = + container_of(listener, struct weston_output_zoom, motion_listener); + struct weston_output *output = + container_of(zoom, struct weston_output, zoom); + + weston_output_update_zoom(output); +} + +WL_EXPORT void +weston_output_activate_zoom(struct weston_output *output, + struct weston_seat *seat) +{ + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + if (!pointer || output->zoom.active) + return; + + output->zoom.active = true; + output->zoom.seat = seat; + weston_output_disable_planes_incr(output); + wl_signal_add(&pointer->motion_signal, + &output->zoom.motion_listener); +} + +WL_EXPORT void +weston_output_init_zoom(struct weston_output *output) +{ + output->zoom.active = false; + output->zoom.seat = NULL; + 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; + 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); + output->zoom.motion_listener.notify = motion; +} diff --git a/man/meson.build b/man/meson.build new file mode 100644 index 0000000..a2b8edc --- /dev/null +++ b/man/meson.build @@ -0,0 +1,55 @@ +man_conf = configuration_data() +man_conf.set('weston_native_backend', opt_backend_native) +man_conf.set('weston_modules_dir', dir_module_weston) +man_conf.set('libweston_modules_dir', dir_module_libweston) +man_conf.set('weston_shell_client', get_option('desktop-shell-client-default')) +man_conf.set('weston_libexecdir', dir_libexec) +man_conf.set('weston_bindir', dir_bin) +man_conf.set('xserver_path', get_option('xwayland-path')) +man_conf.set('version', version_weston) + +configure_file( + input: 'weston.man', + output: 'weston.1', + install_dir: join_paths(dir_man, 'man1'), + configuration: man_conf +) + +configure_file( + input: 'weston-bindings.man', + output: 'weston-bindings.7', + install_dir: join_paths(dir_man, 'man7'), + configuration: man_conf +) + +configure_file( + input: 'weston-debug.man', + output: 'weston-debug.1', + install_dir: join_paths(dir_man, 'man1'), + configuration: man_conf +) + +configure_file( + input: 'weston.ini.man', + output: 'weston.ini.5', + install_dir: join_paths(dir_man, 'man5'), + configuration: man_conf +) + +if get_option('backend-drm') + configure_file( + input: 'weston-drm.man', + output: 'weston-drm.7', + install_dir: join_paths(dir_man, 'man7'), + configuration: man_conf + ) +endif + +if get_option('backend-rdp') + configure_file( + input: 'weston-rdp.man', + output: 'weston-rdp.7', + install_dir: join_paths(dir_man, 'man7'), + configuration: man_conf + ) +endif diff --git a/man/weston-bindings.man b/man/weston-bindings.man new file mode 100644 index 0000000..e88a9e8 --- /dev/null +++ b/man/weston-bindings.man @@ -0,0 +1,136 @@ +.\" shorthand for double quote that works everywhere. +.ds q \N'34' +.TH weston-bindings 7 "2019-03-27" "Weston @version@" +.SH NAME +weston-bindings \- a list of keyboard bindings for +.B Weston +\- the reference Wayland +compositor +.SH INTRODUCTION +The Weston desktop shell has a number of keyboard shortcuts. They are listed here. +.SH DESCRIPTION +Almost all keyboard shortcuts for +.B Weston +include a specified modifier +.I mod +which is determined in the file +.BR weston.ini (5). +.\" Begin list +.P +.RE +.B mod + Shift + F +.RS 4 +Make active window fullscreen +.P +.RE +.B mod + K +.RS 4 +Kill active window +.P +.RE +.B mod + Shift + M +.RS 4 +Maximize active window +.P +.RE +.B mod + PageUp, mod + PageDown +.RS 4 +Zoom desktop in (or out) +.P +.RE +.B mod + Tab +.RS 4 +Switch active window +.P +.RE +.B mod + Up, mod + Down +.RS 4 +Increment/decrement active workspace number, if there are multiple +.P +.RE +.B mod + Shift + Up, mod + Shift + Down +.RS 4 +Move active window to the succeeding/preceding workspace, if possible +.P +.RE +.B mod + F1/F2/F3/F4/F5/F6 +.RS 4 +Jump to the numbered workspace, if it exists +.P +.RE +.B Ctrl + Alt + Backspace +.RS 4 +If supported, terminate Weston. (Note this combination often is used to hard restart Xorg.) +.P +.RE +.B Ctrl + Alt + F +.RS 4 +Toggle if Weston is fullscreen; only works when nested under a Wayland compositor +.P +.RE +.B Ctrl + Alt + S +.RS 4 +Share output screen, if possible +.P +.RE +.B Ctrl + Alt + F1/F2/F3/F4/F5/F6/F7/F8 +.RS 4 +Switch virtual terminal, if possible +.P +.RE +.B Super + S +.RS 4 +Make a screenshot of the desktop +.P +.RE +.B Super + R +.RS 4 +Start or stop recording video of the desktop + +.SS "TOUCH / MOUSE BINDINGS" + +There are also a number of bindings involving a mouse: +.P +.RE +.B \fI\fI, \fI\fB, \fI\fB +.RS 4 +Activate clicked window +.P +.RE +.B Super + Alt + \fI\fB +.RS 4 +Change the opacity of a window +.P +.RE +.B mod + \fI\fB +.RS 4 +Zoom/magnify the visible desktop +.P +.RE +.B mod + \fI\fB +.RS 4 +Click and drag to move a window +.P +.RE +.B mod + Shift + \fI\fB, mod + \fI\fB, mod + \fI\fB +.RS 4 +Click and drag to resize a window +.P +.RE +.B mod + \fI\fB +.RS 4 +Rotate the window (if supported) + +.SS DEBUG BINDINGS +The combination \fBmod + Shift + Space\fR begins a debug binding. Debug +bindings are completed by pressing an additional key. For example, pressing +F may toggle texture mesh wireframes with the GL renderer. +(In fact, most debug effects can be disabled again by repeating the command.) +Debug bindings are often tied to specific backends. + +.SH "SEE ALSO" +.BR weston (1), +.BR weston-launch (1), +.BR weston-drm (7), +.BR weston.ini (5), +.BR xkeyboard-config (7) diff --git a/man/weston-debug.man b/man/weston-debug.man new file mode 100644 index 0000000..907783e --- /dev/null +++ b/man/weston-debug.man @@ -0,0 +1,51 @@ +.TH WESTON-DEBUG 1 "2018-09-11" "Weston @version@" +.SH NAME +weston-debug \- a tool for getting debug messages from compositor. +.SH SYNOPSIS +.B weston-debug [options] [names] +. +.\" *************************************************************** +.SH DESCRIPTION + +.B weston-debug +is a debugging tool which uses weston_debug_v1 interface to get the +debug messages from the compositor. The debug messages are categorized into different +debug streams by the compositor (example: logs, proto, list, etc.,) and the compositor +requires a file descriptor to stream the messages. + +This tool accepts a file name or a file descriptor (not both) and any desired debug stream +names from the user as command line arguments and subscribes the desired streams from the +compositor by using the weston_debug_v1 interface. After the subscription, the +compositor will start to write the debug messages to the shared file descriptor. + +If no file name or file descriptor argument is given, the tool will use the stdout file +descriptor. + +. +.\" *************************************************************** +.SH OPTIONS +. +.B weston-debug +accepts the following command line options. +.TP +. B \-h, \-\-help +Print the help text and exit with success. +.TP +. B \-l, \-\-list +List the available debug streams supported by the compositor. May be used +together with --all or a list of debug stream names. +.TP +. B \-a, \-\-all +Bind all debug streams offered by the compositor. Mututally exclusive with +explicitly specifying stream names. +.TP +. B \-o FILE, \-\-output FILE +Direct output to file named FILE. Use - for stdout. +Stdout is the default. Mutually exclusive with -f. +.TP +. B \-f FD, \-\-outfd FD +Direct output to the file descriptor FD. +Stdout (1) is the default. Mutually exclusive with -o. +.TP +.B [names] +A list of debug streams to bind to. Mutually exclusive with --all. diff --git a/man/weston-drm.man b/man/weston-drm.man new file mode 100644 index 0000000..01a336e --- /dev/null +++ b/man/weston-drm.man @@ -0,0 +1,240 @@ +.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 is 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 . + +The DRM backend also supports virtual outputs that are transmitted over +an RTP session as a series of JPEG images (RTP payload type 26) to a remote +client. Virtual outputs are configured in the +.BR remote-output +section of +.BR weston.ini. +. +.\" *************************************************************** +.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: +.TP +\fBmode\fR=\fIwidth\fBx\fIheight\fR +.TP +\fBmode\fR=\fIwidth\fBx\fIheight\fB@\fIrefresh_rate\fR +Specify a mode with a given refresh-rate measured in Hz. +.TP +\fBmode\fR=\fIwidth\fBx\fIheight\fB@\fIrefresh_rate ratio\fR +Here \fIratio\fR is Picture Aspect-Ratio which can have values as 4:3, 16:9, +64:27, and 256:135. This resolution-format helps to select a CEA mode, if such a +video mode is present in the mode-list of the output. + +CEA defines the timing of a video mode, which is considered as a standard for +HDMI spcification and compliance testing. It defines each and every parameter of +a video mode, like hactive, vactive, vfront, vback etc., including aspect-ratio +information. For CEA modes, the drm layer, stores this aspect-ratio information +in user-mode (drmModeModeInfo) flag bits 19-22. For the non-CEA modes a value of +0 is stored in the aspect-ratio flag bits. + +Each CEA-mode is identified by a unique, Video Identification Code (VIC). +For example, VIC=4 is 1280x720@60 aspect-ratio 16:9. This mode will be +different than a non-CEA mode 1280x720@60 0:0. When the video mode +1280x720@60 0:0 is applied, since its timing doesn't exactly match with the CEA +information for VIC=4, it would be treated as a non-CEA mode. Also, while setting +the HDMI-AVI-Inforframe, VIC parameter will be given as '0'. If video mode +1280x720@60 16:9 is applied, its CEA timimgs matches with that of video mode with +VIC=4, so the VIC parameter in HDMI-AVI-Infoframe will be set to 4. + +Many a times, an output may have both CEA and non-CEA modes, which are similar +in all resepct, differing only in the aspect-ratio. A user can select a CEA mode +by giving the aspect-ratio, along with the other arguments for the mode. +By omitting the aspect-ratio, user can specify the non-CEA modes. +This helps when certification testing is done, in tests like 7-27, the +HDMI-analyzer applies a particular CEA mode, and expects the applied mode to be +with exactly same timings, including the aspect-ratio and VIC field. + +The resolution can also be 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 ", " rotate-90 ", " rotate-180 ", " rotate-270 ", " +.BR flipped ", " flipped-rotate-90 ", " flipped-rotate-180 ", and " +.BR flipped-rotate-270 . +.TP +\fBpixman-shadow\fR=\fIboolean\fR +If using the Pixman-renderer, use shadow framebuffers. Defaults to +.BR true . +.TP +\fBsame-as\fR=\fIname\fR +Make this output (connector) a clone of another. The argument +.IR name " is the " +.BR name " value of another output section. The +referred to output section must exist. When this key is present in an +output section, all other keys have no effect on the configuration. + +NOTE: cms-colord plugin does not work correctly with this option. The plugin +chooses an arbitrary monitor to load the color profile for, but the +profile is applied equally to all cloned monitors regardless of their +properties. +.TP +\fBforce-on\fR=\fItrue\fR +Force the output to be enabled even if the connector is disconnected. +Defaults to false. Note that +.BR mode=off " will override " force-on=true . +When a connector is disconnected, there is no EDID information to provide +a list of video modes. Therefore a forced output should also have a +detailed mode line specified. + +.SS Section remote-output +.TP +\fBname\fR=\fIname\fR +Specify unique name for the output. +.TP +\fBmode\fR=\fImode\fR +Specify the video mode for the output. The argument +.I mode +is a resolution setting, such as: +.TP +\fBmode\fR=\fIwidthxheight\fR +.TP +\fBmode\fR=\fIwidthxheight@refresh_rate +If refresh_rate is not specified it will default to a 60Hz. +.TP +\fBhost\fR=\fIhost\fR +Specify the host name or IP Address that the remote output will be +transmitted to. +.TP +\fBport\fR=\fIport\fR +Specify the port number to transmit the remote output to. Usable port range +is 1-65533. +.TP +\fBgst-pipeline\fR=\fIpipeline\fR +Specify the gstreamer pipeline. It is necessary that source is appsrc, +its name is "src", and sink name is "sink" in +.I pipeline\fR. +Ignore port and host configuration if the gst-pipeline is specified. + +. +.\" *************************************************************** +.SH OPTIONS +. +When the DRM backend is loaded, +.B weston +will understand the following additional command line options. +.TP +.B \-\-current\-mode +By default, use the current video mode of all outputs, instead of +switching to the monitor preferred mode. +.TP +\fB\-\-drm\-device\fR=\fIcardN\fR +Use the DRM device +.I cardN +instead of the default heuristics based on seat assignments and boot VGA +status. For example, use +.BR card0 . +.TP +\fB\-\-seat\fR=\fIseatid\fR +Use graphics and input devices designated for seat +.I seatid +instead of the seat defined in the environment variable +.BR XDG_SEAT ". If neither is specified, seat0 will be assumed." +.TP +\fB\-\-tty\fR=\fIx\fR +Launch Weston on tty +.I x +instead of using the current tty. +.TP +.B \-\-continue\-without\-input +Allow Weston to start without input devices. Used for testing purposes. +. +.\" *************************************************************** +.SH ENVIRONMENT +. +.TP +.B WESTON_LIBINPUT_LOG_PRIORITY +The minimum libinput verbosity level to be printed to Weston's log. +Valid values are +.BR debug ", " info ", and " error ". Default is " info . +.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 . +.TP +.B XDG_SEAT +The seat Weston will start on, unless overridden on the command line. +. +.\" *************************************************************** +.SH "SEE ALSO" +.BR weston (1) +.\".BR weston-launch (1), +.\".BR weston.ini (5) diff --git a/man/weston-rdp.man b/man/weston-rdp.man new file mode 100644 index 0000000..f6cdd1d --- /dev/null +++ b/man/weston-rdp.man @@ -0,0 +1,92 @@ +.TH WESTON-RDP 7 "2017-12-14" "Weston @version@" +.SH NAME +weston-rdp \- the RDP backend for Weston +.SH SYNOPSIS +.B weston --backend=rdp-backend.so +. +.\" *************************************************************** +.SH DESCRIPTION +The RDP backend allows to run a +.B weston +environment without the need of specific graphic hardware, or input devices. Users can interact with +.B weston +only by connecting using the RDP protocol. + +The RDP backend uses FreeRDP to implement the RDP part, it acts as a RDP server +listening for incoming connections. It supports different codecs for encoding the +graphical content. Depending on what is supported by the RDP client, the backend will +encode images using remoteFx codec, NS codec or will fallback to raw bitmapUpdate. + +On the security part, the backend supports RDP security or TLS, keys and certificates +must be provided to the backend depending on which kind of security is requested. The RDP +backend will announce security options based on which files have been given. + +The RDP backend is multi-seat aware, so if two clients connect on the backend, +they will get their own seat. + +.\" *************************************************************** +.SH OPTIONS +. +When the RDP backend is loaded, +.B weston +will understand the following additional command line options. +.TP +.B \-\-address\fR=\fIaddress\fR +The IP address on which the RDP backend will listen for RDP connections. By +default it listens on 0.0.0.0. +.TP +\fB\-\-port\fR=\fIport\fR +The TCP port to listen on for connections, it defaults to 3389. +.TP +\fB\-\-no-clients-resize +By default when a client connects on the RDP backend, it will instruct weston to +resize to the dimensions of the client's announced resolution. When this option is +set, weston will force the client to resize to its own resolution. +.TP +\fB\-\-rdp4\-key\fR=\fIfile\fR +The file containing the RSA key for doing RDP security. As RDP security is known +to be insecure, this option should be avoided in production. +.TP +\fB\-\-rdp\-tls\-key\fR=\fIfile\fR +The file containing the key for doing TLS security. To have TLS security you also need +to ship a file containing a certificate. +.TP +\fB\-\-rdp\-tls\-cert\fR=\fIfile\fR +The file containing the certificate for doing TLS security. To have TLS security you also need +to ship a key file. + + +.\" *************************************************************** +.SH Generating cryptographic material for the RDP backend +. +To generate a key file to use for RDP security, you need the +.BR winpr-makecert +utility shipped with FreeRDP: + +.nf +$ winpr-makecert -rdp -silent -n rdp-security +.fi + +This will create a rdp-security.key file. + + +You can generate a key and certificate file to use with TLS security using a typical +.B openssl +invocations: + +.nf +$ openssl genrsa -out tls.key 2048 +Generating RSA private key, 2048 bit long modulus +[...] +$ openssl req -new -key tls.key -out tls.csr +[...] +$ openssl x509 -req -days 365 -signkey tls.key -in tls.csr -out tls.crt +[...] +.fi + +You will get the tls.key and tls.crt files to use with the RDP backend. +. +.\" *************************************************************** +.SH "SEE ALSO" +.BR weston (1) +.\".BR weston.ini (5) diff --git a/man/weston.ini.man b/man/weston.ini.man new file mode 100644 index 0000000..e1364c6 --- /dev/null +++ b/man/weston.ini.man @@ -0,0 +1,663 @@ +.\" shorthand for double quote that works everywhere. +.ds q \N'34' +.TH weston.ini 5 "2019-03-26" "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)" +.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 and options" +.BR "libinput " "Input device configuration" +.BR "shell " "Desktop customization" +.BR "launcher " "Add launcher to the panel" +.BR "output " "Output configuration" +.BR "input-method " "Onscreen keyboard input" +.BR "keyboard " "Keyboard layouts" +.BR "terminal " "Terminal application options" +.BR "xwayland " "XWayland options" +.BR "screen-share " "Screen sharing 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 and general options. +.TP 7 +.BI "shell=" desktop-shell.so +specifies a shell to load (string). This can be used to load your own +implemented shell or one with Weston as default. Available shells +in the +.IR "@weston_modules_dir@" +directory are: +.PP +.RS 10 +.nf +.BR desktop-shell.so +.fi +.RE +.TP 7 +.BI "xwayland=" true +ask Weston to load the XWayland module (boolean). +.RE +.TP 7 +.BI "modules=" cms-colord.so,screen-share.so +specifies the modules to load (string). Available modules in the +.IR "@weston_modules_dir@" +directory are: +.PP +.RS 10 +.nf +.BR cms-colord.so +.BR screen-share.so +.fi +.RE +.TP 7 +.BI "backend=" headless-backend.so +overrides defaults backend. Available backend modules in the +.IR "@libweston_modules_dir@" +directory are: +.PP +.RS 10 +.nf +.BR drm-backend.so +.BR fbdev-backend.so +.BR headless-backend.so +.BR rdp-backend.so +.BR wayland-backend.so +.BR x11-backend.so +.fi +.RE +.TP 7 +.BI "repaint-window=" N +Set the approximate length of the repaint window in milliseconds. The repaint +window is used to control and reduce the output latency for clients. If the +window is longer than the output refresh period, the repaint will be done +immediately when the previous repaint finishes, not processing client requests +in between. If the repaint window is too short, the compositor may miss the +target vertical blank, increasing output latency. The default value is 7 +milliseconds. The allowed range is from -10 to 1000 milliseconds. Using a +negative value will force the compositor to always miss the target vblank. +.TP 7 +.BI "gbm-format="format +sets the GBM format used for the framebuffer for the GBM backend. Can be +.B xrgb8888, +.B xrgb2101010, +.B rgb565. +By default, xrgb8888 is used. +.RS +.PP +.RE +.TP 7 +.BI "idle-time="seconds +sets Weston's idle timeout in seconds. This idle timeout is the time +after which Weston will enter an "inactive" mode and screen will fade to +black. A value of 0 disables the timeout. + +.IR Important +: This option may also be set via Weston's '-i' command +line option and will take precedence over the current .ini option. This +means that if both weston.ini and command line define this idle-timeout +time, the one specified in the command-line will be used. On the other +hand, if none of these sets the value, default idle timeout will be +set to 300 seconds. +.RS +.PP +.RE +.TP 7 +.BI "require-input=" true +require an input device for launch +.TP 7 +.BI "pageflip-timeout="milliseconds +sets Weston's pageflip timeout in milliseconds. This sets a timer to exit +gracefully with a log message and an exit code of 1 in case the DRM driver is +non-responsive. Setting it to 0 disables this feature. +.TP 7 +.BI "wait-for-debugger=" true +Raises SIGSTOP before initializing the compositor. This allows the user to +attach with a debugger and continue execution by sending SIGCONT. This is +useful for debugging a crash on start-up when it would be inconvenient to +launch weston directly from a debugger. Boolean, defaults to +.BR false . +There is also a command line option to do the same. +.TP 7 +.BI "remoting="remoting-plugin.so +specifies a plugin for remote output to load (string). This can be used to load +your own implemented remoting plugin or one with Weston as default. Available +remoting plugins in the +.IR "__libweston_modules_dir__" +directory are: +.PP +.RS 10 +.nf +.BR remoting-plugin.so +.fi +.RE +.TP 7 +.BI "use-pixman=" true +Enables pixman-based rendering for all outputs on backends that support it. +Boolean, defaults to +.BR false . +There is also a command line option to do the same. + +.SH "LIBINPUT SECTION" +The +.B libinput +section is used to configure input devices when using the libinput input device +backend. The defaults are determined by libinput and vary according to what is +most sensible for any given device. +.PP +Available configuration are: +.TP 7 +.BI "enable-tap=" false +Enables tap to click on touchpad devices. +.TP 7 +.BI "tap-and-drag=" false +For touchpad devices with \fBenable-tap\fR enabled. If the user taps, then +taps a second time, this time holding, the virtual mouse button stays down for +as long as the user keeps their finger on the touchpad, allowing the user to +click and drag with taps alone. +.TP 7 +.BI "tap-and-drag-lock=" false +For touchpad devices with \fBenable-tap\fR and \fBtap-and-drag\fR enabled. +In the middle of a tap-and-drag, if the user releases the touchpad for less +than a certain number of milliseconds, then touches it again, the virtual mouse +button will remain pressed and the drag can continue. +.TP 7 +.BI "disable-while-typing=" true +For devices that may be accidentally triggered while typing on the keyboard, +causing a disruption of the typing. Disables them while the keyboard is in +use. +.TP 7 +.BI "middle-button-emulation=" false +For pointer devices with left and right buttons, but no middle button. When +enabled, a middle button event is emitted when the left and right buttons are +pressed simultaneously. +.TP 7 +.BI "left-handed=" false +Configures the device for use by left-handed people. Exactly what this option +does depends on the device. For pointers with left and right buttons, the +buttons are swapped. On tablets, the tablet is logically turned upside down, +because it will be physically turned upside down. +.TP 7 +.BI "rotation=" n +Changes the direction of the logical north, rotating it \fIn\fR degrees +clockwise away from the default orientation, where \fIn\fR is a whole +number between 0 and 359 inclusive. Needed for trackballs, mainly. Allows the +user to orient the trackball sideways, for example. +.TP 7 +.BI "accel-profile=" "{flat,adaptive}" +Set the pointer acceleration profile. The pointer's screen speed is +proportional to the physical speed with a certain constant of proportionality. +Call that constant alpha. \fIflat\fR keeps alpha fixed. See \fBaccel-speed\fR. +\fIadaptive\fR causes alpha to increase with physical speed, giving the user +more control when the speed is slow, and more reach when the speed is high. +\fIadaptive\fR is the default. +.TP 7 +.BI "accel-speed=" v +If \fBaccel-profile\fR is set to \fIflat\fR, it simply sets the value of alpha. +If \fBaccel-profile\fR is set to \fIadaptive\fR, the effect is more +complicated, but generally speaking, it will change the pointer's speed. +\fIv\fR is normalised and must lie in the range [-1, 1]. The exact mapping +between \fIv\fR and alpha is hardware-dependent, but higher values cause higher +cursor speeds. +.TP 7 +.BI "natural-scroll=" false +Enables natural scrolling, mimicking the behaviour of touchscreen scrolling. +That is, if the wheel, finger, or fingers are moved down, the surface is +scrolled up instead of down, as if the finger, or fingers were in contact with +the surface being scrolled. +.TP 7 +.BI "scroll-method=" {two-finger,edge,button,none} +Sets the scroll method. \fItwo-finger\fR scrolls with two fingers on a +touchpad. \fIedge\fR scrolls with one finger on the right edge of a touchpad. +\fIbutton\fR scrolls when the pointer is moved while a certain button is +pressed. See \fBscroll-button\fR. \fInone\fR disables scrolling altogether. +.TP 7 +.BI "scroll-button=" {BTN_LEFT,BTN_RIGHT,BTN_MIDDLE,...} +For devices with \fBscroll-method\fR set to \fIbutton\fR. Specifies the +button that will trigger scrolling. See /usr/include/linux/input-event-codes.h +for the complete list of possible values. +.TP 7 +.BI "touchscreen_calibrator=" true +Advertise the touchscreen calibrator interface to all clients. This is a +potential denial-of-service attack vector, so it should only be enabled on +trusted userspace. Boolean, defaults to +.BR false . + +The interface is required for running touchscreen calibrator applications. It +provides the application raw touch events, bypassing the normal touch handling. +It also allows the application to upload a new calibration into the compositor. + +Even though this option is listed in the libinput section, it does affect all +Weston configurations regardless of the used backend. If the backend does not +use libinput, the interface can still be advertised, but it will not list any +devices. +.TP 7 +.BI "calibration_helper=" /bin/echo +An optional calibration helper program to permanently save a new touchscreen +calibration. String, defaults to unset. + +The given program will be executed with seven arguments when a calibrator +application requests the server to take a new calibration matrix into use. +The program is executed synchronously and will therefore block Weston for its +duration. If the program exit status is non-zero, Weston will not apply the +new calibration. If the helper is unset or the program exit status is zero, +Weston will use the new calibration immediately. + +The program is invoked as: +.PP +.RS 10 +.I calibration_helper syspath m1 m2 m3 m4 m5 m6 +.RE +.RS +.PP +.RI "where " syspath +is the udev sys path for the device and +.IR m1 " through " m6 +are the calibration matrix elements in libinput's +.BR LIBINPUT_CALIBRATION_MATRIX " udev property format." +The sys path is an absolute path and starts with the sys mount point. +.RE + +.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 "client=" file +sets the path for the shell client to run. If not specified +.I @weston_shell_client@ +is launched (string). +.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 centered ", " scale ", " scale-crop " or " tile " (default)." +Centered shows the image once centered. If the image is smaller than the +output, the rest of the surface will be in background color. If the image +size does fit the output it will be cropped left and right, or top and bottom. +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 "clock-format=" format +sets the panel clock format (string). Can be +.BR "none" "," +.BR "minutes" "," +.BR "seconds" "." +By default, minutes format is used. +.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 "panel-position=" top +sets the position of the panel (string). Can be +.B top, +.B bottom, +.B left, +.B right, +.B none. +.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 "close-animation=" fade +sets the effect used when closing windows (string). Can be +.B fade, +.B none. +By default, the fade 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 "focus-animation=" dim-layer +sets the effect used with the focused and unfocused windows. Can be +.B dim-layer, +.B none. +By default, no animation is used. +.TP 7 +.BI "allow-zap=" true +whether the shell should quit when the Ctrl-Alt-Backspace key combination is +pressed +.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. See +.BR weston-bindings (7). +Possible values: none, 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 +.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. All +Wayland output names start with the letters WL. 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" +.BR "WL1 " "Wayland backend, Wayland 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, along with an option of a modeline string. + +See +.B "weston-drm(7)" +for examples of modes-formats supported by DRM backend. +.RE +.TP 7 +.BI "transform=" normal +How you have rotated your monitor from its normal orientation (string). +The transform key can be one of the following 8 strings: +.PP +.RS 10 +.nf +.BR "normal " "Normal output." +.BR "rotate-90 " "90 degrees clockwise." +.BR "rotate-180 " "Upside down." +.BR "rotate-270 " "90 degrees counter clockwise." +.BR "flipped " "Horizontally flipped" +.BR "flipped-rotate-90 " "Flipped and 90 degrees clockwise" +.BR "flipped-rotate-180 " "Flipped and upside down" +.BR "flipped-rotate-270 " "Flipped and 90 degrees counter clockwise" +.fi +.RE +.TP 7 +.BI "scale=" factor +The scaling multiplier applied to the entire output, in support of high +resolution ("HiDPI" or "retina") displays, that roughly corresponds to the +pixel ratio of the display's physical resolution to the logical resolution. +Applications that do not support high resolution displays typically appear tiny +and unreadable. Weston will scale the output of such applications by this +multiplier, to make them readable. Applications that do support their own output +scaling can draw their content in high resolution, in which case they avoid +compositor scaling. Weston will not scale the output of such applications, and +they are not affected by this multiplier. +.RE +.RS +.PP +An integer, 1 by default, typically configured as 2 or higher when needed, +denoting the scaling multiplier for the output. +.RE +.TP 7 +.BI "seat=" name +The logical seat name 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 +.TP 7 +.BI "allow_hdcp=" true +Allows HDCP support for this output. If set to true, HDCP can be tried for the +content-protection, provided by the backends, on this output. By +default, HDCP support is always allowed for an output. The +content-protection can actually be realized, only if the hardware +(source and sink) support HDCP, and the backend has the implementation +of content-protection protocol. Currently, HDCP is supported by drm-backend. +.RE +.TP 7 +.BI "app-ids=" app-id[,app_id]* +A comma separated list of the IDs of applications to place on this output. +These IDs should match the application IDs as set with the xdg_shell.set_app_id +request. Currently, this option is supported by kiosk-shell. +.RE +.SH "INPUT-METHOD SECTION" +.TP 7 +.BI "path=" "@weston_libexecdir@/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 +.TP 7 +.BI "repeat-rate=" "40" +sets the rate of repeating keys in characters per second (unsigned integer) +.RE +.RE +.TP 7 +.BI "repeat-delay=" "400" +sets the delay in milliseconds since key down until repeating starts (unsigned +integer) +.RE +.RE +.TP 7 +.BI "numlock-on=" "false" +sets the default state of the numlock on weston startup for the backends which +support it. +.RE +.RE +.TP 7 +.BI "vt-switching=" "true" +Whether to allow the use of Ctrl+Alt+Fn key combinations to switch away from +the compositor's virtual console. +.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 recommended +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=" "@xserver_path@" +sets the path to the xserver to run (string). +.RE +.RE +.SH "SCREEN-SHARE SECTION" +.TP 7 +.BI "command=" "@weston_bindir@/weston --backend=rdp-backend.so \ +--shell=fullscreen-shell.so --no-clients-resize" +sets the command to start a fullscreen-shell server for screen sharing (string). +.RE +.RE +.SH "SEE ALSO" +.BR weston (1), +.BR weston-bindings (7), +.BR weston-drm (7), +.BR xkeyboard-config (7) diff --git a/man/weston.man b/man/weston.man new file mode 100644 index 0000000..5d20c03 --- /dev/null +++ b/man/weston.man @@ -0,0 +1,377 @@ +.TH WESTON 1 "2019-03-23" "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. If your system supports +the logind D-Bus API and the support has been built into weston as well, +it is possible to start weston with just +.BR weston . + +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. +.TP +.I rdp-backend.so +The RDP backend runs in memory without the need of graphical hardware. Access +to the desktop is done by using the RDP protocol. Each connecting +client has its own seat making it a cheap way to test multi-seat support. See +.BR weston-rdp (7), +if installed. +. +.\" *************************************************************** +.SH SHELLS +Each of these shells have its own public protocol interface for clients. +This means that a client must be specifically written for a shell protocol, +otherwise it will not work. +.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 +Fullscreen shell +Fullscreen shell is intended for a client that needs to take over +whole outputs, often all outputs. This is primarily intended for +running another compositor on Weston. The other compositor does not +need to handle any platform-specifics like DRM/KMS or evdev/libinput. +The shell consists only of the shell plugin +.IR fullscreen-shell.so . +.TP +IVI-shell +In-vehicle infotainment shell is a special purpose shell that exposes +a GENIVI Layer Manager compatible API to controller modules, and a very +simple shell protocol towards clients. IVI-shell starts with loading +.IR ivi-shell.so , +and then a controller module which may launch helper clients. +. +.\" *************************************************************** +.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 the XWayland 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 +\fB\-\^c\fR\fIconfig.ini\fR, \fB\-\-config\fR=\fIconfig.ini\fR +Load +.IR config.ini " instead of " weston.ini . +The argument can also be an absolute path starting with a +.IR / . +If the path is not absolute, it will be searched in the normal config +paths, see +.BR weston.ini (5). +If also +.B --no-config +is given, no configuration file will be read. +.TP +.BR \-\-debug +Enable debug protocol extension +.I weston_debug_v1 +which any client can use to receive debugging messages from the compositor. + +.B WARNING: +This is risky for two reasons. First, a client may cause a denial-of-service +blocking the compositor by providing an unsuitable file descriptor, and +second, the debug messages may expose sensitive information. +Additionally this will expose weston-screenshooter interface allowing the user +to take screenshots of the outputs using weston-screenshooter application, +which can lead to silently leaking the output contents. This option should +not be used in production. +.TP +\fB\-\^l\fIscope1,scope2\fR, \fB\-\-logger-scopes\fR=\fIscope1,scope2\fR +Specify to which log scopes should subscribe to. When no scopes are supplied, +the log "log" scope will be subscribed by default. Useful to control which +streams to write data into the logger and can be helpful in diagnosing early +start-up code. +.TP +\fB\-\^f\fIscope1,scope2\fR, \fB\-\-flight-rec-scopes\fR=\fIscope1,scope2\fR +Specify to which scopes should subscribe to. Useful to control which streams to +write data into the flight recorder. Flight recorder has limited space, once +the flight recorder is full new data will overwrite the old data. Without any +scopes specified, it subscribes to 'log' and 'drm-backend' scopes. +.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, 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\-\-xwayland\fR +Ask Weston to load the XWayland module. +.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 +.BR \-\-no-config +Do not read +.I weston.ini +for the compositor. Avoids e.g. loading compositor modules via the +configuration file, which is useful for unit tests. +.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. +.TP +\fB\-\-wait-for-debugger\fR +Raises SIGSTOP before initializing the compositor. This allows the user to +attach with a debugger and continue execution by sending SIGCONT. This is +useful for debugging a crash on start-up when it would be inconvenient to +launch weston directly from a debugger. There is also a +.IR weston.ini " option to do the same." +. +.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 +.B \-\-fullscreen +Create a single fullscreen output +.TP +\fB\-\-output\-count\fR=\fIN\fR +Create +.I N +Wayland windows to emulate the same number of outputs. +.TP +\fB\-\-width\fR=\fIW\fR, \fB\-\-height\fR=\fIH\fR +Make all outputs have a size of +.IR W x H " pixels." +.TP +.B \-\-scale\fR=\fIN\fR +Give all outputs a scale factor of +.I N. +.TP +.B \-\-use\-pixman +Use the pixman renderer. By default, weston will try to use EGL and +GLES2 for rendering and will fall back to the pixman-based renderer for +software compositing if EGL cannot be used. Passing this option will force +weston to use the pixman renderer. +. +.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 \-\-scale\fR=\fIN\fR +Give all outputs a scale factor of +.I N. +.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. +. +.SS RDP backend options: +See +.BR weston-rdp (7). +. +. +.\" *************************************************************** +.SH FILES +. +If the environment variable is set, the configuration file is read +from the respective path. +.PP +.BI $XDG_CONFIG_HOME /weston.ini +.br +.BI $HOME /.config/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 WESTON_CONFIG_FILE +Weston sets this variable to the absolute path of the configuration file +it loads, or to the empty string if no file is used. Programs that use +.I weston.ini +will read the file specified by this variable instead, or do not read any +file if it is empty. Unset variable causes falling back to the default +name +.IR weston.ini . +.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 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 -- --xwayland +.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-bindings (7), +.BR weston-debug (1), +.BR weston-drm (7), +.BR weston-rdp (7), +.BR weston.ini (5) diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..e7d053b --- /dev/null +++ b/meson.build @@ -0,0 +1,176 @@ +project('weston', + 'c', + version: '9.0.0', + default_options: [ + 'warning_level=3', + 'c_std=gnu99', + 'b_lundef=true', + ], + meson_version: '>= 0.47', + license: 'MIT/Expat', +) + +libweston_major = 9 + +# libweston_revision is manufactured to follow the autotools build's +# library file naming, thanks to libtool +version_weston = meson.project_version() +version_weston_arr = version_weston.split('.') +if libweston_major > version_weston_arr[0].to_int() + if libweston_major > version_weston_arr[0].to_int() + 1 + error('Bad versions in meson.build: libweston_major is too high') + endif + libweston_revision = 0 +elif libweston_major == version_weston_arr[0].to_int() + libweston_revision = version_weston_arr[2].to_int() +else + error('Bad versions in meson.build: libweston_major is too low') +endif + +dir_prefix = get_option('prefix') +dir_bin = join_paths(dir_prefix, get_option('bindir')) +dir_data = join_paths(dir_prefix, get_option('datadir')) +dir_include_libweston = 'libweston-@0@'.format(libweston_major) +dir_include_libweston_install = join_paths(dir_include_libweston, 'libweston') +dir_lib = join_paths(dir_prefix, get_option('libdir')) +dir_libexec = join_paths(dir_prefix, get_option('libexecdir')) +dir_module_weston = join_paths(dir_lib, 'weston') +dir_module_libweston = join_paths(dir_lib, 'libweston-@0@'.format(libweston_major)) +dir_data_pc = join_paths(dir_data, 'pkgconfig') +dir_lib_pc = join_paths(dir_lib, 'pkgconfig') +dir_man = join_paths(dir_prefix, get_option('mandir')) +dir_protocol_libweston = join_paths('libweston-@0@'.format(libweston_major), 'protocols') + +public_inc = include_directories('include') +common_inc = [ include_directories('.'), public_inc ] + +pkgconfig = import('pkgconfig') + +git_version_h = vcs_tag( + input: 'libweston/git-version.h.meson', + output: 'git-version.h', + fallback: version_weston +) + +config_h = configuration_data() + +cc = meson.get_compiler('c') + +global_args = [] +global_args_maybe = [ + '-Wmissing-prototypes', + '-Wno-unused-parameter', + '-Wno-shift-negative-value', # required due to Pixman + '-Wno-missing-field-initializers', + '-Wno-pedantic', + '-fvisibility=hidden', +] +foreach a : global_args_maybe + if cc.has_argument(a) + global_args += a + endif +endforeach +add_global_arguments(global_args, language: 'c') + +if cc.has_header_symbol('sys/sysmacros.h', 'major') + config_h.set('MAJOR_IN_SYSMACROS', 1) +elif cc.has_header_symbol('sys/mkdev.h', 'major') + config_h.set('MAJOR_IN_MKDEV', 1) +endif + +optional_libc_funcs = [ + 'mkostemp', 'strchrnul', 'initgroups', 'posix_fallocate', 'memfd_create' +] +foreach func : optional_libc_funcs + if cc.has_function(func) + config_h.set('HAVE_' + func.to_upper(), 1) + endif +endforeach + +optional_system_headers = [ + 'linux/sync_file.h' +] +foreach hdr : optional_system_headers + if cc.has_header(hdr) + config_h.set('HAVE_' + hdr.underscorify().to_upper(), 1) + endif +endforeach + +env_modmap = '' + +config_h.set('_GNU_SOURCE', '1') +config_h.set('_ALL_SOURCE', '1') +config_h.set('EGL_NO_X11', '1') +config_h.set('MESA_EGL_NO_X11_HEADERS', '1') + +config_h.set_quoted('PACKAGE_STRING', 'weston @0@'.format(version_weston)) +config_h.set_quoted('PACKAGE_VERSION', version_weston) +config_h.set_quoted('VERSION', version_weston) +config_h.set_quoted('PACKAGE_URL', 'https://wayland.freedesktop.org') +config_h.set_quoted('PACKAGE_BUGREPORT', 'https://gitlab.freedesktop.org/wayland/weston/issues/') + +config_h.set_quoted('BINDIR', dir_bin) +config_h.set_quoted('DATADIR', dir_data) +config_h.set_quoted('LIBEXECDIR', dir_libexec) +config_h.set_quoted('MODULEDIR', dir_module_weston) +config_h.set_quoted('LIBWESTON_MODULEDIR', dir_module_libweston) + +config_h.set10('TEST_GL_RENDERER', get_option('test-gl-renderer')) + +backend_default = get_option('backend-default') +if backend_default == 'auto' + foreach b : [ 'headless', 'fbdev', 'x11', 'wayland', 'drm' ] + if get_option('backend-' + b) + backend_default = b + endif + endforeach +endif +opt_backend_native = backend_default + '-backend.so' +config_h.set_quoted('WESTON_NATIVE_BACKEND', opt_backend_native) +message('The default backend is ' + backend_default) +if not get_option('backend-' + backend_default) + error('Backend @0@ was chosen as native but is not being built.'.format(backend_default)) +endif + +dep_xkbcommon = dependency('xkbcommon', version: '>= 0.3.0') +if dep_xkbcommon.version().version_compare('>= 0.5.0') + config_h.set('HAVE_XKBCOMMON_COMPOSE', '1') +endif + +dep_wayland_server = dependency('wayland-server', version: '>= 1.17.0') +dep_wayland_client = dependency('wayland-client', version: '>= 1.17.0') +dep_pixman = dependency('pixman-1', version: '>= 0.25.2') +dep_libinput = dependency('libinput', version: '>= 0.8.0') +dep_libevdev = dependency('libevdev') +dep_libm = cc.find_library('m') +dep_libdl = cc.find_library('dl') +dep_libdrm = dependency('libdrm', version: '>= 2.4.86') +dep_libdrm_headers = dep_libdrm.partial_dependency(compile_args: true) +dep_threads = dependency('threads') + +subdir('include') +subdir('protocol') +subdir('shared') +subdir('libweston') +subdir('libweston-desktop') +subdir('xwayland') +subdir('compositor') +subdir('desktop-shell') +subdir('fullscreen-shell') +subdir('ivi-shell') +subdir('kiosk-shell') +subdir('remoting') +subdir('pipewire') +subdir('clients') +subdir('wcap') +subdir('tests') +subdir('data') +subdir('man') + +configure_file(output: 'config.h', configuration: config_h) + +if get_option('doc') + subdir('doc/sphinx') +else + message('Documentation will not be built. Use -Ddoc to build it.') +endif diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..239bd2d --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,224 @@ +# This option is not implemented: +# --with-cairo=[image|gl|glesv2] Which Cairo renderer to use for the clients +# It is hardcoded to cairo-image for now. + +option( + 'backend-drm', + type: 'boolean', + value: true, + description: 'Weston backend: DRM/KMS' +) +option( + 'backend-drm-screencast-vaapi', + type: 'boolean', + value: true, + description: 'DRM/KMS backend support for VA-API screencasting' +) +option( + 'backend-headless', + type: 'boolean', + value: true, + description: 'Weston backend: headless (testing)' +) +option( + 'backend-rdp', + type: 'boolean', + value: true, + description: 'Weston backend: RDP remote screensharing' +) +option( + 'screenshare', + type: 'boolean', + value: true, + description: 'Compositor: RDP screen-sharing support' +) +option( + 'backend-wayland', + type: 'boolean', + value: true, + description: 'Weston backend: Wayland (nested)' +) +option( + 'backend-x11', + type: 'boolean', + value: true, + description: 'Weston backend: X11 (nested)' +) +option( + 'backend-fbdev', + type: 'boolean', + value: true, + description: 'Weston backend: fbdev' +) +option( + 'backend-default', + type: 'combo', + choices: [ 'auto', 'drm', 'wayland', 'x11', 'fbdev', 'headless' ], + value: 'drm', + description: 'Default backend when no parent display server detected' +) + +option( + 'renderer-gl', + type: 'boolean', + value: true, + description: 'Weston renderer: EGL / OpenGL ES 2.x' +) + +option( + 'weston-launch', + type: 'boolean', + value: true, + description: 'Weston launcher for systems without logind' +) + +option( + 'xwayland', + type: 'boolean', + value: true, + description: 'Xwayland: support for X11 clients inside Weston' +) +option( + 'xwayland-path', + type: 'string', + value: '/usr/bin/Xwayland', + description: 'Xwayland: path to installed Xwayland binary' +) + +option( + 'systemd', + type: 'boolean', + value: true, + description: 'systemd service plugin: state notify, watchdog, socket activation' +) + +option( + 'remoting', + type: 'boolean', + value: true, + description: 'Virtual remote output with GStreamer on DRM backend' +) + +option( + 'pipewire', + type: 'boolean', + value: true, + description: 'Virtual remote output with Pipewire on DRM backend' +) + +option( + 'shell-desktop', + type: 'boolean', + value: true, + description: 'Weston shell UI: traditional desktop' +) +option( + 'shell-fullscreen', + type: 'boolean', + value: true, + description: 'Weston shell UI: fullscreen/kiosk' +) +option( + 'shell-ivi', + type: 'boolean', + value: true, + description: 'Weston shell UI: IVI (automotive)' +) +option( + 'shell-kiosk', + type: 'boolean', + value: true, + description: 'Weston shell UI: kiosk (desktop apps)' +) + +option( + 'desktop-shell-client-default', + type: 'string', + value: 'weston-desktop-shell', + description: 'Weston desktop shell: default helper client selection' +) + +option( + 'color-management-lcms', + type: 'boolean', + value: true, + description: 'Compositor color management: lcms' +) +option( + 'color-management-colord', + type: 'boolean', + value: true, + description: 'Compositor color management: colord (requires lcms)' +) + +option( + 'launcher-logind', + type: 'boolean', + value: true, + description: 'Compositor: support systemd-logind D-Bus protocol' +) + +option( + 'image-jpeg', + type: 'boolean', + value: true, + description: 'JPEG loading support' +) +option( + 'image-webp', + type: 'boolean', + value: true, + description: 'WebP loading support' +) + +option( + 'tools', + type: 'array', + choices: [ 'calibrator', 'debug', 'info', 'terminal', 'touch-calibrator' ], + description: 'List of accessory clients to build and install' +) +option( + 'demo-clients', + type: 'boolean', + value: true, + description: 'Sample clients: toytoolkit demo programs' +) +option( + 'simple-clients', + type: 'array', + choices: [ 'all', 'damage', 'im', 'egl', 'shm', 'touch', 'dmabuf-v4l', 'dmabuf-egl' ], + value: [ 'all' ], + description: 'Sample clients: simple test programs' +) + +option( + 'resize-pool', + type: 'boolean', + value: true, + description: 'Sample clients: optimize window resize performance' +) +option( + 'wcap-decode', + type: 'boolean', + value: true, + description: 'Tools: screen recording decoder tool' +) + +option( + 'test-junit-xml', + type: 'boolean', + value: true, + description: 'Tests: output JUnit XML results' +) +option( + 'test-gl-renderer', + type: 'boolean', + value: true, + description: 'Tests: allow running with GL-renderer' +) +option( + 'doc', + type: 'boolean', + value: false, + description: 'Generate documentation' +) diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..7a755f5 --- /dev/null +++ b/notes.txt @@ -0,0 +1,87 @@ +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. + + + +----------------------------------------------------------------------- +2015-04-14; Pekka Paalanen +http://lists.freedesktop.org/archives/wayland-devel/2015-April/021309.html + +Never destroy weston_views or weston_surfaces from animation hooks. +Never destroy weston_views from weston_view signals. + +Basically, never manipulate a list while transversing it. + + +----------------------------------------------------------------------- +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 practically + 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/pipewire/meson.build b/pipewire/meson.build new file mode 100644 index 0000000..3d3374b --- /dev/null +++ b/pipewire/meson.build @@ -0,0 +1,31 @@ +if get_option('pipewire') + user_hint = 'If you rather not build this, set "pipewire=false".' + + if not get_option('backend-drm') + error('Attempting to build the pipewire plugin without the required DRM backend. ' + user_hint) + endif + + depnames = [ + 'libpipewire-0.2', 'libspa-0.1' + ] + deps_pipewire = [ dep_libweston_private ] + foreach depname : depnames + dep = dependency(depname, required: false) + if not dep.found() + error('Pipewire plugin requires @0@ which was not found. '.format(depname) + user_hint) + endif + deps_pipewire += dep + endforeach + + plugin_pipewire = shared_library( + 'pipewire-plugin', + 'pipewire-plugin.c', + include_directories: common_inc, + dependencies: deps_pipewire, + name_prefix: '', + install: true, + install_dir: dir_module_libweston + ) + env_modmap += 'pipewire-plugin.so=@0@;'.format(plugin_pipewire.full_path()) + install_headers('pipewire-plugin.h', subdir: dir_include_libweston_install) +endif diff --git a/pipewire/pipewire-plugin.c b/pipewire/pipewire-plugin.c new file mode 100644 index 0000000..6f89257 --- /dev/null +++ b/pipewire/pipewire-plugin.c @@ -0,0 +1,855 @@ +/* + * Copyright © 2019 Pengutronix, Michael Olbrich + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "pipewire-plugin.h" +#include "backend.h" +#include "libweston-internal.h" +#include "shared/timespec-util.h" +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#define PROP_RANGE(min, max) 2, (min), (max) + +struct type { + struct spa_type_media_type media_type; + struct spa_type_media_subtype media_subtype; + struct spa_type_format_video format_video; + struct spa_type_video_format video_format; +}; + +struct weston_pipewire { + struct weston_compositor *compositor; + struct wl_list output_list; + struct wl_listener destroy_listener; + const struct weston_drm_virtual_output_api *virtual_output_api; + + struct weston_log_scope *debug; + + struct pw_loop *loop; + struct wl_event_source *loop_source; + + struct pw_core *core; + struct pw_type *t; + struct type type; + + struct pw_remote *remote; + struct spa_hook remote_listener; +}; + +struct pipewire_output { + struct weston_output *output; + void (*saved_destroy)(struct weston_output *output); + int (*saved_enable)(struct weston_output *output); + int (*saved_disable)(struct weston_output *output); + int (*saved_start_repaint_loop)(struct weston_output *output); + + struct weston_head *head; + + struct weston_pipewire *pipewire; + + uint32_t seq; + struct pw_stream *stream; + struct spa_hook stream_listener; + + struct spa_video_info_raw video_format; + + struct wl_event_source *finish_frame_timer; + struct wl_list link; + bool submitted_frame; + enum dpms_enum dpms; +}; + +struct pipewire_frame_data { + struct pipewire_output *output; + int fd; + int stride; + struct drm_fb *drm_buffer; + int fence_sync_fd; + struct wl_event_source *fence_sync_event_source; +}; + +static inline void init_type(struct type *type, struct spa_type_map *map) +{ + spa_type_media_type_map(map, &type->media_type); + spa_type_media_subtype_map(map, &type->media_subtype); + spa_type_format_video_map(map, &type->format_video); + spa_type_video_format_map(map, &type->video_format); +} + +static void +pipewire_debug_impl(struct weston_pipewire *pipewire, + struct pipewire_output *output, + const char *fmt, va_list ap) +{ + FILE *fp; + char *logstr; + size_t logsize; + char timestr[128]; + + if (!weston_log_scope_is_enabled(pipewire->debug)) + return; + + fp = open_memstream(&logstr, &logsize); + if (!fp) + return; + + weston_log_scope_timestamp(pipewire->debug, timestr, sizeof timestr); + fprintf(fp, "%s", timestr); + + if (output) + fprintf(fp, "[%s]", output->output->name); + + fprintf(fp, " "); + vfprintf(fp, fmt, ap); + fprintf(fp, "\n"); + + if (fclose(fp) == 0) + weston_log_scope_write(pipewire->debug, logstr, logsize); + + free(logstr); +} + +static void +pipewire_debug(struct weston_pipewire *pipewire, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + pipewire_debug_impl(pipewire, NULL, fmt, ap); + va_end(ap); +} + +static void +pipewire_output_debug(struct pipewire_output *output, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + pipewire_debug_impl(output->pipewire, output, fmt, ap); + va_end(ap); +} + +static struct weston_pipewire * +weston_pipewire_get(struct weston_compositor *compositor); + +static struct pipewire_output * +lookup_pipewire_output(struct weston_output *base_output) +{ + struct weston_compositor *c = base_output->compositor; + struct weston_pipewire *pipewire = weston_pipewire_get(c); + struct pipewire_output *output; + + wl_list_for_each(output, &pipewire->output_list, link) { + if (output->output == base_output) + return output; + } + return NULL; +} + +static void +pipewire_output_handle_frame(struct pipewire_output *output, int fd, + int stride, struct drm_fb *drm_buffer) +{ + const struct weston_drm_virtual_output_api *api = + output->pipewire->virtual_output_api; + size_t size = output->output->height * stride; + struct pw_type *t = output->pipewire->t; + struct pw_buffer *buffer; + struct spa_buffer *spa_buffer; + struct spa_meta_header *h; + void *ptr; + + if (pw_stream_get_state(output->stream, NULL) != + PW_STREAM_STATE_STREAMING) + goto out; + + buffer = pw_stream_dequeue_buffer(output->stream); + if (!buffer) { + weston_log("Failed to dequeue a pipewire buffer\n"); + goto out; + } + + spa_buffer = buffer->buffer; + + if ((h = spa_buffer_find_meta(spa_buffer, t->meta.Header))) { + h->pts = -1; + h->flags = 0; + h->seq = output->seq++; + h->dts_offset = 0; + } + + ptr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + memcpy(spa_buffer->datas[0].data, ptr, size); + munmap(ptr, size); + + spa_buffer->datas[0].chunk->offset = 0; + spa_buffer->datas[0].chunk->stride = stride; + spa_buffer->datas[0].chunk->size = spa_buffer->datas[0].maxsize; + + pipewire_output_debug(output, "push frame"); + pw_stream_queue_buffer(output->stream, buffer); + +out: + close(fd); + output->submitted_frame = true; + api->buffer_released(drm_buffer); +} + +static int +pipewire_output_fence_sync_handler(int fd, uint32_t mask, void *data) +{ + struct pipewire_frame_data *frame_data = data; + struct pipewire_output *output = frame_data->output; + + pipewire_output_handle_frame(output, frame_data->fd, frame_data->stride, + frame_data->drm_buffer); + + wl_event_source_remove(frame_data->fence_sync_event_source); + close(frame_data->fence_sync_fd); + free(frame_data); + + return 0; +} + +static int +pipewire_output_submit_frame(struct weston_output *base_output, int fd, + int stride, struct drm_fb *drm_buffer) +{ + struct pipewire_output *output = lookup_pipewire_output(base_output); + struct weston_pipewire *pipewire = output->pipewire; + const struct weston_drm_virtual_output_api *api = + pipewire->virtual_output_api; + struct wl_event_loop *loop; + struct pipewire_frame_data *frame_data; + int fence_sync_fd; + + pipewire_output_debug(output, "submit frame: fd = %d drm_fb = %p", + fd, drm_buffer); + + fence_sync_fd = api->get_fence_sync_fd(output->output); + if (fence_sync_fd == -1) { + pipewire_output_handle_frame(output, fd, stride, drm_buffer); + return 0; + } + + frame_data = zalloc(sizeof *frame_data); + if (!frame_data) { + close(fence_sync_fd); + pipewire_output_handle_frame(output, fd, stride, drm_buffer); + return 0; + } + + loop = wl_display_get_event_loop(pipewire->compositor->wl_display); + + frame_data->output = output; + frame_data->fd = fd; + frame_data->stride = stride; + frame_data->drm_buffer = drm_buffer; + frame_data->fence_sync_fd = fence_sync_fd; + frame_data->fence_sync_event_source = + wl_event_loop_add_fd(loop, frame_data->fence_sync_fd, + WL_EVENT_READABLE, + pipewire_output_fence_sync_handler, + frame_data); + + return 0; +} + +static void +pipewire_output_timer_update(struct pipewire_output *output) +{ + int64_t msec; + int32_t refresh; + + if (pw_stream_get_state(output->stream, NULL) == + PW_STREAM_STATE_STREAMING) + refresh = output->output->current_mode->refresh; + else + refresh = 1000; + + msec = millihz_to_nsec(refresh) / 1000000; + wl_event_source_timer_update(output->finish_frame_timer, msec); +} + +static int +pipewire_output_finish_frame_handler(void *data) +{ + struct pipewire_output *output = data; + const struct weston_drm_virtual_output_api *api + = output->pipewire->virtual_output_api; + struct timespec now; + + if (output->submitted_frame) { + struct weston_compositor *c = output->pipewire->compositor; + output->submitted_frame = false; + weston_compositor_read_presentation_clock(c, &now); + api->finish_frame(output->output, &now, 0); + } + + if (output->dpms == WESTON_DPMS_ON) + pipewire_output_timer_update(output); + else + wl_event_source_timer_update(output->finish_frame_timer, 0); + + return 0; +} + +static void +pipewire_output_destroy(struct weston_output *base_output) +{ + struct pipewire_output *output = lookup_pipewire_output(base_output); + struct weston_mode *mode, *next; + + wl_list_for_each_safe(mode, next, &base_output->mode_list, link) { + wl_list_remove(&mode->link); + free(mode); + } + + output->saved_destroy(base_output); + + pw_stream_destroy(output->stream); + + wl_list_remove(&output->link); + weston_head_release(output->head); + free(output->head); + free(output); +} + +static int +pipewire_output_start_repaint_loop(struct weston_output *base_output) +{ + struct pipewire_output *output = lookup_pipewire_output(base_output); + + pipewire_output_debug(output, "start repaint loop"); + output->saved_start_repaint_loop(base_output); + + pipewire_output_timer_update(output); + + return 0; +} + +static void +pipewire_set_dpms(struct weston_output *base_output, enum dpms_enum level) +{ + struct pipewire_output *output = lookup_pipewire_output(base_output); + + if (output->dpms == level) + return; + + output->dpms = level; + pipewire_output_finish_frame_handler(output); +} + +static int +pipewire_output_connect(struct pipewire_output *output) +{ + struct weston_pipewire *pipewire = output->pipewire; + struct type *type = &pipewire->type; + uint8_t buffer[1024]; + struct spa_pod_builder builder = + SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + const struct spa_pod *params[1]; + struct pw_type *t = pipewire->t; + int frame_rate = output->output->current_mode->refresh / 1000; + int width = output->output->width; + int height = output->output->height; + int ret; + + params[0] = spa_pod_builder_object(&builder, + t->param.idEnumFormat, t->spa_format, + "I", type->media_type.video, + "I", type->media_subtype.raw, + ":", type->format_video.format, + "I", type->video_format.BGRx, + ":", type->format_video.size, + "R", &SPA_RECTANGLE(width, height), + ":", type->format_video.framerate, + "F", &SPA_FRACTION(0, 1), + ":", type->format_video.max_framerate, + "Fru", &SPA_FRACTION(frame_rate, 1), + PROP_RANGE(&SPA_FRACTION(1, 1), + &SPA_FRACTION(frame_rate, 1))); + + ret = pw_stream_connect(output->stream, PW_DIRECTION_OUTPUT, NULL, + (PW_STREAM_FLAG_DRIVER | + PW_STREAM_FLAG_MAP_BUFFERS), + params, 1); + if (ret != 0) { + weston_log("Failed to connect pipewire stream: %s", + spa_strerror(ret)); + return -1; + } + + return 0; +} + +static int +pipewire_output_enable(struct weston_output *base_output) +{ + struct pipewire_output *output = lookup_pipewire_output(base_output); + struct weston_compositor *c = base_output->compositor; + const struct weston_drm_virtual_output_api *api + = output->pipewire->virtual_output_api; + struct wl_event_loop *loop; + int ret; + + api->set_submit_frame_cb(base_output, pipewire_output_submit_frame); + + ret = pipewire_output_connect(output); + if (ret < 0) + return ret; + + ret = output->saved_enable(base_output); + if (ret < 0) + return ret; + + output->saved_start_repaint_loop = base_output->start_repaint_loop; + base_output->start_repaint_loop = pipewire_output_start_repaint_loop; + base_output->set_dpms = pipewire_set_dpms; + + loop = wl_display_get_event_loop(c->wl_display); + output->finish_frame_timer = + wl_event_loop_add_timer(loop, + pipewire_output_finish_frame_handler, + output); + output->dpms = WESTON_DPMS_ON; + + return 0; +} + +static int +pipewire_output_disable(struct weston_output *base_output) +{ + struct pipewire_output *output = lookup_pipewire_output(base_output); + + wl_event_source_remove(output->finish_frame_timer); + + pw_stream_disconnect(output->stream); + + return output->saved_disable(base_output); +} + +static void +pipewire_output_stream_state_changed(void *data, enum pw_stream_state old, + enum pw_stream_state state, + const char *error_message) +{ + struct pipewire_output *output = data; + + pipewire_output_debug(output, "state changed %s -> %s", + pw_stream_state_as_string(old), + pw_stream_state_as_string(state)); + + switch (state) { + case PW_STREAM_STATE_STREAMING: + weston_output_schedule_repaint(output->output); + break; + default: + break; + } +} + +static void +pipewire_output_stream_format_changed(void *data, const struct spa_pod *format) +{ + struct pipewire_output *output = data; + struct weston_pipewire *pipewire = output->pipewire; + uint8_t buffer[1024]; + struct spa_pod_builder builder = + SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + const struct spa_pod *params[2]; + struct pw_type *t = pipewire->t; + int32_t width, height, stride, size; + const int bpp = 4; + + if (!format) { + pipewire_output_debug(output, "format = None"); + pw_stream_finish_format(output->stream, 0, NULL, 0); + return; + } + + spa_format_video_raw_parse(format, &output->video_format, + &pipewire->type.format_video); + + width = output->video_format.size.width; + height = output->video_format.size.height; + stride = SPA_ROUND_UP_N(width * bpp, 4); + size = height * stride; + + pipewire_output_debug(output, "format = %dx%d", width, height); + + params[0] = spa_pod_builder_object(&builder, + t->param.idBuffers, t->param_buffers.Buffers, + ":", t->param_buffers.size, + "i", size, + ":", t->param_buffers.stride, + "i", stride, + ":", t->param_buffers.buffers, + "iru", 4, PROP_RANGE(2, 8), + ":", t->param_buffers.align, + "i", 16); + + params[1] = spa_pod_builder_object(&builder, + t->param.idMeta, t->param_meta.Meta, + ":", t->param_meta.type, "I", t->meta.Header, + ":", t->param_meta.size, "i", sizeof(struct spa_meta_header)); + + pw_stream_finish_format(output->stream, 0, params, 2); +} + +static const struct pw_stream_events stream_events = { + PW_VERSION_STREAM_EVENTS, + .state_changed = pipewire_output_stream_state_changed, + .format_changed = pipewire_output_stream_format_changed, +}; + +static struct weston_output * +pipewire_output_create(struct weston_compositor *c, char *name) +{ + struct weston_pipewire *pipewire = weston_pipewire_get(c); + struct pipewire_output *output; + struct weston_head *head; + const struct weston_drm_virtual_output_api *api; + const char *make = "Weston"; + const char *model = "Virtual Display"; + const char *serial_number = "unknown"; + const char *connector_name = "pipewire"; + + if (!name || !strlen(name)) + return NULL; + + api = pipewire->virtual_output_api; + + output = zalloc(sizeof *output); + if (!output) + return NULL; + + head = zalloc(sizeof *head); + if (!head) + goto err; + + output->stream = pw_stream_new(pipewire->remote, name, NULL); + if (!output->stream) { + weston_log("Cannot initialize pipewire stream\n"); + goto err; + } + + pw_stream_add_listener(output->stream, &output->stream_listener, + &stream_events, output); + + output->output = api->create_output(c, name); + if (!output->output) { + weston_log("Cannot create virtual output\n"); + goto err; + } + + output->saved_destroy = output->output->destroy; + output->output->destroy = pipewire_output_destroy; + output->saved_enable = output->output->enable; + output->output->enable = pipewire_output_enable; + output->saved_disable = output->output->disable; + output->output->disable = pipewire_output_disable; + output->pipewire = pipewire; + wl_list_insert(pipewire->output_list.prev, &output->link); + + weston_head_init(head, connector_name); + weston_head_set_subpixel(head, WL_OUTPUT_SUBPIXEL_NONE); + weston_head_set_monitor_strings(head, make, model, serial_number); + head->compositor = c; + output->head = head; + + weston_output_attach_head(output->output, head); + + pipewire_output_debug(output, "created"); + + return output->output; +err: + if (output->stream) + pw_stream_destroy(output->stream); + if (head) + free(head); + free(output); + return NULL; +} + +static bool +pipewire_output_is_pipewire(struct weston_output *output) +{ + return lookup_pipewire_output(output) != NULL; +} + +static int +pipewire_output_set_mode(struct weston_output *base_output, const char *modeline) +{ + struct pipewire_output *output = lookup_pipewire_output(base_output); + const struct weston_drm_virtual_output_api *api = + output->pipewire->virtual_output_api; + struct weston_mode *mode; + int n, width, height, refresh = 0; + + if (output == NULL) { + weston_log("Output is not pipewire.\n"); + return -1; + } + + if (!modeline) + return -1; + + n = sscanf(modeline, "%dx%d@%d", &width, &height, &refresh); + if (n != 2 && n != 3) + return -1; + + if (pw_stream_get_state(output->stream, NULL) != + PW_STREAM_STATE_UNCONNECTED) { + return -1; + } + + mode = zalloc(sizeof *mode); + if (!mode) + return -1; + + pipewire_output_debug(output, "mode = %dx%d@%d", width, height, refresh); + + mode->flags = WL_OUTPUT_MODE_CURRENT; + mode->width = width; + mode->height = height; + mode->refresh = (refresh ? refresh : 60) * 1000LL; + + wl_list_insert(base_output->mode_list.prev, &mode->link); + + base_output->current_mode = mode; + + api->set_gbm_format(base_output, "XRGB8888"); + + return 0; +} + +static void +pipewire_output_set_seat(struct weston_output *output, const char *seat) +{ +} + +static void +weston_pipewire_destroy(struct wl_listener *l, void *data) +{ + struct weston_pipewire *pipewire = + wl_container_of(l, pipewire, destroy_listener); + + weston_log_scope_destroy(pipewire->debug); + pipewire->debug = NULL; + + wl_event_source_remove(pipewire->loop_source); + pw_loop_leave(pipewire->loop); + pw_loop_destroy(pipewire->loop); +} + +static struct weston_pipewire * +weston_pipewire_get(struct weston_compositor *compositor) +{ + struct wl_listener *listener; + struct weston_pipewire *pipewire; + + listener = wl_signal_get(&compositor->destroy_signal, + weston_pipewire_destroy); + if (!listener) + return NULL; + + pipewire = wl_container_of(listener, pipewire, destroy_listener); + return pipewire; +} + +static int +weston_pipewire_loop_handler(int fd, uint32_t mask, void *data) +{ + struct weston_pipewire *pipewire = data; + int ret; + + ret = pw_loop_iterate(pipewire->loop, 0); + if (ret < 0) + weston_log("pipewire_loop_iterate failed: %s", + spa_strerror(ret)); + + return 0; +} + +static void +weston_pipewire_state_changed(void *data, enum pw_remote_state old, + enum pw_remote_state state, const char *error) +{ + struct weston_pipewire *pipewire = data; + + pipewire_debug(pipewire, "[remote] state changed %s -> %s", + pw_remote_state_as_string(old), + pw_remote_state_as_string(state)); + + switch (state) { + case PW_REMOTE_STATE_ERROR: + weston_log("pipewire remote error: %s\n", error); + break; + case PW_REMOTE_STATE_CONNECTED: + weston_log("connected to pipewire daemon\n"); + break; + default: + break; + } +} + + +static const struct pw_remote_events remote_events = { + PW_VERSION_REMOTE_EVENTS, + .state_changed = weston_pipewire_state_changed, +}; + +static int +weston_pipewire_init(struct weston_pipewire *pipewire) +{ + struct wl_event_loop *loop; + + pw_init(NULL, NULL); + + pipewire->loop = pw_loop_new(NULL); + if (!pipewire->loop) + return -1; + + pw_loop_enter(pipewire->loop); + + pipewire->core = pw_core_new(pipewire->loop, NULL); + pipewire->t = pw_core_get_type(pipewire->core); + init_type(&pipewire->type, pipewire->t->map); + + pipewire->remote = pw_remote_new(pipewire->core, NULL, 0); + pw_remote_add_listener(pipewire->remote, + &pipewire->remote_listener, + &remote_events, pipewire); + + pw_remote_connect(pipewire->remote); + + while (true) { + enum pw_remote_state state; + const char *error = NULL; + int ret; + + state = pw_remote_get_state(pipewire->remote, &error); + if (state == PW_REMOTE_STATE_CONNECTED) + break; + + if (state == PW_REMOTE_STATE_ERROR) { + weston_log("pipewire error: %s\n", error); + goto err; + } + + ret = pw_loop_iterate(pipewire->loop, -1); + if (ret < 0) { + weston_log("pipewire_loop_iterate failed: %s", + spa_strerror(ret)); + goto err; + } + } + + loop = wl_display_get_event_loop(pipewire->compositor->wl_display); + pipewire->loop_source = + wl_event_loop_add_fd(loop, pw_loop_get_fd(pipewire->loop), + WL_EVENT_READABLE, + weston_pipewire_loop_handler, + pipewire); + + return 0; +err: + if (pipewire->remote) + pw_remote_destroy(pipewire->remote); + pw_loop_leave(pipewire->loop); + pw_loop_destroy(pipewire->loop); + return -1; +} + +static const struct weston_pipewire_api pipewire_api = { + pipewire_output_create, + pipewire_output_is_pipewire, + pipewire_output_set_mode, + pipewire_output_set_seat, +}; + +WL_EXPORT int +weston_module_init(struct weston_compositor *compositor) +{ + int ret; + struct weston_pipewire *pipewire; + const struct weston_drm_virtual_output_api *api = + weston_drm_virtual_output_get_api(compositor); + + if (!api) + return -1; + + pipewire = zalloc(sizeof *pipewire); + if (!pipewire) + return -1; + + if (!weston_compositor_add_destroy_listener_once(compositor, + &pipewire->destroy_listener, + weston_pipewire_destroy)) { + free(pipewire); + return 0; + } + + pipewire->virtual_output_api = api; + pipewire->compositor = compositor; + wl_list_init(&pipewire->output_list); + + ret = weston_plugin_api_register(compositor, WESTON_PIPEWIRE_API_NAME, + &pipewire_api, sizeof(pipewire_api)); + + if (ret < 0) { + weston_log("Failed to register pipewire API.\n"); + goto failed; + } + + ret = weston_pipewire_init(pipewire); + if (ret < 0) { + weston_log("Failed to initialize pipewire.\n"); + goto failed; + } + + pipewire->debug = + weston_compositor_add_log_scope(compositor, "pipewire", + "Debug messages from pipewire plugin\n", + NULL, NULL, NULL); + + return 0; + +failed: + wl_list_remove(&pipewire->destroy_listener.link); + free(pipewire); + return -1; +} diff --git a/pipewire/pipewire-plugin.h b/pipewire/pipewire-plugin.h new file mode 100644 index 0000000..88f728a --- /dev/null +++ b/pipewire/pipewire-plugin.h @@ -0,0 +1,62 @@ +/* + * Copyright © 2019 Pengutronix, Michael Olbrich + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef PIPEWIRE_PLUGIN_H +#define PIPEWIRE_PLUGIN_H + +#include +#include + +#define WESTON_PIPEWIRE_API_NAME "weston_pipewire_api_v1" + +struct weston_pipewire_api { + /** Create pipewire outputs + * + * Returns 0 on success, -1 on failure. + */ + struct weston_output *(*create_output)(struct weston_compositor *c, + char *name); + + /** Check if output is pipewire */ + bool (*is_pipewire_output)(struct weston_output *output); + + /** Set mode */ + int (*set_mode)(struct weston_output *output, const char *modeline); + + /** Set seat */ + void (*set_seat)(struct weston_output *output, const char *seat); +}; + +static inline const struct weston_pipewire_api * +weston_pipewire_get_api(struct weston_compositor *compositor) +{ + const void *api; + api = weston_plugin_api_get(compositor, WESTON_PIPEWIRE_API_NAME, + sizeof(struct weston_pipewire_api)); + + return (const struct weston_pipewire_api *)api; +} + +#endif /* PIPEWIRE_PLUGIN_H */ diff --git a/protocol/drm-auth.xml b/protocol/drm-auth.xml new file mode 100755 index 0000000..f302105 --- /dev/null +++ b/protocol/drm-auth.xml @@ -0,0 +1,34 @@ + + + + Copyright (c) 2021 Huawei Device Co., Ltd. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/protocol/ivi-application.xml b/protocol/ivi-application.xml new file mode 100644 index 0000000..f51b7f4 --- /dev/null +++ b/protocol/ivi-application.xml @@ -0,0 +1,100 @@ + + + + + Copyright (C) 2013 DENSO CORPORATION + Copyright (c) 2013 BMW Car IT GmbH + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + + + + This removes the link from ivi_id to wl_surface and destroys ivi_surface. + The ID, ivi_id, is free and can be used for surface_create again. + + + + + + The configure event asks the client to resize its surface. + + The size is a hint, in the sense that the client is free to + ignore it if it doesn't resize, pick a smaller size (to + satisfy aspect ratio or resize in steps of NxM pixels). + + The client is free to dismiss all but the last configure + event it received. + + The width and height arguments specify the size of the window + in surface-local coordinates. + + + + + + + + + This interface is exposed as a global singleton. + This interface is implemented by servers that provide IVI-style user interfaces. + It allows clients to associate an ivi_surface with wl_surface. + + + + + + + + + + This request gives the wl_surface the role of an IVI Surface. Creating more than + one ivi_surface for a wl_surface is not allowed. Note, that this still allows the + following example: + + 1. create a wl_surface + 2. create ivi_surface for the wl_surface + 3. destroy the ivi_surface + 4. create ivi_surface for the wl_surface (with the same or another ivi_id as before) + + surface_create will create an interface:ivi_surface with numeric ID; ivi_id in + ivi compositor. These ivi_ids are defined as unique in the system to identify + it inside of ivi compositor. The ivi compositor implements business logic how to + set properties of the surface with ivi_id according to the status of the system. + E.g. a unique ID for Car Navigation application is used for implementing special + logic of the application about where it shall be located. + The server regards the following cases as protocol errors and disconnects the client. + - wl_surface already has another role. + - ivi_id is already assigned to another wl_surface. + + If client destroys ivi_surface or wl_surface which is assigne to the ivi_surface, + ivi_id which is assigned to the ivi_surface is free for reuse. + + + + + + + + + diff --git a/protocol/ivi-hmi-controller.xml b/protocol/ivi-hmi-controller.xml new file mode 100644 index 0000000..2015a38 --- /dev/null +++ b/protocol/ivi-hmi-controller.xml @@ -0,0 +1,98 @@ + + + + + Copyright (C) 2013 DENSO CORPORATION + Copyright (c) 2013 BMW Car IT GmbH + + 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. + + + + + + + + + + + + Reference protocol to control a surface by server. + To control a surface by server, it gives seat to the server + to e.g. control Home screen. Home screen has several workspaces + to group launchers of wayland application. These workspaces + are drawn on a horizontally long surface to be controlled + by motion of input device. E.g. A motion from right to left + happens, the viewport of surface is controlled in the ivi-shell + by using ivi-layout. client can recognizes the end of controlling + by event "workspace_end_control". + + + + + + + + + + + + + + + hmi-controller loaded to ivi-shall implements 4 types of layout + as a reference; tiling, side by side, full_screen, and random. + + + + + + + + + + + + home screen is a reference implementation of launcher to launch + wayland applications. The home screen has several workspaces to + group wayland applications. By defining the following keys in + weston.ini, user can add launcher icon to launch a wayland application + to a workspace. + [ivi-launcher] + workspace-id=0 + : id of workspace to add a launcher + icon-id=4001 + : ivi id of ivi_surface to draw an icon + icon=/home/user/review/build-ivi-shell/data/icon_ivi_flower.png + : path to icon image + path=/home/user/review/build-ivi-shell/weston-dnd + : path to wayland application + + + + + + + + + + + + diff --git a/protocol/meson.build b/protocol/meson.build new file mode 100644 index 0000000..25cea5a --- /dev/null +++ b/protocol/meson.build @@ -0,0 +1,77 @@ +dep_scanner = dependency('wayland-scanner', native: true) +prog_scanner = find_program(dep_scanner.get_pkgconfig_variable('wayland_scanner')) + +dep_wp = dependency('wayland-protocols', version: '>= 1.18') +dir_wp_base = dep_wp.get_pkgconfig_variable('pkgdatadir') + +install_data( + [ + 'weston-debug.xml', + 'weston-direct-display.xml', + ], + install_dir: join_paths(dir_data, dir_protocol_libweston) +) + +generated_protocols = [ + [ 'input-method', 'v1' ], + [ 'input-timestamps', 'v1' ], + [ 'ivi-application', 'internal' ], + [ 'ivi-hmi-controller', 'internal' ], + [ 'fullscreen-shell', 'v1' ], + [ 'linux-dmabuf', 'v1' ], + [ 'linux-explicit-synchronization', 'v1' ], + [ 'presentation-time', 'stable' ], + [ 'pointer-constraints', 'v1' ], + [ 'relative-pointer', 'v1' ], + [ 'tablet', 'v2' ], + [ 'text-cursor-position', 'internal' ], + [ 'text-input', 'v1' ], + [ 'viewporter', 'stable' ], + [ 'weston-debug', 'internal' ], + [ 'weston-desktop-shell', 'internal' ], + [ 'weston-screenshooter', 'internal' ], + [ 'weston-content-protection', 'internal' ], + [ 'weston-test', 'internal' ], + [ 'weston-touch-calibration', 'internal' ], + [ 'weston-direct-display', 'internal' ], + [ 'xdg-output', 'v1' ], + [ 'xdg-shell', 'v6' ], + [ 'xdg-shell', 'stable' ], +] + +foreach proto: generated_protocols + proto_name = proto[0] + if proto[1] == 'internal' + base_file = proto_name + xml_path = '@0@.xml'.format(proto_name) + elif proto[1] == 'stable' + base_file = proto_name + xml_path = '@0@/stable/@1@/@1@.xml'.format(dir_wp_base, base_file) + else + base_file = '@0@-unstable-@1@'.format(proto_name, proto[1]) + xml_path = '@0@/unstable/@1@/@2@.xml'.format(dir_wp_base, proto_name, base_file) + endif + + foreach output_type: [ 'client-header', 'server-header', 'private-code' ] + if output_type == 'client-header' + output_file = '@0@-client-protocol.h'.format(base_file) + elif output_type == 'server-header' + output_file = '@0@-server-protocol.h'.format(base_file) + else + output_file = '@0@-protocol.c'.format(base_file) + if dep_scanner.version().version_compare('< 1.14.91') + output_type = 'code' + endif + endif + + var_name = output_file.underscorify() + target = custom_target( + '@0@ @1@'.format(base_file, output_type), + command: [ prog_scanner, output_type, '@INPUT@', '@OUTPUT@' ], + input: xml_path, + output: output_file, + ) + + set_variable(var_name, target) + endforeach +endforeach diff --git a/protocol/text-cursor-position.xml b/protocol/text-cursor-position.xml new file mode 100644 index 0000000..0fbc54e --- /dev/null +++ b/protocol/text-cursor-position.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/protocol/weston-content-protection.xml b/protocol/weston-content-protection.xml new file mode 100644 index 0000000..6fdbb95 --- /dev/null +++ b/protocol/weston-content-protection.xml @@ -0,0 +1,251 @@ + + + + + Copyright 2016 The Chromium Authors. + Copyright 2018-2019 Collabora, Ltd. + Copyright © 2018-2019 Intel Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol specifies a set of interfaces used to provide + content-protection for e.g. HDCP, and protect surface contents on the + secured outputs and prevent from appearing in screenshots or from being + visible on non-secure outputs. + + A secure-output is defined as an output that is secured by some + content-protection mechanism e.g. HDCP, and meets at least the minimum + required content-protection level requested by a client. + + The term content-protection is defined in terms of HDCP type 0 and + HDCP type 1, but this may be extended in future. + + This protocol is not intended for implementing Digital Rights Management on + general (e.g. Desktop) systems, and would only be useful for closed systems. + As the server is the one responsible for implementing + the content-protection, the client can only trust the content-protection as + much they can trust the server. + + In order to protect the content and prevent surface contents from appearing + in screenshots or from being visible on non-secure outputs, a client must + first bind the global interface "weston_content_protection" which, if a + compositor supports secure output, is exposed by the registry. + Using the bound global object, the client uses the "get_protection" request + to instantiate an interface extension for a wl_surface object. + This extended interface will then allow surfaces to request for + content-protection, and also to censor the visibility of the surface on + non-secure outputs. Client applications should not wait for the protection + to change, as it might never change in case the content-protection cannot be + achieved. Alternatively, clients can use a timeout and start showing the + content in lower quality. + + Censored visibility is defined as the compositor censoring the protected + content on non-secure outputs. Censoring may include artificially reducing + image quality or replacing the protected content completely with + placeholder graphics. + + Censored visibility is controlled by protection mode, set by the client. + In "relax" mode, the compositor may show protected content on non-secure + outputs. It will be up to the client to adapt to secure and non-secure + presentation. In "enforce" mode, the compositor will censor the parts of + protected content that would otherwise show on non-secure outputs. + + + + + The global interface weston_content_protection is used for exposing the + content protection capabilities to a client. It provides a way for clients + to request their wl_surface contents to not be displayed on an output + below their required level of content-protection. + Using this interface clients can request for a weston_protected_surface + which is an extension to the wl_surface to provide content-protection, and + set the censored-visibility on the non-secured-outputs. + + + + + Informs the server that the client will not be using this + protocol object anymore. This does not affect any other objects, + protected_surface objects included. + + + + + + + + + + Instantiate an interface extension for the given wl_surface to + provide surface protection. If the given wl_surface already has + a weston_protected_surface associated, the surface_exists protocol + error is raised. + + + + + + + + + An additional interface to a wl_surface object, which allows a client to + request the minimum level of content-protection, request to change the + visibility of their contents, and receive notifications about changes in + content-protection. + + A protected surface has a 'status' associated with it, that indicates + what type of protection it is currently providing, specified by + content-type. Updates to this status are sent to the client + via the 'status' event. Before the first status event is sent, the client + should assume that the status is 'unprotected'. + + A client can request a content protection level to be the minimum for an + output to be considered secure, using the 'set_type' request. + It is responsibility of the client to monitor the actual + content-protection level achieved via the 'status' event, and make + decisions as to what content to show based on this. + + The server should make its best effort to achieve the desired + content-protection level on all of the outputs the client's contents are + being displayed on. Any changes to the content protection status should be + reported to the client, even if they are below the requested + content-protection level. If the client's contents are being displayed on + multiple outputs, the lowest content protection level achieved should be + reported. + + A client can also request that its content only be displayed on outputs + that are considered secure. The 'enforce/relax' requests can achieve this. + In enforce mode, the content is censored for non-secure outputs. + The implementation of censored-visibility is compositor-defined. + In relax mode there are no such limitation. On an attempt to show the + client on unsecured output, compositor would keep on showing the content + and send the 'status' event to the client. Client can take a call to + downgrade the content. + + If the wl_surface associated with the protected_surface is destroyed, + the protected_surface becomes inert. + + + + + + + + + Description of a particular type of content protection. + + A server may not necessarily support all of these types. + + Note that there is no ordering between enum members unless specified. + Over time, different types of content protection may be added, which + may be considered less secure than what is already here. + + + + + + + + + If the protected_surface is destroyed, the wl_surface desired protection + level returns to unprotected, as if set_type request was sent with type + as 'unprotected'. + + + + + + Informs the server about the type of content. The level of + content-protection depends upon the content-type set by the client + through this request. Initially, this is set to 'unprotected'. + + If the requested value is not a valid content_type enum value, the + 'invalid_type' protocol error is raised. It is not an error to request + a valid protection type the compositor does not implement or cannot + achieve. + + The requested content protection is double-buffered, see + wl_surface.commit. + + + + + + + Censor the visibility of the wl_surface contents on non-secure outputs. + See weston_protected_surface for the description. + + The force constrain mode is double-buffered, see wl_surface.commit + + + + + + Do not enforce censored-visibility of the wl_surface contents on + non-secure-outputs. See weston_protected_surface for the description. + + The relax mode is selected by default, if no explicit request is made + for enforcing the censored-visibility. + + The relax mode is double-buffered, see wl_surface.commit + + + + + + This event is sent to the client to inform about the actual protection + level for its surface in the relax mode. + + The 'type' argument indicates what that current level of content + protection that the server has currently established. + + The 'status' event is first sent, when a weston_protected_surface is + created. + + Until this event is sent for the first time, the client should assume + that its contents are not secure, and the type is 'unprotected'. + + Possible reasons the content protection status can change is due to + change in censored-visibility mode from enforced to relaxed, a new + connector being added, movement of window to another output, or, + the client attaching a buffer too large for what the server may secure. + However, it is not limited to these reasons. + + A client may want to listen to this event and lower the resolution of + their content until it can successfully be shown securely. + + In case of "enforce" mode, the client will not get any status event. + If the mode is then changed to "relax", the client will receive the + status event. + + + + + + diff --git a/protocol/weston-debug.xml b/protocol/weston-debug.xml new file mode 100644 index 0000000..ac62661 --- /dev/null +++ b/protocol/weston-debug.xml @@ -0,0 +1,139 @@ + + + + + Copyright © 2017 Pekka Paalanen pq@iki.fi + Copyright © 2018 Zodiac Inflight Innovations + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This is a generic debugging interface for Weston internals, the global + object advertized through wl_registry. + + WARNING: This interface by design allows a denial-of-service attack. It + should not be offered in production, or proper authorization mechanisms + must be enforced. + + The idea is for a client to provide a file descriptor that the server + uses for printing debug information. The server uses the file + descriptor in blocking writes mode, which exposes the denial-of-service + risk. The blocking mode is necessary to ensure all debug messages can + be easily printed in place. It also ensures message ordering if a + client subscribes to more than one debug stream. + + The available debugging features depend on the server. + + A debug stream can be one-shot where the server prints the requested + information and then closes it, or continuous where server keeps on + printing until the client stops it. Or anything in between. + + + + + Destroys the factory object, but does not affect any other objects. + + + + + + Advertises an available debug scope which the client may be able to + bind to. No information is provided by the server about the content + contained within the debug streams provided by the scope, once a + client has subscribed. + + + + + + + + + Subscribe to a named debug stream. The server will start printing + to the given file descriptor. + + If the named debug stream is a one-shot dump, the server will send + weston_debug_stream_v1.complete event once all requested data has + been printed. Otherwise, the server will continue streaming debug + prints until the subscription object is destroyed. + + If the debug stream name is unknown to the server, the server will + immediately respond with weston_debug_stream_v1.failure event. + + + + + + + + + + + Represents one subscribed debug stream, created with + weston_debug_v1.subscribe. When the object is created, it is associated + with a given file descriptor. The server will continue writing to the + file descriptor until the object is destroyed or the server sends an + event through the object. + + + + + Destroys the object, which causes the server to stop writing into + and closes the associated file descriptor if it was not closed + already. + + Use a wl_display.sync if the clients needs to guarantee the file + descriptor is closed before continuing. + + + + + + The server has successfully finished writing to and has closed the + associated file descriptor. + + This event is delivered only for one-shot debug streams where the + server dumps some data and stop. This is never delivered for + continuous debbug streams because they by definition never complete. + + + + + + The server has stopped writing to and has closed the + associated file descriptor. The data already written to the file + descriptor is correct, but it may be truncated. + + This event may be delivered at any time and for any kind of debug + stream. It may be due to a failure in or shutdown of the server. + The message argument may provide a hint of the reason. + + + + + + diff --git a/protocol/weston-desktop-shell.xml b/protocol/weston-desktop-shell.xml new file mode 100644 index 0000000..91c5eb4 --- /dev/null +++ b/protocol/weston-desktop-shell.xml @@ -0,0 +1,135 @@ + + + + + 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 client 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 client 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 which side of the screen the panel is + located. This is so that new windows do not overlap the panel + and maximized windows maximize properly. + + + + + + + + + + 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/weston-direct-display.xml b/protocol/weston-direct-display.xml new file mode 100644 index 0000000..274aadd --- /dev/null +++ b/protocol/weston-direct-display.xml @@ -0,0 +1,82 @@ + + + + + Copyright © 2019 Collabora Ltd. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + Weston extension to instruct the compositor to avoid any import + of the dmabuf created by 'linux-dmabuf' protocol other than the display + controller. + + Compositors are already going to use direct scan-out as much as possible but + there's no assurance that while doing so, they won't first import the dmabuf + in to the GPU. This extension assures the client that the compositor will + never attempt to import in to the GPU and pass it directly to the display + controller. + + Clients can make use of this extension to pass the dmabuf buffer to the + display controller, potentially increasing the performance and lowering the + bandwidth usage. + + Lastly, clients can make use of this extension in tandem with content-protection + one thus avoiding any GPU interaction and providing a secure-content path. + Also, in some cases, the memory where dmabuf are allocated are in specially + crafted memory zone which would be seen as an illegal memory access when the + GPU will attempt to read it. + + WARNING: This interface by design might break screenshoting functionality + as compositing might be involved while doing that. Also, do note, that in + case the dmabufer provided can't be imported by KMS, the client connection + will be terminated. + + WARNING: This extension requires 'linux-dmabuf' protocol and + 'zwp_linux_buffer_params_v1' be already created by 'zwp_linux_buffer_v1'. + + + + + This request tells the compositor not to import the dmabuf to the GPU + in order to bypass it entirely, such that the buffer will be directly + scanned-out by the display controller. If HW is not capable/or there + aren't any available resources to directly scan-out the buffer, a + placeholder should be installed in-place by the compositor. The + compositor may perform checks on the dmabuf and refuse to create a + wl_buffer if the dmabuf seems unusable for being used directly. + + Assumes that 'zwp_linux_buffer_params_v1' was already created + by 'zwp_linux_dmabuf_v1_create_params'. + + + + + + + Destroys the factory object, but does not affect any other objects. + + + + + diff --git a/protocol/weston-screenshooter.xml b/protocol/weston-screenshooter.xml new file mode 100644 index 0000000..8c4486c --- /dev/null +++ b/protocol/weston-screenshooter.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/protocol/weston-test.xml b/protocol/weston-test.xml new file mode 100644 index 0000000..00b7185 --- /dev/null +++ b/protocol/weston-test.xml @@ -0,0 +1,144 @@ + + + + + Copyright © 2012 Intel Corporation + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + Internal testing facilities for the weston compositor. + + It can't be stressed enough that these should never ever be used + outside of running weston's tests. The weston-test.so module should + never be installed. + + These requests may allow clients to do very bad things. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Records an image of what is currently displayed on a given + display output, returning the image as an event. + + + + + + + The capture_screenshot_done signal is sent when a screenshot has been copied into the + provided buffer. + + + + + + + + + + + + + + + + This is a global singleton interface for Weston internal tests. + + This interface allows a test client to trigger compositor-side + test procedures. This is useful for cases, where the actual tests + are in compositor plugins, but they also require the presence of + a particular client. + + This interface is implemented by the compositor plugins containing + the testing code. + + A test client starts a test with the "run" request. It must not send + another "run" request until receiving the "finished" event. If the + compositor-side test succeeds, the "finished" event is sent. If the + compositor-side test fails, the compositor should send the protocol + error "test_failed", but it may also exit with an error (e.g. SEGV). + + Unknown test name will raise "unknown_test" protocol error. + + + + + + + + + + + + + + + + diff --git a/protocol/weston-touch-calibration.xml b/protocol/weston-touch-calibration.xml new file mode 100644 index 0000000..7e898c0 --- /dev/null +++ b/protocol/weston-touch-calibration.xml @@ -0,0 +1,342 @@ + + + + + Copyright 2017-2018 Collabora, Ltd. + Copyright 2017-2018 General Electric Company + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This is the global interface for calibrating a touchscreen input + coordinate transformation. It is recommended to make this interface + privileged. + + This interface can be used by a client to show a calibration pattern and + receive uncalibrated touch coordinates, facilitating the computation of + a calibration transformation that will align actual touch positions + on screen with their expected coordinates. + + Immediately after being bound by a client, the compositor sends the + touch_device events. + + The client chooses a touch device from the touch_device events, creates a + wl_surface and then a weston_touch_calibrator for the wl_surface and the + chosen touch device. The client waits for the compositor to send a + configure event before it starts drawing the first calibration pattern. + After receiving the configure event, the client will iterate drawing a + pattern, getting touch input via weston_touch_calibrator, and converting + pixel coordinates to expected touch coordinates with + weston_touch_calibrator.convert until it has enough correspondences to + compute the calibration transformation or the compositor cancels the + calibration. + + Once the client has successfully computed a new calibration, it can use + weston_touch_calibration.save request to load the new calibration into + the compositor. The compositor may take this new calibration into use and + may write it into persistent storage. + + + + + + + + + + + + Destroy the binding to the global interface, does not affect any + objects already created through this interface. + + + + + + This gives the calibrator role to the surface and ties it with the + given touch input device. + + If the surface already has a role, then invalid_surface error is raised. + + If the device string is not one advertised with touch_device event's + device argument, then invalid_device error is raised. + + If a weston_touch_calibrator protocol object exists in the compositor + already, then already_exists error is raised. This limitation is + compositor-wide and not specific to any particular client. + + + + + + + + + This request asks the compositor to save the calibration data for the + given touch input device. The compositor may ignore this request. + + If the device string is not one advertised with touch_device event's + device argument, then invalid_device error is raised. + + The array must contain exactly six 'float' (the 32-bit floating + point format used by the C language on the system) numbers. For a 3x3 + calibration matrix in the form + @code + ( a b c ) + ( d e f ) + ( 0 0 1 ) + @endcode + the array must contain the values { a, b, c, d, e, f }. For the + definition of the coordinate spaces, see + libinput_device_config_calibration_set_matrix(). + + + + + + + + When a client binds to weston_touch_calibration, one touch_device event + is sent for each touchscreen that is available to be calibrated. This + is the only time the event is sent. Touch devices added in the + compositor will not generate events for existing + weston_touch_calibration objects. + + An event carries the touch device identification and the associated + output or head (display connector) name. + + On platforms using udev, the device identification is the udev sys + path. It is an absolute path and starts with the sys mount point. + + + + + + + + + On creation, this object is tied to a specific touch device. The + compositor sends a configure event which the client must obey with the + associated wl_surface. + + Once the client has committed content to the surface, the compositor can + grab the touch input device, prevent it from emitting normal touch + events, show the surface on the correct output, and relay input events + from the touch device via this protocol object. + + Touch events from other touch devices than the one tied to this object + must generate wrong_touch events on at least touch-down and must not + generate normal or calibration touch events. + + At any time, the compositor can choose to cancel the calibration + procedure by sending the cancel_calibration event. This should also be + used if the touch device disappears or anything else prevents the + calibration from continuing on the compositor side. + + If the wl_surface is destroyed, the compositor must cancel the + calibration. + + The touch event coordinates and conversion results are delivered in + calibration units. The calibration units cover the device coordinate + range exactly. Calibration units are in the closed interval [0.0, 1.0] + mapped into 32-bit unsigned integers. An integer can be converted into a + real value by dividing by 2^32-1. A calibration matrix must be computed + from the [0.0, 1.0] real values, but the matrix elements do not need to + fall into that range. + + + + + + + + + + + + This unmaps the surface if it was mapped. The input device grab + is dropped, if it was present. The surface loses its role as a + calibrator. + + + + + + This request asks the compositor to convert the surface-local + coordinates into the expected touch input coordinates appropriate for + the associated touch device. The intention is that a client uses this + request to convert marker positions that the user is supposed to touch + during calibration. + + If the compositor has cancelled the calibration, the conversion result + shall be zeroes and no errors will be raised. + + The coordinates given as arguments to this request are relative to + the associated wl_surface. + + If a client asks for conversion before it has committed valid + content to the wl_surface, the not_mapped error is raised. + + If the coordinates x, y are outside of the wl_surface content, the + bad_coordinates error is raised. + + + + + + + + + This event tells the client what size to make the surface. The client + must obey the size exactly on the next commit with a wl_buffer. + + This event shall be sent once as a response to creating a + weston_touch_calibrator object. + + + + + + + + This is sent when the compositor wants to cancel the calibration and + drop the touch device grab. The compositor unmaps the surface, if it + was mapped. + + The weston_touch_calibrator object will not send any more events. The + client should destroy it. + + + + + + For whatever reason, a touch event resulting from a user action cannot + be used for calibration. The client should show feedback to the user + that the touch was rejected. + + Possible causes for this event include the user touching a wrong + touchscreen when there are multiple ones present. This is particularly + useful when the touchscreens are cloned and there is no other way to + identify which screen the user should be touching. + + Another cause could be a touch device that sends coordinates beyond its + declared range. If motion takes a touch point outside the range, the + compositor should also send 'cancel' event to undo the touch-down. + + + + + + + A new touch point has appeared on the surface. This touch point is + assigned a unique ID. Future events from this touch point reference + this ID. The ID ceases to be valid after a touch up event and may be + reused in the future. + + For the coordinate units, see weston_touch_calibrator. + + + + + + + + + + The touch point has disappeared. No further events will be sent for + this touch point and the touch point's ID is released and may be + reused in a future touch down event. + + + + + + + + A touch point has changed coordinates. + + For the coordinate units, see weston_touch_calibrator. + + + + + + + + + + Indicates the end of a set of events that logically belong together. + A client is expected to accumulate the data in all events within the + frame before proceeding. + + A wl_touch.frame terminates at least one event but otherwise no + guarantee is provided about the set of events within a frame. A client + must assume that any state not updated in a frame is unchanged from the + previously known state. + + + + + + Sent if the compositor decides the touch stream is a global + gesture. No further events are sent to the clients from that + particular gesture. Touch cancellation applies to all touch points + currently active on this client's surface. The client is + responsible for finalizing the touch points, future touch points on + this surface may reuse the touch point ID. + + + + + + + + + + + + + + This event returns the conversion result from surface coordinates to + the expected touch device coordinates. + + For details, see weston_touch_calibrator.convert. For the coordinate + units, see weston_touch_calibrator. + + This event destroys the weston_touch_coordinate object. + + + + + + diff --git a/releasing.md b/releasing.md new file mode 100644 index 0000000..1864dc3 --- /dev/null +++ b/releasing.md @@ -0,0 +1,72 @@ +# Releasing + +To make a release of Weston, follow these steps. + +0. Verify the test suites and codebase checks pass. All of the tests should + either pass or skip. + + ninja -C build/ test + +1. Verify that the wayland and wayland-protocols version dependencies are + correct, and that wayland-protocols has had a release with any needed + protocol updates. + +2. Update the first stanza of `meson.build` to the intended version. + + If the ABI has been broken, make sure `libweston_major` has been bumped since + the last release. + + Then commit your changes: + + RELEASE_NUMBER="x.y.z" + RELEASE_NAME="[alpha|beta|RC1|RC2|official|point]" + git status + git commit meson.build -m "build: bump to version $RELEASE_NUMBER for the $RELEASE_NAME release" + git push + +3. Run the `release.sh` script to generate the tarballs, sign and upload them, + and generate a release announcement template. This script can be obtained + from X.org's modular package: + + https://gitlab.freedesktop.org/xorg/util/modular/blob/master/release.sh + + The script supports a `--dry-run` option to test it without actually doing a + release. If the script fails on the distcheck step due to a test suite error + that can't be fixed for some reason, you can skip testsuite by specifying + the `--dist` argument. Pass `--help` to see other supported options. + + release.sh . + +5. Compose the release announcements. The script will generate *.x.y.z.announce + files with a list of changes and tags. Prepend these with a human-readable + listing of the most notable changes. For x.y.0 releases, indicate the + schedule for the x.y+1.0 release. + +6. PGP sign the release announcement and send it to + . + +7. Update `releases.html` in wayland.freedesktop.org with links to tarballs and + the release email URL. Copy tarballs produced by `release.sh` to `releases/`. + + Once satisfied: + + git commit -am "releases: add ${RELEASE_NUMBER} release" + git push + +For x.y.0 releases, also create the release series x.y branch. The x.y branch +is for bug fixes and conservative changes to the x.y.0 release, and is where we +create x.y.z releases from. Creating the x.y branch opens up master for new +development and lets new development move on. We've done this both after the +x.y.0 release (to focus development on bug fixing for the x.y.1 release for a +little longer) or before the x.y.0 release (like we did with the 1.5.0 release, +to unblock master development early). + + git branch x.y [sha] + git push origin x.y + +The master branch's `meson.build` version should always be (at least) x.y.90, +with x.y being the most recent stable branch. The stable branch's `meson.build` +version is just whatever was most recently released from that branch. + +For stable branches, we commit fixes to master first, then `git cherry-pick -x` +them back to the stable branch. diff --git a/remoting/README b/remoting/README new file mode 100644 index 0000000..a5e8ea5 --- /dev/null +++ b/remoting/README @@ -0,0 +1,28 @@ + Remoting plugin for Weston + + +The Remoting plugin creates a streaming image of a virtual output and transmits +it to a remote host. It is currently only supported on the drm-backend. Virtual +outputs are created and configured by adding a remote-output section to +weston.ini. See man weston-drm(7) for configuration details. This plugin is +loaded automatically if any remote-output sections are present. + +This plugin sends motion jpeg images to a client via RTP using gstreamer, and +so requires gstreamer-1.0. This plugin starts sending images immediately when +weston is run, and keeps sending them until weston shuts down. The image stream +can be received by any appropriately configured RTP client, but a sample +gstreamer RTP client script can be found at doc/scripts/remoting-client-receive.bash. + +Script usage: + remoting-client-receive.bash + + +How to compile +--------------- +Set --enable-remoting=true when configuring weston. The remoting-plugin.so +module is created and installed in the libweston path. + + +How to configure weston.ini +---------------------------- +See man weston-drm(7). diff --git a/remoting/meson.build b/remoting/meson.build new file mode 100644 index 0000000..ac9fa51 --- /dev/null +++ b/remoting/meson.build @@ -0,0 +1,33 @@ +if get_option('remoting') + user_hint = 'If you rather not build this, set \'-Dremoting=false\'.' + + if not get_option('backend-drm') or not get_option('renderer-gl') + error('Attempting to build the remoting plugin without the required DRM backend and GL renderer. ' + user_hint) + endif + + depnames = [ + 'gstreamer-1.0', 'gstreamer-allocators-1.0', + 'gstreamer-app-1.0', 'gstreamer-video-1.0', + 'gobject-2.0', 'glib-2.0' + ] + deps_remoting = [ dep_libweston_private, dep_libdrm_headers ] + foreach depname : depnames + dep = dependency(depname, required: false) + if not dep.found() + error('Remoting plugin requires @0@ which was not found. '.format(depname) + user_hint) + endif + deps_remoting += dep + endforeach + + plugin_remoting = shared_library( + 'remoting-plugin', + 'remoting-plugin.c', + include_directories: common_inc, + dependencies: deps_remoting, + name_prefix: '', + install: true, + install_dir: dir_module_libweston + ) + env_modmap += 'remoting-plugin.so=@0@;'.format(plugin_remoting.full_path()) + install_headers('remoting-plugin.h', subdir: dir_include_libweston_install) +endif diff --git a/remoting/remoting-plugin.c b/remoting/remoting-plugin.c new file mode 100644 index 0000000..85b6bcf --- /dev/null +++ b/remoting/remoting-plugin.c @@ -0,0 +1,967 @@ +/* + * Copyright © 2018 Renesas Electronics Corp. + * + * Based on vaapi-recorder by: + * Copyright (c) 2012 Intel Corporation. All Rights Reserved. + * Copyright © 2013 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: IGEL Co., Ltd. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "remoting-plugin.h" +#include +#include "shared/helpers.h" +#include "shared/timespec-util.h" +#include "backend.h" +#include "libweston-internal.h" + +#define MAX_RETRY_COUNT 3 + +struct weston_remoting { + struct weston_compositor *compositor; + struct wl_list output_list; + struct wl_listener destroy_listener; + const struct weston_drm_virtual_output_api *virtual_output_api; + + GstAllocator *allocator; +}; + +struct remoted_gstpipe { + int readfd; + int writefd; + struct wl_event_source *source; +}; + +/* supported gbm format list */ +struct remoted_output_support_gbm_format { + /* GBM_FORMAT_* tokens are strictly aliased with DRM_FORMAT_*, so we + * use the latter to avoid a dependency on GBM */ + uint32_t gbm_format; + const char *gst_format_string; + GstVideoFormat gst_video_format; +}; + +static const struct remoted_output_support_gbm_format supported_formats[] = { + { + .gbm_format = DRM_FORMAT_XRGB8888, + .gst_format_string = "BGRx", + .gst_video_format = GST_VIDEO_FORMAT_BGRx, + }, { + .gbm_format = DRM_FORMAT_RGB565, + .gst_format_string = "RGB16", + .gst_video_format = GST_VIDEO_FORMAT_RGB16, + }, { + .gbm_format = DRM_FORMAT_XRGB2101010, + .gst_format_string = "r210", + .gst_video_format = GST_VIDEO_FORMAT_r210, + } +}; + +struct remoted_output { + struct weston_output *output; + void (*saved_destroy)(struct weston_output *output); + int (*saved_enable)(struct weston_output *output); + int (*saved_disable)(struct weston_output *output); + int (*saved_start_repaint_loop)(struct weston_output *output); + + char *host; + int port; + char *gst_pipeline; + const struct remoted_output_support_gbm_format *format; + + struct weston_head *head; + + struct weston_remoting *remoting; + struct wl_event_source *finish_frame_timer; + struct wl_list link; + bool submitted_frame; + int fence_sync_fd; + struct wl_event_source *fence_sync_event_source; + + GstElement *pipeline; + GstAppSrc *appsrc; + GstBus *bus; + struct remoted_gstpipe gstpipe; + GstClockTime start_time; + int retry_count; + enum dpms_enum dpms; +}; + +struct mem_free_cb_data { + struct remoted_output *output; + struct drm_fb *output_buffer; +}; + +struct gst_frame_buffer_data { + struct remoted_output *output; + GstBuffer *buffer; +}; + +/* message type for pipe */ +#define GSTPIPE_MSG_BUS_SYNC 1 +#define GSTPIPE_MSG_BUFFER_RELEASE 2 + +struct gstpipe_msg_data { + int type; + void *data; +}; + +static int +remoting_gst_init(struct weston_remoting *remoting) +{ + GError *err = NULL; + + if (!gst_init_check(NULL, NULL, &err)) { + weston_log("GStreamer initialization error: %s\n", + err->message); + g_error_free(err); + return -1; + } + + remoting->allocator = gst_dmabuf_allocator_new(); + + return 0; +} + +static void +remoting_gst_deinit(struct weston_remoting *remoting) +{ + gst_object_unref(remoting->allocator); +} + +static GstBusSyncReply +remoting_gst_bus_sync_handler(GstBus *bus, GstMessage *message, + gpointer user_data) +{ + struct remoted_gstpipe *pipe = user_data; + struct gstpipe_msg_data msg = { + .type = GSTPIPE_MSG_BUS_SYNC, + .data = NULL + }; + ssize_t ret; + + ret = write(pipe->writefd, &msg, sizeof(msg)); + if (ret != sizeof(msg)) + weston_log("ERROR: failed to write, ret=%zd, errno=%d\n", + ret, errno); + + return GST_BUS_PASS; +} + +static int +remoting_gst_pipeline_init(struct remoted_output *output) +{ + GstCaps *caps; + GError *err = NULL; + GstStateChangeReturn ret; + struct weston_mode *mode = output->output->current_mode; + + if (!output->gst_pipeline) { + char pipeline_str[1024]; + /* TODO: use encodebin instead of jpegenc */ + snprintf(pipeline_str, sizeof(pipeline_str), + "rtpbin name=rtpbin " + "appsrc name=src ! videoconvert ! " + "video/x-raw,format=I420 ! jpegenc ! rtpjpegpay ! " + "rtpbin.send_rtp_sink_0 " + "rtpbin.send_rtp_src_0 ! " + "udpsink name=sink host=%s port=%d " + "rtpbin.send_rtcp_src_0 ! " + "udpsink host=%s port=%d sync=false async=false " + "udpsrc port=%d ! rtpbin.recv_rtcp_sink_0", + output->host, output->port, output->host, + output->port + 1, output->port + 2); + output->gst_pipeline = strdup(pipeline_str); + } + weston_log("GST pipeline: %s\n", output->gst_pipeline); + + output->pipeline = gst_parse_launch(output->gst_pipeline, &err); + if (!output->pipeline) { + weston_log("Could not create gstreamer pipeline. Error: %s\n", + err->message); + g_error_free(err); + return -1; + } + + output->appsrc = (GstAppSrc*) + gst_bin_get_by_name(GST_BIN(output->pipeline), "src"); + if (!output->appsrc) { + weston_log("Could not get appsrc from gstreamer pipeline\n"); + goto err; + } + + /* check sink */ + if (!gst_bin_get_by_name(GST_BIN(output->pipeline), "sink")) { + weston_log("Could not get sink from gstreamer pipeline\n"); + goto err; + } + + caps = gst_caps_new_simple("video/x-raw", + "format", G_TYPE_STRING, + output->format->gst_format_string, + "width", G_TYPE_INT, mode->width, + "height", G_TYPE_INT, mode->height, + "framerate", GST_TYPE_FRACTION, + mode->refresh, 1000, + NULL); + if (!caps) { + weston_log("Could not create gstreamer caps.\n"); + goto err; + } + g_object_set(G_OBJECT(output->appsrc), + "caps", caps, + "stream-type", 0, + "format", GST_FORMAT_TIME, + "is-live", TRUE, + NULL); + gst_caps_unref(caps); + + output->bus = gst_pipeline_get_bus(GST_PIPELINE(output->pipeline)); + if (!output->bus) { + weston_log("Could not get bus from gstreamer pipeline\n"); + goto err; + } + gst_bus_set_sync_handler(output->bus, remoting_gst_bus_sync_handler, + &output->gstpipe, NULL); + + output->start_time = 0; + ret = gst_element_set_state(output->pipeline, GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) { + weston_log("Couldn't set GST_STATE_PLAYING to pipeline\n"); + goto err; + } + + return 0; + +err: + gst_object_unref(GST_OBJECT(output->pipeline)); + output->pipeline = NULL; + return -1; +} + +static void +remoting_gst_pipeline_deinit(struct remoted_output *output) +{ + if (!output->pipeline) + return; + + gst_element_set_state(output->pipeline, GST_STATE_NULL); + if (output->bus) + gst_object_unref(GST_OBJECT(output->bus)); + gst_object_unref(GST_OBJECT(output->pipeline)); + output->pipeline = NULL; +} + +static int +remoting_output_disable(struct weston_output *output); + +static void +remoting_gst_restart(void *data) +{ + struct remoted_output *output = data; + + if (remoting_gst_pipeline_init(output) < 0) { + weston_log("gst: Could not restart pipeline!!\n"); + remoting_output_disable(output->output); + } +} + +static void +remoting_gst_schedule_restart(struct remoted_output *output) +{ + struct wl_event_loop *loop; + struct weston_compositor *c = output->remoting->compositor; + + loop = wl_display_get_event_loop(c->wl_display); + wl_event_loop_add_idle(loop, remoting_gst_restart, output); +} + +static void +remoting_gst_bus_message_handler(struct remoted_output *output) +{ + GstMessage *message; + GError *error; + gchar *debug; + + /* get message from bus queue */ + message = gst_bus_pop(output->bus); + if (!message) + return; + + switch (GST_MESSAGE_TYPE(message)) { + case GST_MESSAGE_STATE_CHANGED: { + GstState new_state; + gst_message_parse_state_changed(message, NULL, &new_state, + NULL); + if (!strcmp(GST_OBJECT_NAME(message->src), "sink") && + new_state == GST_STATE_PLAYING) + output->retry_count = 0; + break; + } + case GST_MESSAGE_WARNING: + gst_message_parse_warning(message, &error, &debug); + weston_log("gst: Warning: %s: %s\n", + GST_OBJECT_NAME(message->src), error->message); + break; + case GST_MESSAGE_ERROR: + gst_message_parse_error(message, &error, &debug); + weston_log("gst: Error: %s: %s\n", + GST_OBJECT_NAME(message->src), error->message); + if (output->retry_count < MAX_RETRY_COUNT) { + output->retry_count++; + remoting_gst_pipeline_deinit(output); + remoting_gst_schedule_restart(output); + } else { + remoting_output_disable(output->output); + } + break; + default: + break; + } +} + +static void +remoting_output_buffer_release(struct remoted_output *output, void *buffer) +{ + const struct weston_drm_virtual_output_api *api + = output->remoting->virtual_output_api; + + api->buffer_released(buffer); +} + +static int +remoting_gstpipe_handler(int fd, uint32_t mask, void *data) +{ + ssize_t ret; + struct gstpipe_msg_data msg; + struct remoted_output *output = data; + + /* receive message */ + ret = read(fd, &msg, sizeof(msg)); + if (ret != sizeof(msg)) { + weston_log("ERROR: failed to read, ret=%zd, errno=%d\n", + ret, errno); + remoting_output_disable(output->output); + return 0; + } + + switch (msg.type) { + case GSTPIPE_MSG_BUS_SYNC: + remoting_gst_bus_message_handler(output); + break; + case GSTPIPE_MSG_BUFFER_RELEASE: + remoting_output_buffer_release(output, msg.data); + break; + default: + weston_log("Received unknown message! msg=%d\n", msg.type); + } + return 1; +} + +static int +remoting_gstpipe_init(struct weston_compositor *c, + struct remoted_output *output) +{ + struct wl_event_loop *loop; + int fd[2]; + + if (pipe2(fd, O_CLOEXEC) == -1) + return -1; + + output->gstpipe.readfd = fd[0]; + output->gstpipe.writefd = fd[1]; + loop = wl_display_get_event_loop(c->wl_display); + output->gstpipe.source = + wl_event_loop_add_fd(loop, output->gstpipe.readfd, + WL_EVENT_READABLE, + remoting_gstpipe_handler, output); + if (!output->gstpipe.source) { + close(fd[0]); + close(fd[1]); + return -1; + } + + return 0; +} + +static void +remoting_gstpipe_release(struct remoted_gstpipe *pipe) +{ + wl_event_source_remove(pipe->source); + close(pipe->readfd); + close(pipe->writefd); +} + +static void +remoting_output_destroy(struct weston_output *output); + +static void +weston_remoting_destroy(struct wl_listener *l, void *data) +{ + struct weston_remoting *remoting = + container_of(l, struct weston_remoting, destroy_listener); + struct remoted_output *output, *next; + + wl_list_for_each_safe(output, next, &remoting->output_list, link) + remoting_output_destroy(output->output); + + /* Finalize gstreamer */ + remoting_gst_deinit(remoting); + + wl_list_remove(&remoting->destroy_listener.link); + free(remoting); +} + +static struct weston_remoting * +weston_remoting_get(struct weston_compositor *compositor) +{ + struct wl_listener *listener; + struct weston_remoting *remoting; + + listener = wl_signal_get(&compositor->destroy_signal, + weston_remoting_destroy); + if (!listener) + return NULL; + + remoting = wl_container_of(listener, remoting, destroy_listener); + return remoting; +} + +static int +remoting_output_finish_frame_handler(void *data) +{ + struct remoted_output *output = data; + const struct weston_drm_virtual_output_api *api + = output->remoting->virtual_output_api; + struct timespec now; + int64_t msec; + + if (output->submitted_frame) { + struct weston_compositor *c = output->remoting->compositor; + output->submitted_frame = false; + weston_compositor_read_presentation_clock(c, &now); + api->finish_frame(output->output, &now, 0); + } + + if (output->dpms == WESTON_DPMS_ON) { + msec = millihz_to_nsec(output->output->current_mode->refresh) / 1000000; + wl_event_source_timer_update(output->finish_frame_timer, msec); + } else { + wl_event_source_timer_update(output->finish_frame_timer, 0); + } + return 0; +} + +static void +remoting_gst_mem_free_cb(struct mem_free_cb_data *cb_data, GstMiniObject *obj) +{ + struct remoted_output *output = cb_data->output; + struct remoted_gstpipe *pipe = &output->gstpipe; + struct gstpipe_msg_data msg = { + .type = GSTPIPE_MSG_BUFFER_RELEASE, + .data = cb_data->output_buffer + }; + ssize_t ret; + + ret = write(pipe->writefd, &msg, sizeof(msg)); + if (ret != sizeof(msg)) + weston_log("ERROR: failed to write, ret=%zd, errno=%d\n", ret, + errno); + free(cb_data); +} + +static struct remoted_output * +lookup_remoted_output(struct weston_output *output) +{ + struct weston_compositor *c = output->compositor; + struct weston_remoting *remoting = weston_remoting_get(c); + struct remoted_output *remoted_output; + + wl_list_for_each(remoted_output, &remoting->output_list, link) { + if (remoted_output->output == output) + return remoted_output; + } + + weston_log("%s: %s: could not find output\n", __FILE__, __func__); + return NULL; +} + +static void +remoting_output_gst_push_buffer(struct remoted_output *output, + GstBuffer *buffer) +{ + struct timespec current_frame_ts; + GstClockTime ts, current_frame_time; + + weston_compositor_read_presentation_clock(output->remoting->compositor, + ¤t_frame_ts); + current_frame_time = GST_TIMESPEC_TO_TIME(current_frame_ts); + if (output->start_time == 0) + output->start_time = current_frame_time; + ts = current_frame_time - output->start_time; + + if (GST_CLOCK_TIME_IS_VALID(ts)) + GST_BUFFER_PTS(buffer) = ts; + else + GST_BUFFER_PTS(buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_DURATION(buffer) = GST_CLOCK_TIME_NONE; + + gst_app_src_push_buffer(output->appsrc, buffer); + output->submitted_frame = true; +} + +static int +remoting_output_fence_sync_handler(int fd, uint32_t mask, void *data) +{ + struct gst_frame_buffer_data *frame_data = data; + struct remoted_output *output = frame_data->output; + + remoting_output_gst_push_buffer(output, frame_data->buffer); + + wl_event_source_remove(output->fence_sync_event_source); + close(output->fence_sync_fd); + free(frame_data); + + return 0; +} + +static int +remoting_output_frame(struct weston_output *output_base, int fd, int stride, + struct drm_fb *output_buffer) +{ + struct remoted_output *output = lookup_remoted_output(output_base); + struct weston_remoting *remoting = output->remoting; + struct weston_mode *mode; + const struct weston_drm_virtual_output_api *api + = output->remoting->virtual_output_api; + struct wl_event_loop *loop; + GstBuffer *buf; + GstMemory *mem; + gsize offset = 0; + struct mem_free_cb_data *cb_data; + struct gst_frame_buffer_data *frame_data; + + if (!output) + return -1; + + cb_data = zalloc(sizeof *cb_data); + if (!cb_data) + return -1; + + mode = output->output->current_mode; + buf = gst_buffer_new(); + mem = gst_dmabuf_allocator_alloc(remoting->allocator, fd, + stride * mode->height); + gst_buffer_append_memory(buf, mem); + gst_buffer_add_video_meta_full(buf, + GST_VIDEO_FRAME_FLAG_NONE, + output->format->gst_video_format, + mode->width, + mode->height, + 1, + &offset, + &stride); + + cb_data->output = output; + cb_data->output_buffer = output_buffer; + gst_mini_object_weak_ref(GST_MINI_OBJECT(mem), + (GstMiniObjectNotify)remoting_gst_mem_free_cb, + cb_data); + + output->fence_sync_fd = api->get_fence_sync_fd(output->output); + /* Push buffer to gstreamer immediately on get_fence_sync_fd failure */ + if (output->fence_sync_fd == -1) { + remoting_output_gst_push_buffer(output, buf); + return 0; + } + + frame_data = zalloc(sizeof *frame_data); + if (!frame_data) { + close(output->fence_sync_fd); + remoting_output_gst_push_buffer(output, buf); + return 0; + } + + frame_data->output = output; + frame_data->buffer = buf; + loop = wl_display_get_event_loop(remoting->compositor->wl_display); + output->fence_sync_event_source = + wl_event_loop_add_fd(loop, output->fence_sync_fd, + WL_EVENT_READABLE, + remoting_output_fence_sync_handler, + frame_data); + + return 0; +} + +static void +remoting_output_destroy(struct weston_output *output) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + struct weston_mode *mode, *next; + + wl_list_for_each_safe(mode, next, &output->mode_list, link) { + wl_list_remove(&mode->link); + free(mode); + } + + remoted_output->saved_destroy(output); + + remoting_gst_pipeline_deinit(remoted_output); + remoting_gstpipe_release(&remoted_output->gstpipe); + + if (remoted_output->host) + free(remoted_output->host); + if (remoted_output->gst_pipeline) + free(remoted_output->gst_pipeline); + + wl_list_remove(&remoted_output->link); + weston_head_release(remoted_output->head); + free(remoted_output->head); + free(remoted_output); +} + +static int +remoting_output_start_repaint_loop(struct weston_output *output) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + int64_t msec; + + remoted_output->saved_start_repaint_loop(output); + + msec = millihz_to_nsec(remoted_output->output->current_mode->refresh) + / 1000000; + wl_event_source_timer_update(remoted_output->finish_frame_timer, msec); + + return 0; +} + +static void +remoting_output_set_dpms(struct weston_output *base_output, enum dpms_enum level) +{ + struct remoted_output *output = lookup_remoted_output(base_output); + + if (output->dpms == level) + return; + + output->dpms = level; + remoting_output_finish_frame_handler(output); +} + +static int +remoting_output_enable(struct weston_output *output) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + struct weston_compositor *c = output->compositor; + const struct weston_drm_virtual_output_api *api + = remoted_output->remoting->virtual_output_api; + struct wl_event_loop *loop; + int ret; + + api->set_submit_frame_cb(output, remoting_output_frame); + + ret = remoted_output->saved_enable(output); + if (ret < 0) + return ret; + + remoted_output->saved_start_repaint_loop = output->start_repaint_loop; + output->start_repaint_loop = remoting_output_start_repaint_loop; + output->set_dpms = remoting_output_set_dpms; + + ret = remoting_gst_pipeline_init(remoted_output); + if (ret < 0) { + remoted_output->saved_disable(output); + return ret; + } + + loop = wl_display_get_event_loop(c->wl_display); + remoted_output->finish_frame_timer = + wl_event_loop_add_timer(loop, + remoting_output_finish_frame_handler, + remoted_output); + + remoted_output->dpms = WESTON_DPMS_ON; + return 0; +} + +static int +remoting_output_disable(struct weston_output *output) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + + wl_event_source_remove(remoted_output->finish_frame_timer); + remoting_gst_pipeline_deinit(remoted_output); + + return remoted_output->saved_disable(output); +} + +static struct weston_output * +remoting_output_create(struct weston_compositor *c, char *name) +{ + struct weston_remoting *remoting = weston_remoting_get(c); + struct remoted_output *output; + struct weston_head *head; + const struct weston_drm_virtual_output_api *api; + const char *make = "Renesas"; + const char *model = "Virtual Display"; + const char *serial_number = "unknown"; + const char *connector_name = "remoting"; + + if (!name || !strlen(name)) + return NULL; + + api = remoting->virtual_output_api; + + output = zalloc(sizeof *output); + if (!output) + return NULL; + + head = zalloc(sizeof *head); + if (!head) + goto err; + + if (remoting_gstpipe_init(c, output) < 0) { + weston_log("Can not create pipe for gstreamer\n"); + goto err; + } + + output->output = api->create_output(c, name); + if (!output->output) { + weston_log("Can not create virtual output\n"); + goto err; + } + + output->saved_destroy = output->output->destroy; + output->output->destroy = remoting_output_destroy; + output->saved_enable = output->output->enable; + output->output->enable = remoting_output_enable; + output->saved_disable = output->output->disable; + output->output->disable = remoting_output_disable; + output->remoting = remoting; + wl_list_insert(remoting->output_list.prev, &output->link); + + weston_head_init(head, connector_name); + weston_head_set_subpixel(head, WL_OUTPUT_SUBPIXEL_NONE); + weston_head_set_monitor_strings(head, make, model, serial_number); + head->compositor = c; + + weston_output_attach_head(output->output, head); + output->head = head; + + /* set XRGB8888 format */ + output->format = &supported_formats[0]; + + return output->output; + +err: + if (output->gstpipe.source) + remoting_gstpipe_release(&output->gstpipe); + if (head) + free(head); + free(output); + return NULL; +} + +static bool +remoting_output_is_remoted(struct weston_output *output) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + + if (remoted_output) + return true; + + return false; +} + +static int +remoting_output_set_mode(struct weston_output *output, const char *modeline) +{ + struct weston_mode *mode; + int n, width, height, refresh = 0; + + if (!remoting_output_is_remoted(output)) { + weston_log("Output is not remoted.\n"); + return -1; + } + + if (!modeline) + return -1; + + n = sscanf(modeline, "%dx%d@%d", &width, &height, &refresh); + if (n != 2 && n != 3) + return -1; + + mode = zalloc(sizeof *mode); + if (!mode) + return -1; + + mode->flags = WL_OUTPUT_MODE_CURRENT; + mode->width = width; + mode->height = height; + mode->refresh = (refresh ? refresh : 60) * 1000LL; + + wl_list_insert(output->mode_list.prev, &mode->link); + + output->current_mode = mode; + + return 0; +} + +static void +remoting_output_set_gbm_format(struct weston_output *output, + const char *gbm_format) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + const struct weston_drm_virtual_output_api *api; + uint32_t format, i; + + if (!remoted_output) + return; + + api = remoted_output->remoting->virtual_output_api; + format = api->set_gbm_format(output, gbm_format); + + for (i = 0; i < ARRAY_LENGTH(supported_formats); i++) { + if (format == supported_formats[i].gbm_format) { + remoted_output->format = &supported_formats[i]; + return; + } + } +} + +static void +remoting_output_set_seat(struct weston_output *output, const char *seat) +{ + /* for now, nothing todo */ +} + +static void +remoting_output_set_host(struct weston_output *output, char *host) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + + if (!remoted_output) + return; + + if (remoted_output->host) + free(remoted_output->host); + remoted_output->host = strdup(host); +} + +static void +remoting_output_set_port(struct weston_output *output, int port) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + + if (remoted_output) + remoted_output->port = port; +} + +static void +remoting_output_set_gst_pipeline(struct weston_output *output, + char *gst_pipeline) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + + if (!remoted_output) + return; + + if (remoted_output->gst_pipeline) + free(remoted_output->gst_pipeline); + remoted_output->gst_pipeline = strdup(gst_pipeline); +} + +static const struct weston_remoting_api remoting_api = { + remoting_output_create, + remoting_output_is_remoted, + remoting_output_set_mode, + remoting_output_set_gbm_format, + remoting_output_set_seat, + remoting_output_set_host, + remoting_output_set_port, + remoting_output_set_gst_pipeline, +}; + +WL_EXPORT int +weston_module_init(struct weston_compositor *compositor) +{ + int ret; + struct weston_remoting *remoting; + const struct weston_drm_virtual_output_api *api = + weston_drm_virtual_output_get_api(compositor); + + if (!api) + return -1; + + remoting = zalloc(sizeof *remoting); + if (!remoting) + return -1; + + if (!weston_compositor_add_destroy_listener_once(compositor, + &remoting->destroy_listener, + weston_remoting_destroy)) { + free(remoting); + return 0; + } + + remoting->virtual_output_api = api; + remoting->compositor = compositor; + wl_list_init(&remoting->output_list); + + ret = weston_plugin_api_register(compositor, WESTON_REMOTING_API_NAME, + &remoting_api, sizeof(remoting_api)); + + if (ret < 0) { + weston_log("Failed to register remoting API.\n"); + goto failed; + } + + /* Initialize gstreamer */ + ret = remoting_gst_init(remoting); + if (ret < 0) { + weston_log("Failed to initialize gstreamer.\n"); + goto failed; + } + + return 0; + +failed: + wl_list_remove(&remoting->destroy_listener.link); + free(remoting); + return -1; +} diff --git a/remoting/remoting-plugin.h b/remoting/remoting-plugin.h new file mode 100644 index 0000000..fdd429c --- /dev/null +++ b/remoting/remoting-plugin.h @@ -0,0 +1,78 @@ +/* + * Copyright © 2018 Renesas Electronics Corp. + * + * 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: IGEL Co., Ltd. + */ + +#ifndef REMOTING_PLUGIN_H +#define REMOTING_PLUGIN_H + +#include +#include + +#define WESTON_REMOTING_API_NAME "weston_remoting_api_v1" + +struct weston_remoting_api { + /** Create remoted outputs + * + * Returns 0 on success, -1 on failure. + */ + struct weston_output *(*create_output)(struct weston_compositor *c, + char *name); + + /** Check if output is remoted */ + bool (*is_remoted_output)(struct weston_output *output); + + /** Set mode */ + int (*set_mode)(struct weston_output *output, const char *modeline); + + /** Set gbm format */ + void (*set_gbm_format)(struct weston_output *output, + const char *gbm_format); + + /** Set seat */ + void (*set_seat)(struct weston_output *output, const char *seat); + + /** Set the destination Host(IP Address) */ + void (*set_host)(struct weston_output *output, char *ip); + + /** Set the port number */ + void (*set_port)(struct weston_output *output, int port); + + /** Set the pipeline for gstreamer */ + void (*set_gst_pipeline)(struct weston_output *output, + char *gst_pipeline); +}; + +static inline const struct weston_remoting_api * +weston_remoting_get_api(struct weston_compositor *compositor) +{ + const void *api; + api = weston_plugin_api_get(compositor, WESTON_REMOTING_API_NAME, + sizeof(struct weston_remoting_api)); + + return (const struct weston_remoting_api *)api; +} + +#endif /* REMOTING_PLUGIN_H */ diff --git a/shared/cairo-util.c b/shared/cairo-util.c new file mode 100644 index 0000000..f558e1d --- /dev/null +++ b/shared/cairo-util.c @@ -0,0 +1,635 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include "cairo-util.h" + +#include "shared/helpers.h" +#include "image-loader.h" +#include + +#ifdef HAVE_PANGO +#include +#endif + +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 +render_shadow(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, shadow_height, shadow_width; + + cairo_set_source_rgba(cr, 0, 0, 0, 0.45); + 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++) { + /* when fy is set, then we are working with lower corners, + * when fx is set, then we are working with right corners + * + * 00 ------- 01 + * | | + * | | + * 10 ------- 11 + */ + 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); + + shadow_width = margin; + shadow_height = fy ? margin : top_margin; + + /* if the shadows together are greater than the surface, we need + * to fix it - set the shadow size to the half of + * the size of surface. Also handle the case when the size is + * not divisible by 2. In that case we need one part of the + * shadow to be one pixel greater. !fy or !fx, respectively, + * will do the work. + */ + if (height < 2 * shadow_height) + shadow_height = (height + !fy) / 2; + + if (width < 2 * shadow_width) + shadow_width = (width + !fx) / 2; + + cairo_reset_clip(cr); + cairo_rectangle(cr, + x + fx * (width - shadow_width), + y + fy * (height - shadow_height), + shadow_width, shadow_height); + cairo_clip (cr); + cairo_mask(cr, pattern); + } + + + shadow_width = width - 2 * margin; + shadow_height = top_margin; + if (height < 2 * shadow_height) + shadow_height = height / 2; + + if (shadow_width > 0 && shadow_height) { + /* 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, shadow_width, shadow_height); + + cairo_reset_clip(cr); + cairo_rectangle(cr, + x + margin, y, + shadow_width, shadow_height); + 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, + shadow_width, margin); + cairo_clip (cr); + cairo_mask(cr, pattern); + } + + shadow_width = margin; + if (width < 2 * shadow_width) + shadow_width = width / 2; + + shadow_height = height - margin - top_margin; + + /* if height is smaller than sum of margins, + * then the shadow is already done by the corners */ + if (shadow_height > 0 && shadow_width) { + /* 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 + top_margin, + shadow_width, shadow_height); + 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 - shadow_width, y + top_margin, + shadow_width, shadow_height); + 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); +} + +void +theme_set_background_source(struct theme *t, cairo_t *cr, uint32_t flags) +{ + cairo_pattern_t *pattern; + + if (flags & THEME_FRAME_ACTIVE) { + 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); + } else { + cairo_set_source_rgba(cr, 0.75, 0.75, 0.75, 1); + } +} + +struct theme * +theme_create(void) +{ + struct theme *t; + cairo_t *cr; + + 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); + + theme_set_background_source(t, cr, THEME_FRAME_ACTIVE); + 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); + theme_set_background_source(t, cr, 0); + 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); +} + +#ifdef HAVE_PANGO +static PangoLayout * +create_layout(cairo_t *cr, const char *title) +{ + PangoLayout *layout; + PangoFontDescription *desc; + + layout = pango_cairo_create_layout(cr); + if (title) { + pango_layout_set_text(layout, title, -1); + desc = pango_font_description_from_string("Sans Bold 10"); + pango_layout_set_font_description(layout, desc); + pango_font_description_free(desc); + } + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT); + pango_layout_set_auto_dir (layout, FALSE); + pango_layout_set_single_paragraph_mode (layout, TRUE); + pango_layout_set_width (layout, -1); + + return layout; +} +#endif + +#ifdef HAVE_PANGO +#define SHOW_TEXT(cr) \ + pango_cairo_show_layout(cr, title_layout) +#else +#define SHOW_TEXT(cr) \ + cairo_show_text(cr, title) +#endif + +void +theme_render_frame(struct theme *t, + cairo_t *cr, int width, int height, + const char *title, cairo_rectangle_int_t *title_rect, + struct wl_list *buttons, uint32_t flags) +{ + cairo_surface_t *source; + int x, y, margin, top_margin; + int text_width, text_height; + + 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 { + render_shadow(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; + + if (title || !wl_list_empty(buttons)) + top_margin = t->titlebar_height; + else + top_margin = t->width; + + tile_source(cr, source, + margin, margin, + width - margin * 2, height - margin * 2, + t->width, top_margin); + + if (title || !wl_list_empty(buttons)) { + + cairo_rectangle (cr, title_rect->x, title_rect->y, + title_rect->width, title_rect->height); + cairo_clip(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + +#ifdef HAVE_PANGO + PangoLayout *title_layout; + PangoRectangle logical; + + title_layout = create_layout(cr, title); + + pango_layout_get_pixel_extents (title_layout, NULL, &logical); + text_width = MIN(title_rect->width, logical.width); + text_height = logical.height; + if (text_width < logical.width) + pango_layout_set_width (title_layout, text_width * PANGO_SCALE); + +#else + cairo_text_extents_t extents; + cairo_font_extents_t font_extents; + + 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); + text_width = extents.width; + text_height = font_extents.descent - font_extents.ascent; +#endif + + x = (width - text_width) / 2; + y = margin + (t->titlebar_height - text_height) / 2; + if (x < title_rect->x) + x = title_rect->x; + else if (x + text_width > (title_rect->x + title_rect->width)) + x = (title_rect->x + title_rect->width) - text_width; + + if (flags & THEME_FRAME_ACTIVE) { + cairo_move_to(cr, x + 1, y + 1); + cairo_set_source_rgb(cr, 1, 1, 1); + SHOW_TEXT(cr); + cairo_move_to(cr, x, y); + cairo_set_source_rgb(cr, 0, 0, 0); + SHOW_TEXT(cr); + } else { + cairo_move_to(cr, x, y); + cairo_set_source_rgb(cr, 0.4, 0.4, 0.4); + SHOW_TEXT(cr); + } + } +} + +enum theme_location +theme_get_location(struct theme *t, int x, int y, + int width, int height, int flags) +{ + int vlocation, hlocation, location; + int margin, top_margin, grip_size; + + if (flags & THEME_FRAME_MAXIMIZED) { + margin = 0; + grip_size = 0; + } else { + margin = t->margin; + grip_size = 8; + } + + if (flags & THEME_FRAME_NO_TITLE) + top_margin = t->width; + else + top_margin = t->titlebar_height; + + if (x < margin) + hlocation = THEME_LOCATION_EXTERIOR; + else if (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 (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 + top_margin) + 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 0000000..6fd11f6 --- /dev/null +++ b/shared/cairo-util.h @@ -0,0 +1,233 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _CAIRO_UTIL_H +#define _CAIRO_UTIL_H + +#include +#include + +#include +#include + +void +surface_flush_device(cairo_surface_t *surface); + +void +render_shadow(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 = 2, + THEME_FRAME_NO_TITLE = 4 +}; + +void +theme_set_background_source(struct theme *t, cairo_t *cr, uint32_t flags); +void +theme_render_frame(struct theme *t, + cairo_t *cr, int width, int height, + const char *title, cairo_rectangle_int_t *title_rect, + struct wl_list *buttons, 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); + +struct frame; + +enum frame_status { + FRAME_STATUS_NONE = 0, + FRAME_STATUS_REPAINT = 0x1, + FRAME_STATUS_MINIMIZE = 0x2, + FRAME_STATUS_MAXIMIZE = 0x4, + FRAME_STATUS_CLOSE = 0x8, + FRAME_STATUS_MENU = 0x10, + FRAME_STATUS_RESIZE = 0x20, + FRAME_STATUS_MOVE = 0x40, + FRAME_STATUS_ALL = 0x7f +}; + +enum frame_flag { + FRAME_FLAG_ACTIVE = 0x1, + FRAME_FLAG_MAXIMIZED = 0x2 +}; + +enum { + FRAME_BUTTON_NONE = 0, + FRAME_BUTTON_CLOSE = 0x1, + FRAME_BUTTON_MAXIMIZE = 0x2, + FRAME_BUTTON_MINIMIZE = 0x4, + FRAME_BUTTON_ALL = 0x7 +}; + +struct frame * +frame_create(struct theme *t, int32_t width, int32_t height, uint32_t buttons, + const char *title, cairo_surface_t *icon); + +void +frame_destroy(struct frame *frame); + +/* May set FRAME_STATUS_REPAINT */ +int +frame_set_title(struct frame *frame, const char *title); + +/* May set FRAME_STATUS_REPAINT */ +void +frame_set_icon(struct frame *frame, cairo_surface_t *icon); + +/* May set FRAME_STATUS_REPAINT */ +void +frame_set_flag(struct frame *frame, enum frame_flag flag); + +/* May set FRAME_STATUS_REPAINT */ +void +frame_unset_flag(struct frame *frame, enum frame_flag flag); + +/* May set FRAME_STATUS_REPAINT */ +void +frame_resize(struct frame *frame, int32_t width, int32_t height); + +/* May set FRAME_STATUS_REPAINT */ +void +frame_resize_inside(struct frame *frame, int32_t width, int32_t height); + +int32_t +frame_width(struct frame *frame); + +int32_t +frame_height(struct frame *frame); + +void +frame_interior(struct frame *frame, int32_t *x, int32_t *y, + int32_t *width, int32_t *height); +void +frame_input_rect(struct frame *frame, int32_t *x, int32_t *y, + int32_t *width, int32_t *height); +void +frame_opaque_rect(struct frame *frame, int32_t *x, int32_t *y, + int32_t *width, int32_t *height); + +int +frame_get_shadow_margin(struct frame *frame); + +uint32_t +frame_status(struct frame *frame); + +void +frame_status_clear(struct frame *frame, enum frame_status status); + +/* May set FRAME_STATUS_REPAINT */ +enum theme_location +frame_pointer_enter(struct frame *frame, void *pointer, int x, int y); + +/* May set FRAME_STATUS_REPAINT */ +enum theme_location +frame_pointer_motion(struct frame *frame, void *pointer, int x, int y); + +/* May set FRAME_STATUS_REPAINT */ +void +frame_pointer_leave(struct frame *frame, void *pointer); + +/* Call to indicate that a button has been pressed/released. The return + * value for a button release will be the same as for the corresponding + * press. This allows you to more easily track grabs. If you want the + * actual location, simply keep the location from the last + * frame_pointer_motion call. + * + * May set: + * FRAME_STATUS_MINIMIZE + * FRAME_STATUS_MAXIMIZE + * FRAME_STATUS_CLOSE + * FRAME_STATUS_MENU + * FRAME_STATUS_RESIZE + * FRAME_STATUS_MOVE + */ +enum theme_location +frame_pointer_button(struct frame *frame, void *pointer, + uint32_t button, enum wl_pointer_button_state state); + +enum theme_location +frame_touch_down(struct frame *frame, void *data, int32_t id, int x, int y); + +void +frame_touch_up(struct frame *frame, void *data, int32_t id); + +enum theme_location +frame_double_click(struct frame *frame, void *pointer, + uint32_t button, enum wl_pointer_button_state state); + +void +frame_double_touch_down(struct frame *frame, void *data, int32_t id, + int x, int y); + +void +frame_double_touch_up(struct frame *frame, void *data, int32_t id); + +void +frame_repaint(struct frame *frame, cairo_t *cr); + +#endif diff --git a/shared/config-parser.c b/shared/config-parser.c new file mode 100644 index 0000000..64e4827 --- /dev/null +++ b/shared/config-parser.c @@ -0,0 +1,525 @@ +/* + * Copyright © 2011 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "helpers.h" +#include "string-helpers.h" + +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, + * then to directories listed in XDG_CONFIG_DIRS. */ + + /* $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++; + } + + return -1; +} + +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; + + entry = config_section_get_entry(section, key); + if (entry == NULL) { + *value = default_value; + errno = ENOENT; + return -1; + } + + if (!safe_strtoint(entry->value, value)) { + *value = default_value; + 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) +{ + long int ret; + struct weston_config_entry *entry; + char *end; + + entry = config_section_get_entry(section, key); + if (entry == NULL) { + *value = default_value; + errno = ENOENT; + return -1; + } + + errno = 0; + ret = strtol(entry->value, &end, 0); + if (errno != 0 || end == entry->value || *end != '\0') { + *value = default_value; + errno = EINVAL; + return -1; + } + + /* check range */ + if (ret < 0 || ret > INT_MAX) { + *value = default_value; + errno = ERANGE; + return -1; + } + + *value = ret; + + return 0; +} + +WL_EXPORT +int +weston_config_section_get_color(struct weston_config_section *section, + const char *key, + uint32_t *color, uint32_t default_color) +{ + struct weston_config_entry *entry; + int len; + char *end; + + entry = config_section_get_entry(section, key); + if (entry == NULL) { + *color = default_color; + errno = ENOENT; + return -1; + } + + len = strlen(entry->value); + if (len == 1 && entry->value[0] == '0') { + *color = 0; + return 0; + } else if (len != 8 && len != 10) { + *color = default_color; + errno = EINVAL; + return -1; + } + + errno = 0; + *color = strtoul(entry->value, &end, 16); + if (errno != 0 || end == entry->value || *end != '\0') { + *color = default_color; + 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, + bool *value, bool 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 = false; + else if (strcmp(entry->value, "true") == 0) + *value = true; + else { + *value = default_value; + errno = EINVAL; + return -1; + } + + return 0; +} + +WL_EXPORT +const char * +weston_config_get_name_from_env(void) +{ + const char *name; + + name = getenv(WESTON_CONFIG_FILE_ENV_VAR); + if (name) + return name; + + return "weston.ini"; +} + +static struct weston_config_section * +config_add_section(struct weston_config *config, const char *name) +{ + struct weston_config_section *section; + + section = malloc(sizeof *section); + if (section == NULL) + return NULL; + + section->name = strdup(name); + if (section->name == NULL) { + free(section); + return NULL; + } + + 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); + if (entry == NULL) + return NULL; + + entry->key = strdup(key); + if (entry->key == NULL) { + free(entry); + return NULL; + } + + entry->value = strdup(value); + if (entry->value == NULL) { + free(entry->key); + free(entry); + return NULL; + } + + wl_list_insert(section->entry_list.prev, &entry->link); + + return entry; +} + +WL_EXPORT +struct weston_config * +weston_config_parse(const char *name) +{ + FILE *fp; + char line[512], *p; + struct stat filestat; + 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; + } + + if (fstat(fd, &filestat) < 0 || + !S_ISREG(filestat.st_mode)) { + close(fd); + 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; +} + +WL_EXPORT +const char * +weston_config_get_full_path(struct weston_config *config) +{ + return config == NULL ? NULL : config->path; +} + +WL_EXPORT +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; +} + +WL_EXPORT +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/fd-util.h b/shared/fd-util.h new file mode 100644 index 0000000..da4ef6f --- /dev/null +++ b/shared/fd-util.h @@ -0,0 +1,56 @@ +/* + * Copyright © 2018 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef FD_UTIL_H +#define FD_UTIL_H + +#include + +static inline void +fd_update(int *fd, int new_fd) +{ + if (*fd == new_fd) + return; + if (*fd >= 0) + close(*fd); + *fd = new_fd; +} + +static inline void +fd_move(int *dest, int *src) +{ + if (dest == src) + return; + fd_update(dest, *src); + *src = -1; +} + +static inline void +fd_clear(int *fd) +{ + fd_update(fd, -1); +} + +#endif /* FD_UTIL_H */ diff --git a/shared/file-util.c b/shared/file-util.c new file mode 100644 index 0000000..151ef2a --- /dev/null +++ b/shared/file-util.c @@ -0,0 +1,146 @@ +/* + * Copyright © 2015 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "file-util.h" + +static int +current_time_str(char *str, size_t len, const char *fmt) +{ + time_t t; + struct tm *t_local; + int ret; + + t = time(NULL); + t_local = localtime(&t); + if (!t_local) { + errno = ETIME; + return -1; + } + + ret = strftime(str, len, fmt, t_local); + if (ret == 0) { + errno = ETIME; + return -1; + } + + return ret; +} + +static int +create_file_excl(const char *fname) +{ + return open(fname, O_RDWR | O_CLOEXEC | O_CREAT | O_EXCL, 00666); +} + +/** Create a unique file with date and time in the name + * + * \param path File path + * \param prefix File name prefix. + * \param suffix File name suffix. + * \param[out] name_out Buffer for the resulting file name. + * \param name_len Number of bytes usable in name_out. + * \return stdio FILE pointer, or NULL on failure. + * + * Create and open a new file with the name concatenated from + * path/prefix, date and time, and suffix. If a file with this name + * already exists, an counter number is added to the end of the + * date and time sub-string. The counter is increased until a free file + * name is found. + * + * Once creating the file succeeds, the name of the file is in name_out. + * On failure, the contents of name_out are undefined and errno is set. + */ +FILE * +file_create_dated(const char *path, const char *prefix, const char *suffix, + char *name_out, size_t name_len) +{ + char timestr[128]; + int ret; + int fd; + int cnt = 0; + int with_path; + + with_path = path && path[0]; + + if (current_time_str(timestr, sizeof(timestr), "%F_%H-%M-%S") < 0) + return NULL; + + ret = snprintf(name_out, name_len, "%s%s%s%s%s", + with_path ? path : "", with_path ? "/" : "", + prefix, timestr, suffix); + if (ret < 0 || (size_t)ret >= name_len) { + errno = ENOBUFS; + return NULL; + } + + fd = create_file_excl(name_out); + + while (fd == -1 && errno == EEXIST) { + cnt++; + + ret = snprintf(name_out, name_len, "%s%s%s%s-%d%s", + with_path ? path : "", with_path ? "/" : "", + prefix, timestr, cnt, suffix); + if (ret < 0 || (size_t)ret >= name_len) { + errno = ENOBUFS; + return NULL; + } + + fd = create_file_excl(name_out); + } + + if (fd == -1) + return NULL; + + return fdopen(fd, "w"); +} + +char * +file_name_with_datadir(const char *filename) +{ + const char *base = getenv("WESTON_DATA_DIR"); + char *out; + int len; + + if (base) + len = asprintf(&out, "%s/%s", base, filename); + else + len = asprintf(&out, "%s/weston/%s", DATADIR, filename); + + if (len == -1) + return NULL; + + return out; +} diff --git a/shared/file-util.h b/shared/file-util.h new file mode 100644 index 0000000..b20bbda --- /dev/null +++ b/shared/file-util.h @@ -0,0 +1,46 @@ +/* + * Copyright © 2015 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_FILE_UTIL_H +#define WESTON_FILE_UTIL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +FILE * +file_create_dated(const char *path, const char *prefix, const char *suffix, + char *name_out, size_t name_len); + +char * +file_name_with_datadir(const char *); + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_FILE_UTIL_H */ diff --git a/shared/frame.c b/shared/frame.c new file mode 100644 index 0000000..e8a5cad --- /dev/null +++ b/shared/frame.c @@ -0,0 +1,1021 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2012-2013 Collabora, Ltd. + * Copyright © 2013 Jason Ekstrand + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "cairo-util.h" +#include "shared/file-util.h" + +enum frame_button_flags { + FRAME_BUTTON_ALIGN_RIGHT = 0x1, + FRAME_BUTTON_DECORATED = 0x2, + FRAME_BUTTON_CLICK_DOWN = 0x4, +}; + +struct frame_button { + struct frame *frame; + struct wl_list link; /* buttons_list */ + + cairo_surface_t *icon; + enum frame_button_flags flags; + int hover_count; + int press_count; + + struct { + int x, y; + int width, height; + } allocation; + + enum frame_status status_effect; +}; + +struct frame_pointer_button { + struct wl_list link; + uint32_t button; + enum theme_location press_location; + struct frame_button *frame_button; +}; + +struct frame_pointer { + struct wl_list link; + void *data; + + int x, y; + + struct frame_button *hover_button; + struct wl_list down_buttons; +}; + +struct frame_touch { + struct wl_list link; + void *data; + + int x, y; + + struct frame_button *button; +}; + +struct frame { + int32_t width, height; + char *title; + uint32_t flags; + struct theme *theme; + + struct { + int32_t x, y; + int32_t width, height; + } interior; + int shadow_margin; + int opaque_margin; + int geometry_dirty; + + cairo_rectangle_int_t title_rect; + + uint32_t status; + + struct wl_list buttons; + struct wl_list pointers; + struct wl_list touches; +}; + +static struct frame_button * +frame_button_create_from_surface(struct frame *frame, cairo_surface_t *icon, + enum frame_status status_effect, + enum frame_button_flags flags) +{ + struct frame_button *button; + + button = calloc(1, sizeof *button); + if (!button) + return NULL; + + button->icon = icon; + button->frame = frame; + button->flags = flags; + button->status_effect = status_effect; + + wl_list_insert(frame->buttons.prev, &button->link); + + return button; +} + +static struct frame_button * +frame_button_create(struct frame *frame, const char *icon_name, + enum frame_status status_effect, + enum frame_button_flags flags) +{ + struct frame_button *button; + cairo_surface_t *icon; + + icon = cairo_image_surface_create_from_png(icon_name); + if (cairo_surface_status(icon) != CAIRO_STATUS_SUCCESS) + goto error; + + button = frame_button_create_from_surface(frame, icon, status_effect, + flags); + if (!button) + goto error; + + return button; + +error: + cairo_surface_destroy(icon); + return NULL; +} + +static void +frame_button_destroy(struct frame_button *button) +{ + cairo_surface_destroy(button->icon); + free(button); +} + +static void +frame_button_enter(struct frame_button *button) +{ + if (!button->hover_count) + button->frame->status |= FRAME_STATUS_REPAINT; + button->hover_count++; +} + +static void +frame_button_leave(struct frame_button *button, struct frame_pointer *pointer) +{ + button->hover_count--; + if (!button->hover_count) + button->frame->status |= FRAME_STATUS_REPAINT; +} + +static void +frame_button_press(struct frame_button *button) +{ + if (!button->press_count) + button->frame->status |= FRAME_STATUS_REPAINT; + button->press_count++; + + if (button->flags & FRAME_BUTTON_CLICK_DOWN) + button->frame->status |= button->status_effect; +} + +static void +frame_button_release(struct frame_button *button) +{ + button->press_count--; + if (button->press_count) + return; + + button->frame->status |= FRAME_STATUS_REPAINT; + + if (!(button->flags & FRAME_BUTTON_CLICK_DOWN)) + button->frame->status |= button->status_effect; +} + +static void +frame_button_cancel(struct frame_button *button) +{ + button->press_count--; + if (!button->press_count) + button->frame->status |= FRAME_STATUS_REPAINT; +} + +static void +frame_button_repaint(struct frame_button *button, cairo_t *cr) +{ + int x, y; + + if (!button->allocation.width) + return; + if (!button->allocation.height) + return; + + x = button->allocation.x; + y = button->allocation.y; + + cairo_save(cr); + + if (button->flags & FRAME_BUTTON_DECORATED) { + 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); + + if (button->press_count) { + cairo_set_source_rgb(cr, 0.7, 0.7, 0.7); + } else if (button->hover_count) { + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + } else { + cairo_set_source_rgb(cr, 0.88, 0.88, 0.88); + } + + cairo_fill (cr); + + x += 4; + } + + cairo_set_source_surface(cr, button->icon, x, y); + cairo_paint(cr); + + cairo_restore(cr); +} + +static struct frame_pointer * +frame_pointer_get(struct frame *frame, void *data) +{ + struct frame_pointer *pointer; + + wl_list_for_each(pointer, &frame->pointers, link) + if (pointer->data == data) + return pointer; + + pointer = calloc(1, sizeof *pointer); + if (!pointer) + return NULL; + + pointer->data = data; + wl_list_init(&pointer->down_buttons); + wl_list_insert(&frame->pointers, &pointer->link); + + return pointer; +} + +static void +frame_pointer_destroy(struct frame_pointer *pointer) +{ + wl_list_remove(&pointer->link); + free(pointer); +} + +static struct frame_touch * +frame_touch_get(struct frame *frame, void *data) +{ + struct frame_touch *touch; + + wl_list_for_each(touch, &frame->touches, link) + if (touch->data == data) + return touch; + + touch = calloc(1, sizeof *touch); + if (!touch) + return NULL; + + touch->data = data; + wl_list_insert(&frame->touches, &touch->link); + + return touch; +} + +static void +frame_touch_destroy(struct frame_touch *touch) +{ + wl_list_remove(&touch->link); + free(touch); +} + +void +frame_destroy(struct frame *frame) +{ + struct frame_button *button, *next; + struct frame_touch *touch, *next_touch; + struct frame_pointer *pointer, *next_pointer; + + wl_list_for_each_safe(button, next, &frame->buttons, link) + frame_button_destroy(button); + + wl_list_for_each_safe(touch, next_touch, &frame->touches, link) + frame_touch_destroy(touch); + + wl_list_for_each_safe(pointer, next_pointer, &frame->pointers, link) + frame_pointer_destroy(pointer); + + free(frame->title); + free(frame); +} + +struct frame * +frame_create(struct theme *t, int32_t width, int32_t height, uint32_t buttons, + const char *title, cairo_surface_t *icon) +{ + struct frame *frame; + struct frame_button *button; + + frame = calloc(1, sizeof *frame); + if (!frame) + return NULL; + + frame->width = width; + frame->height = height; + frame->flags = 0; + frame->theme = t; + frame->status = FRAME_STATUS_REPAINT; + frame->geometry_dirty = 1; + + wl_list_init(&frame->buttons); + wl_list_init(&frame->pointers); + wl_list_init(&frame->touches); + + if (title) { + frame->title = strdup(title); + if (!frame->title) + goto free_frame; + } + + if (title) { + if (icon) { + button = frame_button_create_from_surface(frame, + icon, + FRAME_STATUS_MENU, + FRAME_BUTTON_CLICK_DOWN); + } else { + char *name = file_name_with_datadir("icon_window.png"); + + if (!name) + goto free_frame; + + button = frame_button_create(frame, + name, + FRAME_STATUS_MENU, + FRAME_BUTTON_CLICK_DOWN); + free(name); + } + if (!button) + goto free_frame; + } + + if (buttons & FRAME_BUTTON_CLOSE) { + char *name = file_name_with_datadir("sign_close.png"); + + if (!name) + goto free_frame; + + button = frame_button_create(frame, + name, + FRAME_STATUS_CLOSE, + FRAME_BUTTON_ALIGN_RIGHT | + FRAME_BUTTON_DECORATED); + free(name); + if (!button) + goto free_frame; + } + + if (buttons & FRAME_BUTTON_MAXIMIZE) { + char *name = file_name_with_datadir("sign_maximize.png"); + + if (!name) + goto free_frame; + + button = frame_button_create(frame, + name, + FRAME_STATUS_MAXIMIZE, + FRAME_BUTTON_ALIGN_RIGHT | + FRAME_BUTTON_DECORATED); + free(name); + if (!button) + goto free_frame; + } + + if (buttons & FRAME_BUTTON_MINIMIZE) { + char *name = file_name_with_datadir("sign_minimize.png"); + + if (!name) + goto free_frame; + + button = frame_button_create(frame, + name, + FRAME_STATUS_MINIMIZE, + FRAME_BUTTON_ALIGN_RIGHT | + FRAME_BUTTON_DECORATED); + free(name); + if (!button) + goto free_frame; + } + + return frame; + +free_frame: + frame_destroy(frame); + return NULL; +} + +int +frame_set_title(struct frame *frame, const char *title) +{ + char *dup = NULL; + + if (title) { + dup = strdup(title); + if (!dup) + return -1; + } + + free(frame->title); + frame->title = dup; + + frame->geometry_dirty = 1; + frame->status |= FRAME_STATUS_REPAINT; + + return 0; +} + +void +frame_set_icon(struct frame *frame, cairo_surface_t *icon) +{ + struct frame_button *button; + wl_list_for_each(button, &frame->buttons, link) { + if (button->status_effect != FRAME_STATUS_MENU) + continue; + if (button->icon) + cairo_surface_destroy(button->icon); + button->icon = icon; + frame->status |= FRAME_STATUS_REPAINT; + } +} + +void +frame_set_flag(struct frame *frame, enum frame_flag flag) +{ + if (flag & FRAME_FLAG_MAXIMIZED && !(frame->flags & FRAME_FLAG_MAXIMIZED)) + frame->geometry_dirty = 1; + + frame->flags |= flag; + frame->status |= FRAME_STATUS_REPAINT; +} + +void +frame_unset_flag(struct frame *frame, enum frame_flag flag) +{ + if (flag & FRAME_FLAG_MAXIMIZED && frame->flags & FRAME_FLAG_MAXIMIZED) + frame->geometry_dirty = 1; + + frame->flags &= ~flag; + frame->status |= FRAME_STATUS_REPAINT; +} + +void +frame_resize(struct frame *frame, int32_t width, int32_t height) +{ + frame->width = width; + frame->height = height; + + frame->geometry_dirty = 1; + frame->status |= FRAME_STATUS_REPAINT; +} + +void +frame_resize_inside(struct frame *frame, int32_t width, int32_t height) +{ + struct theme *t = frame->theme; + int decoration_width, decoration_height, titlebar_height; + + if (frame->title || !wl_list_empty(&frame->buttons)) + titlebar_height = t->titlebar_height; + else + titlebar_height = t->width; + + if (frame->flags & FRAME_FLAG_MAXIMIZED) { + decoration_width = t->width * 2; + decoration_height = t->width + titlebar_height; + } else { + decoration_width = (t->width + t->margin) * 2; + decoration_height = t->width + + titlebar_height + t->margin * 2; + } + + frame_resize(frame, width + decoration_width, + height + decoration_height); +} + +int32_t +frame_width(struct frame *frame) +{ + return frame->width; +} + +int32_t +frame_height(struct frame *frame) +{ + return frame->height; +} + +static void +frame_refresh_geometry(struct frame *frame) +{ + struct frame_button *button; + struct theme *t = frame->theme; + int x_l, x_r, y, w, h, titlebar_height; + int32_t decoration_width, decoration_height; + + if (!frame->geometry_dirty) + return; + + if (frame->title || !wl_list_empty(&frame->buttons)) + titlebar_height = t->titlebar_height; + else + titlebar_height = t->width; + + if (frame->flags & FRAME_FLAG_MAXIMIZED) { + decoration_width = t->width * 2; + decoration_height = t->width + titlebar_height; + + frame->interior.x = t->width; + frame->interior.y = titlebar_height; + frame->interior.width = frame->width - decoration_width; + frame->interior.height = frame->height - decoration_height; + + frame->opaque_margin = 0; + frame->shadow_margin = 0; + } else { + decoration_width = (t->width + t->margin) * 2; + decoration_height = t->width + titlebar_height + t->margin * 2; + + frame->interior.x = t->width + t->margin; + frame->interior.y = titlebar_height + t->margin; + frame->interior.width = frame->width - decoration_width; + frame->interior.height = frame->height - decoration_height; + + frame->opaque_margin = t->margin + t->frame_radius; + frame->shadow_margin = t->margin; + } + + x_r = frame->width - t->width - frame->shadow_margin; + x_l = t->width + frame->shadow_margin; + y = t->width + frame->shadow_margin; + wl_list_for_each(button, &frame->buttons, link) { + const int button_padding = 4; + w = cairo_image_surface_get_width(button->icon); + h = cairo_image_surface_get_height(button->icon); + + if (button->flags & FRAME_BUTTON_DECORATED) + w += 10; + + if (button->flags & FRAME_BUTTON_ALIGN_RIGHT) { + x_r -= w; + + button->allocation.x = x_r; + button->allocation.y = y; + button->allocation.width = w + 1; + button->allocation.height = h + 1; + + x_r -= button_padding; + } else { + button->allocation.x = x_l; + button->allocation.y = y; + button->allocation.width = w + 1; + button->allocation.height = h + 1; + + x_l += w; + x_l += button_padding; + } + } + + frame->title_rect.x = x_l; + frame->title_rect.y = y; + frame->title_rect.width = x_r - x_l; + frame->title_rect.height = titlebar_height; + + frame->geometry_dirty = 0; +} + +void +frame_interior(struct frame *frame, int32_t *x, int32_t *y, + int32_t *width, int32_t *height) +{ + frame_refresh_geometry(frame); + + if (x) + *x = frame->interior.x; + if (y) + *y = frame->interior.y; + if (width) + *width = frame->interior.width; + if (height) + *height = frame->interior.height; +} + +void +frame_input_rect(struct frame *frame, int32_t *x, int32_t *y, + int32_t *width, int32_t *height) +{ + frame_refresh_geometry(frame); + + if (x) + *x = frame->shadow_margin; + if (y) + *y = frame->shadow_margin; + if (width) + *width = frame->width - frame->shadow_margin * 2; + if (height) + *height = frame->height - frame->shadow_margin * 2; +} + +void +frame_opaque_rect(struct frame *frame, int32_t *x, int32_t *y, + int32_t *width, int32_t *height) +{ + frame_refresh_geometry(frame); + + if (x) + *x = frame->opaque_margin; + if (y) + *y = frame->opaque_margin; + if (width) + *width = frame->width - frame->opaque_margin * 2; + if (height) + *height = frame->height - frame->opaque_margin * 2; +} + +int +frame_get_shadow_margin(struct frame *frame) +{ + frame_refresh_geometry(frame); + + return frame->shadow_margin; +} + +uint32_t +frame_status(struct frame *frame) +{ + return frame->status; +} + +void +frame_status_clear(struct frame *frame, enum frame_status status) +{ + frame->status &= ~status; +} + +static struct frame_button * +frame_find_button(struct frame *frame, int x, int y) +{ + struct frame_button *button; + int rel_x, rel_y; + + wl_list_for_each(button, &frame->buttons, link) { + rel_x = x - button->allocation.x; + rel_y = y - button->allocation.y; + + if (0 <= rel_x && rel_x < button->allocation.width && + 0 <= rel_y && rel_y < button->allocation.height) + return button; + } + + return NULL; +} + +enum theme_location +frame_pointer_enter(struct frame *frame, void *data, int x, int y) +{ + return frame_pointer_motion(frame, data, x, y); +} + +enum theme_location +frame_pointer_motion(struct frame *frame, void *data, int x, int y) +{ + struct frame_pointer *pointer = frame_pointer_get(frame, data); + struct frame_button *button = frame_find_button(frame, x, y); + enum theme_location location; + + location = theme_get_location(frame->theme, x, y, + frame->width, frame->height, + frame->flags & FRAME_FLAG_MAXIMIZED ? + THEME_FRAME_MAXIMIZED : 0); + if (!pointer) + return location; + + pointer->x = x; + pointer->y = y; + + if (pointer->hover_button == button) + return location; + + if (pointer->hover_button) + frame_button_leave(pointer->hover_button, pointer); + + pointer->hover_button = button; + + if (pointer->hover_button) + frame_button_enter(pointer->hover_button); + + return location; +} + +static void +frame_pointer_button_destroy(struct frame_pointer_button *button) +{ + wl_list_remove(&button->link); + free(button); +} + +static void +frame_pointer_button_press(struct frame *frame, struct frame_pointer *pointer, + struct frame_pointer_button *button) +{ + if (button->button == BTN_RIGHT) { + if (button->press_location == THEME_LOCATION_TITLEBAR) + frame->status |= FRAME_STATUS_MENU; + + frame_pointer_button_destroy(button); + + } else if (button->button == BTN_LEFT) { + if (pointer->hover_button) { + frame_button_press(pointer->hover_button); + } else { + switch (button->press_location) { + case THEME_LOCATION_TITLEBAR: + frame->status |= FRAME_STATUS_MOVE; + + frame_pointer_button_destroy(button); + 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: + frame->status |= FRAME_STATUS_RESIZE; + + frame_pointer_button_destroy(button); + break; + default: + break; + } + } + } +} + +static void +frame_pointer_button_release(struct frame *frame, struct frame_pointer *pointer, + struct frame_pointer_button *button) +{ + if (button->button == BTN_LEFT && button->frame_button) { + if (button->frame_button == pointer->hover_button) + frame_button_release(button->frame_button); + else + frame_button_cancel(button->frame_button); + } +} + +static void +frame_pointer_button_cancel(struct frame *frame, struct frame_pointer *pointer, + struct frame_pointer_button *button) +{ + if (button->frame_button) + frame_button_cancel(button->frame_button); +} + +void +frame_pointer_leave(struct frame *frame, void *data) +{ + struct frame_pointer *pointer = frame_pointer_get(frame, data); + struct frame_pointer_button *button, *next; + if (!pointer) + return; + + if (pointer->hover_button) + frame_button_leave(pointer->hover_button, pointer); + + wl_list_for_each_safe(button, next, &pointer->down_buttons, link) { + frame_pointer_button_cancel(frame, pointer, button); + frame_pointer_button_destroy(button); + } + + frame_pointer_destroy(pointer); +} + +enum theme_location +frame_pointer_button(struct frame *frame, void *data, + uint32_t btn, enum wl_pointer_button_state state) +{ + struct frame_pointer *pointer = frame_pointer_get(frame, data); + struct frame_pointer_button *button; + enum theme_location location = THEME_LOCATION_EXTERIOR; + + if (!pointer) + return location; + + location = theme_get_location(frame->theme, pointer->x, pointer->y, + frame->width, frame->height, + frame->flags & FRAME_FLAG_MAXIMIZED ? + THEME_FRAME_MAXIMIZED : 0); + + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + button = malloc(sizeof *button); + if (!button) + return location; + + button->button = btn; + button->press_location = location; + button->frame_button = pointer->hover_button; + wl_list_insert(&pointer->down_buttons, &button->link); + + frame_pointer_button_press(frame, pointer, button); + } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { + button = NULL; + wl_list_for_each(button, &pointer->down_buttons, link) + if (button->button == btn) + break; + /* Make sure we didn't hit the end */ + if (&button->link == &pointer->down_buttons) + return location; + + location = button->press_location; + frame_pointer_button_release(frame, pointer, button); + frame_pointer_button_destroy(button); + } + + return location; +} + +enum theme_location +frame_touch_down(struct frame *frame, void *data, int32_t id, int x, int y) +{ + struct frame_touch *touch = frame_touch_get(frame, data); + struct frame_button *button = frame_find_button(frame, x, y); + enum theme_location location; + + location = theme_get_location(frame->theme, x, y, + frame->width, frame->height, + frame->flags & FRAME_FLAG_MAXIMIZED ? + THEME_FRAME_MAXIMIZED : 0); + + if (id > 0) + return location; + + if (touch && button) { + touch->button = button; + frame_button_press(touch->button); + return location; + } + + switch (location) { + case THEME_LOCATION_TITLEBAR: + frame->status |= FRAME_STATUS_MOVE; + 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: + frame->status |= FRAME_STATUS_RESIZE; + break; + default: + break; + } + return location; +} + +void +frame_touch_up(struct frame *frame, void *data, int32_t id) +{ + struct frame_touch *touch = frame_touch_get(frame, data); + + if (id > 0) + return; + + if (touch && touch->button) { + frame_button_release(touch->button); + frame_touch_destroy(touch); + } +} + +enum theme_location +frame_double_click(struct frame *frame, void *data, + uint32_t btn, enum wl_pointer_button_state state) +{ + struct frame_pointer *pointer = frame_pointer_get(frame, data); + struct frame_button *button; + enum theme_location location = THEME_LOCATION_EXTERIOR; + + location = theme_get_location(frame->theme, pointer->x, pointer->y, + frame->width, frame->height, + frame->flags & FRAME_FLAG_MAXIMIZED ? + THEME_FRAME_MAXIMIZED : 0); + + button = frame_find_button(frame, pointer->x, pointer->y); + + if (location != THEME_LOCATION_TITLEBAR || btn != BTN_LEFT) + return location; + + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + if (button) + frame_button_press(button); + else + frame->status |= FRAME_STATUS_MAXIMIZE; + } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { + if (button) + frame_button_release(button); + } + + return location; +} + +void +frame_double_touch_down(struct frame *frame, void *data, int32_t id, + int x, int y) +{ + struct frame_touch *touch = frame_touch_get(frame, data); + struct frame_button *button = frame_find_button(frame, x, y); + enum theme_location location; + + if (touch && button) { + touch->button = button; + frame_button_press(touch->button); + return; + } + + location = theme_get_location(frame->theme, x, y, + frame->width, frame->height, + frame->flags & FRAME_FLAG_MAXIMIZED ? + THEME_FRAME_MAXIMIZED : 0); + + switch (location) { + case THEME_LOCATION_TITLEBAR: + frame->status |= FRAME_STATUS_MAXIMIZE; + 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: + frame->status |= FRAME_STATUS_RESIZE; + break; + default: + break; + } +} + +void +frame_double_touch_up(struct frame *frame, void *data, int32_t id) +{ + struct frame_touch *touch = frame_touch_get(frame, data); + + if (touch && touch->button) { + frame_button_release(touch->button); + frame_touch_destroy(touch); + } +} + +void +frame_repaint(struct frame *frame, cairo_t *cr) +{ + struct frame_button *button; + uint32_t flags = 0; + + frame_refresh_geometry(frame); + + if (frame->flags & FRAME_FLAG_MAXIMIZED) + flags |= THEME_FRAME_MAXIMIZED; + + if (frame->flags & FRAME_FLAG_ACTIVE) + flags |= THEME_FRAME_ACTIVE; + + cairo_save(cr); + theme_render_frame(frame->theme, cr, frame->width, frame->height, + frame->title, &frame->title_rect, + &frame->buttons, flags); + cairo_restore(cr); + + wl_list_for_each(button, &frame->buttons, link) + frame_button_repaint(button, cr); + + frame_status_clear(frame, FRAME_STATUS_REPAINT); +} diff --git a/shared/helpers.h b/shared/helpers.h new file mode 100644 index 0000000..adb6889 --- /dev/null +++ b/shared/helpers.h @@ -0,0 +1,141 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_HELPERS_H +#define WESTON_HELPERS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file + * Simple misc helper macros. + */ + +/** + * Compile-time computation of number of items in a hardcoded array. + * + * @param a the array being measured. + * @return the number of items hardcoded into the array. + */ +#ifndef ARRAY_LENGTH +#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) +#endif + +/** + * Returns the smaller of two values. + * + * @param x the first item to compare. + * @param y the second item to compare. + * @return the value that evaluates to lesser than the other. + */ +#ifndef MIN +#define MIN(x,y) (((x) < (y)) ? (x) : (y)) +#endif + +/** + * Returns the bigger of two values. + * + * @param x the first item to compare. + * @param y the second item to compare. + * @return the value that evaluates to more than the other. + */ +#ifndef MAX +#define MAX(x,y) (((x) > (y)) ? (x) : (y)) +#endif + +/** + * Returns a pointer to the containing struct of a given member item. + * + * To demonstrate, the following example retrieves a pointer to + * `example_container` given only its `destroy_listener` member: + * + * @code + * struct example_container { + * struct wl_listener destroy_listener; + * // other members... + * }; + * + * void example_container_destroy(struct wl_listener *listener, void *data) + * { + * struct example_container *ctr; + * + * ctr = wl_container_of(listener, ctr, destroy_listener); + * // destroy ctr... + * } + * @endcode + * + * @param ptr A valid pointer to the contained item. + * + * @param type A pointer to the type of content that the list item + * stores. Type does not need be a valid pointer; a null or + * an uninitialised pointer will suffice. + * + * @param member The named location of ptr within the sample type. + * + * @return The container for the specified pointer. + */ +#ifndef container_of +#define container_of(ptr, type, member) ({ \ + const __typeof__( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) +#endif + +/** + * Build-time static assertion support + * + * A build-time equivalent to assert(), will generate a compilation error + * if the supplied condition does not evaluate true. + * + * The following example demonstrates use of static_assert to ensure that + * arrays which are supposed to mirror each other have a consistent + * size. + * + * This is only a fallback definition; support must be provided by the + * compiler itself. + * + * @code + * int small[4]; + * long expanded[4]; + * + * static_assert(ARRAY_LENGTH(small) == ARRAY_LENGTH(expanded), + * "size mismatch between small and expanded arrays"); + * for (i = 0; i < ARRAY_LENGTH(small); i++) + * expanded[i] = small[4]; + * @endcode + * + * @param cond Expression to check for truth + * @param msg Message to print on failure + */ +#ifndef static_assert +# ifdef _Static_assert +# define static_assert(cond, msg) _Static_assert(cond, msg) +# else +# define static_assert(cond, msg) +# endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_HELPERS_H */ diff --git a/shared/image-loader.c b/shared/image-loader.c new file mode 100644 index 0000000..2385c7a --- /dev/null +++ b/shared/image-loader.c @@ -0,0 +1,440 @@ +/* + * Copyright © 2008-2012 Kristian Høgsberg + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "shared/helpers.h" +#include "image-loader.h" + +#ifdef HAVE_JPEG +#include +#endif + +#ifdef HAVE_WEBP +#include +#endif + +static int +stride_for_width(int width) +{ + return width * 4; +} + +static void +pixman_image_destroy_func(pixman_image_t *image, void *data) +{ + free(data); +} + +#ifdef HAVE_JPEG + +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 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; +} + +#else + +static pixman_image_t * +load_jpeg(FILE *fp) +{ + fprintf(stderr, "JPEG support disabled at compile-time\n"); + return NULL; +} + +#endif + +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) { + uint32_t alpha = p[3]; + uint32_t w; + + if (alpha == 0) { + w = 0; + } else { + uint32_t red = p[0]; + uint32_t green = p[1]; + uint32_t 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 *volatile data = NULL; + png_byte **volatile 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); +} + +#else + +static pixman_image_t * +load_webp(FILE *fp) +{ + fprintf(stderr, "WebP support disabled at compile-time\n"); + return NULL; +} + +#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 }, + { { 'R', 'I', 'F', 'F' }, 4, load_webp } +}; + +pixman_image_t * +load_image(const char *filename) +{ + pixman_image_t *image = NULL; + unsigned char header[4]; + FILE *fp; + unsigned int i; + + if (!filename || !*filename) + return NULL; + + fp = fopen(filename, "rb"); + if (!fp) { + fprintf(stderr, "%s: %s\n", filename, strerror(errno)); + return NULL; + } + + if (fread(header, sizeof header, 1, fp) != 1) { + fclose(fp); + fprintf(stderr, "%s: unable to read file header\n", filename); + 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, "%s: unrecognized file header " + "0x%02x 0x%02x 0x%02x 0x%02x\n", + filename, header[0], header[1], header[2], header[3]); + } else if (!image) { + /* load probably printed something, but just in case */ + fprintf(stderr, "%s: error reading image\n", filename); + } + + return image; +} diff --git a/shared/image-loader.h b/shared/image-loader.h new file mode 100644 index 0000000..7def60b --- /dev/null +++ b/shared/image-loader.h @@ -0,0 +1,34 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#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 0000000..4e8d6b4 --- /dev/null +++ b/shared/matrix.c @@ -0,0 +1,276 @@ +/* + * Copyright © 2011 Intel Corporation + * Copyright © 2012 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifdef UNIT_TEST +#define WL_EXPORT +#else +#include +#endif + +#include + + +/* + * 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/meson.build b/shared/meson.build new file mode 100644 index 0000000..8073dcd --- /dev/null +++ b/shared/meson.build @@ -0,0 +1,84 @@ +srcs_libshared = [ + 'config-parser.c', + 'option-parser.c', + 'file-util.c', + 'os-compatibility.c', + 'xalloc.c', +] +deps_libshared = dep_wayland_client + +lib_libshared = static_library( + 'shared', + srcs_libshared, + include_directories: common_inc, + dependencies: deps_libshared, + pic: true, + install: false +) +dep_libshared = declare_dependency( + link_with: lib_libshared, + include_directories: public_inc, + dependencies: deps_libshared +) + +srcs_cairo_shared = [ + 'image-loader.c', + 'cairo-util.c', + 'frame.c', +] + +deps_cairo_shared = [ + dep_libshared, + dependency('cairo'), + dependency('libpng'), + dep_pixman, + dep_libm, +] + +dep_pango = dependency('pango', required: false) +dep_pangocairo = dependency('pangocairo', required: false) +dep_glib = dependency('glib-2.0', version: '>= 2.36', required: false) + +if dep_pango.found() and dep_pangocairo.found() and dep_glib.found() + deps_cairo_shared += [ dep_pango, dep_pangocairo, dep_glib ] + config_h.set('HAVE_PANGO', '1') +endif + +if get_option('image-jpeg') + dep_libjpeg = dependency('libjpeg', required: false) + if not dep_libjpeg.found() + dep_libjpeg = cc.find_library('jpeg', required: false) + endif + if not dep_libjpeg.found() + error('JPEG image loading requires libjpeg or jpeg, neither was found. Or, you can use \'-Dimage-jpeg=false\'.') + endif + deps_cairo_shared += dep_libjpeg + config_h.set('HAVE_JPEG', '1') +endif + +if get_option('image-webp') + dep_webp = dependency('libwebp', required: false) + if not dep_webp.found() + error('WEBP image loading requires libwebp which was not found. Or, you can use \'-Dimage-webp=false\'.') + endif + deps_cairo_shared += dep_webp + config_h.set('HAVE_WEBP', '1') +endif + +lib_cairo_shared = static_library( + 'cairo-shared', + srcs_cairo_shared, + include_directories: common_inc, + dependencies: deps_cairo_shared, + install: false +) +dep_lib_cairo_shared = declare_dependency( + link_with: lib_cairo_shared, + dependencies: deps_cairo_shared +) + +dep_matrix_c = declare_dependency( + sources: 'matrix.c', + include_directories: public_inc, + dependencies: dep_libm +) diff --git a/shared/option-parser.c b/shared/option-parser.c new file mode 100644 index 0000000..55be1b5 --- /dev/null +++ b/shared/option-parser.c @@ -0,0 +1,204 @@ +/* + * Copyright © 2012 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "string-helpers.h" + +static bool +handle_option(const struct weston_option *option, char *value) +{ + char* p; + + switch (option->type) { + case WESTON_OPTION_INTEGER: + if (!safe_strtoint(value, option->data)) + return false; + return true; + case WESTON_OPTION_UNSIGNED_INTEGER: + errno = 0; + * (uint32_t *) option->data = strtoul(value, &p, 10); + if (errno != 0 || p == value || *p != '\0') + return false; + return true; + case WESTON_OPTION_STRING: + * (char **) option->data = strdup(value); + return true; + default: + assert(0); + return false; + } +} + +static bool +long_option(const struct weston_option *options, int count, char *arg) +{ + int k, len; + + for (k = 0; k < count; k++) { + if (!options[k].name) + continue; + + len = strlen(options[k].name); + if (strncmp(options[k].name, arg + 2, len) != 0) + continue; + + if (options[k].type == WESTON_OPTION_BOOLEAN) { + if (!arg[len + 2]) { + * (bool *) options[k].data = true; + + return true; + } + } else if (arg[len+2] == '=') { + return handle_option(options + k, arg + len + 3); + } + } + + return false; +} + +static bool +long_option_with_arg(const struct weston_option *options, int count, char *arg, + char *param) +{ + int k, len; + + for (k = 0; k < count; k++) { + if (!options[k].name) + continue; + + len = strlen(options[k].name); + if (strncmp(options[k].name, arg + 2, len) != 0) + continue; + + /* Since long_option() should handle all booleans, we should + * never reach this + */ + assert(options[k].type != WESTON_OPTION_BOOLEAN); + + return handle_option(options + k, param); + } + + return false; +} + +static bool +short_option(const struct weston_option *options, int count, char *arg) +{ + int k; + + if (!arg[1]) + return false; + + for (k = 0; k < count; k++) { + if (options[k].short_name != arg[1]) + continue; + + if (options[k].type == WESTON_OPTION_BOOLEAN) { + if (!arg[2]) { + * (bool *) options[k].data = true; + + return true; + } + } else if (arg[2]) { + return handle_option(options + k, arg + 2); + } else { + return false; + } + } + + return false; +} + +static bool +short_option_with_arg(const struct weston_option *options, int count, char *arg, char *param) +{ + int k; + + if (!arg[1]) + return false; + + for (k = 0; k < count; k++) { + if (options[k].short_name != arg[1]) + continue; + + if (options[k].type == WESTON_OPTION_BOOLEAN) + continue; + + return handle_option(options + k, param); + } + + return false; +} + +int +parse_options(const struct weston_option *options, + int count, int *argc, char *argv[]) +{ + int i, j; + + for (i = 1, j = 1; i < *argc; i++) { + if (argv[i][0] == '-') { + if (argv[i][1] == '-') { + /* Long option, e.g. --foo or --foo=bar */ + if (long_option(options, count, argv[i])) + continue; + + /* ...also handle --foo bar */ + if (i + 1 < *argc && + long_option_with_arg(options, count, + argv[i], argv[i+1])) { + i++; + continue; + } + } else { + /* Short option, e.g -f or -f42 */ + if (short_option(options, count, argv[i])) + continue; + + /* ...also handle -f 42 */ + if (i+1 < *argc && + short_option_with_arg(options, count, argv[i], argv[i+1])) { + i++; + continue; + } + } + } + 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 0000000..f761080 --- /dev/null +++ b/shared/os-compatibility.c @@ -0,0 +1,407 @@ +/* + * Copyright © 2012 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "os-compatibility.h" + +#define READONLY_SEALS (F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE) + +int +os_fd_set_cloexec(int fd) +{ + long flags; + + if (fd == -1) + return -1; + + flags = fcntl(fd, F_GETFD); + if (flags == -1) + return -1; + + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + return -1; + + return 0; +} + +static int +set_cloexec_or_close(int fd) +{ + if (os_fd_set_cloexec(fd) != 0) { + close(fd); + return -1; + } + return fd; +} + +int +os_socketpair_cloexec(int domain, int type, int protocol, int *sv) +{ + int ret; + +#ifdef SOCK_CLOEXEC + ret = socketpair(domain, type | SOCK_CLOEXEC, protocol, sv); + if (ret == 0 || errno != EINVAL) + return ret; +#endif + + ret = socketpair(domain, type, protocol, sv); + if (ret < 0) + return ret; + + sv[0] = set_cloexec_or_close(sv[0]); + sv[1] = set_cloexec_or_close(sv[1]); + + if (sv[0] != -1 && sv[1] != -1) + return 0; + + close(sv[0]); + close(sv[1]); + return -1; +} + +int +os_epoll_create_cloexec(void) +{ + int fd; + +#ifdef EPOLL_CLOEXEC + fd = epoll_create1(EPOLL_CLOEXEC); + if (fd >= 0) + return fd; + if (errno != EINVAL) + return -1; +#endif + + fd = epoll_create(1); + return set_cloexec_or_close(fd); +} + +static int +create_tmpfile_cloexec(char *tmpname) +{ + int fd; + +#ifdef HAVE_MKOSTEMP + fd = mkostemp(tmpname, O_CLOEXEC); + if (fd >= 0) + unlink(tmpname); +#else + fd = mkstemp(tmpname); + if (fd >= 0) { + fd = set_cloexec_or_close(fd); + unlink(tmpname); + } +#endif + + return fd; +} + +/* + * Create a new, unique, anonymous file of the given size, and + * return the file descriptor for it. The file descriptor is set + * CLOEXEC. The file is immediately suitable for mmap()'ing + * the given size at offset zero. + * + * The file should not have a permanent backing store like a disk, + * but may have if XDG_RUNTIME_DIR is not properly implemented in OS. + * + * The file name is deleted from the file system. + * + * The file is suitable for buffer sharing between processes by + * transmitting the file descriptor over Unix sockets using the + * SCM_RIGHTS methods. + * + * If the C library implements posix_fallocate(), it is used to + * guarantee that disk space is available for the file at the + * given size. If disk space is insufficient, errno is set to ENOSPC. + * If posix_fallocate() is not supported, program may receive + * SIGBUS on accessing mmap()'ed file contents instead. + * + * If the C library implements memfd_create(), it is used to create the + * file purely in memory, without any backing file name on the file + * system, and then sealing off the possibility of shrinking it. This + * can then be checked before accessing mmap()'ed file contents, to + * make sure SIGBUS can't happen. It also avoids requiring + * XDG_RUNTIME_DIR. + */ +int +os_create_anonymous_file(off_t size) +{ + static const char template[] = "/weston-shared-XXXXXX"; + const char *path; + char *name; + int fd; + int ret; + +#ifdef HAVE_MEMFD_CREATE + fd = memfd_create("weston-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd >= 0) { + /* We can add this seal before calling posix_fallocate(), as + * the file is currently zero-sized anyway. + * + * There is also no need to check for the return value, we + * couldn't do anything with it anyway. + */ + fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK); + } else +#endif + { + path = getenv("XDG_RUNTIME_DIR"); + if (!path) { + errno = ENOENT; + return -1; + } + + name = malloc(strlen(path) + sizeof(template)); + if (!name) + return -1; + + strcpy(name, path); + strcat(name, template); + + fd = create_tmpfile_cloexec(name); + + free(name); + + if (fd < 0) + return -1; + } + +#ifdef HAVE_POSIX_FALLOCATE + do { + ret = posix_fallocate(fd, 0, size); + } while (ret == EINTR); + if (ret != 0) { + close(fd); + errno = ret; + return -1; + } +#else + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + close(fd); + return -1; + } +#endif + + return fd; +} + +#ifndef HAVE_STRCHRNUL +char * +strchrnul(const char *s, int c) +{ + while (*s && *s != c) + s++; + return (char *)s; +} +#endif + +struct ro_anonymous_file { + int fd; + size_t size; +}; + +/** Create a new anonymous read-only file of the given size and the given data + * + * \param size The size of \p data. + * \param data The data of the file with the size \p size. + * \return A new \c ro_anonymous_file, or NULL on failure. + * + * The intended use-case is for sending mid-sized data from the compositor + * to clients. + * If the function fails errno is set. + */ +struct ro_anonymous_file * +os_ro_anonymous_file_create(size_t size, + const char *data) +{ + struct ro_anonymous_file *file; + void *map; + + file = zalloc(sizeof *file); + if (!file) { + errno = ENOMEM; + return NULL; + } + + file->size = size; + file->fd = os_create_anonymous_file(size); + if (file->fd == -1) + goto err_free; + + map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, file->fd, 0); + if (map == MAP_FAILED) + goto err_close; + + memcpy(map, data, size); + + munmap(map, size); + +#ifdef HAVE_MEMFD_CREATE + /* try to put seals on the file to make it read-only so that we can + * return the fd later directly when support_shared is not set. + * os_ro_anonymous_file_get_fd can handle the fd even if it is not + * sealed read-only and will instead create a new anonymous file on + * each invocation. + */ + fcntl(file->fd, F_ADD_SEALS, READONLY_SEALS); +#endif + + return file; + +err_close: + close(file->fd); +err_free: + free(file); + return NULL; +} + +/** Destroy an anonymous read-only file + * + * \param file The file to destroy. + */ +void +os_ro_anonymous_file_destroy(struct ro_anonymous_file *file) +{ + close(file->fd); + free(file); +} + +/** Get the size of an anonymous read-only file + * + * \param file The file to get the size of. + * \return The size of the file. + */ +size_t +os_ro_anonymous_file_size(struct ro_anonymous_file *file) +{ + return file->size; +} + +/** Returns a file descriptor for the given file, ready to be send to a client. + * + * \param file The file for which to get a file descriptor. + * \param mapmode Describes the ways in which the returned file descriptor can + * be used with mmap. + * \return A file descriptor for the given file that can be send to a client + * or -1 on failure. + * + * The returned file descriptor must not be shared between multiple clients. + * When \p mapmode is RO_ANONYMOUS_FILE_MAPMODE_PRIVATE the file descriptor is + * only guaranteed to be mmapable with \c MAP_PRIVATE, when \p mapmode is + * RO_ANONYMOUS_FILE_MAPMODE_SHARED the file descriptor can be mmaped with + * either MAP_PRIVATE or MAP_SHARED. + * When you're done with the fd you must call \c os_ro_anonymous_file_put_fd + * instead of calling \c close. + * If the function fails errno is set. + */ +int +os_ro_anonymous_file_get_fd(struct ro_anonymous_file *file, + enum ro_anonymous_file_mapmode mapmode) +{ + void *src, *dst; + int fd; + +#ifdef HAVE_MEMFD_CREATE + int seals = fcntl(file->fd, F_GET_SEALS); + + /* file was sealed for read-only and we don't have to support MAP_SHARED + * so we can simply pass the memfd fd + */ + if (seals != -1 && mapmode == RO_ANONYMOUS_FILE_MAPMODE_PRIVATE && + (seals & READONLY_SEALS) == READONLY_SEALS) + return file->fd; +#endif + + /* for all other cases we create a new anonymous file that can be mapped + * with MAP_SHARED and copy the contents to it and return that instead + */ + fd = os_create_anonymous_file(file->size); + if (fd == -1) + return fd; + + src = mmap(NULL, file->size, PROT_READ, MAP_PRIVATE, file->fd, 0); + if (src == MAP_FAILED) { + close(fd); + return -1; + } + + dst = mmap(NULL, file->size, PROT_WRITE, MAP_SHARED, fd, 0); + if (dst == MAP_FAILED) { + close(fd); + munmap(src, file->size); + return -1; + } + + memcpy(dst, src, file->size); + munmap(src, file->size); + munmap(dst, file->size); + + return fd; +} + +/** Release a file descriptor returned by \c os_ro_anonymous_file_get_fd + * + * \param fd A file descriptor returned by \c os_ro_anonymous_file_get_fd. + * \return 0 on success, or -1 on failure. + * + * This function must be called for every file descriptor created with + * \c os_ro_anonymous_file_get_fd to not leake any resources. + * If the function fails errno is set. + */ +int +os_ro_anonymous_file_put_fd(int fd) +{ +#ifdef HAVE_MEMFD_CREATE + int seals = fcntl(fd, F_GET_SEALS); + if (seals == -1 && errno != EINVAL) + return -1; + + /* The only case in which we do NOT have to close the file is when the file + * was sealed for read-only + */ + if (seals != -1 && (seals & READONLY_SEALS) == READONLY_SEALS) + return 0; +#endif + + close(fd); + return 0; +} diff --git a/shared/os-compatibility.h b/shared/os-compatibility.h new file mode 100644 index 0000000..6aaa268 --- /dev/null +++ b/shared/os-compatibility.h @@ -0,0 +1,74 @@ +/* + * Copyright © 2012 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef OS_COMPATIBILITY_H +#define OS_COMPATIBILITY_H + +#include "config.h" + +#include + +int +os_fd_set_cloexec(int fd); + +int +os_socketpair_cloexec(int domain, int type, int protocol, int *sv); + +int +os_epoll_create_cloexec(void); + +int +os_create_anonymous_file(off_t size); + +#ifndef HAVE_STRCHRNUL +char * +strchrnul(const char *s, int c); +#endif + +struct ro_anonymous_file; + +enum ro_anonymous_file_mapmode { + RO_ANONYMOUS_FILE_MAPMODE_PRIVATE, + RO_ANONYMOUS_FILE_MAPMODE_SHARED, +}; + +struct ro_anonymous_file * +os_ro_anonymous_file_create(size_t size, + const char *data); + +void +os_ro_anonymous_file_destroy(struct ro_anonymous_file *file); + +size_t +os_ro_anonymous_file_size(struct ro_anonymous_file *file); + +int +os_ro_anonymous_file_get_fd(struct ro_anonymous_file *file, + enum ro_anonymous_file_mapmode mapmode); + +int +os_ro_anonymous_file_put_fd(int fd); + +#endif /* OS_COMPATIBILITY_H */ diff --git a/shared/platform.h b/shared/platform.h new file mode 100644 index 0000000..9264bb4 --- /dev/null +++ b/shared/platform.h @@ -0,0 +1,167 @@ +/* + * Copyright © 2015 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_PLATFORM_H +#define WESTON_PLATFORM_H + +#include +#include + +#ifdef ENABLE_EGL +#include +#include +#include + +#include "weston-egl-ext.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef ENABLE_EGL + +static bool +weston_check_egl_extension(const char *extensions, const char *extension) +{ + size_t extlen = strlen(extension); + const char *end = extensions + strlen(extensions); + + while (extensions < end) { + size_t n = 0; + + /* Skip whitespaces, if any */ + if (*extensions == ' ') { + extensions++; + continue; + } + + n = strcspn(extensions, " "); + + /* Compare strings */ + if (n == extlen && strncmp(extension, extensions, n) == 0) + return true; /* Found */ + + extensions += n; + } + + /* Not found */ + return false; +} + +static inline void * +weston_platform_get_egl_proc_address(const char *address) +{ + const char *extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + + if (extensions && + (weston_check_egl_extension(extensions, "EGL_EXT_platform_wayland") || + weston_check_egl_extension(extensions, "EGL_KHR_platform_wayland"))) { + return (void *) eglGetProcAddress(address); + } + + return NULL; +} + +static inline EGLDisplay +weston_platform_get_egl_display(EGLenum platform, void *native_display, + const EGLint *attrib_list) +{ + static PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display = NULL; + + if (!get_platform_display) { + get_platform_display = (PFNEGLGETPLATFORMDISPLAYEXTPROC) + weston_platform_get_egl_proc_address( + "eglGetPlatformDisplayEXT"); + } + + if (get_platform_display) + return get_platform_display(platform, + native_display, attrib_list); + + return eglGetDisplay((EGLNativeDisplayType) native_display); +} + +static inline EGLSurface +weston_platform_create_egl_surface(EGLDisplay dpy, EGLConfig config, + void *native_window, + const EGLint *attrib_list) +{ + static PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC + create_platform_window = NULL; + + if (!create_platform_window) { + create_platform_window = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC) + weston_platform_get_egl_proc_address( + "eglCreatePlatformWindowSurfaceEXT"); + } + + if (create_platform_window) + return create_platform_window(dpy, config, + native_window, + attrib_list); + + return eglCreateWindowSurface(dpy, config, + (EGLNativeWindowType) native_window, + attrib_list); +} + +static inline EGLBoolean +weston_platform_destroy_egl_surface(EGLDisplay display, + EGLSurface surface) +{ + return eglDestroySurface(display, surface); +} + +#else /* ENABLE_EGL */ + +static inline void * +weston_platform_get_egl_display(int platform, void *native_display, + const int *attrib_list) +{ + return NULL; +} + +static inline void * +weston_platform_create_egl_surface(void *dpy, void *config, + void *native_window, + const int *attrib_list) +{ + return NULL; +} + +static inline unsigned int +weston_platform_destroy_egl_surface(void *display, + void *surface) +{ + return 1; +} +#endif /* ENABLE_EGL */ + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_PLATFORM_H */ diff --git a/shared/string-helpers.h b/shared/string-helpers.h new file mode 100644 index 0000000..c8ce449 --- /dev/null +++ b/shared/string-helpers.h @@ -0,0 +1,71 @@ +/* + * Copyright © 2016 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_STRING_HELPERS_H +#define WESTON_STRING_HELPERS_H + +#include +#include +#include +#include +#include + +/* Convert string to integer + * + * Parses a base-10 number from the given string. Checks that the + * string is not blank, contains only numerical characters, and is + * within the range of INT32_MIN to INT32_MAX. If the validation is + * successful the result is stored in *value; otherwise *value is + * unchanged and errno is set appropriately. + * + * \return true if the number parsed successfully, false on error + */ +static inline bool +safe_strtoint(const char *str, int32_t *value) +{ + long ret; + char *end; + + assert(str != NULL); + + errno = 0; + ret = strtol(str, &end, 10); + if (errno != 0) { + return false; + } else if (end == str || *end != '\0') { + errno = EINVAL; + return false; + } + + if ((long)((int32_t)ret) != ret) { + errno = ERANGE; + return false; + } + *value = (int32_t)ret; + + return true; +} + +#endif /* WESTON_STRING_HELPERS_H */ diff --git a/shared/timespec-util.h b/shared/timespec-util.h new file mode 100644 index 0000000..f79969b --- /dev/null +++ b/shared/timespec-util.h @@ -0,0 +1,259 @@ +/* + * Copyright © 2014 - 2015 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef TIMESPEC_UTIL_H +#define TIMESPEC_UTIL_H + +#include +#include +#include +#include + +#define NSEC_PER_SEC 1000000000 + +/* Subtract timespecs + * + * \param r[out] result: a - b + * \param a[in] operand + * \param b[in] operand + */ +static inline void +timespec_sub(struct timespec *r, + const struct timespec *a, const struct timespec *b) +{ + r->tv_sec = a->tv_sec - b->tv_sec; + r->tv_nsec = a->tv_nsec - b->tv_nsec; + if (r->tv_nsec < 0) { + r->tv_sec--; + r->tv_nsec += NSEC_PER_SEC; + } +} + +/* Add a nanosecond value to a timespec + * + * \param r[out] result: a + b + * \param a[in] base operand as timespec + * \param b[in] operand in nanoseconds + */ +static inline void +timespec_add_nsec(struct timespec *r, const struct timespec *a, int64_t b) +{ + r->tv_sec = a->tv_sec + (b / NSEC_PER_SEC); + r->tv_nsec = a->tv_nsec + (b % NSEC_PER_SEC); + + if (r->tv_nsec >= NSEC_PER_SEC) { + r->tv_sec++; + r->tv_nsec -= NSEC_PER_SEC; + } else if (r->tv_nsec < 0) { + r->tv_sec--; + r->tv_nsec += NSEC_PER_SEC; + } +} + +/* Add a millisecond value to a timespec + * + * \param r[out] result: a + b + * \param a[in] base operand as timespec + * \param b[in] operand in milliseconds + */ +static inline void +timespec_add_msec(struct timespec *r, const struct timespec *a, int64_t b) +{ + timespec_add_nsec(r, a, b * 1000000); +} + +/* Convert timespec to nanoseconds + * + * \param a timespec + * \return nanoseconds + */ +static inline int64_t +timespec_to_nsec(const struct timespec *a) +{ + return (int64_t)a->tv_sec * NSEC_PER_SEC + a->tv_nsec; +} + +/* Subtract timespecs and return result in nanoseconds + * + * \param a[in] operand + * \param b[in] operand + * \return to_nanoseconds(a - b) + */ +static inline int64_t +timespec_sub_to_nsec(const struct timespec *a, const struct timespec *b) +{ + struct timespec r; + timespec_sub(&r, a, b); + return timespec_to_nsec(&r); +} + +/* Convert timespec to milliseconds + * + * \param a timespec + * \return milliseconds + * + * Rounding to integer milliseconds happens always down (floor()). + */ +static inline int64_t +timespec_to_msec(const struct timespec *a) +{ + return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000; +} + +/* Subtract timespecs and return result in milliseconds + * + * \param a[in] operand + * \param b[in] operand + * \return to_milliseconds(a - b) + */ +static inline int64_t +timespec_sub_to_msec(const struct timespec *a, const struct timespec *b) +{ + return timespec_sub_to_nsec(a, b) / 1000000; +} + +/* Convert timespec to microseconds + * + * \param a timespec + * \return microseconds + * + * Rounding to integer microseconds happens always down (floor()). + */ +static inline int64_t +timespec_to_usec(const struct timespec *a) +{ + return (int64_t)a->tv_sec * 1000000 + a->tv_nsec / 1000; +} + +/* Convert timespec to protocol data + * + * \param a timespec + * \param tv_sec_hi[out] the high bytes of the seconds part + * \param tv_sec_lo[out] the low bytes of the seconds part + * \param tv_nsec[out] the nanoseconds part + * + * The input timespec must be normalized (the nanoseconds part should + * be less than 1 second) and non-negative. + */ +static inline void +timespec_to_proto(const struct timespec *a, uint32_t *tv_sec_hi, + uint32_t *tv_sec_lo, uint32_t *tv_nsec) +{ + assert(a->tv_sec >= 0); + assert(a->tv_nsec >= 0 && a->tv_nsec < NSEC_PER_SEC); + + uint64_t sec64 = a->tv_sec; + + *tv_sec_hi = sec64 >> 32; + *tv_sec_lo = sec64 & 0xffffffff; + *tv_nsec = a->tv_nsec; +} + +/* Convert nanoseconds to timespec + * + * \param a timespec + * \param b nanoseconds + */ +static inline void +timespec_from_nsec(struct timespec *a, int64_t b) +{ + a->tv_sec = b / NSEC_PER_SEC; + a->tv_nsec = b % NSEC_PER_SEC; +} + +/* Convert microseconds to timespec + * + * \param a timespec + * \param b microseconds + */ +static inline void +timespec_from_usec(struct timespec *a, int64_t b) +{ + timespec_from_nsec(a, b * 1000); +} + +/* Convert milliseconds to timespec + * + * \param a timespec + * \param b milliseconds + */ +static inline void +timespec_from_msec(struct timespec *a, int64_t b) +{ + timespec_from_nsec(a, b * 1000000); +} + +/* Convert protocol data to timespec + * + * \param a[out] timespec + * \param tv_sec_hi the high bytes of seconds part + * \param tv_sec_lo the low bytes of seconds part + * \param tv_nsec the nanoseconds part + */ +static inline void +timespec_from_proto(struct timespec *a, uint32_t tv_sec_hi, + uint32_t tv_sec_lo, uint32_t tv_nsec) +{ + a->tv_sec = ((uint64_t)tv_sec_hi << 32) + tv_sec_lo; + a->tv_nsec = tv_nsec; +} + +/* Check if a timespec is zero + * + * \param a timespec + * \return whether the timespec is zero + */ +static inline bool +timespec_is_zero(const struct timespec *a) +{ + return a->tv_sec == 0 && a->tv_nsec == 0; +} + +/* Check if two timespecs are equal + * + * \param a[in] timespec to check + * \param b[in] timespec to check + * \return whether timespecs a and b are equal + */ +static inline bool +timespec_eq(const struct timespec *a, const struct timespec *b) +{ + return a->tv_sec == b->tv_sec && + a->tv_nsec == b->tv_nsec; +} + +/* Convert milli-Hertz to nanoseconds + * + * \param mhz frequency in mHz, not zero + * \return period in nanoseconds + */ +static inline int64_t +millihz_to_nsec(uint32_t mhz) +{ + assert(mhz > 0); + return 1000000000000LL / mhz; +} + +#endif /* TIMESPEC_UTIL_H */ diff --git a/shared/weston-egl-ext.h b/shared/weston-egl-ext.h new file mode 100644 index 0000000..4a757c8 --- /dev/null +++ b/shared/weston-egl-ext.h @@ -0,0 +1,221 @@ +/* + * 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, 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. + */ +/* Extensions used by Weston, copied from Mesa's eglmesaext.h, */ + +#ifndef WESTON_EGL_EXT_H +#define WESTON_EGL_EXT_H + +#ifdef ENABLE_EGL + +#ifndef EGL_WL_bind_wayland_display +#define EGL_WL_bind_wayland_display 1 + +struct wl_display; + +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglBindWaylandDisplayWL(EGLDisplay dpy, struct wl_display *display); +EGLAPI EGLBoolean EGLAPIENTRY eglUnbindWaylandDisplayWL(EGLDisplay dpy, struct wl_display *display); +#endif +typedef EGLBoolean (EGLAPIENTRYP PFNEGLBINDWAYLANDDISPLAYWL) (EGLDisplay dpy, struct wl_display *display); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLUNBINDWAYLANDDISPLAYWL) (EGLDisplay dpy, struct wl_display *display); +#endif + +/* + * This is a little different to the tests shipped with EGL implementations, + * which wrap the entire thing in #ifndef EGL_WL_bind_wayland_display, then go + * on to define both BindWaylandDisplay and QueryWaylandBuffer. + * + * Unfortunately, some implementations (particularly the version of Mesa shipped + * in Ubuntu 12.04) define EGL_WL_bind_wayland_display, but then only provide + * prototypes for (Un)BindWaylandDisplay, completely omitting + * QueryWaylandBuffer. + * + * Detect this, and provide our own definitions if necessary. + */ +#ifndef EGL_WAYLAND_BUFFER_WL +#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_resource; +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQueryWaylandBufferWL(EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value); +#endif +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYWAYLANDBUFFERWL) (EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value); +#endif + +#ifndef EGL_WL_create_wayland_buffer_from_image +#define EGL_WL_create_wayland_buffer_from_image 1 + +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI struct wl_buffer * EGLAPIENTRY eglCreateWaylandBufferFromImageWL(EGLDisplay dpy, EGLImageKHR image); +#endif +typedef struct wl_buffer * (EGLAPIENTRYP PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWL) (EGLDisplay dpy, EGLImageKHR image); +#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 + +#ifndef GL_EXT_unpack_subimage +#define GL_EXT_unpack_subimage 1 +#define GL_UNPACK_ROW_LENGTH_EXT 0x0CF2 +#define GL_UNPACK_SKIP_ROWS_EXT 0x0CF3 +#define GL_UNPACK_SKIP_PIXELS_EXT 0x0CF4 +#endif /* GL_EXT_unpack_subimage */ + +/* 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 + +/* Define needed tokens from EGL_EXT_image_dma_buf_import extension + * here to avoid having to add ifdefs everywhere.*/ +#ifndef EGL_EXT_image_dma_buf_import +#define EGL_LINUX_DMA_BUF_EXT 0x3270 +#define EGL_LINUX_DRM_FOURCC_EXT 0x3271 +#define EGL_DMA_BUF_PLANE0_FD_EXT 0x3272 +#define EGL_DMA_BUF_PLANE0_OFFSET_EXT 0x3273 +#define EGL_DMA_BUF_PLANE0_PITCH_EXT 0x3274 +#define EGL_DMA_BUF_PLANE1_FD_EXT 0x3275 +#define EGL_DMA_BUF_PLANE1_OFFSET_EXT 0x3276 +#define EGL_DMA_BUF_PLANE1_PITCH_EXT 0x3277 +#define EGL_DMA_BUF_PLANE2_FD_EXT 0x3278 +#define EGL_DMA_BUF_PLANE2_OFFSET_EXT 0x3279 +#define EGL_DMA_BUF_PLANE2_PITCH_EXT 0x327A +#endif + +/* Define tokens from EGL_EXT_image_dma_buf_import_modifiers */ +#ifndef EGL_EXT_image_dma_buf_import_modifiers +#define EGL_EXT_image_dma_buf_import_modifiers 1 +#define EGL_DMA_BUF_PLANE3_FD_EXT 0x3440 +#define EGL_DMA_BUF_PLANE3_OFFSET_EXT 0x3441 +#define EGL_DMA_BUF_PLANE3_PITCH_EXT 0x3442 +#define EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT 0x3443 +#define EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT 0x3444 +#define EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT 0x3445 +#define EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT 0x3446 +#define EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT 0x3447 +#define EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT 0x3448 +#define EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT 0x3449 +#define EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT 0x344A +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFFORMATSEXTPROC) (EGLDisplay dpy, EGLint max_formats, EGLint *formats, EGLint *num_formats); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFMODIFIERSEXTPROC) (EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR *modifiers, EGLBoolean *external_only, EGLint *num_modifiers); +#endif + +#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 /* EGL_EXT_swap_buffers_with_damage */ + +#ifndef EGL_MESA_configless_context +#define EGL_MESA_configless_context 1 +#define EGL_NO_CONFIG_MESA ((EGLConfig)0) +#endif + +#ifndef EGL_NO_CONFIG_KHR +#define EGL_NO_CONFIG_KHR ((EGLConfig)0) +#endif + +#ifndef EGL_EXT_platform_base +#define EGL_EXT_platform_base 1 +typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC) (EGLenum platform, void *native_display, const EGLint *attrib_list); +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC) (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLint *attrib_list); +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMPIXMAPSURFACEEXTPROC) (EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLint *attrib_list); +#endif /* EGL_EXT_platform_base */ + +#ifndef EGL_PLATFORM_GBM_KHR +#define EGL_PLATFORM_GBM_KHR 0x31D7 +#endif + +#ifndef EGL_PLATFORM_WAYLAND_KHR +#define EGL_PLATFORM_WAYLAND_KHR 0x31D8 +#endif + +#ifndef EGL_PLATFORM_X11_KHR +#define EGL_PLATFORM_X11_KHR 0x31D5 +#endif + +#ifndef EGL_PLATFORM_SURFACELESS_MESA +#define EGL_PLATFORM_SURFACELESS_MESA 0x31DD +#endif + +#ifndef EGL_KHR_cl_event2 +#define EGL_KHR_cl_event2 1 +typedef void *EGLSyncKHR; +#endif /* EGL_KHR_cl_event2 */ + +#ifndef EGL_NO_SYNC_KHR +#define EGL_NO_SYNC_KHR ((EGLSyncKHR)0) +#endif + +#ifndef EGL_KHR_fence_sync +#define EGL_KHR_fence_sync 1 +typedef EGLSyncKHR (EGLAPIENTRYP PFNEGLCREATESYNCKHRPROC) (EGLDisplay dpy, EGLenum type, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSYNCKHRPROC) (EGLDisplay dpy, EGLSyncKHR sync); +#endif /* EGL_KHR_fence_sync */ + +#ifndef EGL_ANDROID_native_fence_sync +#define EGL_ANDROID_native_fence_sync 1 +typedef EGLint (EGLAPIENTRYP PFNEGLDUPNATIVEFENCEFDANDROIDPROC) (EGLDisplay dpy, EGLSyncKHR sync); +#endif /* EGL_ANDROID_native_fence_sync */ + +#ifndef EGL_SYNC_NATIVE_FENCE_ANDROID +#define EGL_SYNC_NATIVE_FENCE_ANDROID 0x3144 +#endif + +#ifndef EGL_NO_NATIVE_FENCE_FD_ANDROID +#define EGL_NO_NATIVE_FENCE_FD_ANDROID -1 +#endif + +#else /* ENABLE_EGL */ + +/* EGL platform definition are keept to allow compositor-xx.c to build */ +#define EGL_PLATFORM_GBM_KHR 0x31D7 +#define EGL_PLATFORM_WAYLAND_KHR 0x31D8 +#define EGL_PLATFORM_X11_KHR 0x31D5 +#define EGL_PLATFORM_SURFACELESS_MESA 0x31DD + +#endif /* ENABLE_EGL */ + +#endif diff --git a/shared/xalloc.c b/shared/xalloc.c new file mode 100644 index 0000000..1b24937 --- /dev/null +++ b/shared/xalloc.c @@ -0,0 +1,55 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "xalloc.h" + +// OHOS build +#ifndef program_invocation_short_name +#define program_invocation_short_name __func__ +#endif + +void * +fail_on_null(void *p, size_t size, char *file, int32_t line) +{ + if (p == NULL) { + fprintf(stderr, "[%s] ", program_invocation_short_name); + if (file) + fprintf(stderr, "%s:%d: ", file, line); + fprintf(stderr, "out of memory"); + if (size) + fprintf(stderr, " (%zd)", size); + fprintf(stderr, "\n"); + exit(EXIT_FAILURE); + } + + return p; +} diff --git a/shared/xalloc.h b/shared/xalloc.h new file mode 100644 index 0000000..cd39dd8 --- /dev/null +++ b/shared/xalloc.h @@ -0,0 +1,51 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_XALLOC_H +#define WESTON_XALLOC_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include + +void * +fail_on_null(void *p, size_t size, char *file, int32_t line); + +#define xmalloc(s) (fail_on_null(malloc(s), (s), __FILE__, __LINE__)) +#define xzalloc(s) (fail_on_null(zalloc(s), (s), __FILE__, __LINE__)) +#define xstrdup(s) (fail_on_null(strdup(s), 0, __FILE__, __LINE__)) +#define xrealloc(p, s) (fail_on_null(realloc(p, s), (s), __FILE__, __LINE__)) + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_XALLOC_H */ diff --git a/test-qc.c b/test-qc.c new file mode 100644 index 0000000..8040ffc --- /dev/null +++ b/test-qc.c @@ -0,0 +1,2 @@ + +this is check test 2021-7-22) diff --git a/tests/bad-buffer-test.c b/tests/bad-buffer-test.c new file mode 100644 index 0000000..360a81c --- /dev/null +++ b/tests/bad-buffer-test.c @@ -0,0 +1,189 @@ +/* + * Copyright © 2012 Intel Corporation + * Copyright © 2013 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "shared/os-compatibility.h" +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +/* These three functions are copied from shared/os-compatibility.c in order to + * behave like older clients, and allow ftruncate() to shrink the file’s size, + * so SIGBUS can still happen. + * + * There is no reason not to use os_create_anonymous_file() otherwise. */ + +#ifndef HAVE_MKOSTEMP +static int +set_cloexec_or_close(int fd) +{ + if (os_fd_set_cloexec(fd) != 0) { + close(fd); + return -1; + } + return fd; +} +#endif + +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; +} + +static int +create_anonymous_file_without_seals(off_t size) +{ + static const char template[] = "/weston-test-XXXXXX"; + const char *path; + char *name; + int fd; + int ret; + + path = getenv("XDG_RUNTIME_DIR"); + if (!path) { + errno = ENOENT; + return -1; + } + + name = malloc(strlen(path) + sizeof(template)); + if (!name) + return -1; + + strcpy(name, path); + strcat(name, template); + + fd = create_tmpfile_cloexec(name); + + free(name); + + if (fd < 0) + return -1; + +#ifdef HAVE_POSIX_FALLOCATE + do { + ret = posix_fallocate(fd, 0, size); + } while (ret == EINTR); + if (ret != 0) { + close(fd); + errno = ret; + return -1; + } +#else + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + close(fd); + return -1; + } +#endif + + return fd; +} + +/* tests, that attempt to crash the compositor on purpose */ + +static struct wl_buffer * +create_bad_shm_buffer(struct client *client, int width, int height) +{ + 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; + + fd = create_anonymous_file_without_seals(size); + assert(fd >= 0); + + 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); + + /* Truncate the file to a small size, so that the compositor + * will access it out-of-bounds, and hit SIGBUS. + */ + assert(ftruncate(fd, 12) == 0); + close(fd); + + return buffer; +} + +TEST(test_truncated_shm_file) +{ + struct client *client; + struct wl_buffer *bad_buffer; + struct wl_surface *surface; + int frame; + + client = create_client_and_test_surface(46, 76, 111, 134); + assert(client); + surface = client->surface->wl_surface; + + bad_buffer = create_bad_shm_buffer(client, 200, 200); + + wl_surface_attach(surface, bad_buffer, 0, 0); + wl_surface_damage(surface, 0, 0, 200, 200); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait_nofail(client, &frame); + + expect_protocol_error(client, &wl_buffer_interface, + WL_SHM_ERROR_INVALID_FD); +} diff --git a/tests/buffer-transforms-test.c b/tests/buffer-transforms-test.c new file mode 100644 index 0000000..fdc86bb --- /dev/null +++ b/tests/buffer-transforms-test.c @@ -0,0 +1,137 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +#define TRANSFORM(x) WL_OUTPUT_TRANSFORM_ ## x, #x +#define RENDERERS(s, t) \ + { RENDERER_PIXMAN, s, TRANSFORM(t) }, \ + { RENDERER_GL, s, TRANSFORM(t) } + +struct setup_args { + enum renderer_type renderer; + int scale; + enum wl_output_transform transform; + const char *transform_name; +}; + +static const struct setup_args my_setup_args[] = { + RENDERERS(1, NORMAL), + RENDERERS(2, 90), +}; + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg) +{ + struct compositor_setup setup; + + /* The width and height are chosen to produce 324x240 framebuffer, to + * emulate keeping the video mode constant. + * This resolution is divisible by 2 and 3. + * Headless multiplies the given size by scale. + */ + + compositor_setup_defaults(&setup); + setup.renderer = arg->renderer; + setup.width = 324 / arg->scale; + setup.height = 240 / arg->scale; + setup.scale = arg->scale; + setup.transform = arg->transform; + setup.shell = SHELL_TEST_DESKTOP; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args); + +struct buffer_args { + int scale; + enum wl_output_transform transform; + const char *transform_name; +}; + +static const struct buffer_args my_buffer_args[] = { + /* { 1, TRANSFORM(NORMAL) }, done in output-transforms-test.c */ + { 1, TRANSFORM(90) }, + { 1, TRANSFORM(180) }, + { 1, TRANSFORM(270) }, + { 1, TRANSFORM(FLIPPED) }, + { 1, TRANSFORM(FLIPPED_90) }, + { 1, TRANSFORM(FLIPPED_180) }, + { 1, TRANSFORM(FLIPPED_270) }, + { 2, TRANSFORM(NORMAL) }, + /* { 2, TRANSFORM(90) }, done in output-transforms-test.c */ + { 2, TRANSFORM(180) }, + { 2, TRANSFORM(FLIPPED) }, + { 3, TRANSFORM(NORMAL) }, + { 3, TRANSFORM(FLIPPED_90) }, +}; + +TEST_P(buffer_transform, my_buffer_args) +{ + const struct buffer_args *bargs = data; + const struct setup_args *oargs; + struct client *client; + bool match; + char *refname; + int ret; + + oargs = &my_setup_args[get_test_fixture_index()]; + + ret = asprintf(&refname, "output_%d-%s_buffer_%d-%s", + oargs->scale, oargs->transform_name, + bargs->scale, bargs->transform_name); + assert(ret); + + testlog("%s: %s\n", get_test_name(), refname); + + /* + * NOTE! The transform set below is a lie. + * Take that into account when analyzing screenshots. + */ + + client = create_client(); + client->surface = create_test_surface(client); + client->surface->width = 10000; /* used only for damage */ + client->surface->height = 10000; + client->surface->buffer = client_buffer_from_image_file(client, + "basic-test-card", + bargs->scale); + wl_surface_set_buffer_scale(client->surface->wl_surface, bargs->scale); + wl_surface_set_buffer_transform(client->surface->wl_surface, + bargs->transform); + move_client(client, 19, 19); + + match = verify_screen_content(client, refname, 0, NULL, 0); + assert(match); + + client_destroy(client); +} diff --git a/tests/config-parser-test.c b/tests/config-parser-test.c new file mode 100644 index 0000000..583c83f --- /dev/null +++ b/tests/config-parser-test.c @@ -0,0 +1,632 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "shared/helpers.h" +#include "zunitc/zunitc.h" + +struct fixture_data { + const char *text; + struct weston_config *config; +}; + +static struct weston_config * +load_config(const char *text) +{ + struct weston_config *config = NULL; + int len = 0; + int fd = -1; + char file[] = "/tmp/weston-config-parser-test-XXXXXX"; + + ZUC_ASSERTG_NOT_NULL(text, out); + + fd = mkstemp(file); + ZUC_ASSERTG_NE(-1, fd, out); + + len = write(fd, text, strlen(text)); + ZUC_ASSERTG_EQ((int)strlen(text), len, out_close); + + config = weston_config_parse(file); + +out_close: + close(fd); + unlink(file); +out: + return config; +} + +static void * +setup_test_config(void *data) +{ + struct weston_config *config = load_config(data); + ZUC_ASSERTG_NOT_NULL(config, out); + +out: + return config; +} + +static void * +setup_test_config_failing(void *data) +{ + struct weston_config *config = load_config(data); + ZUC_ASSERTG_NULL(config, err_free); + + return config; +err_free: + weston_config_destroy(config); + return NULL; +} + +static void +cleanup_test_config(void *data) +{ + struct weston_config *config = data; + ZUC_ASSERT_NOT_NULL(config); + weston_config_destroy(config); +} + +static struct zuc_fixture config_test_t0 = { + .data = "# nothing in this file...\n", + .set_up = setup_test_config, + .tear_down = cleanup_test_config +}; + +static struct zuc_fixture config_test_t1 = { + .data = + "# comment line here...\n" + "\n" + "[foo]\n" + "a=b\n" + "name= Roy Batty \n" + "\n" + "\n" + "[bar]\n" + "# more comments\n" + "number=5252\n" + "zero=0\n" + "negative=-42\n" + "flag=false\n" + "\n" + "[colors]\n" + "none=0x00000000\n" + "low=0x11223344\n" + "high=0xff00ff00\n" + "oct=01234567\n" + "dec=12345670\n" + "short=1234567\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", + .set_up = setup_test_config, + .tear_down = cleanup_test_config +}; + +static const char *section_names[] = { + "foo", "bar", "colors", "stuff", "bucket", "bucket" +}; + +/* + * Since these next few won't parse, we don't add the tear_down to + * attempt cleanup. + */ + +static struct zuc_fixture config_test_t2 = { + .data = + "# invalid section...\n" + "[this bracket isn't closed\n", + .set_up = setup_test_config_failing, +}; + +static struct zuc_fixture config_test_t3 = { + .data = + "# line without = ...\n" + "[bambam]\n" + "this line isn't any kind of valid\n", + .set_up = setup_test_config_failing, +}; + +static struct zuc_fixture config_test_t4 = { + .data = + "# starting with = ...\n" + "[bambam]\n" + "=not valid at all\n", + .set_up = setup_test_config_failing, +}; + +ZUC_TEST_F(config_test_t0, comment_only, data) +{ + struct weston_config *config = data; + ZUC_ASSERT_NOT_NULL(config); +} + +/** @todo individual t1 tests should have more descriptive names. */ + +ZUC_TEST_F(config_test_t1, test001, data) +{ + struct weston_config_section *section; + struct weston_config *config = data; + ZUC_ASSERT_NOT_NULL(config); + section = weston_config_get_section(config, + "mollusc", NULL, NULL); + ZUC_ASSERT_NULL(section); +} + +ZUC_TEST_F(config_test_t1, test002, data) +{ + char *s; + int r; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "foo", NULL, NULL); + r = weston_config_section_get_string(section, "a", &s, NULL); + + ZUC_ASSERTG_EQ(0, r, out_free); + ZUC_ASSERTG_STREQ("b", s, out_free); + +out_free: + free(s); +} + +ZUC_TEST_F(config_test_t1, test003, data) +{ + char *s; + int r; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "foo", NULL, NULL); + r = weston_config_section_get_string(section, "b", &s, NULL); + + ZUC_ASSERT_EQ(-1, r); + ZUC_ASSERT_EQ(ENOENT, errno); + ZUC_ASSERT_NULL(s); +} + +ZUC_TEST_F(config_test_t1, test004, data) +{ + char *s; + int r; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "foo", NULL, NULL); + r = weston_config_section_get_string(section, "name", &s, NULL); + + ZUC_ASSERTG_EQ(0, r, out_free); + ZUC_ASSERTG_STREQ("Roy Batty", s, out_free); + +out_free: + free(s); +} + +ZUC_TEST_F(config_test_t1, test005, data) +{ + char *s; + int r; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_string(section, "a", &s, "boo"); + + ZUC_ASSERTG_EQ(-1, r, out_free); + ZUC_ASSERTG_EQ(ENOENT, errno, out_free); + ZUC_ASSERTG_STREQ("boo", s, out_free); + +out_free: + free(s); +} + +ZUC_TEST_F(config_test_t1, test006, data) +{ + int r; + int32_t n; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_int(section, "number", &n, 600); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_EQ(5252, n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, test007, data) +{ + int r; + int32_t n; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_int(section, "+++", &n, 700); + + ZUC_ASSERT_EQ(-1, r); + ZUC_ASSERT_EQ(ENOENT, errno); + ZUC_ASSERT_EQ(700, n); +} + +ZUC_TEST_F(config_test_t1, test008, data) +{ + int r; + uint32_t u; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_uint(section, "number", &u, 600); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_EQ(5252, u); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, test009, data) +{ + int r; + uint32_t u; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_uint(section, "+++", &u, 600); + ZUC_ASSERT_EQ(-1, r); + ZUC_ASSERT_EQ(ENOENT, errno); + ZUC_ASSERT_EQ(600, u); +} + +ZUC_TEST_F(config_test_t1, test010, data) +{ + int r; + bool b; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_bool(section, "flag", &b, true); + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_EQ(false, b); +} + +ZUC_TEST_F(config_test_t1, test011, data) +{ + int r; + bool b; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "stuff", NULL, NULL); + r = weston_config_section_get_bool(section, "flag", &b, false); + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_EQ(true, b); +} + +ZUC_TEST_F(config_test_t1, test012, data) +{ + int r; + bool b; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "stuff", NULL, NULL); + r = weston_config_section_get_bool(section, "bonk", &b, false); + ZUC_ASSERT_EQ(-1, r); + ZUC_ASSERT_EQ(ENOENT, errno); + ZUC_ASSERT_EQ(false, b); +} + +ZUC_TEST_F(config_test_t1, test013, data) +{ + char *s; + int r; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, + "bucket", "color", "blue"); + r = weston_config_section_get_string(section, "contents", &s, NULL); + + ZUC_ASSERTG_EQ(0, r, out_free); + ZUC_ASSERTG_STREQ("live crabs", s, out_free); + +out_free: + free(s); +} + +ZUC_TEST_F(config_test_t1, test014, data) +{ + char *s; + int r; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, + "bucket", "color", "red"); + r = weston_config_section_get_string(section, "contents", &s, NULL); + + ZUC_ASSERTG_EQ(0, r, out_free); + ZUC_ASSERTG_STREQ("sand", s, out_free); + +out_free: + free(s); +} + +ZUC_TEST_F(config_test_t1, test015, data) +{ + char *s; + int r; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, + "bucket", "color", "pink"); + ZUC_ASSERT_NULL(section); + r = weston_config_section_get_string(section, "contents", &s, "eels"); + + ZUC_ASSERTG_EQ(-1, r, out_free); + ZUC_ASSERTG_EQ(ENOENT, errno, out_free); + ZUC_ASSERTG_STREQ("eels", s, out_free); + +out_free: + free(s); +} + +ZUC_TEST_F(config_test_t1, test016, data) +{ + const char *name; + int i; + struct weston_config_section *section; + struct weston_config *config = data; + + section = NULL; + i = 0; + while (weston_config_next_section(config, §ion, &name)) + ZUC_ASSERT_STREQ(section_names[i++], name); + + ZUC_ASSERT_EQ(6, i); +} + +ZUC_TEST_F(config_test_t1, test017, data) +{ + int r; + int32_t n; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_int(section, "zero", &n, 600); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_EQ(0, n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, test018, data) +{ + int r; + uint32_t n; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_uint(section, "zero", &n, 600); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_EQ(0, n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, test019, data) +{ + int r; + uint32_t n; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "colors", NULL, NULL); + r = weston_config_section_get_color(section, "none", &n, 0xff336699); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_EQ(0x000000, n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, test020, data) +{ + int r; + uint32_t n; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "colors", NULL, NULL); + r = weston_config_section_get_color(section, "low", &n, 0xff336699); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_EQ(0x11223344, n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, test021, data) +{ + int r; + uint32_t n; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "colors", NULL, NULL); + r = weston_config_section_get_color(section, "high", &n, 0xff336699); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_EQ(0xff00ff00, n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, test022, data) +{ + int r; + uint32_t n; + struct weston_config_section *section; + struct weston_config *config = data; + + // Treat colors as hex values even if missing the leading 0x + section = weston_config_get_section(config, "colors", NULL, NULL); + r = weston_config_section_get_color(section, "oct", &n, 0xff336699); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_EQ(0x01234567, n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, test023, data) +{ + int r; + uint32_t n; + struct weston_config_section *section; + struct weston_config *config = data; + + // Treat colors as hex values even if missing the leading 0x + section = weston_config_get_section(config, "colors", NULL, NULL); + r = weston_config_section_get_color(section, "dec", &n, 0xff336699); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_EQ(0x12345670, n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, test024, data) +{ + int r; + uint32_t n; + struct weston_config_section *section; + struct weston_config *config = data; + + // 7-digit colors are not valid (most likely typos) + section = weston_config_get_section(config, "colors", NULL, NULL); + r = weston_config_section_get_color(section, "short", &n, 0xff336699); + + ZUC_ASSERT_EQ(-1, r); + ZUC_ASSERT_EQ(0xff336699, n); + ZUC_ASSERT_EQ(EINVAL, errno); +} + +ZUC_TEST_F(config_test_t1, test025, data) +{ + int r; + uint32_t n; + struct weston_config_section *section; + struct weston_config *config = data; + + // String color names are unsupported + section = weston_config_get_section(config, "bucket", NULL, NULL); + r = weston_config_section_get_color(section, "color", &n, 0xff336699); + + ZUC_ASSERT_EQ(-1, r); + ZUC_ASSERT_EQ(0xff336699, n); + ZUC_ASSERT_EQ(EINVAL, errno); +} + +ZUC_TEST_F(config_test_t1, test026, data) +{ + int r; + int32_t n; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_int(section, "negative", &n, 600); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_EQ(-42, n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, test027, data) +{ + int r; + uint32_t n; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_uint(section, "negative", &n, 600); + + ZUC_ASSERT_EQ(-1, r); + ZUC_ASSERT_EQ(600, n); + ZUC_ASSERT_EQ(ERANGE, errno); +} + +ZUC_TEST_F(config_test_t2, doesnt_parse, data) +{ + struct weston_config *config = data; + ZUC_ASSERT_NULL(config); +} + +ZUC_TEST_F(config_test_t3, doesnt_parse, data) +{ + struct weston_config *config = data; + ZUC_ASSERT_NULL(config); +} + +ZUC_TEST_F(config_test_t4, doesnt_parse, data) +{ + struct weston_config *config = data; + ZUC_ASSERT_NULL(config); +} + +ZUC_TEST(config_test, destroy_null) +{ + weston_config_destroy(NULL); + ZUC_ASSERT_EQ(0, weston_config_next_section(NULL, NULL, NULL)); +} + +ZUC_TEST(config_test, section_from_null) +{ + struct weston_config_section *section; + section = weston_config_get_section(NULL, "bucket", NULL, NULL); + ZUC_ASSERT_NULL(section); +} diff --git a/tests/devices-test.c b/tests/devices-test.c new file mode 100644 index 0000000..6b15419 --- /dev/null +++ b/tests/devices-test.c @@ -0,0 +1,356 @@ +/* + * Copyright © 2015 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +/** + * Test (un)plugging devices + * + * At the end of each test we must return Weston to the previous state + * (add all removed devices and remove extra devices), so that + * the environment is prepared for the other tests too + */ + +#define WL_SEAT_CAPABILITY_ALL (WL_SEAT_CAPABILITY_KEYBOARD |\ + WL_SEAT_CAPABILITY_POINTER |\ + WL_SEAT_CAPABILITY_TOUCH) + +/* simply test if weston sends the right capabilities when + * some devices are removed */ +TEST(seat_capabilities_test) +{ + struct client *cl = create_client_and_test_surface(100, 100, 100, 100); + assert(cl->input->caps == WL_SEAT_CAPABILITY_ALL); + + assert(cl->input->pointer); + weston_test_device_release(cl->test->weston_test, "pointer"); + client_roundtrip(cl); + assert(!cl->input->pointer); + assert(!(cl->input->caps & WL_SEAT_CAPABILITY_POINTER)); + + assert(cl->input->keyboard); + weston_test_device_release(cl->test->weston_test, "keyboard"); + client_roundtrip(cl); + assert(!cl->input->keyboard); + assert(!(cl->input->caps & WL_SEAT_CAPABILITY_KEYBOARD)); + + assert(cl->input->touch); + weston_test_device_release(cl->test->weston_test, "touch"); + client_roundtrip(cl); + assert(!cl->input->touch); + assert(!(cl->input->caps & WL_SEAT_CAPABILITY_TOUCH)); + + /* restore previous state */ + weston_test_device_add(cl->test->weston_test, "keyboard"); + weston_test_device_add(cl->test->weston_test, "pointer"); + weston_test_device_add(cl->test->weston_test, "touch"); + client_roundtrip(cl); + + assert(cl->input->pointer); + assert(cl->input->keyboard); + assert(cl->input->touch); + + /* add extra devices */ + weston_test_device_add(cl->test->weston_test, "keyboard"); + weston_test_device_add(cl->test->weston_test, "pointer"); + weston_test_device_add(cl->test->weston_test, "touch"); + client_roundtrip(cl); + + /* remove extra devices */ + weston_test_device_release(cl->test->weston_test, "keyboard"); + weston_test_device_release(cl->test->weston_test, "pointer"); + weston_test_device_release(cl->test->weston_test, "touch"); + client_roundtrip(cl); + + /* we still should have all the capabilities, since the devices + * were doubled */ + assert(cl->input->caps == WL_SEAT_CAPABILITY_ALL); + + assert(cl->input->pointer); + assert(cl->input->keyboard); + assert(cl->input->touch); +} + +#define COUNT 15 +TEST(multiple_device_add_and_remove) +{ + int i; + struct client *cl = create_client_and_test_surface(100, 100, 100, 100); + + /* add device a lot of times */ + for (i = 0; i < COUNT; ++i) { + weston_test_device_add(cl->test->weston_test, "keyboard"); + weston_test_device_add(cl->test->weston_test, "pointer"); + weston_test_device_add(cl->test->weston_test, "touch"); + } + + client_roundtrip(cl); + + assert(cl->input->pointer); + assert(cl->input->keyboard); + assert(cl->input->touch); + + assert(cl->input->caps == WL_SEAT_CAPABILITY_ALL); + + /* release all new devices */ + for (i = 0; i < COUNT; ++i) { + weston_test_device_release(cl->test->weston_test, "keyboard"); + weston_test_device_release(cl->test->weston_test, "pointer"); + weston_test_device_release(cl->test->weston_test, "touch"); + } + + client_roundtrip(cl); + + /* there is still one from each device left */ + assert(cl->input->caps == WL_SEAT_CAPABILITY_ALL); + + assert(cl->input->pointer); + assert(cl->input->keyboard); + assert(cl->input->touch); +} + +TEST(device_release_before_destroy) +{ + struct client *cl = create_client_and_test_surface(100, 100, 100, 100); + + /* we can release pointer when we won't be using it anymore. + * Do it and see what happens if the device is destroyed + * right after that */ + wl_pointer_release(cl->input->pointer->wl_pointer); + /* we must free and set to NULL the structures, otherwise + * seat capabilities will double-free them */ + free(cl->input->pointer); + cl->input->pointer = NULL; + + wl_keyboard_release(cl->input->keyboard->wl_keyboard); + free(cl->input->keyboard); + cl->input->keyboard = NULL; + + wl_touch_release(cl->input->touch->wl_touch); + free(cl->input->touch); + cl->input->touch = NULL; + + weston_test_device_release(cl->test->weston_test, "pointer"); + weston_test_device_release(cl->test->weston_test, "keyboard"); + weston_test_device_release(cl->test->weston_test, "touch"); + client_roundtrip(cl); + + assert(cl->input->caps == 0); + + /* restore previous state */ + weston_test_device_add(cl->test->weston_test, "pointer"); + weston_test_device_add(cl->test->weston_test, "keyboard"); + weston_test_device_add(cl->test->weston_test, "touch"); + client_roundtrip(cl); + + assert(cl->input->caps == WL_SEAT_CAPABILITY_ALL); +} + +TEST(device_release_before_destroy_multiple) +{ + int i; + + /* if weston crashed during this test, then there is + * some inconsistency */ + for (i = 0; i < 30; ++i) { + /* Fifty times run the previous test. This will create + * fifty clients, because we don't have any + * way how to destroy them (worth of adding!). Only one + * client will run at a time though and so should have no + * effect on the result of the test (after the client + * finishes its body, it just 'is' and does nothing). */ + device_release_before_destroy(); + } +} + +/* normal work-flow test */ +TEST(device_release_after_destroy) +{ + struct client *cl = create_client_and_test_surface(100, 100, 100, 100); + + weston_test_device_release(cl->test->weston_test, "pointer"); + wl_pointer_release(cl->input->pointer->wl_pointer); + /* we must free the memory manually, otherwise seat.capabilities + * will try to free it and will use invalid proxy */ + free(cl->input->pointer); + cl->input->pointer = NULL; + + client_roundtrip(cl); + + weston_test_device_release(cl->test->weston_test, "keyboard"); + wl_keyboard_release(cl->input->keyboard->wl_keyboard); + free(cl->input->keyboard); + cl->input->keyboard = NULL; + + client_roundtrip(cl); + + weston_test_device_release(cl->test->weston_test, "touch"); + wl_touch_release(cl->input->touch->wl_touch); + free(cl->input->touch); + cl->input->touch = NULL; + + client_roundtrip(cl); + + assert(cl->input->caps == 0); + + /* restore previous state */ + weston_test_device_add(cl->test->weston_test, "pointer"); + weston_test_device_add(cl->test->weston_test, "keyboard"); + weston_test_device_add(cl->test->weston_test, "touch"); + client_roundtrip(cl); + + assert(cl->input->caps == WL_SEAT_CAPABILITY_ALL); +} + +TEST(device_release_after_destroy_multiple) +{ + int i; + + /* if weston crashed during this test, then there is + * some inconsistency */ + for (i = 0; i < 30; ++i) { + device_release_after_destroy(); + } +} + +/* see https://bugzilla.gnome.org/show_bug.cgi?id=745008 + * It is a mutter bug, but highly relevant. Weston does not + * suffer from this bug atm, but it is worth of testing. */ +TEST(get_device_after_destroy) +{ + struct client *cl = create_client_and_test_surface(100, 100, 100, 100); + struct wl_pointer *wl_pointer; + struct wl_keyboard *wl_keyboard; + struct wl_touch *wl_touch; + + /* There's a race: + * 1) compositor destroys device + * 2) client asks for the device, because it has not received + * the new capabilities yet + * 3) compositor gets the request with a new_id for the + * destroyed device + * 4) client uses the new_id + * 5) client gets new capabilities, destroying the objects + * + * If the compositor just bails out in step 3) and does not + * create the resource, then the client gets an error in step 4) + * - even though it followed the protocol (it just didn't know + * about new capabilities). + * + * This test simulates this situation + */ + + /* connection is buffered, so after calling client_roundtrip(), + * this whole batch will be delivered to compositor and will + * exactly simulate our situation */ + weston_test_device_release(cl->test->weston_test, "pointer"); + wl_pointer = wl_seat_get_pointer(cl->input->wl_seat); + assert(wl_pointer); + + /* this should be ignored */ + wl_pointer_set_cursor(wl_pointer, 0, NULL, 0, 0); + + /* this should not be ignored */ + wl_pointer_release(wl_pointer); + client_roundtrip(cl); + + weston_test_device_release(cl->test->weston_test, "keyboard"); + wl_keyboard = wl_seat_get_keyboard(cl->input->wl_seat); + assert(wl_keyboard); + wl_keyboard_release(wl_keyboard); + client_roundtrip(cl); + + weston_test_device_release(cl->test->weston_test, "touch"); + wl_touch = wl_seat_get_touch(cl->input->wl_seat); + assert(wl_touch); + wl_touch_release(wl_touch); + client_roundtrip(cl); + + /* get weston to the previous state */ + weston_test_device_add(cl->test->weston_test, "pointer"); + weston_test_device_add(cl->test->weston_test, "keyboard"); + weston_test_device_add(cl->test->weston_test, "touch"); + client_roundtrip(cl); + + assert(cl->input->caps == WL_SEAT_CAPABILITY_ALL); +} + +TEST(get_device_after_destroy_multiple) +{ + int i; + + /* if weston crashed during this test, then there is + * some inconsistency */ + for (i = 0; i < 30; ++i) { + get_device_after_destroy(); + } +} + +TEST(seats_have_names) +{ + struct client *cl = create_client_and_test_surface(100, 100, 100, 100); + struct input *input; + + wl_list_for_each(input, &cl->inputs, link) { + assert(input->seat_name); + } +} + +TEST(seat_destroy_and_recreate) +{ + struct client *cl = create_client_and_test_surface(100, 100, 100, 100); + + weston_test_device_release(cl->test->weston_test, "seat"); + /* Roundtrip to receive and handle the seat global removal event */ + client_roundtrip(cl); + + assert(!cl->input); + + weston_test_device_add(cl->test->weston_test, "seat"); + /* First roundtrip to send request and receive new seat global */ + client_roundtrip(cl); + /* Second roundtrip to handle seat events and set up input devices */ + client_roundtrip(cl); + + assert(cl->input); + assert(cl->input->pointer); + assert(cl->input->keyboard); + assert(cl->input->touch); +} diff --git a/tests/drm-smoke-test.c b/tests/drm-smoke-test.c new file mode 100644 index 0000000..4c5b6a5 --- /dev/null +++ b/tests/drm-smoke-test.c @@ -0,0 +1,72 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; + setup.backend = WESTON_BACKEND_DRM; + setup.renderer = RENDERER_PIXMAN; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +TEST(drm_smoke) { + + struct client *client; + struct buffer *buffer; + struct wl_surface *surface; + pixman_color_t red; + int i, frame; + + color_rgb888(&red, 255, 0, 0); + + client = create_client_and_test_surface(0, 0, 200, 200); + assert(client); + + surface = client->surface->wl_surface; + buffer = create_shm_buffer_a8r8g8b8(client, 200, 200); + + fill_image_with_color(buffer->image, &red); + + for (i = 0; i < 5; i++) { + wl_surface_attach(surface, buffer->proxy, 0, 0); + wl_surface_damage(surface, 0, 0, 200, 200); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + } + + client_destroy(client); +} diff --git a/tests/event-test.c b/tests/event-test.c new file mode 100644 index 0000000..75419a6 --- /dev/null +++ b/tests/event-test.c @@ -0,0 +1,179 @@ +/* + * Copyright © 2012 Intel Corporation + * Copyright © 2013 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +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 = create_client_and_test_surface(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 buffer *buf1; + struct buffer *buf2; + struct buffer *buf3; + int buf1_released = 0; + int buf2_released = 0; + int buf3_released = 0; + int frame; + + client = create_client_and_test_surface(100, 100, 100, 100); + assert(client); + surface = client->surface->wl_surface; + + buf1 = create_shm_buffer_a8r8g8b8(client, 100, 100); + wl_buffer_add_listener(buf1->proxy, &buffer_listener, &buf1_released); + + buf2 = create_shm_buffer_a8r8g8b8(client, 100, 100); + wl_buffer_add_listener(buf2->proxy, &buffer_listener, &buf2_released); + + buf3 = create_shm_buffer_a8r8g8b8(client, 100, 100); + wl_buffer_add_listener(buf3->proxy, &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->proxy, 0, 0); + wl_surface_attach(surface, buf2->proxy, 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->proxy, 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->buffer->proxy, 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/input-timestamps-helper.c b/tests/input-timestamps-helper.c new file mode 100644 index 0000000..05cafec --- /dev/null +++ b/tests/input-timestamps-helper.c @@ -0,0 +1,177 @@ +/* + * Copyright © 2017 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "input-timestamps-helper.h" +#include "input-timestamps-unstable-v1-client-protocol.h" +#include "shared/timespec-util.h" +#include +#include "weston-test-client-helper.h" + +struct input_timestamps { + struct zwp_input_timestamps_v1 *proxy; +}; + +static struct zwp_input_timestamps_manager_v1 * +get_input_timestamps_manager(struct client *client) +{ + struct global *g; + struct global *global_ts = NULL; + struct zwp_input_timestamps_manager_v1 *ts = NULL; + + wl_list_for_each(g, &client->global_list, link) { + if (strcmp(g->interface, zwp_input_timestamps_manager_v1_interface.name)) + continue; + + if (global_ts) + assert(!"Multiple input timestamp managers"); + + global_ts = g; + } + + assert(global_ts); + assert(global_ts->version == 1); + + ts = wl_registry_bind(client->wl_registry, global_ts->name, + &zwp_input_timestamps_manager_v1_interface, 1); + assert(ts); + + return ts; +} + +static void +input_timestamp(void *data, + struct zwp_input_timestamps_v1 *zwp_input_timestamps_v1, + uint32_t tv_sec_hi, + uint32_t tv_sec_lo, + uint32_t tv_nsec) +{ + struct timespec *timestamp = data; + + timespec_from_proto(timestamp, tv_sec_hi, tv_sec_lo, + tv_nsec); + + testlog("test-client: got input timestamp %ld.%ld\n", + timestamp->tv_sec, timestamp->tv_nsec); +} + +static const struct zwp_input_timestamps_v1_listener +input_timestamps_listener = { + .timestamp = input_timestamp, +}; + +struct input_timestamps * +input_timestamps_create_for_keyboard(struct client *client) +{ + struct zwp_input_timestamps_manager_v1 *manager = + get_input_timestamps_manager(client); + struct timespec *timestamp= &client->input->keyboard->input_timestamp; + struct input_timestamps *input_ts; + + input_ts = zalloc(sizeof *input_ts); + assert(input_ts); + + input_ts->proxy = + zwp_input_timestamps_manager_v1_get_keyboard_timestamps( + manager, client->input->keyboard->wl_keyboard); + assert(input_ts->proxy); + + zwp_input_timestamps_v1_add_listener(input_ts->proxy, + &input_timestamps_listener, + timestamp); + + zwp_input_timestamps_manager_v1_destroy(manager); + + client_roundtrip(client); + + return input_ts; +} + +struct input_timestamps * +input_timestamps_create_for_pointer(struct client *client) +{ + struct zwp_input_timestamps_manager_v1 *manager = + get_input_timestamps_manager(client); + struct timespec *timestamp= &client->input->pointer->input_timestamp; + struct input_timestamps *input_ts; + + input_ts = zalloc(sizeof *input_ts); + assert(input_ts); + + input_ts->proxy = + zwp_input_timestamps_manager_v1_get_pointer_timestamps( + manager, client->input->pointer->wl_pointer); + assert(input_ts->proxy); + + zwp_input_timestamps_v1_add_listener(input_ts->proxy, + &input_timestamps_listener, + timestamp); + + zwp_input_timestamps_manager_v1_destroy(manager); + + client_roundtrip(client); + + return input_ts; +} + +struct input_timestamps * +input_timestamps_create_for_touch(struct client *client) +{ + struct zwp_input_timestamps_manager_v1 *manager = + get_input_timestamps_manager(client); + struct timespec *timestamp= &client->input->touch->input_timestamp; + struct input_timestamps *input_ts; + + input_ts = zalloc(sizeof *input_ts); + assert(input_ts); + + input_ts->proxy = + zwp_input_timestamps_manager_v1_get_touch_timestamps( + manager, client->input->touch->wl_touch); + assert(input_ts->proxy); + + zwp_input_timestamps_v1_add_listener(input_ts->proxy, + &input_timestamps_listener, + timestamp); + + zwp_input_timestamps_manager_v1_destroy(manager); + + client_roundtrip(client); + + return input_ts; +} + +void +input_timestamps_destroy(struct input_timestamps *input_ts) +{ + zwp_input_timestamps_v1_destroy(input_ts->proxy); + free(input_ts); +} diff --git a/tests/input-timestamps-helper.h b/tests/input-timestamps-helper.h new file mode 100644 index 0000000..5301df0 --- /dev/null +++ b/tests/input-timestamps-helper.h @@ -0,0 +1,46 @@ +/* + * Copyright © 2017 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef INPUT_TIMESTAMPS_HELPER_H +#define INPUT_TIMESTAMPS_HELPER_H + +#include "config.h" + +struct client; +struct input_timestamps; + +struct input_timestamps * +input_timestamps_create_for_keyboard(struct client *client); + +struct input_timestamps * +input_timestamps_create_for_pointer(struct client *client); + +struct input_timestamps * +input_timestamps_create_for_touch(struct client *client); + +void +input_timestamps_destroy(struct input_timestamps *input_ts); + +#endif /* INPUT_TIMESTAMPS_HELPER_H */ diff --git a/tests/internal-screenshot-test.c b/tests/internal-screenshot-test.c new file mode 100644 index 0000000..6c1b774 --- /dev/null +++ b/tests/internal-screenshot-test.c @@ -0,0 +1,178 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" +#include "test-config.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.renderer = RENDERER_PIXMAN; + setup.width = 320; + setup.height = 240; + setup.shell = SHELL_DESKTOP; + setup.config_file = TESTSUITE_INTERNAL_SCREENSHOT_CONFIG_PATH; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +static void +draw_stuff(pixman_image_t *image) +{ + int w, h; + int stride; /* bytes */ + int x, y; + uint32_t r, g, b; + uint32_t *pixels; + uint32_t *pixel; + pixman_format_code_t fmt; + + fmt = pixman_image_get_format(image); + w = pixman_image_get_width(image); + h = pixman_image_get_height(image); + stride = pixman_image_get_stride(image); + pixels = pixman_image_get_data(image); + + assert(PIXMAN_FORMAT_BPP(fmt) == 32); + + for (x = 0; x < w; x++) + for (y = 0; y < h; y++) { + b = x; + g = x + y; + r = y; + pixel = pixels + (y * stride / 4) + x; + *pixel = (255U << 24) | (r << 16) | (g << 8) | b; + } +} + +TEST(internal_screenshot) +{ + struct buffer *buf; + struct client *client; + struct wl_surface *surface; + struct buffer *screenshot = NULL; + pixman_image_t *reference_good = NULL; + pixman_image_t *reference_bad = NULL; + pixman_image_t *diffimg; + struct rectangle clip; + const char *fname; + bool match = false; + bool dump_all_images = true; + + /* Create the client */ + testlog("Creating client for test\n"); + client = create_client_and_test_surface(100, 100, 100, 100); + assert(client); + surface = client->surface->wl_surface; + + /* + * We are racing our screenshooting against weston-desktop-shell + * setting the cursor. If w-d-s wins, our screenshot will have a cursor + * shown, which makes the image comparison fail. Our window and the + * default pointer position are accidentally causing an overlap that + * intersects our test clip rectangle. + * + * w-d-s wins very rarely though, so the race is easy to miss. You can + * make it happen by putting a delay before the call to + * create_client_and_test_surface(). + * + * The weston_test_move_pointer() below makes the race irrelevant, as + * the cursor won't overlap with anything we care about. + */ + + /* Move the pointer away from the screenshot area. */ + weston_test_move_pointer(client->test->weston_test, 0, 1, 0, 0, 0); + + buf = create_shm_buffer_a8r8g8b8(client, 100, 100); + draw_stuff(buf->image); + wl_surface_attach(surface, buf->proxy, 0, 0); + wl_surface_damage(surface, 0, 0, 100, 100); + wl_surface_commit(surface); + + /* Take a snapshot. Result will be in screenshot->wl_buffer. */ + testlog("Taking a screenshot\n"); + screenshot = capture_screenshot_of_output(client); + assert(screenshot); + + /* Load good reference image */ + fname = screenshot_reference_filename("internal-screenshot-good", 0); + testlog("Loading good reference image %s\n", fname); + reference_good = load_image_from_png(fname); + assert(reference_good); + + /* Load bad reference image */ + fname = screenshot_reference_filename("internal-screenshot-bad", 0); + testlog("Loading bad reference image %s\n", fname); + reference_bad = load_image_from_png(fname); + assert(reference_bad); + + /* Test check_images_match() without a clip. + * We expect this to fail since we use a bad reference image + */ + match = check_images_match(screenshot->image, reference_bad, NULL, NULL); + testlog("Screenshot %s reference image\n", match? "equal to" : "different from"); + assert(!match); + pixman_image_unref(reference_bad); + + /* Test check_images_match() with clip. + * Alpha-blending and other effects can cause irrelevant discrepancies, so look only + * at a small portion of the solid-colored background + */ + clip.x = 100; + clip.y = 100; + clip.width = 100; + clip.height = 100; + testlog("Clip: %d,%d %d x %d\n", clip.x, clip.y, clip.width, clip.height); + match = check_images_match(screenshot->image, reference_good, &clip, NULL); + testlog("Screenshot %s reference image in clipped area\n", match? "matches" : "doesn't match"); + if (!match) { + diffimg = visualize_image_difference(screenshot->image, reference_good, &clip, NULL); + fname = screenshot_output_filename("internal-screenshot-error", 0); + write_image_as_png(diffimg, fname); + pixman_image_unref(diffimg); + } + pixman_image_unref(reference_good); + + /* Test dumping of non-matching images */ + if (!match || dump_all_images) { + fname = screenshot_output_filename("internal-screenshot", 0); + write_image_as_png(screenshot->image, fname); + } + + buffer_destroy(screenshot); + + testlog("Test complete\n"); + assert(match); +} diff --git a/tests/internal-screenshot.ini b/tests/internal-screenshot.ini new file mode 100644 index 0000000..abc046e --- /dev/null +++ b/tests/internal-screenshot.ini @@ -0,0 +1,3 @@ +[shell] +startup-animation=none +background-color=0xCC336699 diff --git a/tests/ivi-layout-internal-test.c b/tests/ivi-layout-internal-test.c new file mode 100644 index 0000000..8f2d6be --- /dev/null +++ b/tests/ivi-layout-internal-test.c @@ -0,0 +1,1022 @@ +/* + * Copyright © 2013 DENSO CORPORATION + * Copyright © 2015 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include "compositor/weston.h" +#include "ivi-shell/ivi-layout-export.h" +#include "ivi-shell/ivi-layout-private.h" +#include "ivi-test.h" +#include "shared/helpers.h" +#include "weston-test-runner.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.shell = SHELL_IVI; + + return weston_test_harness_execute_as_plugin(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +struct test_context { + struct weston_compositor *compositor; + const struct ivi_layout_interface *layout_interface; + uint32_t user_flags; + + struct wl_listener layer_property_changed; + struct wl_listener layer_created; + struct wl_listener layer_removed; +}; + +static void +iassert_fail(const char *cond, const char *file, int line, + const char *func, struct test_context *ctx) +{ + weston_log("Assert failure in %s:%d, %s: '%s'\n", + file, line, func, cond); + weston_compositor_exit_with_code(ctx->compositor, EXIT_FAILURE); +} + +#define iassert(cond) ({ \ + bool b_ = (cond); \ + if (!b_) \ + iassert_fail(#cond, __FILE__, __LINE__, __func__, ctx); \ + b_; \ +}) + +/************************ tests begin ******************************/ + +/* + * These are all internal ivi_layout API tests that do not require + * any client objects. + */ +static void +test_surface_bad_visibility(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + iassert(lyt->surface_set_visibility(NULL, true) == IVI_FAILED); + + lyt->commit_changes(); +} + +static void +test_surface_bad_destination_rectangle(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + iassert(lyt->surface_set_destination_rectangle(NULL, 20, 30, 200, 300) == IVI_FAILED); +} + +static void +test_surface_bad_source_rectangle(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + iassert(lyt->surface_set_source_rectangle(NULL, 20, 30, 200, 300) == IVI_FAILED); +} + +static void +test_surface_bad_properties(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + iassert(lyt->get_properties_of_surface(NULL) == NULL); +} + +static void +test_layer_create(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + uint32_t id1; + uint32_t id2; + struct ivi_layout_layer *ivilayer; + struct ivi_layout_layer *new_ivilayer; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + iassert(IVI_TEST_LAYER_ID(0) == lyt->get_id_of_layer(ivilayer)); + + new_ivilayer = lyt->get_layer_from_id(IVI_TEST_LAYER_ID(0)); + iassert(ivilayer == new_ivilayer); + + id1 = lyt->get_id_of_layer(ivilayer); + id2 = lyt->get_id_of_layer(new_ivilayer); + iassert(id1 == id2); + + lyt->layer_destroy(ivilayer); + iassert(lyt->get_layer_from_id(IVI_TEST_LAYER_ID(0)) == NULL); +} + +static void +test_layer_visibility(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + const struct ivi_layout_layer_properties *prop; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + prop = lyt->get_properties_of_layer(ivilayer); + + iassert(prop->visibility == false); + + iassert(lyt->layer_set_visibility(ivilayer, true) == IVI_SUCCEEDED); + + iassert(prop->visibility == false); + + lyt->commit_changes(); + + iassert(prop->visibility == true); + + lyt->layer_destroy(ivilayer); +} + +static void +test_layer_opacity(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + const struct ivi_layout_layer_properties *prop; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + prop = lyt->get_properties_of_layer(ivilayer); + iassert(prop->opacity == wl_fixed_from_double(1.0)); + + iassert(lyt->layer_set_opacity( + ivilayer, wl_fixed_from_double(0.5)) == IVI_SUCCEEDED); + + iassert(prop->opacity == wl_fixed_from_double(1.0)); + + lyt->commit_changes(); + + iassert(prop->opacity == wl_fixed_from_double(0.5)); + + lyt->layer_destroy(ivilayer); +} + +static void +test_layer_dimension(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + const struct ivi_layout_layer_properties *prop; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + prop = lyt->get_properties_of_layer(ivilayer); + iassert(prop->dest_width == 200); + iassert(prop->dest_height == 300); + + iassert(lyt->layer_set_destination_rectangle(ivilayer, prop->dest_x, prop->dest_y, + 400, 600) == IVI_SUCCEEDED); + + iassert(prop->dest_width == 200); + iassert(prop->dest_height == 300); + + lyt->commit_changes(); + + iassert(prop->dest_width == 400); + iassert(prop->dest_height == 600); + + lyt->layer_destroy(ivilayer); +} + +static void +test_layer_position(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + const struct ivi_layout_layer_properties *prop; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + prop = lyt->get_properties_of_layer(ivilayer); + iassert(prop->dest_x == 0); + iassert(prop->dest_y == 0); + + iassert(lyt->layer_set_destination_rectangle(ivilayer, 20, 30, + prop->dest_width, prop->dest_height) == IVI_SUCCEEDED); + + iassert(prop->dest_x == 0); + iassert(prop->dest_y == 0); + + lyt->commit_changes(); + + prop = lyt->get_properties_of_layer(ivilayer); + iassert(prop->dest_x == 20); + iassert(prop->dest_y == 30); + + lyt->layer_destroy(ivilayer); +} + +static void +test_layer_destination_rectangle(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + const struct ivi_layout_layer_properties *prop; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + prop = lyt->get_properties_of_layer(ivilayer); + iassert(prop->dest_width == 200); + iassert(prop->dest_height == 300); + iassert(prop->dest_x == 0); + iassert(prop->dest_y == 0); + + iassert(lyt->layer_set_destination_rectangle( + ivilayer, 20, 30, 400, 600) == IVI_SUCCEEDED); + + prop = lyt->get_properties_of_layer(ivilayer); + iassert(prop->dest_width == 200); + iassert(prop->dest_height == 300); + iassert(prop->dest_x == 0); + iassert(prop->dest_y == 0); + + lyt->commit_changes(); + + iassert(prop->dest_width == 400); + iassert(prop->dest_height == 600); + iassert(prop->dest_x == 20); + iassert(prop->dest_y == 30); + + lyt->layer_destroy(ivilayer); +} + +static void +test_layer_source_rectangle(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + const struct ivi_layout_layer_properties *prop; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + prop = lyt->get_properties_of_layer(ivilayer); + iassert(prop->source_width == 200); + iassert(prop->source_height == 300); + iassert(prop->source_x == 0); + iassert(prop->source_y == 0); + + iassert(lyt->layer_set_source_rectangle( + ivilayer, 20, 30, 400, 600) == IVI_SUCCEEDED); + + prop = lyt->get_properties_of_layer(ivilayer); + iassert(prop->source_width == 200); + iassert(prop->source_height == 300); + iassert(prop->source_x == 0); + iassert(prop->source_y == 0); + + lyt->commit_changes(); + + prop = lyt->get_properties_of_layer(ivilayer); + iassert(prop->source_width == 400); + iassert(prop->source_height == 600); + iassert(prop->source_x == 20); + iassert(prop->source_y == 30); + + lyt->layer_destroy(ivilayer); +} + +static void +test_layer_bad_remove(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + lyt->layer_destroy(NULL); +} + +static void +test_layer_bad_visibility(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + iassert(lyt->layer_set_visibility(NULL, true) == IVI_FAILED); + + lyt->commit_changes(); +} + +static void +test_layer_bad_opacity(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + const struct ivi_layout_layer_properties *prop; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + iassert(lyt->layer_set_opacity( + NULL, wl_fixed_from_double(0.3)) == IVI_FAILED); + + iassert(lyt->layer_set_opacity( + ivilayer, wl_fixed_from_double(0.3)) == IVI_SUCCEEDED); + + iassert(lyt->layer_set_opacity( + ivilayer, wl_fixed_from_double(-1)) == IVI_FAILED); + + lyt->commit_changes(); + + prop = lyt->get_properties_of_layer(ivilayer); + iassert(prop->opacity == wl_fixed_from_double(0.3)); + + iassert(lyt->layer_set_opacity( + ivilayer, wl_fixed_from_double(1.1)) == IVI_FAILED); + + lyt->commit_changes(); + + iassert(prop->opacity == wl_fixed_from_double(0.3)); + + iassert(lyt->layer_set_opacity( + NULL, wl_fixed_from_double(0.5)) == IVI_FAILED); + + lyt->commit_changes(); + + lyt->layer_destroy(ivilayer); +} + +static void +test_layer_bad_destination_rectangle(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + iassert(lyt->layer_set_destination_rectangle( + NULL, 20, 30, 200, 300) == IVI_FAILED); +} + +static void +test_layer_bad_source_rectangle(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + iassert(lyt->layer_set_source_rectangle( + NULL, 20, 30, 200, 300) == IVI_FAILED); +} + +static void +test_layer_bad_properties(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + iassert(lyt->get_properties_of_layer(NULL) == NULL); +} + +static void +test_commit_changes_after_visibility_set_layer_destroy(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + iassert(lyt->layer_set_visibility(ivilayer, true) == IVI_SUCCEEDED); + lyt->layer_destroy(ivilayer); + lyt->commit_changes(); +} + +static void +test_commit_changes_after_opacity_set_layer_destroy(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + iassert(lyt->layer_set_opacity( + ivilayer, wl_fixed_from_double(0.5)) == IVI_SUCCEEDED); + lyt->layer_destroy(ivilayer); + lyt->commit_changes(); +} + +static void +test_commit_changes_after_source_rectangle_set_layer_destroy(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + iassert(lyt->layer_set_source_rectangle( + ivilayer, 20, 30, 200, 300) == IVI_SUCCEEDED); + lyt->layer_destroy(ivilayer); + lyt->commit_changes(); +} + +static void +test_commit_changes_after_destination_rectangle_set_layer_destroy(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + iassert(lyt->layer_set_destination_rectangle( + ivilayer, 20, 30, 200, 300) == IVI_SUCCEEDED); + lyt->layer_destroy(ivilayer); + lyt->commit_changes(); +} + +static void +test_layer_create_duplicate(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + struct ivi_layout_layer *duplicatelayer; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + if (ivilayer != NULL) + iassert(ivilayer->ref_count == 1); + + duplicatelayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer == duplicatelayer); + + if (ivilayer != NULL) + iassert(ivilayer->ref_count == 2); + + lyt->layer_destroy(ivilayer); + + if (ivilayer != NULL) + iassert(ivilayer->ref_count == 1); + + lyt->layer_destroy(ivilayer); +} + +static void +test_get_layer_after_destory_layer(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + lyt->layer_destroy(ivilayer); + + ivilayer = lyt->get_layer_from_id(IVI_TEST_LAYER_ID(0)); + iassert(ivilayer == NULL); +} + +static void +test_screen_render_order(struct test_context *ctx) +{ +#define LAYER_NUM (3) + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct weston_output *output; + struct ivi_layout_layer *ivilayers[LAYER_NUM] = {}; + struct ivi_layout_layer **array; + int32_t length = 0; + uint32_t i; + + if (!iassert(!wl_list_empty(&ctx->compositor->output_list))) + return; + + output = wl_container_of(ctx->compositor->output_list.next, output, link); + + for (i = 0; i < LAYER_NUM; i++) + ivilayers[i] = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(i), 200, 300); + + iassert(lyt->screen_set_render_order(output, ivilayers, LAYER_NUM) == IVI_SUCCEEDED); + + lyt->commit_changes(); + + iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); + iassert(length == LAYER_NUM); + for (i = 0; i < LAYER_NUM; i++) + iassert(array[i] == ivilayers[i]); + + if (length > 0) + free(array); + + array = NULL; + + iassert(lyt->screen_set_render_order(output, NULL, 0) == IVI_SUCCEEDED); + + lyt->commit_changes(); + + iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); + iassert(length == 0 && array == NULL); + + for (i = 0; i < LAYER_NUM; i++) + lyt->layer_destroy(ivilayers[i]); + +#undef LAYER_NUM +} + +static void +test_screen_bad_render_order(struct test_context *ctx) +{ +#define LAYER_NUM (3) + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct weston_output *output; + struct ivi_layout_layer *ivilayers[LAYER_NUM] = {}; + struct ivi_layout_layer **array; + int32_t length = 0; + uint32_t i; + + if (!iassert(!wl_list_empty(&ctx->compositor->output_list))) + return; + + output = wl_container_of(ctx->compositor->output_list.next, output, link); + + for (i = 0; i < LAYER_NUM; i++) + ivilayers[i] = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(i), 200, 300); + + iassert(lyt->screen_set_render_order(NULL, ivilayers, LAYER_NUM) == IVI_FAILED); + + lyt->commit_changes(); + + iassert(lyt->get_layers_on_screen(NULL, &length, &array) == IVI_FAILED); + iassert(lyt->get_layers_on_screen(output, NULL, &array) == IVI_FAILED); + iassert(lyt->get_layers_on_screen(output, &length, NULL) == IVI_FAILED); + + for (i = 0; i < LAYER_NUM; i++) + lyt->layer_destroy(ivilayers[i]); + +#undef LAYER_NUM +} + +static void +test_screen_add_layers(struct test_context *ctx) +{ +#define LAYER_NUM (3) + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct weston_output *output; + struct ivi_layout_layer *ivilayers[LAYER_NUM] = {}; + struct ivi_layout_layer **array; + int32_t length = 0; + uint32_t i; + + if (!iassert(!wl_list_empty(&ctx->compositor->output_list))) + return; + + output = wl_container_of(ctx->compositor->output_list.next, output, link); + + for (i = 0; i < LAYER_NUM; i++) { + ivilayers[i] = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(i), 200, 300); + iassert(lyt->screen_add_layer(output, ivilayers[i]) == IVI_SUCCEEDED); + } + + lyt->commit_changes(); + + iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); + iassert(length == LAYER_NUM); + for (i = 0; i < (uint32_t)length; i++) + iassert(array[i] == ivilayers[i]); + + if (length > 0) + free(array); + + array = NULL; + + iassert(lyt->screen_set_render_order(output, NULL, 0) == IVI_SUCCEEDED); + for (i = LAYER_NUM; i-- > 0;) + iassert(lyt->screen_add_layer(output, ivilayers[i]) == IVI_SUCCEEDED); + + lyt->commit_changes(); + + iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); + iassert(length == LAYER_NUM); + for (i = 0; i < (uint32_t)length; i++) + iassert(array[i] == ivilayers[LAYER_NUM - (i + 1)]); + + if (length > 0) + free(array); + + for (i = 0; i < LAYER_NUM; i++) + lyt->layer_destroy(ivilayers[i]); + +#undef LAYER_NUM +} + +static void +test_screen_remove_layer(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + struct weston_output *output; + struct ivi_layout_layer **array; + int32_t length = 0; + + if (wl_list_empty(&ctx->compositor->output_list)) + return; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + output = wl_container_of(ctx->compositor->output_list.next, output, link); + + iassert(lyt->screen_add_layer(output, ivilayer) == IVI_SUCCEEDED); + lyt->commit_changes(); + + iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); + iassert(length == 1); + iassert(array[0] == ivilayer); + + iassert(lyt->screen_remove_layer(output, ivilayer) == IVI_SUCCEEDED); + lyt->commit_changes(); + + if (length > 0) + free(array); + + array = NULL; + + iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); + iassert(length == 0); + iassert(array == NULL); + + lyt->layer_destroy(ivilayer); +} + +static void +test_screen_bad_remove_layer(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + struct weston_output *output; + + if (wl_list_empty(&ctx->compositor->output_list)) + return; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + output = wl_container_of(ctx->compositor->output_list.next, output, link); + + iassert(lyt->screen_remove_layer(NULL, ivilayer) == IVI_FAILED); + lyt->commit_changes(); + + iassert(lyt->screen_remove_layer(output, NULL) == IVI_FAILED); + lyt->commit_changes(); + + iassert(lyt->screen_remove_layer(NULL, NULL) == IVI_FAILED); + lyt->commit_changes(); + + lyt->layer_destroy(ivilayer); +} + + +static void +test_commit_changes_after_render_order_set_layer_destroy( + struct test_context *ctx) +{ +#define LAYER_NUM (3) + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct weston_output *output; + struct ivi_layout_layer *ivilayers[LAYER_NUM] = {}; + uint32_t i; + + if (!iassert(!wl_list_empty(&ctx->compositor->output_list))) + return; + + output = wl_container_of(ctx->compositor->output_list.next, output, link); + + for (i = 0; i < LAYER_NUM; i++) + ivilayers[i] = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(i), 200, 300); + + iassert(lyt->screen_set_render_order(output, ivilayers, LAYER_NUM) == IVI_SUCCEEDED); + + lyt->layer_destroy(ivilayers[1]); + + lyt->commit_changes(); + + lyt->layer_destroy(ivilayers[0]); + lyt->layer_destroy(ivilayers[2]); +#undef LAYER_NUM +} + +static void +test_layer_properties_changed_notification_callback(struct wl_listener *listener, void *data) +{ + struct test_context *ctx = + container_of(listener, struct test_context, + layer_property_changed); + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer = data; + const struct ivi_layout_layer_properties *prop = lyt->get_properties_of_layer(ivilayer); + + iassert(lyt->get_id_of_layer(ivilayer) == IVI_TEST_LAYER_ID(0)); + iassert(prop->source_width == 200); + iassert(prop->source_height == 300); + + if (lyt->get_id_of_layer(ivilayer) == IVI_TEST_LAYER_ID(0) && + prop->source_width == 200 && prop->source_height == 300) + ctx->user_flags = 1; +} + +static void +test_layer_properties_changed_notification(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + + ctx->user_flags = 0; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + + ctx->layer_property_changed.notify = test_layer_properties_changed_notification_callback; + + iassert(lyt->layer_add_listener(ivilayer, &ctx->layer_property_changed) == IVI_SUCCEEDED); + + lyt->commit_changes(); + + iassert(ctx->user_flags == 0); + + iassert(lyt->layer_set_destination_rectangle( + ivilayer, 20, 30, 200, 300) == IVI_SUCCEEDED); + + lyt->commit_changes(); + + iassert(ctx->user_flags == 1); + + ctx->user_flags = 0; + iassert(lyt->layer_set_destination_rectangle( + ivilayer, 20, 30, 200, 300) == IVI_SUCCEEDED); + + lyt->commit_changes(); + + iassert(ctx->user_flags == 0); + + // remove layer property changed listener. + wl_list_remove(&ctx->layer_property_changed.link); + + ctx->user_flags = 0; + lyt->commit_changes(); + + iassert(ctx->user_flags == 0); + + lyt->layer_destroy(ivilayer); +} + +static void +test_layer_create_notification_callback(struct wl_listener *listener, void *data) +{ + struct test_context *ctx = + container_of(listener, struct test_context, + layer_created); + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer = data; + const struct ivi_layout_layer_properties *prop = lyt->get_properties_of_layer(ivilayer); + + iassert(lyt->get_id_of_layer(ivilayer) == IVI_TEST_LAYER_ID(0)); + iassert(prop->source_width == 200); + iassert(prop->source_height == 300); + + if (lyt->get_id_of_layer(ivilayer) == IVI_TEST_LAYER_ID(0) && + prop->source_width == 200 && prop->source_height == 300) + ctx->user_flags = 1; +} + +static void +test_layer_create_notification(struct test_context *ctx) +{ +#define LAYER_NUM (2) + const struct ivi_layout_interface *lyt = ctx->layout_interface; + static const uint32_t layers[LAYER_NUM] = {IVI_TEST_LAYER_ID(0), IVI_TEST_LAYER_ID(1)}; + struct ivi_layout_layer *ivilayers[LAYER_NUM] = {}; + + ctx->user_flags = 0; + ctx->layer_created.notify = test_layer_create_notification_callback; + + iassert(lyt->add_listener_create_layer(&ctx->layer_created) == IVI_SUCCEEDED); + ivilayers[0] = lyt->layer_create_with_dimension(layers[0], 200, 300); + + iassert(ctx->user_flags == 1); + + ctx->user_flags = 0; + // remove layer created listener. + wl_list_remove(&ctx->layer_created.link); + + ivilayers[1] = lyt->layer_create_with_dimension(layers[1], 400, 500); + + iassert(ctx->user_flags == 0); + + lyt->layer_destroy(ivilayers[0]); + lyt->layer_destroy(ivilayers[1]); +#undef LAYER_NUM +} + +static void +test_layer_remove_notification_callback(struct wl_listener *listener, void *data) +{ + struct test_context *ctx = + container_of(listener, struct test_context, + layer_removed); + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer = data; + const struct ivi_layout_layer_properties *prop = + lyt->get_properties_of_layer(ivilayer); + + iassert(lyt->get_id_of_layer(ivilayer) == IVI_TEST_LAYER_ID(0)); + iassert(prop->source_width == 200); + iassert(prop->source_height == 300); + + if (lyt->get_id_of_layer(ivilayer) == IVI_TEST_LAYER_ID(0) && + prop->source_width == 200 && prop->source_height == 300) + ctx->user_flags = 1; +} + +static void +test_layer_remove_notification(struct test_context *ctx) +{ +#define LAYER_NUM (2) + const struct ivi_layout_interface *lyt = ctx->layout_interface; + static const uint32_t layers[LAYER_NUM] = {IVI_TEST_LAYER_ID(0), IVI_TEST_LAYER_ID(1)}; + struct ivi_layout_layer *ivilayers[LAYER_NUM] = {}; + + ctx->user_flags = 0; + ctx->layer_removed.notify = test_layer_remove_notification_callback; + + ivilayers[0] = lyt->layer_create_with_dimension(layers[0], 200, 300); + iassert(lyt->add_listener_remove_layer(&ctx->layer_removed) == IVI_SUCCEEDED); + lyt->layer_destroy(ivilayers[0]); + + iassert(ctx->user_flags == 1); + + ctx->user_flags = 0; + ivilayers[1] = lyt->layer_create_with_dimension(layers[1], 250, 350); + + // remove layer property changed listener. + wl_list_remove(&ctx->layer_removed.link); + lyt->layer_destroy(ivilayers[1]); + + iassert(ctx->user_flags == 0); +#undef LAYER_NUM +} + +static void +test_layer_bad_properties_changed_notification_callback(struct wl_listener *listener, void *data) +{ +} + +static void +test_layer_bad_properties_changed_notification(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + + ctx->layer_property_changed.notify = test_layer_bad_properties_changed_notification_callback; + + iassert(lyt->layer_add_listener(NULL, &ctx->layer_property_changed) == IVI_FAILED); + iassert(lyt->layer_add_listener(ivilayer, NULL) == IVI_FAILED); + + lyt->layer_destroy(ivilayer); +} + +static void +test_surface_bad_configure_notification(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + iassert(lyt->add_listener_configure_surface(NULL) == IVI_FAILED); +} + +static void +test_layer_bad_create_notification(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + iassert(lyt->add_listener_create_layer(NULL) == IVI_FAILED); +} + +static void +test_surface_bad_create_notification(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + iassert(lyt->add_listener_create_surface(NULL) == IVI_FAILED); +} + +static void +test_layer_bad_remove_notification(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + iassert(lyt->add_listener_remove_layer(NULL) == IVI_FAILED); +} + +static void +test_surface_bad_remove_notification(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + iassert(lyt->add_listener_remove_surface(NULL) == IVI_FAILED); +} + +/************************ tests end ********************************/ + +static void +run_internal_tests(struct test_context *ctx) +{ + test_surface_bad_visibility(ctx); + test_surface_bad_destination_rectangle(ctx); + test_surface_bad_source_rectangle(ctx); + test_surface_bad_properties(ctx); + + test_layer_create(ctx); + test_layer_visibility(ctx); + test_layer_opacity(ctx); + test_layer_dimension(ctx); + test_layer_position(ctx); + test_layer_destination_rectangle(ctx); + test_layer_source_rectangle(ctx); + test_layer_bad_remove(ctx); + test_layer_bad_visibility(ctx); + test_layer_bad_opacity(ctx); + test_layer_bad_destination_rectangle(ctx); + test_layer_bad_source_rectangle(ctx); + test_layer_bad_properties(ctx); + test_commit_changes_after_visibility_set_layer_destroy(ctx); + test_commit_changes_after_opacity_set_layer_destroy(ctx); + test_commit_changes_after_source_rectangle_set_layer_destroy(ctx); + test_commit_changes_after_destination_rectangle_set_layer_destroy(ctx); + test_layer_create_duplicate(ctx); + test_get_layer_after_destory_layer(ctx); + + test_screen_render_order(ctx); + test_screen_bad_render_order(ctx); + test_screen_add_layers(ctx); + test_screen_remove_layer(ctx); + test_screen_bad_remove_layer(ctx); + test_commit_changes_after_render_order_set_layer_destroy(ctx); + + test_layer_properties_changed_notification(ctx); + test_layer_create_notification(ctx); + test_layer_remove_notification(ctx); + test_layer_bad_properties_changed_notification(ctx); + test_surface_bad_configure_notification(ctx); + test_layer_bad_create_notification(ctx); + test_surface_bad_create_notification(ctx); + test_layer_bad_remove_notification(ctx); + test_surface_bad_remove_notification(ctx); +} + +PLUGIN_TEST(ivi_layout_internal) +{ + /* struct weston_compositor *compositor; */ + struct test_context ctx = {}; + const struct ivi_layout_interface *iface; + + iface = ivi_layout_get_api(compositor); + + if (!iface) { + weston_log("fatal: cannot use ivi_layout_interface.\n"); + weston_compositor_exit_with_code(compositor, RESULT_HARD_ERROR); + return; + } + + ctx.compositor = compositor; + ctx.layout_interface = iface; + + run_internal_tests(&ctx); +} diff --git a/tests/ivi-layout-test-client.c b/tests/ivi-layout-test-client.c new file mode 100644 index 0000000..b7edf8e --- /dev/null +++ b/tests/ivi-layout-test-client.c @@ -0,0 +1,509 @@ +/* + * Copyright © 2015 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include "shared/helpers.h" +#include "shared/xalloc.h" +#include "weston-test-client-helper.h" +#include "ivi-application-client-protocol.h" +#include "ivi-test.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.shell = SHELL_IVI; + setup.extra_module = "test-ivi-layout.so"; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +struct runner { + struct client *client; + struct weston_test_runner *test_runner; + int done; +}; + +static void +runner_finished_handler(void *data, struct weston_test_runner *test_runner) +{ + struct runner *runner = data; + + runner->done = 1; +} + +static const struct weston_test_runner_listener test_runner_listener = { + runner_finished_handler +}; + +static struct runner * +client_create_runner(struct client *client) +{ + struct runner *runner; + struct global *g; + struct global *global_runner = NULL; + + runner = xzalloc(sizeof(*runner)); + runner->client = client; + + wl_list_for_each(g, &client->global_list, link) { + if (strcmp(g->interface, "weston_test_runner")) + continue; + + if (global_runner) + assert(0 && "multiple weston_test_runner objects"); + + global_runner = g; + } + + assert(global_runner && "no weston_test_runner found"); + assert(global_runner->version == 1); + + runner->test_runner = wl_registry_bind(client->wl_registry, + global_runner->name, + &weston_test_runner_interface, + 1); + assert(runner->test_runner); + + weston_test_runner_add_listener(runner->test_runner, + &test_runner_listener, runner); + + return runner; +} + +static void +runner_destroy(struct runner *runner) +{ + weston_test_runner_destroy(runner->test_runner); + client_roundtrip(runner->client); + free(runner); +} + +static void +runner_run(struct runner *runner, const char *test_name) +{ + testlog("weston_test_runner.run(\"%s\")\n", test_name); + + runner->done = 0; + weston_test_runner_run(runner->test_runner, test_name); + + while (!runner->done) { + if (wl_display_dispatch(runner->client->wl_display) < 0) + assert(0 && "runner wait"); + } +} + +static struct ivi_application * +get_ivi_application(struct client *client) +{ + struct global *g; + struct global *global_iviapp = NULL; + struct ivi_application *iviapp; + + wl_list_for_each(g, &client->global_list, link) { + if (strcmp(g->interface, "ivi_application")) + continue; + + if (global_iviapp) + assert(0 && "multiple ivi_application objects"); + + global_iviapp = g; + } + + assert(global_iviapp && "no ivi_application found"); + + assert(global_iviapp->version == 1); + + iviapp = wl_registry_bind(client->wl_registry, global_iviapp->name, + &ivi_application_interface, 1); + assert(iviapp); + + return iviapp; +} + +struct ivi_window { + struct wl_surface *wl_surface; + struct ivi_surface *ivi_surface; + uint32_t ivi_id; +}; + +static struct ivi_window * +client_create_ivi_window(struct client *client, + struct ivi_application *iviapp, + uint32_t ivi_id) +{ + struct ivi_window *wnd; + + wnd = xzalloc(sizeof(*wnd)); + wnd->wl_surface = wl_compositor_create_surface(client->wl_compositor); + wnd->ivi_surface = ivi_application_surface_create(iviapp, ivi_id, + wnd->wl_surface); + wnd->ivi_id = ivi_id; + + return wnd; +} + +static void +ivi_window_destroy(struct ivi_window *wnd) +{ + ivi_surface_destroy(wnd->ivi_surface); + wl_surface_destroy(wnd->wl_surface); + free(wnd); +} + +/******************************** tests ********************************/ + +/* + * These tests make use of weston_test_runner global interface exposed by + * ivi-layout-test-plugin.c. This allows these tests to trigger compositor-side + * checks. + * + * See ivi-layout-test-plugin.c for further details. + */ + +/** + * RUNNER_TEST() names are defined in ivi-layout-test-plugin.c. + * Each RUNNER_TEST name listed here uses the same simple initial client setup. + */ +const char * const basic_test_names[] = { + "surface_visibility", + "surface_opacity", + "surface_dimension", + "surface_position", + "surface_destination_rectangle", + "surface_source_rectangle", + "surface_bad_opacity", + "surface_properties_changed_notification", + "surface_bad_properties_changed_notification", + "surface_on_many_layer", +}; + +const char * const surface_property_commit_changes_test_names[] = { + "commit_changes_after_visibility_set_surface_destroy", + "commit_changes_after_opacity_set_surface_destroy", + "commit_changes_after_source_rectangle_set_surface_destroy", + "commit_changes_after_destination_rectangle_set_surface_destroy", +}; + +const char * const render_order_test_names[] = { + "layer_render_order", + "layer_bad_render_order", + "layer_add_surfaces", +}; + +TEST_P(ivi_layout_runner, basic_test_names) +{ + /* an element from basic_test_names */ + const char * const *test_name = data; + struct client *client; + struct runner *runner; + struct ivi_application *iviapp; + struct ivi_window *wnd; + + client = create_client(); + runner = client_create_runner(client); + iviapp = get_ivi_application(client); + + wnd = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); + + runner_run(runner, *test_name); + + ivi_window_destroy(wnd); + runner_destroy(runner); +} + +TEST(ivi_layout_surface_create) +{ + struct client *client; + struct runner *runner; + struct ivi_application *iviapp; + struct ivi_window *winds[2]; + + client = create_client(); + runner = client_create_runner(client); + iviapp = get_ivi_application(client); + + winds[0] = client_create_ivi_window(client, iviapp, + IVI_TEST_SURFACE_ID(0)); + winds[1] = client_create_ivi_window(client, iviapp, + IVI_TEST_SURFACE_ID(1)); + + runner_run(runner, "surface_create_p1"); + + ivi_window_destroy(winds[0]); + + runner_run(runner, "surface_create_p2"); + + ivi_window_destroy(winds[1]); + runner_destroy(runner); +} + +TEST_P(commit_changes_after_properties_set_surface_destroy, surface_property_commit_changes_test_names) +{ + /* an element from surface_property_commit_changes_test_names */ + const char * const *test_name = data; + struct client *client; + struct runner *runner; + struct ivi_application *iviapp; + struct ivi_window *wnd; + + client = create_client(); + runner = client_create_runner(client); + iviapp = get_ivi_application(client); + + wnd = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); + + runner_run(runner, *test_name); + + ivi_window_destroy(wnd); + + runner_run(runner, "ivi_layout_commit_changes"); + + runner_destroy(runner); +} + +TEST(get_surface_after_destroy_ivi_surface) +{ + struct client *client; + struct runner *runner; + struct ivi_application *iviapp; + struct ivi_window *wnd; + + client = create_client(); + runner = client_create_runner(client); + iviapp = get_ivi_application(client); + + wnd = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); + + ivi_surface_destroy(wnd->ivi_surface); + + runner_run(runner, "get_surface_after_destroy_surface"); + + wl_surface_destroy(wnd->wl_surface); + free(wnd); + runner_destroy(runner); +} + +TEST(get_surface_after_destroy_wl_surface) +{ + struct client *client; + struct runner *runner; + struct ivi_application *iviapp; + struct ivi_window *wnd; + + client = create_client(); + runner = client_create_runner(client); + iviapp = get_ivi_application(client); + + wnd = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); + + wl_surface_destroy(wnd->wl_surface); + + runner_run(runner, "get_surface_after_destroy_surface"); + + ivi_surface_destroy(wnd->ivi_surface); + free(wnd); + runner_destroy(runner); +} + +TEST_P(ivi_layout_layer_render_order_runner, render_order_test_names) +{ + /* an element from render_order_test_names */ + const char * const *test_name = data; + struct client *client; + struct runner *runner; + struct ivi_application *iviapp; + struct ivi_window *winds[3]; + + client = create_client(); + runner = client_create_runner(client); + iviapp = get_ivi_application(client); + + winds[0] = client_create_ivi_window(client, iviapp, + IVI_TEST_SURFACE_ID(0)); + winds[1] = client_create_ivi_window(client, iviapp, + IVI_TEST_SURFACE_ID(1)); + winds[2] = client_create_ivi_window(client, iviapp, + IVI_TEST_SURFACE_ID(2)); + + runner_run(runner, *test_name); + + ivi_window_destroy(winds[0]); + ivi_window_destroy(winds[1]); + ivi_window_destroy(winds[2]); + runner_destroy(runner); +} + +TEST(destroy_surface_after_layer_render_order) +{ + struct client *client; + struct runner *runner; + struct ivi_application *iviapp; + struct ivi_window *winds[3]; + + client = create_client(); + runner = client_create_runner(client); + iviapp = get_ivi_application(client); + + winds[0] = client_create_ivi_window(client, iviapp, + IVI_TEST_SURFACE_ID(0)); + winds[1] = client_create_ivi_window(client, iviapp, + IVI_TEST_SURFACE_ID(1)); + winds[2] = client_create_ivi_window(client, iviapp, + IVI_TEST_SURFACE_ID(2)); + + runner_run(runner, "test_layer_render_order_destroy_one_surface_p1"); + + ivi_window_destroy(winds[1]); + + runner_run(runner, "test_layer_render_order_destroy_one_surface_p2"); + + ivi_window_destroy(winds[0]); + ivi_window_destroy(winds[2]); + runner_destroy(runner); +} + +TEST(commit_changes_after_render_order_set_surface_destroy) +{ + struct client *client; + struct runner *runner; + struct ivi_application *iviapp; + struct ivi_window *winds[3]; + + client = create_client(); + runner = client_create_runner(client); + iviapp = get_ivi_application(client); + + winds[0] = client_create_ivi_window(client, iviapp, + IVI_TEST_SURFACE_ID(0)); + winds[1] = client_create_ivi_window(client, iviapp, + IVI_TEST_SURFACE_ID(1)); + winds[2] = client_create_ivi_window(client, iviapp, + IVI_TEST_SURFACE_ID(2)); + + runner_run(runner, "commit_changes_after_render_order_set_surface_destroy"); + + ivi_window_destroy(winds[1]); + + runner_run(runner, "ivi_layout_commit_changes"); + runner_run(runner, "cleanup_layer"); + + ivi_window_destroy(winds[0]); + ivi_window_destroy(winds[2]); + runner_destroy(runner); +} + +TEST(ivi_layout_surface_configure_notification) +{ + struct client *client; + struct runner *runner; + struct ivi_application *iviapp; + struct ivi_window *wind; + struct buffer *buffer; + + client = create_client(); + runner = client_create_runner(client); + iviapp = get_ivi_application(client); + + runner_run(runner, "surface_configure_notification_p1"); + + wind = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); + + buffer = create_shm_buffer_a8r8g8b8(client, 200, 300); + + wl_surface_attach(wind->wl_surface, buffer->proxy, 0, 0); + wl_surface_damage(wind->wl_surface, 0, 0, 20, 30); + wl_surface_commit(wind->wl_surface); + + runner_run(runner, "surface_configure_notification_p2"); + + wl_surface_attach(wind->wl_surface, buffer->proxy, 0, 0); + wl_surface_damage(wind->wl_surface, 0, 0, 40, 50); + wl_surface_commit(wind->wl_surface); + + runner_run(runner, "surface_configure_notification_p3"); + + buffer_destroy(buffer); + ivi_window_destroy(wind); + runner_destroy(runner); +} + +TEST(ivi_layout_surface_create_notification) +{ + struct client *client; + struct runner *runner; + struct ivi_application *iviapp; + struct ivi_window *wind; + + client = create_client(); + runner = client_create_runner(client); + iviapp = get_ivi_application(client); + + runner_run(runner, "surface_create_notification_p1"); + + wind = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); + + runner_run(runner, "surface_create_notification_p2"); + + ivi_window_destroy(wind); + wind = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); + runner_run(runner, "surface_create_notification_p3"); + + ivi_window_destroy(wind); + runner_destroy(runner); +} + +TEST(ivi_layout_surface_remove_notification) +{ + struct client *client; + struct runner *runner; + struct ivi_application *iviapp; + struct ivi_window *wind; + + client = create_client(); + runner = client_create_runner(client); + iviapp = get_ivi_application(client); + + wind = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); + runner_run(runner, "surface_remove_notification_p1"); + ivi_window_destroy(wind); + + runner_run(runner, "surface_remove_notification_p2"); + + wind = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); + ivi_window_destroy(wind); + runner_run(runner, "surface_remove_notification_p3"); + + runner_destroy(runner); +} diff --git a/tests/ivi-layout-test-plugin.c b/tests/ivi-layout-test-plugin.c new file mode 100644 index 0000000..cad2fa5 --- /dev/null +++ b/tests/ivi-layout-test-plugin.c @@ -0,0 +1,1018 @@ +/* + * Copyright © 2012 Intel Corporation + * Copyright © 2013 DENSO CORPORATION + * Copyright © 2015 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "compositor/weston.h" +#include "weston-test-server-protocol.h" +#include "ivi-test.h" +#include "ivi-shell/ivi-layout-export.h" +#include "shared/helpers.h" + +struct test_context; + +struct runner_test { + const char *name; + void (*run)(struct test_context *); +} __attribute__ ((aligned (32))); + +#define RUNNER_TEST(name) \ + static void runner_func_##name(struct test_context *); \ + \ + const struct runner_test runner_test_##name \ + __attribute__ ((section ("plugin_test_section"))) = \ + { \ + #name, runner_func_##name \ + }; \ + \ + static void runner_func_##name(struct test_context *ctx) + +extern const struct runner_test __start_plugin_test_section; +extern const struct runner_test __stop_plugin_test_section; + +static const struct runner_test * +find_runner_test(const char *name) +{ + const struct runner_test *t; + + for (t = &__start_plugin_test_section; + t < &__stop_plugin_test_section; t++) { + if (strcmp(t->name, name) == 0) + return t; + } + + return NULL; +} + +struct test_context { + const struct ivi_layout_interface *layout_interface; + struct wl_resource *runner_resource; + uint32_t user_flags; + + struct wl_listener surface_property_changed; + struct wl_listener surface_created; + struct wl_listener surface_removed; + struct wl_listener surface_configured; +}; + +struct test_launcher { + struct weston_compositor *compositor; + struct test_context context; + const struct ivi_layout_interface *layout_interface; +}; + +static void +destroy_runner(struct wl_resource *resource) +{ + struct test_launcher *launcher = wl_resource_get_user_data(resource); + struct test_context *ctx = &launcher->context; + + assert(ctx->runner_resource == NULL || + ctx->runner_resource == resource); + + ctx->layout_interface = NULL; + ctx->runner_resource = NULL; +} + +static void +runner_destroy_handler(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +runner_run_handler(struct wl_client *client, struct wl_resource *resource, + const char *test_name) +{ + struct test_launcher *launcher; + const struct runner_test *t; + struct test_context *ctx; + + launcher = wl_resource_get_user_data(resource); + ctx = &launcher->context; + + assert(ctx->runner_resource == NULL || + ctx->runner_resource == resource); + + ctx->layout_interface = launcher->layout_interface; + ctx->runner_resource = resource; + + t = find_runner_test(test_name); + if (!t) { + weston_log("Error: runner test \"%s\" not found.\n", + test_name); + wl_resource_post_error(resource, + WESTON_TEST_RUNNER_ERROR_UNKNOWN_TEST, + "weston_test_runner: unknown: '%s'", + test_name); + return; + } + + weston_log("weston_test_runner.run(\"%s\")\n", test_name); + + t->run(ctx); + + weston_test_runner_send_finished(resource); +} + +static const struct weston_test_runner_interface runner_implementation = { + runner_destroy_handler, + runner_run_handler +}; + +static void +bind_runner(struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct test_launcher *launcher = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, &weston_test_runner_interface, + 1, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &runner_implementation, + launcher, destroy_runner); + + if (launcher->context.runner_resource != NULL) { + weston_log("test FATAL: " + "attempting to run several tests in parallel.\n"); + wl_resource_post_error(resource, + WESTON_TEST_RUNNER_ERROR_TEST_FAILED, + "attempt to run parallel tests"); + } +} + +WL_EXPORT int +wet_module_init(struct weston_compositor *compositor, + int *argc, char *argv[]) +{ + struct test_launcher *launcher; + const struct ivi_layout_interface *iface; + + iface = ivi_layout_get_api(compositor); + + if (!iface) { + weston_log("fatal: cannot use ivi_layout_interface.\n"); + return -1; + } + + launcher = zalloc(sizeof *launcher); + if (!launcher) + return -1; + + launcher->compositor = compositor; + launcher->layout_interface = iface; + + if (wl_global_create(compositor->wl_display, + &weston_test_runner_interface, 1, + launcher, bind_runner) == NULL) + return -1; + + return 0; +} + +static void +runner_assert_fail(const char *cond, const char *file, int line, + const char *func, struct test_context *ctx) +{ + weston_log("Assert failure in %s:%d, %s: '%s'\n", + file, line, func, cond); + + assert(ctx->runner_resource); + wl_resource_post_error(ctx->runner_resource, + WESTON_TEST_RUNNER_ERROR_TEST_FAILED, + "Assert failure in %s:%d, %s: '%s'\n", + file, line, func, cond); +} + +#define runner_assert(cond) ({ \ + bool b_ = (cond); \ + if (!b_) \ + runner_assert_fail(#cond, __FILE__, __LINE__, \ + __func__, ctx); \ + b_; \ +}) + +#define runner_assert_or_return(cond) do { \ + bool b_ = (cond); \ + if (!b_) { \ + runner_assert_fail(#cond, __FILE__, __LINE__, \ + __func__, ctx); \ + return; \ + } \ +} while (0) + + +/*************************** tests **********************************/ + +/* + * This is a IVI controller module requiring ivi-shell.so. + * This module is specially written to execute tests that target the + * ivi_layout API. + * + * The test program containing the fixture setup and initiating the tests is + * test-ivi-layout-client (ivi-layout-test-client.c). + * That program uses the weston-test-runner framework to execute each TEST() + * in ivi-layout-test-client.c with a fresh connection to the single + * compositor instance. + * + * Each TEST() in ivi-layout-test-client.c will bind to weston_test_runner global + * interface. A TEST() will set up the client state, and issue + * weston_test_runner.run request to execute the compositor-side of the test. + * + * The compositor-side parts of the tests are in this file. They are specified + * by RUNNER_TEST() macro, where the name argument matches the name string + * passed to weston_test_runner.run. + * + * A RUNNER_TEST() function simply returns when it succeeds. If it fails, + * a fatal protocol error is sent to the client from runner_assert() or + * runner_assert_or_return(). + * + * A single TEST() in ivi-layout-test-client.c may use multiple RUNNER_TEST()s to + * achieve multiple test points over a client action sequence. + */ + +RUNNER_TEST(surface_create_p1) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf[2]; + uint32_t ivi_id; + + ivisurf[0] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + runner_assert(ivisurf[0]); + + ivisurf[1] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(1)); + runner_assert(ivisurf[1]); + + ivi_id = lyt->get_id_of_surface(ivisurf[0]); + runner_assert(ivi_id == IVI_TEST_SURFACE_ID(0)); + + ivi_id = lyt->get_id_of_surface(ivisurf[1]); + runner_assert(ivi_id == IVI_TEST_SURFACE_ID(1)); +} + +RUNNER_TEST(surface_create_p2) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf; + + /* the ivi_surface was destroyed by the client */ + ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + runner_assert(ivisurf == NULL); +} + +RUNNER_TEST(surface_visibility) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf; + int32_t ret; + const struct ivi_layout_surface_properties *prop; + + ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + runner_assert(ivisurf); + + ret = lyt->surface_set_visibility(ivisurf, true); + runner_assert(ret == IVI_SUCCEEDED); + + lyt->commit_changes(); + + prop = lyt->get_properties_of_surface(ivisurf); + runner_assert(prop->visibility == true); +} + +RUNNER_TEST(surface_opacity) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf; + int32_t ret; + const struct ivi_layout_surface_properties *prop; + + ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + runner_assert(ivisurf); + + prop = lyt->get_properties_of_surface(ivisurf); + runner_assert(prop->opacity == wl_fixed_from_double(1.0)); + + ret = lyt->surface_set_opacity(ivisurf, wl_fixed_from_double(0.5)); + runner_assert(ret == IVI_SUCCEEDED); + + runner_assert(prop->opacity == wl_fixed_from_double(1.0)); + + lyt->commit_changes(); + + runner_assert(prop->opacity == wl_fixed_from_double(0.5)); +} + +RUNNER_TEST(surface_dimension) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf; + const struct ivi_layout_surface_properties *prop; + + ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + runner_assert(ivisurf != NULL); + + prop = lyt->get_properties_of_surface(ivisurf); + runner_assert_or_return(prop); + runner_assert(prop->dest_width == 1); + runner_assert(prop->dest_height == 1); + + runner_assert(IVI_SUCCEEDED == + lyt->surface_set_destination_rectangle(ivisurf, prop->dest_x, + prop->dest_y, 200, 300)); + + runner_assert(prop->dest_width == 1); + runner_assert(prop->dest_height == 1); + + lyt->commit_changes(); + + prop = lyt->get_properties_of_surface(ivisurf); + runner_assert_or_return(prop); + runner_assert(prop->dest_width == 200); + runner_assert(prop->dest_height == 300); +} + +RUNNER_TEST(surface_position) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf; + const struct ivi_layout_surface_properties *prop; + + ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + runner_assert(ivisurf != NULL); + + prop = lyt->get_properties_of_surface(ivisurf); + runner_assert_or_return(prop); + runner_assert(prop->dest_x == 0); + runner_assert(prop->dest_y == 0); + + runner_assert(lyt->surface_set_destination_rectangle( + ivisurf, 20, 30, + prop->dest_width, prop->dest_height) == IVI_SUCCEEDED); + + runner_assert(prop->dest_x == 0); + runner_assert(prop->dest_y == 0); + + lyt->commit_changes(); + + prop = lyt->get_properties_of_surface(ivisurf); + runner_assert_or_return(prop); + runner_assert(prop->dest_x == 20); + runner_assert(prop->dest_y == 30); +} + +RUNNER_TEST(surface_destination_rectangle) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf; + const struct ivi_layout_surface_properties *prop; + + ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + runner_assert(ivisurf != NULL); + + prop = lyt->get_properties_of_surface(ivisurf); + runner_assert_or_return(prop); + runner_assert(prop->dest_width == 1); + runner_assert(prop->dest_height == 1); + runner_assert(prop->dest_x == 0); + runner_assert(prop->dest_y == 0); + + runner_assert(lyt->surface_set_destination_rectangle( + ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); + + prop = lyt->get_properties_of_surface(ivisurf); + runner_assert_or_return(prop); + runner_assert(prop->dest_width == 1); + runner_assert(prop->dest_height == 1); + runner_assert(prop->dest_x == 0); + runner_assert(prop->dest_y == 0); + + lyt->commit_changes(); + + prop = lyt->get_properties_of_surface(ivisurf); + runner_assert_or_return(prop); + runner_assert(prop->dest_width == 200); + runner_assert(prop->dest_height == 300); + runner_assert(prop->dest_x == 20); + runner_assert(prop->dest_y == 30); +} + +RUNNER_TEST(surface_source_rectangle) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf; + const struct ivi_layout_surface_properties *prop; + + ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + runner_assert(ivisurf != NULL); + + prop = lyt->get_properties_of_surface(ivisurf); + runner_assert_or_return(prop); + runner_assert(prop->source_width == 0); + runner_assert(prop->source_height == 0); + runner_assert(prop->source_x == 0); + runner_assert(prop->source_y == 0); + + runner_assert(lyt->surface_set_source_rectangle( + ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); + + prop = lyt->get_properties_of_surface(ivisurf); + runner_assert_or_return(prop); + runner_assert(prop->source_width == 0); + runner_assert(prop->source_height == 0); + runner_assert(prop->source_x == 0); + runner_assert(prop->source_y == 0); + + lyt->commit_changes(); + + prop = lyt->get_properties_of_surface(ivisurf); + runner_assert_or_return(prop); + runner_assert(prop->source_width == 200); + runner_assert(prop->source_height == 300); + runner_assert(prop->source_x == 20); + runner_assert(prop->source_y == 30); +} + +RUNNER_TEST(surface_bad_opacity) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf; + const struct ivi_layout_surface_properties *prop; + + ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + runner_assert(ivisurf != NULL); + + runner_assert(lyt->surface_set_opacity( + NULL, wl_fixed_from_double(0.3)) == IVI_FAILED); + + runner_assert(lyt->surface_set_opacity( + ivisurf, wl_fixed_from_double(0.3)) == IVI_SUCCEEDED); + + runner_assert(lyt->surface_set_opacity( + ivisurf, wl_fixed_from_double(-1)) == IVI_FAILED); + + lyt->commit_changes(); + + prop = lyt->get_properties_of_surface(ivisurf); + runner_assert(prop->opacity == wl_fixed_from_double(0.3)); + + runner_assert(lyt->surface_set_opacity( + ivisurf, wl_fixed_from_double(1.1)) == IVI_FAILED); + + lyt->commit_changes(); + + runner_assert(prop->opacity == wl_fixed_from_double(0.3)); + + runner_assert(lyt->surface_set_opacity( + NULL, wl_fixed_from_double(0.5)) == IVI_FAILED); + + lyt->commit_changes(); +} + +RUNNER_TEST(surface_on_many_layer) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf; + struct ivi_layout_layer *ivilayers[IVI_TEST_LAYER_COUNT] = {}; + struct ivi_layout_layer **array; + int32_t length = 0; + uint32_t i; + + ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + runner_assert(ivisurf != NULL); + + for (i = 0; i < IVI_TEST_LAYER_COUNT; i++) { + ivilayers[i] = lyt->layer_create_with_dimension( + IVI_TEST_LAYER_ID(i), 200, 300); + runner_assert(lyt->layer_add_surface( + ivilayers[i], ivisurf) == IVI_SUCCEEDED); + } + + lyt->commit_changes(); + + runner_assert(lyt->get_layers_under_surface( + ivisurf, &length, &array) == IVI_SUCCEEDED); + runner_assert(IVI_TEST_LAYER_COUNT == length); + for (i = 0; i < IVI_TEST_LAYER_COUNT; i++) + runner_assert(array[i] == ivilayers[i]); + + if (length > 0) + free(array); + + for (i = 0; i < IVI_TEST_LAYER_COUNT; i++) + lyt->layer_remove_surface(ivilayers[i], ivisurf); + + array = NULL; + + lyt->commit_changes(); + + runner_assert(lyt->get_layers_under_surface( + ivisurf, &length, &array) == IVI_SUCCEEDED); + runner_assert(length == 0 && array == NULL); + + for (i = 0; i < IVI_TEST_LAYER_COUNT; i++) + lyt->layer_destroy(ivilayers[i]); +} + +RUNNER_TEST(ivi_layout_commit_changes) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + lyt->commit_changes(); +} + +RUNNER_TEST(commit_changes_after_visibility_set_surface_destroy) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf; + + ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + runner_assert(ivisurf != NULL); + runner_assert(lyt->surface_set_visibility( + ivisurf, true) == IVI_SUCCEEDED); +} + +RUNNER_TEST(commit_changes_after_opacity_set_surface_destroy) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf; + + ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + runner_assert(ivisurf != NULL); + runner_assert(lyt->surface_set_opacity( + ivisurf, wl_fixed_from_double(0.5)) == IVI_SUCCEEDED); +} + +RUNNER_TEST(commit_changes_after_source_rectangle_set_surface_destroy) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf; + + ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + runner_assert(ivisurf != NULL); + runner_assert(lyt->surface_set_source_rectangle( + ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); +} + +RUNNER_TEST(commit_changes_after_destination_rectangle_set_surface_destroy) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf; + + ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + runner_assert(ivisurf != NULL); + runner_assert(lyt->surface_set_destination_rectangle( + ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); +} + +RUNNER_TEST(get_surface_after_destroy_surface) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf; + + ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + runner_assert(ivisurf == NULL); +} + +RUNNER_TEST(layer_render_order) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + struct ivi_layout_surface *ivisurfs[IVI_TEST_SURFACE_COUNT] = {}; + struct ivi_layout_surface **array; + int32_t length = 0; + uint32_t i; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + + for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) + ivisurfs[i] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(i)); + + runner_assert(lyt->layer_set_render_order( + ivilayer, ivisurfs, IVI_TEST_SURFACE_COUNT) == IVI_SUCCEEDED); + + lyt->commit_changes(); + + runner_assert(lyt->get_surfaces_on_layer( + ivilayer, &length, &array) == IVI_SUCCEEDED); + runner_assert(IVI_TEST_SURFACE_COUNT == length); + for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) + runner_assert(array[i] == ivisurfs[i]); + + if (length > 0) + free(array); + + runner_assert(lyt->layer_set_render_order( + ivilayer, NULL, 0) == IVI_SUCCEEDED); + + array = NULL; + + lyt->commit_changes(); + + runner_assert(lyt->get_surfaces_on_layer( + ivilayer, &length, &array) == IVI_SUCCEEDED); + runner_assert(length == 0 && array == NULL); + + lyt->layer_destroy(ivilayer); +} + +RUNNER_TEST(test_layer_render_order_destroy_one_surface_p1) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + struct ivi_layout_surface *ivisurfs[IVI_TEST_SURFACE_COUNT] = {}; + struct ivi_layout_surface **array; + int32_t length = 0; + int32_t i; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + + for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) + ivisurfs[i] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(i)); + + runner_assert(lyt->layer_set_render_order( + ivilayer, ivisurfs, IVI_TEST_SURFACE_COUNT) == IVI_SUCCEEDED); + + lyt->commit_changes(); + + runner_assert(lyt->get_surfaces_on_layer( + ivilayer, &length, &array) == IVI_SUCCEEDED); + runner_assert(IVI_TEST_SURFACE_COUNT == length); + for (i = 0; i < length; i++) + runner_assert(array[i] == ivisurfs[i]); + + if (length > 0) + free(array); +} + +RUNNER_TEST(test_layer_render_order_destroy_one_surface_p2) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + struct ivi_layout_surface *ivisurfs[2] = {}; + struct ivi_layout_surface **array; + int32_t length = 0; + int32_t i; + + ivilayer = lyt->get_layer_from_id(IVI_TEST_LAYER_ID(0)); + ivisurfs[0] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + ivisurfs[1] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(2)); + + runner_assert(lyt->get_surfaces_on_layer( + ivilayer, &length, &array) == IVI_SUCCEEDED); + runner_assert(2 == length); + for (i = 0; i < length; i++) + runner_assert(array[i] == ivisurfs[i]); + + if (length > 0) + free(array); + + lyt->layer_destroy(ivilayer); +} + +RUNNER_TEST(layer_bad_render_order) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + struct ivi_layout_surface *ivisurfs[IVI_TEST_SURFACE_COUNT] = {}; + struct ivi_layout_surface **array = NULL; + int32_t length = 0; + uint32_t i; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + + for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) + ivisurfs[i] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(i)); + + runner_assert(lyt->layer_set_render_order( + NULL, ivisurfs, IVI_TEST_SURFACE_COUNT) == IVI_FAILED); + + lyt->commit_changes(); + + runner_assert(lyt->get_surfaces_on_layer( + NULL, &length, &array) == IVI_FAILED); + runner_assert(length == 0 && array == NULL); + + runner_assert(lyt->get_surfaces_on_layer( + ivilayer, NULL, &array) == IVI_FAILED); + runner_assert(array == NULL); + + runner_assert(lyt->get_surfaces_on_layer( + ivilayer, &length, NULL) == IVI_FAILED); + runner_assert(length == 0); + + lyt->layer_destroy(ivilayer); +} + +RUNNER_TEST(layer_add_surfaces) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + struct ivi_layout_surface *ivisurfs[IVI_TEST_SURFACE_COUNT] = {}; + struct ivi_layout_surface **array; + int32_t length = 0; + uint32_t i; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + + for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) { + ivisurfs[i] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(i)); + runner_assert(lyt->layer_add_surface( + ivilayer, ivisurfs[i]) == IVI_SUCCEEDED); + } + + lyt->commit_changes(); + + runner_assert(lyt->get_surfaces_on_layer( + ivilayer, &length, &array) == IVI_SUCCEEDED); + runner_assert(IVI_TEST_SURFACE_COUNT == length); + for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) + runner_assert(array[i] == ivisurfs[i]); + + if (length > 0) + free(array); + + runner_assert(lyt->layer_set_render_order( + ivilayer, NULL, 0) == IVI_SUCCEEDED); + + for (i = IVI_TEST_SURFACE_COUNT; i-- > 0;) + runner_assert(lyt->layer_add_surface( + ivilayer, ivisurfs[i]) == IVI_SUCCEEDED); + + lyt->commit_changes(); + + runner_assert(lyt->get_surfaces_on_layer( + ivilayer, &length, &array) == IVI_SUCCEEDED); + runner_assert(IVI_TEST_SURFACE_COUNT == length); + for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) + runner_assert(array[i] == ivisurfs[IVI_TEST_SURFACE_COUNT - (i + 1)]); + + if (length > 0) + free(array); + + lyt->layer_destroy(ivilayer); +} + +RUNNER_TEST(commit_changes_after_render_order_set_surface_destroy) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + struct ivi_layout_surface *ivisurfs[IVI_TEST_SURFACE_COUNT] = {}; + int i; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + + for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) + ivisurfs[i] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(i)); + + runner_assert(lyt->layer_set_render_order( + ivilayer, ivisurfs, IVI_TEST_SURFACE_COUNT) == IVI_SUCCEEDED); +} + +RUNNER_TEST(cleanup_layer) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + + ivilayer = lyt->get_layer_from_id(IVI_TEST_LAYER_ID(0)); + lyt->layer_destroy(ivilayer); +} + +static void +test_surface_properties_changed_notification_callback(struct wl_listener *listener, void *data) + +{ + struct test_context *ctx = + container_of(listener, struct test_context, + surface_property_changed); + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf = data; + + runner_assert_or_return(lyt->get_id_of_surface(ivisurf) == IVI_TEST_SURFACE_ID(0)); + + ctx->user_flags = 1; +} + +RUNNER_TEST(surface_properties_changed_notification) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + const uint32_t id_surface = IVI_TEST_SURFACE_ID(0); + struct ivi_layout_surface *ivisurf; + + ctx->user_flags = 0; + + ivisurf = lyt->get_surface_from_id(id_surface); + runner_assert(ivisurf != NULL); + + ctx->surface_property_changed.notify = test_surface_properties_changed_notification_callback; + + runner_assert(lyt->surface_add_listener( + ivisurf, &ctx->surface_property_changed) == IVI_SUCCEEDED); + + lyt->commit_changes(); + + runner_assert(ctx->user_flags == 0); + + runner_assert(lyt->surface_set_destination_rectangle( + ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); + + lyt->commit_changes(); + + runner_assert(ctx->user_flags == 1); + + ctx->user_flags = 0; + runner_assert(lyt->surface_set_destination_rectangle( + ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); + + lyt->commit_changes(); + + runner_assert(ctx->user_flags == 0); + + // remove surface property changed listener. + wl_list_remove(&ctx->surface_property_changed.link); + ctx->user_flags = 0; + runner_assert(lyt->surface_set_destination_rectangle( + ivisurf, 40, 50, 400, 500) == IVI_SUCCEEDED); + + lyt->commit_changes(); + + runner_assert(ctx->user_flags == 0); +} + +static void +test_surface_configure_notification_callback(struct wl_listener *listener, void *data) +{ + struct test_context *ctx = + container_of(listener, struct test_context, + surface_configured); + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf = data; + + runner_assert_or_return(lyt->get_id_of_surface(ivisurf) == IVI_TEST_SURFACE_ID(0)); + + ctx->user_flags = 1; +} + +RUNNER_TEST(surface_configure_notification_p1) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + ctx->surface_configured.notify = test_surface_configure_notification_callback; + runner_assert(IVI_SUCCEEDED == lyt->add_listener_configure_surface(&ctx->surface_configured)); + lyt->commit_changes(); + + ctx->user_flags = 0; +} + +RUNNER_TEST(surface_configure_notification_p2) +{ + runner_assert(ctx->user_flags == 1); + + // remove surface configured listener. + wl_list_remove(&ctx->surface_configured.link); + ctx->user_flags = 0; +} + +RUNNER_TEST(surface_configure_notification_p3) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + lyt->commit_changes(); + runner_assert(ctx->user_flags == 0); +} + +static void +test_surface_create_notification_callback(struct wl_listener *listener, void *data) +{ + struct test_context *ctx = + container_of(listener, struct test_context, + surface_created); + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf = data; + + runner_assert_or_return(lyt->get_id_of_surface(ivisurf) == IVI_TEST_SURFACE_ID(0)); + + ctx->user_flags = 1; +} + +RUNNER_TEST(surface_create_notification_p1) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + ctx->surface_created.notify = test_surface_create_notification_callback; + runner_assert(lyt->add_listener_create_surface( + &ctx->surface_created) == IVI_SUCCEEDED); + + ctx->user_flags = 0; +} + +RUNNER_TEST(surface_create_notification_p2) +{ + runner_assert(ctx->user_flags == 1); + + // remove surface created listener. + wl_list_remove(&ctx->surface_created.link); + ctx->user_flags = 0; +} + +RUNNER_TEST(surface_create_notification_p3) +{ + runner_assert(ctx->user_flags == 0); +} + +static void +test_surface_remove_notification_callback(struct wl_listener *listener, void *data) +{ + struct test_context *ctx = + container_of(listener, struct test_context, + surface_removed); + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf = data; + + runner_assert_or_return(lyt->get_id_of_surface(ivisurf) == IVI_TEST_SURFACE_ID(0)); + + ctx->user_flags = 1; +} + +RUNNER_TEST(surface_remove_notification_p1) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + + ctx->surface_removed.notify = test_surface_remove_notification_callback; + runner_assert(lyt->add_listener_remove_surface(&ctx->surface_removed) + == IVI_SUCCEEDED); + + ctx->user_flags = 0; +} + +RUNNER_TEST(surface_remove_notification_p2) +{ + runner_assert(ctx->user_flags == 1); + + // remove surface removed listener. + wl_list_remove(&ctx->surface_removed.link); + ctx->user_flags = 0; +} + +RUNNER_TEST(surface_remove_notification_p3) +{ + runner_assert(ctx->user_flags == 0); +} + +static void +test_surface_bad_properties_changed_notification_callback(struct wl_listener *listener, void *data) +{ +} + +RUNNER_TEST(surface_bad_properties_changed_notification) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_surface *ivisurf; + + ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); + runner_assert(ivisurf != NULL); + + ctx->surface_property_changed.notify = test_surface_bad_properties_changed_notification_callback; + + runner_assert(lyt->surface_add_listener( + NULL, &ctx->surface_property_changed) == IVI_FAILED); + runner_assert(lyt->surface_add_listener( + ivisurf, NULL) == IVI_FAILED); +} diff --git a/tests/ivi-shell-app-test.c b/tests/ivi-shell-app-test.c new file mode 100644 index 0000000..5652c0d --- /dev/null +++ b/tests/ivi-shell-app-test.c @@ -0,0 +1,90 @@ +/* + * Copyright © 2015 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include "weston-test-client-helper.h" +#include "ivi-application-client-protocol.h" +#include "weston-test-fixture-compositor.h" +#include "test-config.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.shell = SHELL_IVI; + setup.config_file = TESTSUITE_IVI_CONFIG_PATH; + setup.logging_scopes = "log,test-harness-plugin,proto"; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +static struct ivi_application * +get_ivi_application(struct client *client) +{ + struct global *g; + struct global *global_iviapp = NULL; + struct ivi_application *iviapp; + + wl_list_for_each(g, &client->global_list, link) { + if (strcmp(g->interface, "ivi_application")) + continue; + + if (global_iviapp) + assert(0 && "multiple ivi_application objects"); + + global_iviapp = g; + } + + assert(global_iviapp && "no ivi_application found"); + + assert(global_iviapp->version == 1); + + iviapp = wl_registry_bind(client->wl_registry, global_iviapp->name, + &ivi_application_interface, 1); + assert(iviapp); + + return iviapp; +} + +TEST(ivi_application_exists) +{ + struct client *client; + struct ivi_application *iviapp; + + client = create_client(); + iviapp = get_ivi_application(client); + client_roundtrip(client); + + testlog("Successful bind: %p\n", iviapp); + + client_destroy(client); +} diff --git a/tests/ivi-test.h b/tests/ivi-test.h new file mode 100644 index 0000000..5706ed8 --- /dev/null +++ b/tests/ivi-test.h @@ -0,0 +1,42 @@ +/* + * Copyright © 2015 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef IVI_TEST_H +#define IVI_TEST_H + +/* + * IVI_TEST_SURFACE_ID_BASE is an arbitrary number picked for the + * IVI tests. The only requirement is that it does not clash with + * any other ivi-id range used in Weston. + */ +#define IVI_TEST_SURFACE_ID_BASE 0xffc01200 +#define IVI_TEST_SURFACE_ID(i) (IVI_TEST_SURFACE_ID_BASE + i) +#define IVI_TEST_LAYER_ID_BASE 0xeef01200 +#define IVI_TEST_LAYER_ID(i) (IVI_TEST_LAYER_ID_BASE + i) + +#define IVI_TEST_SURFACE_COUNT (3) +#define IVI_TEST_LAYER_COUNT (3) + +#endif /* IVI_TEST_H */ diff --git a/tests/keyboard-test.c b/tests/keyboard-test.c new file mode 100644 index 0000000..f2a769e --- /dev/null +++ b/tests/keyboard-test.c @@ -0,0 +1,169 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include + +#include "input-timestamps-helper.h" +#include "shared/timespec-util.h" +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +static const struct timespec t1 = { .tv_sec = 1, .tv_nsec = 1000001 }; +static const struct timespec t2 = { .tv_sec = 2, .tv_nsec = 2000001 }; +static const struct timespec t_other = { .tv_sec = 123, .tv_nsec = 456 }; + +static struct client * +create_client_with_keyboard_focus(void) +{ + struct client *cl = create_client_and_test_surface(10, 10, 1, 1); + assert(cl); + + weston_test_activate_surface(cl->test->weston_test, + cl->surface->wl_surface); + client_roundtrip(cl); + + return cl; +} + +static void +send_key(struct client *client, const struct timespec *time, + uint32_t key, uint32_t state) +{ + uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; + + timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + weston_test_send_key(client->test->weston_test, tv_sec_hi, tv_sec_lo, + tv_nsec, key, state); + client_roundtrip(client); +} + +TEST(simple_keyboard_test) +{ + struct client *client = create_client_with_keyboard_focus(); + struct keyboard *keyboard = client->input->keyboard; + struct surface *expect_focus = client->surface; + uint32_t expect_key = 0; + uint32_t expect_state = 0; + + 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; + send_key(client, &t1, expect_key, expect_state); + } else if (keyboard->focus) { + expect_focus = NULL; + weston_test_activate_surface( + client->test->weston_test, NULL); + client_roundtrip(client); + } else if (expect_key < 10) { + expect_key++; + expect_focus = client->surface; + expect_state = WL_KEYBOARD_KEY_STATE_PRESSED; + weston_test_activate_surface( + client->test->weston_test, + expect_focus->wl_surface); + send_key(client, &t1, expect_key, expect_state); + } else { + break; + } + } +} + +TEST(keyboard_key_event_time) +{ + struct client *client = create_client_with_keyboard_focus(); + struct keyboard *keyboard = client->input->keyboard; + struct input_timestamps *input_ts = + input_timestamps_create_for_keyboard(client); + + send_key(client, &t1, 1, WL_KEYBOARD_KEY_STATE_PRESSED); + assert(keyboard->key_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&keyboard->key_time_timespec, &t1)); + + send_key(client, &t2, 1, WL_KEYBOARD_KEY_STATE_RELEASED); + assert(keyboard->key_time_msec == timespec_to_msec(&t2)); + assert(timespec_eq(&keyboard->key_time_timespec, &t2)); + + input_timestamps_destroy(input_ts); +} + +TEST(keyboard_timestamps_stop_after_input_timestamps_object_is_destroyed) +{ + struct client *client = create_client_with_keyboard_focus(); + struct keyboard *keyboard = client->input->keyboard; + struct input_timestamps *input_ts = + input_timestamps_create_for_keyboard(client); + + send_key(client, &t1, 1, WL_KEYBOARD_KEY_STATE_PRESSED); + assert(keyboard->key_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&keyboard->key_time_timespec, &t1)); + + input_timestamps_destroy(input_ts); + + send_key(client, &t2, 1, WL_KEYBOARD_KEY_STATE_RELEASED); + assert(keyboard->key_time_msec == timespec_to_msec(&t2)); + assert(timespec_is_zero(&keyboard->key_time_timespec)); +} + +TEST(keyboard_timestamps_stop_after_client_releases_wl_keyboard) +{ + struct client *client = create_client_with_keyboard_focus(); + struct keyboard *keyboard = client->input->keyboard; + struct input_timestamps *input_ts = + input_timestamps_create_for_keyboard(client); + + send_key(client, &t1, 1, WL_KEYBOARD_KEY_STATE_PRESSED); + assert(keyboard->key_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&keyboard->key_time_timespec, &t1)); + + wl_keyboard_release(client->input->keyboard->wl_keyboard); + + /* Set input_timestamp to an arbitrary value (different from t1, t2 + * and 0) and check that it is not changed by sending the event. + * This is preferred over just checking for 0, since 0 is used + * internally for resetting the timestamp after handling an input + * event and checking for it here may lead to false negatives. */ + keyboard->input_timestamp = t_other; + send_key(client, &t2, 1, WL_KEYBOARD_KEY_STATE_RELEASED); + assert(timespec_eq(&keyboard->input_timestamp, &t_other)); + + input_timestamps_destroy(input_ts); +} diff --git a/tests/linux-explicit-synchronization-test.c b/tests/linux-explicit-synchronization-test.c new file mode 100644 index 0000000..4d4c24b --- /dev/null +++ b/tests/linux-explicit-synchronization-test.c @@ -0,0 +1,498 @@ +/* + * Copyright © 2018 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include "linux-explicit-synchronization-unstable-v1-client-protocol.h" +#include "weston-test-client-helper.h" +#include "wayland-server-protocol.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + /* We need to use the pixman renderer, since a few of the tests depend + * on the renderer holding onto a surface buffer until the next one + * is committed, which the noop renderer doesn't do. */ + setup.renderer = RENDERER_PIXMAN; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +static struct zwp_linux_explicit_synchronization_v1 * +get_linux_explicit_synchronization(struct client *client) +{ + struct global *g; + struct global *global_sync = NULL; + struct zwp_linux_explicit_synchronization_v1 *sync = NULL; + + wl_list_for_each(g, &client->global_list, link) { + if (strcmp(g->interface, + zwp_linux_explicit_synchronization_v1_interface.name)) + continue; + + if (global_sync) + assert(!"Multiple linux explicit sync objects"); + + global_sync = g; + } + + assert(global_sync); + assert(global_sync->version == 2); + + sync = wl_registry_bind( + client->wl_registry, global_sync->name, + &zwp_linux_explicit_synchronization_v1_interface, 2); + assert(sync); + + return sync; +} + +static struct client * +create_test_client(void) +{ + struct client *cl = create_client_and_test_surface(0, 0, 100, 100); + assert(cl); + return cl; +} + +TEST(second_surface_synchronization_on_surface_raises_error) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync1; + struct zwp_linux_surface_synchronization_v1 *surface_sync2; + + surface_sync1 = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + client_roundtrip(client); + + /* Second surface_synchronization creation should fail */ + surface_sync2 = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + expect_protocol_error( + client, + &zwp_linux_explicit_synchronization_v1_interface, + ZWP_LINUX_EXPLICIT_SYNCHRONIZATION_V1_ERROR_SYNCHRONIZATION_EXISTS); + + zwp_linux_surface_synchronization_v1_destroy(surface_sync2); + zwp_linux_surface_synchronization_v1_destroy(surface_sync1); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} + +TEST(set_acquire_fence_with_invalid_fence_raises_error) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + int pipefd[2] = { -1, -1 }; + + assert(pipe(pipefd) == 0); + + zwp_linux_surface_synchronization_v1_set_acquire_fence(surface_sync, + pipefd[0]); + expect_protocol_error( + client, + &zwp_linux_surface_synchronization_v1_interface, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_INVALID_FENCE); + + close(pipefd[0]); + close(pipefd[1]); + zwp_linux_surface_synchronization_v1_destroy(surface_sync); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} + +TEST(set_acquire_fence_on_destroyed_surface_raises_error) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + int pipefd[2] = { -1, -1 }; + + assert(pipe(pipefd) == 0); + + wl_surface_destroy(client->surface->wl_surface); + zwp_linux_surface_synchronization_v1_set_acquire_fence(surface_sync, + pipefd[0]); + expect_protocol_error( + client, + &zwp_linux_surface_synchronization_v1_interface, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_SURFACE); + + close(pipefd[0]); + close(pipefd[1]); + zwp_linux_surface_synchronization_v1_destroy(surface_sync); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} + +TEST(second_buffer_release_in_commit_raises_error) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + struct zwp_linux_buffer_release_v1 *buffer_release1; + struct zwp_linux_buffer_release_v1 *buffer_release2; + + buffer_release1 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + client_roundtrip(client); + + /* Second buffer_release creation should fail */ + buffer_release2 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + expect_protocol_error( + client, + &zwp_linux_surface_synchronization_v1_interface, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_DUPLICATE_RELEASE); + + zwp_linux_buffer_release_v1_destroy(buffer_release2); + zwp_linux_buffer_release_v1_destroy(buffer_release1); + zwp_linux_surface_synchronization_v1_destroy(surface_sync); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} + +TEST(get_release_without_buffer_raises_commit_error) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + struct wl_surface *surface = client->surface->wl_surface; + struct zwp_linux_buffer_release_v1 *buffer_release; + + buffer_release = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + wl_surface_commit(surface); + expect_protocol_error( + client, + &zwp_linux_surface_synchronization_v1_interface, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_BUFFER); + + zwp_linux_buffer_release_v1_destroy(buffer_release); + zwp_linux_surface_synchronization_v1_destroy(surface_sync); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} + +TEST(get_release_on_destroyed_surface_raises_error) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + struct zwp_linux_buffer_release_v1 *buffer_release; + + wl_surface_destroy(client->surface->wl_surface); + buffer_release = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + expect_protocol_error( + client, + &zwp_linux_surface_synchronization_v1_interface, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_SURFACE); + + zwp_linux_buffer_release_v1_destroy(buffer_release); + zwp_linux_surface_synchronization_v1_destroy(surface_sync); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} + +TEST(get_release_after_commit_succeeds) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct wl_surface *surface = client->surface->wl_surface; + struct zwp_linux_surface_synchronization_v1 *surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, surface); + struct buffer *buf1 = create_shm_buffer_a8r8g8b8(client, 100, 100); + struct zwp_linux_buffer_release_v1 *buffer_release1; + struct zwp_linux_buffer_release_v1 *buffer_release2; + + buffer_release1 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + client_roundtrip(client); + + wl_surface_attach(surface, buf1->proxy, 0, 0); + wl_surface_commit(surface); + + buffer_release2 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + client_roundtrip(client); + + buffer_destroy(buf1); + zwp_linux_buffer_release_v1_destroy(buffer_release2); + zwp_linux_buffer_release_v1_destroy(buffer_release1); + zwp_linux_surface_synchronization_v1_destroy(surface_sync); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} + +static void +buffer_release_fenced_handler(void *data, + struct zwp_linux_buffer_release_v1 *buffer_release, + int32_t fence) +{ + assert(!"Fenced release not supported yet"); +} + +static void +buffer_release_immediate_handler(void *data, + struct zwp_linux_buffer_release_v1 *buffer_release) +{ + int *released = data; + + *released += 1; +} + +struct zwp_linux_buffer_release_v1_listener buffer_release_listener = { + buffer_release_fenced_handler, + buffer_release_immediate_handler +}; + +/* The following release event tests depend on the behavior of the used + * backend, in this case the pixman backend. This doesn't limit their + * usefulness, though, since it allows them to check if, given a typical + * backend implementation, weston core supports the per commit nature of the + * release events. + */ + +TEST(get_release_events_are_emitted_for_different_buffers) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + struct buffer *buf1 = create_shm_buffer_a8r8g8b8(client, 100, 100); + struct buffer *buf2 = create_shm_buffer_a8r8g8b8(client, 100, 100); + struct wl_surface *surface = client->surface->wl_surface; + struct zwp_linux_buffer_release_v1 *buffer_release1; + struct zwp_linux_buffer_release_v1 *buffer_release2; + int buf_released1 = 0; + int buf_released2 = 0; + int frame; + + buffer_release1 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + zwp_linux_buffer_release_v1_add_listener(buffer_release1, + &buffer_release_listener, + &buf_released1); + wl_surface_attach(surface, buf1->proxy, 0, 0); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + /* No release event should have been emitted yet (we are using the + * pixman renderer, which holds buffers until they are replaced). */ + assert(buf_released1 == 0); + + buffer_release2 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + zwp_linux_buffer_release_v1_add_listener(buffer_release2, + &buffer_release_listener, + &buf_released2); + wl_surface_attach(surface, buf2->proxy, 0, 0); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + /* Check that exactly one buffer_release event was emitted for the + * previous commit (buf1). */ + assert(buf_released1 == 1); + assert(buf_released2 == 0); + + wl_surface_attach(surface, buf1->proxy, 0, 0); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + /* Check that exactly one buffer_release event was emitted for the + * previous commit (buf2). */ + assert(buf_released1 == 1); + assert(buf_released2 == 1); + + buffer_destroy(buf2); + buffer_destroy(buf1); + zwp_linux_buffer_release_v1_destroy(buffer_release2); + zwp_linux_buffer_release_v1_destroy(buffer_release1); + zwp_linux_surface_synchronization_v1_destroy(surface_sync); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} + +TEST(get_release_events_are_emitted_for_same_buffer_on_surface) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + struct buffer *buf = create_shm_buffer_a8r8g8b8(client, 100, 100); + struct wl_surface *surface = client->surface->wl_surface; + struct zwp_linux_buffer_release_v1 *buffer_release1; + struct zwp_linux_buffer_release_v1 *buffer_release2; + int buf_released1 = 0; + int buf_released2 = 0; + int frame; + + buffer_release1 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + zwp_linux_buffer_release_v1_add_listener(buffer_release1, + &buffer_release_listener, + &buf_released1); + wl_surface_attach(surface, buf->proxy, 0, 0); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + /* No release event should have been emitted yet (we are using the + * pixman renderer, which holds buffers until they are replaced). */ + assert(buf_released1 == 0); + + buffer_release2 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + zwp_linux_buffer_release_v1_add_listener(buffer_release2, + &buffer_release_listener, + &buf_released2); + wl_surface_attach(surface, buf->proxy, 0, 0); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + /* Check that exactly one buffer_release event was emitted for the + * previous commit (buf). */ + assert(buf_released1 == 1); + assert(buf_released2 == 0); + + wl_surface_attach(surface, buf->proxy, 0, 0); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + /* Check that exactly one buffer_release event was emitted for the + * previous commit (buf again). */ + assert(buf_released1 == 1); + assert(buf_released2 == 1); + + buffer_destroy(buf); + zwp_linux_buffer_release_v1_destroy(buffer_release2); + zwp_linux_buffer_release_v1_destroy(buffer_release1); + zwp_linux_surface_synchronization_v1_destroy(surface_sync); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} + +TEST(get_release_events_are_emitted_for_same_buffer_on_different_surfaces) +{ + struct client *client = create_test_client(); + struct surface *other_surface = create_test_surface(client); + struct wl_surface *surface1 = client->surface->wl_surface; + struct wl_surface *surface2 = other_surface->wl_surface; + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync1 = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, surface1); + struct zwp_linux_surface_synchronization_v1 *surface_sync2 = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, surface2); + struct buffer *buf1 = create_shm_buffer_a8r8g8b8(client, 100, 100); + struct buffer *buf2 = create_shm_buffer_a8r8g8b8(client, 100, 100); + struct zwp_linux_buffer_release_v1 *buffer_release1; + struct zwp_linux_buffer_release_v1 *buffer_release2; + int buf_released1 = 0; + int buf_released2 = 0; + int frame; + + weston_test_move_surface(client->test->weston_test, surface2, 0, 0); + + /* Attach buf1 to both surface1 and surface2. */ + buffer_release1 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync1); + zwp_linux_buffer_release_v1_add_listener(buffer_release1, + &buffer_release_listener, + &buf_released1); + wl_surface_attach(surface1, buf1->proxy, 0, 0); + frame_callback_set(surface1, &frame); + wl_surface_commit(surface1); + frame_callback_wait(client, &frame); + + buffer_release2 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync2); + zwp_linux_buffer_release_v1_add_listener(buffer_release2, + &buffer_release_listener, + &buf_released2); + wl_surface_attach(surface2, buf1->proxy, 0, 0); + frame_callback_set(surface2, &frame); + wl_surface_commit(surface2); + frame_callback_wait(client, &frame); + + assert(buf_released1 == 0); + assert(buf_released2 == 0); + + /* Attach buf2 to surface1, and check that a buffer_release event for + * the previous commit (buf1) for that surface is emitted. */ + wl_surface_attach(surface1, buf2->proxy, 0, 0); + frame_callback_set(surface1, &frame); + wl_surface_commit(surface1); + frame_callback_wait(client, &frame); + + assert(buf_released1 == 1); + assert(buf_released2 == 0); + + /* Attach buf2 to surface2, and check that a buffer_release event for + * the previous commit (buf1) for that surface is emitted. */ + wl_surface_attach(surface2, buf2->proxy, 0, 0); + frame_callback_set(surface2, &frame); + wl_surface_commit(surface2); + frame_callback_wait(client, &frame); + + assert(buf_released1 == 1); + assert(buf_released2 == 1); + + buffer_destroy(buf2); + buffer_destroy(buf1); + zwp_linux_buffer_release_v1_destroy(buffer_release2); + zwp_linux_buffer_release_v1_destroy(buffer_release1); + zwp_linux_surface_synchronization_v1_destroy(surface_sync2); + zwp_linux_surface_synchronization_v1_destroy(surface_sync1); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} diff --git a/tests/matrix-test.c b/tests/matrix-test.c new file mode 100644 index 0000000..03b9216 --- /dev/null +++ b/tests/matrix-test.c @@ -0,0 +1,424 @@ +/* + * Copyright © 2012 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include + +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 ns/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 ns/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/meson.build b/tests/meson.build new file mode 100644 index 0000000..1b9ecdc --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,346 @@ +plugin_test_shell_desktop = shared_library( + 'weston-test-desktop-shell', + 'weston-test-desktop-shell.c', + include_directories: common_inc, + dependencies: [ dep_lib_desktop, dep_libweston_public ], + name_prefix: '', + install: false +) +env_modmap += 'weston-test-desktop-shell.so=@0@;'.format(plugin_test_shell_desktop.full_path()) + +lib_test_runner = static_library( + 'test-runner', + 'weston-test-runner.c', + dependencies: [ + dep_libweston_private_h_deps, + dep_wayland_client, + ], + include_directories: common_inc, + install: false, +) +dep_test_runner = declare_dependency( + dependencies: dep_wayland_client, + link_with: lib_test_runner +) + +lib_test_client = static_library( + 'test-client', + [ + 'weston-test-client-helper.c', + 'weston-test-fixture-compositor.c', + weston_test_client_protocol_h, + weston_test_protocol_c, + viewporter_client_protocol_h, + viewporter_protocol_c, + ], + include_directories: common_inc, + dependencies: [ + dep_libshared, + dep_wayland_client, + dep_libexec_weston, + dep_pixman, + dependency('cairo'), + ], + install: false, +) +dep_test_client = declare_dependency( + link_with: lib_test_client, + sources: [ + viewporter_client_protocol_h, + ], + dependencies: [ + dep_wayland_client, + dep_test_runner, + dep_pixman, + dependency('libudev', version: '>= 136'), + ] +) + +exe_plugin_test = shared_library( + 'test-plugin', + 'weston-test.c', + weston_test_server_protocol_h, + weston_test_protocol_c, + include_directories: common_inc, + dependencies: [ + dep_libexec_weston, + dep_libweston_private, + dep_threads + ], + name_prefix: '', + install: false, +) + +deps_zuc = [ dep_libshared ] +if get_option('test-junit-xml') + d = dependency('libxml-2.0', version: '>= 2.6', required: false) + if not d.found() + error('JUnit XML support requires libxml-2.0 >= 2.6 which was not found. Or, you can use \'-Dtest-junit-xml=false\'.') + endif + deps_zuc += d + config_h.set('ENABLE_JUNIT_XML', '1') +endif + +lib_zuc = static_library( + 'zunitc', + '../tools/zunitc/inc/zunitc/zunitc.h', + '../tools/zunitc/inc/zunitc/zunitc_impl.h', + '../tools/zunitc/src/zuc_base_logger.c', + '../tools/zunitc/src/zuc_base_logger.h', + '../tools/zunitc/src/zuc_collector.c', + '../tools/zunitc/src/zuc_collector.h', + '../tools/zunitc/src/zuc_context.h', + '../tools/zunitc/src/zuc_event.h', + '../tools/zunitc/src/zuc_event_listener.h', + '../tools/zunitc/src/zuc_junit_reporter.c', + '../tools/zunitc/src/zuc_junit_reporter.h', + '../tools/zunitc/src/zuc_types.h', + '../tools/zunitc/src/zunitc_impl.c', + include_directories: [ common_inc, include_directories('../tools/zunitc/inc') ], + dependencies: deps_zuc, +) +dep_zuc = declare_dependency( + link_with: lib_zuc, + dependencies: dep_libshared, + include_directories: include_directories('../tools/zunitc/inc') +) + +lib_zucmain = static_library( + 'zunitcmain', + '../tools/zunitc/src/main.c', + include_directories: [ common_inc, include_directories('../tools/zunitc/inc') ], + dependencies: [ dep_libshared, dep_zuc ], +) +dep_zucmain = declare_dependency( + link_with: lib_zucmain, + dependencies: dep_zuc +) + +tests = [ + { 'name': 'bad-buffer', }, + { 'name': 'drm-smoke', }, + { 'name': 'buffer-transforms', }, + { 'name': 'devices', }, + { 'name': 'event', }, + { 'name': 'internal-screenshot', }, + { + 'name': 'keyboard', + 'sources': [ + 'keyboard-test.c', + 'input-timestamps-helper.c', + input_timestamps_unstable_v1_client_protocol_h, + input_timestamps_unstable_v1_protocol_c, + ], + }, + { + 'name': 'linux-explicit-synchronization', + 'sources': [ + 'linux-explicit-synchronization-test.c', + linux_explicit_synchronization_unstable_v1_client_protocol_h, + linux_explicit_synchronization_unstable_v1_protocol_c, + ], + }, + { 'name': 'output-transforms', }, + { 'name': 'plugin-registry', }, + { + 'name': 'pointer', + 'sources': [ + 'pointer-test.c', + 'input-timestamps-helper.c', + input_timestamps_unstable_v1_client_protocol_h, + input_timestamps_unstable_v1_protocol_c, + ], + }, + { + 'name': 'presentation', + 'sources': [ + 'presentation-test.c', + presentation_time_client_protocol_h, + presentation_time_protocol_c, + ], + }, + { 'name': 'roles', }, + { 'name': 'string', }, + { 'name': 'subsurface', }, + { 'name': 'subsurface-shot', }, + { 'name': 'surface', }, + { 'name': 'surface-global', }, + { + 'name': 'text', + 'sources': [ + 'text-test.c', + text_input_unstable_v1_client_protocol_h, + text_input_unstable_v1_protocol_c, + ], + }, + { + 'name': 'touch', + 'sources': [ + 'touch-test.c', + 'input-timestamps-helper.c', + input_timestamps_unstable_v1_client_protocol_h, + input_timestamps_unstable_v1_protocol_c, + ], + }, + { + 'name': 'vertex-clip', + 'dep_objs': dep_vertex_clipping, + }, + { 'name': 'viewporter', }, + { 'name': 'viewporter-shot', }, +] + +tests_standalone = [ + ['config-parser', [], [ dep_zucmain ]], + ['matrix', [], [ dep_libm, dep_matrix_c ]], + ['timespec', [], [ dep_zucmain ]], + ['zuc', + [ + '../tools/zunitc/test/fixtures_test.c', + '../tools/zunitc/test/zunitc_test.c' + ], + [ dep_zucmain ] + ], +] + +if get_option('xwayland') + d = dependency('x11', required: false) + if not d.found() + error('Xwayland tests require libX11 which was not found. Or, you can use \'-Dxwayland=false\'.') + endif + tests += { + 'name': 'xwayland', + 'dep_objs': d, + } +endif + +# Manual test plugin, not used in the automatic suite +surface_screenshot_test = shared_library( + 'test-surface-screenshot', + 'surface-screenshot-test.c', + include_directories: common_inc, + dependencies: [ dep_libweston_private, dep_libshared ], + name_prefix: '', + install: false, +) + +if get_option('shell-ivi') + ivi_layout_test_plugin = shared_library( + 'test-ivi-layout', + [ + 'ivi-layout-test-plugin.c', + weston_test_server_protocol_h, + weston_test_protocol_c, + ], + include_directories: common_inc, + dependencies: [ dep_libweston_private, dep_libexec_weston ], + name_prefix: '', + install: false, + ) + env_modmap += 'test-ivi-layout.so=' + ivi_layout_test_plugin.full_path() + ';' + + tests += [ + { + 'name': 'ivi-layout-client', + 'sources': [ + 'ivi-layout-test-client.c', + ivi_application_client_protocol_h, + ivi_application_protocol_c, + ], + 'test_deps': [ ivi_layout_test_plugin ], + }, + { 'name': 'ivi-layout-internal', }, + { + 'name': 'ivi-shell-app', + 'sources': [ + 'ivi-shell-app-test.c', + ivi_application_client_protocol_h, + ivi_application_protocol_c, + ], + }, + ] +endif + +test_config_h = configuration_data() +test_config_h.set_quoted('WESTON_TEST_REFERENCE_PATH', meson.current_source_dir() + '/reference') +test_config_h.set_quoted('WESTON_MODULE_MAP', env_modmap) +test_config_h.set_quoted('WESTON_DATA_DIR', join_paths(meson.current_source_dir(), '..', 'data')) +test_config_h.set_quoted('TESTSUITE_PLUGIN_PATH', exe_plugin_test.full_path()) +test_config_h.set_quoted('TESTSUITE_IVI_CONFIG_PATH', join_paths(meson.current_build_dir(), '../ivi-shell/weston-ivi-test.ini')) +test_config_h.set_quoted('TESTSUITE_INTERNAL_SCREENSHOT_CONFIG_PATH', join_paths(meson.current_source_dir(), 'internal-screenshot.ini')) +configure_file(output: 'test-config.h', configuration: test_config_h) + +foreach t : tests + t_name = 'test-' + t.get('name') + t_sources = t.get('sources', [t.get('name') + '-test.c']) + t_sources += weston_test_client_protocol_h + + t_deps = [ dep_test_client, dep_libweston_private_h ] + t_deps += t.get('dep_objs', []) + + t_exe = executable( + t_name, + t_sources, + c_args: [ + '-DUNIT_TEST', + '-DTHIS_TEST_NAME="' + t_name + '"', + ], + build_by_default: true, + include_directories: common_inc, + dependencies: t_deps, + install: false, + ) + + test(t.get('name'), t_exe, depends: t.get('test_deps', [])) +endforeach + +# FIXME: the multiple loops is lame. rethink this. +foreach t : tests_standalone + if t[0] != 'zuc' + srcs_t = [ + '@0@-test.c'.format(t.get(0)), + weston_test_client_protocol_h, + ] + else + srcs_t = [] + endif + + if t.length() > 1 + srcs_t += t.get(1) + endif + + if t.length() > 2 + deps_t = t[2] + else + deps_t = [ dep_test_client ] + endif + + exe_t = executable( + 'test-@0@'.format(t.get(0)), + srcs_t, + c_args: [ '-DUNIT_TEST' ], + build_by_default: true, + include_directories: common_inc, + dependencies: deps_t, + install: false, + ) + + # matrix-test is a manual test + if t[0] != 'matrix' + test(t.get(0), exe_t) + endif +endforeach + +if get_option('backend-drm') + executable( + 'setbacklight', + 'setbacklight.c', + dependencies: [ + dep_backlight, + dep_libdrm, + dependency('libudev') + ], + include_directories: common_inc, + install: false + ) +endif diff --git a/tests/output-transforms-test.c b/tests/output-transforms-test.c new file mode 100644 index 0000000..3ed19e3 --- /dev/null +++ b/tests/output-transforms-test.c @@ -0,0 +1,137 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +#define TRANSFORM(x) WL_OUTPUT_TRANSFORM_ ## x, #x +#define RENDERERS(s, t) \ + { RENDERER_PIXMAN, s, TRANSFORM(t) }, \ + { RENDERER_GL, s, TRANSFORM(t) } + +struct setup_args { + enum renderer_type renderer; + int scale; + enum wl_output_transform transform; + const char *transform_name; +}; + +static const struct setup_args my_setup_args[] = { + RENDERERS(1, NORMAL), + RENDERERS(1, 90), + RENDERERS(1, 180), + RENDERERS(1, 270), + RENDERERS(1, FLIPPED), + RENDERERS(1, FLIPPED_90), + RENDERERS(1, FLIPPED_180), + RENDERERS(1, FLIPPED_270), + RENDERERS(2, NORMAL), + RENDERERS(3, NORMAL), + RENDERERS(2, 90), + RENDERERS(2, 180), + RENDERERS(2, FLIPPED), + RENDERERS(3, FLIPPED_270), +}; + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg) +{ + struct compositor_setup setup; + + /* The width and height are chosen to produce 324x240 framebuffer, to + * emulate keeping the video mode constant. + * This resolution is divisible by 2 and 3. + * Headless multiplies the given size by scale. + */ + + compositor_setup_defaults(&setup); + setup.renderer = arg->renderer; + setup.width = 324 / arg->scale; + setup.height = 240 / arg->scale; + setup.scale = arg->scale; + setup.transform = arg->transform; + setup.shell = SHELL_TEST_DESKTOP; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args); + +struct buffer_args { + int scale; + enum wl_output_transform transform; + const char *transform_name; +}; + +static const struct buffer_args my_buffer_args[] = { + { 1, TRANSFORM(NORMAL) }, + { 2, TRANSFORM(90) }, +}; + +TEST_P(output_transform, my_buffer_args) +{ + const struct buffer_args *bargs = data; + const struct setup_args *oargs; + struct client *client; + bool match; + char *refname; + int ret; + + oargs = &my_setup_args[get_test_fixture_index()]; + + ret = asprintf(&refname, "output_%d-%s_buffer_%d-%s", + oargs->scale, oargs->transform_name, + bargs->scale, bargs->transform_name); + assert(ret); + + testlog("%s: %s\n", get_test_name(), refname); + + /* + * NOTE! The transform set below is a lie. + * Take that into account when analyzing screenshots. + */ + + client = create_client(); + client->surface = create_test_surface(client); + client->surface->width = 10000; /* used only for damage */ + client->surface->height = 10000; + client->surface->buffer = client_buffer_from_image_file(client, + "basic-test-card", + bargs->scale); + wl_surface_set_buffer_scale(client->surface->wl_surface, bargs->scale); + wl_surface_set_buffer_transform(client->surface->wl_surface, + bargs->transform); + move_client(client, 19, 19); + + match = verify_screen_content(client, refname, 0, NULL, 0); + assert(match); + + client_destroy(client); +} diff --git a/tests/plugin-registry-test.c b/tests/plugin-registry-test.c new file mode 100644 index 0000000..85de5f8 --- /dev/null +++ b/tests/plugin-registry-test.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 DENSO CORPORATION + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include + +#include +#include "compositor/weston.h" +#include + +#include "weston-test-runner.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_plugin(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +static void +dummy_func(void) +{ +} + +static const struct my_api { + void (*func1)(void); + void (*func2)(void); + void (*func3)(void); +} my_test_api = { + dummy_func, + dummy_func, + dummy_func, +}; + +#define MY_API_NAME "test_my_api_v1" + +static void +init_tests(struct weston_compositor *compositor) +{ + assert(weston_plugin_api_get(compositor, MY_API_NAME, + sizeof(my_test_api)) == NULL); + + assert(weston_plugin_api_register(compositor, MY_API_NAME, &my_test_api, + sizeof(my_test_api)) == 0); + + assert(weston_plugin_api_register(compositor, MY_API_NAME, &my_test_api, + sizeof(my_test_api)) == -2); + + assert(weston_plugin_api_get(compositor, MY_API_NAME, + sizeof(my_test_api)) == &my_test_api); + + assert(weston_plugin_api_register(compositor, "another", &my_test_api, + sizeof(my_test_api)) == 0); +} + +PLUGIN_TEST(plugin_registry_test) +{ + /* struct weston_compositor *compositor; */ + const struct my_api *api; + size_t sz = sizeof(struct my_api); + + init_tests(compositor); + + assert(weston_plugin_api_get(compositor, MY_API_NAME, sz) == + &my_test_api); + + assert(weston_plugin_api_get(compositor, MY_API_NAME, sz - 4) == + &my_test_api); + + assert(weston_plugin_api_get(compositor, MY_API_NAME, sz + 4) == NULL); + + api = weston_plugin_api_get(compositor, MY_API_NAME, sz); + assert(api && api->func2 == dummy_func); +} diff --git a/tests/pointer-test.c b/tests/pointer-test.c new file mode 100644 index 0000000..5d618c2 --- /dev/null +++ b/tests/pointer-test.c @@ -0,0 +1,467 @@ +/* + * Copyright © 2012 Intel Corporation + * Copyright © 2013 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include + +#include "input-timestamps-helper.h" +#include "shared/timespec-util.h" +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +static const struct timespec t0 = { .tv_sec = 0, .tv_nsec = 100000000 }; +static const struct timespec t1 = { .tv_sec = 1, .tv_nsec = 1000001 }; +static const struct timespec t2 = { .tv_sec = 2, .tv_nsec = 2000001 }; +static const struct timespec t_other = { .tv_sec = 123, .tv_nsec = 456 }; + +static void +send_motion(struct client *client, const struct timespec *time, int x, int y) +{ + uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; + + timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + weston_test_move_pointer(client->test->weston_test, tv_sec_hi, tv_sec_lo, + tv_nsec, x, y); + client_roundtrip(client); +} + +static void +send_button(struct client *client, const struct timespec *time, + uint32_t button, uint32_t state) +{ + uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; + + timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + weston_test_send_button(client->test->weston_test, tv_sec_hi, tv_sec_lo, + tv_nsec, button, state); + client_roundtrip(client); +} + +static void +send_axis(struct client *client, const struct timespec *time, uint32_t axis, + double value) +{ + uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; + + timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + weston_test_send_axis(client->test->weston_test, tv_sec_hi, tv_sec_lo, + tv_nsec, axis, wl_fixed_from_double(value)); + client_roundtrip(client); +} + +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) +{ + send_motion(client, &t0, x, y); + check_pointer(client, x, y); +} + +static struct client * +create_client_with_pointer_focus(int x, int y, int w, int h) +{ + struct client *cl = create_client_and_test_surface(x, y, w, h); + assert(cl); + /* Move the pointer inside the surface to ensure that the surface + * has the pointer focus. */ + check_pointer_move(cl, x, y); + return cl; +} + +TEST(test_pointer_top_left) +{ + struct client *client; + int x, y; + + client = create_client_and_test_surface(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 = create_client_and_test_surface(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 = create_client_and_test_surface(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 = create_client_and_test_surface(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 = create_client_and_test_surface(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 = create_client_and_test_surface(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 = create_client_and_test_surface(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 = create_client_and_test_surface(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 = create_client_and_test_surface(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); +} + +TEST(pointer_motion_events) +{ + struct client *client = create_client_with_pointer_focus(100, 100, + 100, 100); + struct pointer *pointer = client->input->pointer; + struct input_timestamps *input_ts = + input_timestamps_create_for_pointer(client); + + send_motion(client, &t1, 150, 150); + assert(pointer->x == 50); + assert(pointer->y == 50); + assert(pointer->motion_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&pointer->motion_time_timespec, &t1)); + + input_timestamps_destroy(input_ts); +} + +TEST(pointer_button_events) +{ + struct client *client = create_client_with_pointer_focus(100, 100, + 100, 100); + struct pointer *pointer = client->input->pointer; + struct input_timestamps *input_ts = + input_timestamps_create_for_pointer(client); + + assert(pointer->button == 0); + assert(pointer->state == 0); + + send_button(client, &t1, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); + assert(pointer->button == BTN_LEFT); + assert(pointer->state == WL_POINTER_BUTTON_STATE_PRESSED); + assert(pointer->button_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&pointer->button_time_timespec, &t1)); + + send_button(client, &t2, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); + assert(pointer->button == BTN_LEFT); + assert(pointer->state == WL_POINTER_BUTTON_STATE_RELEASED); + assert(pointer->button_time_msec == timespec_to_msec(&t2)); + assert(timespec_eq(&pointer->button_time_timespec, &t2)); + + input_timestamps_destroy(input_ts); +} + +TEST(pointer_axis_events) +{ + struct client *client = create_client_with_pointer_focus(100, 100, + 100, 100); + struct pointer *pointer = client->input->pointer; + struct input_timestamps *input_ts = + input_timestamps_create_for_pointer(client); + + send_axis(client, &t1, 1, 1.0); + assert(pointer->axis == 1); + assert(pointer->axis_value == 1.0); + assert(pointer->axis_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&pointer->axis_time_timespec, &t1)); + + send_axis(client, &t2, 2, 0.0); + assert(pointer->axis == 2); + assert(pointer->axis_stop_time_msec == timespec_to_msec(&t2)); + assert(timespec_eq(&pointer->axis_stop_time_timespec, &t2)); + + input_timestamps_destroy(input_ts); +} + +TEST(pointer_timestamps_stop_after_input_timestamps_object_is_destroyed) +{ + struct client *client = create_client_with_pointer_focus(100, 100, + 100, 100); + struct pointer *pointer = client->input->pointer; + struct input_timestamps *input_ts = + input_timestamps_create_for_pointer(client); + + send_button(client, &t1, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); + assert(pointer->button == BTN_LEFT); + assert(pointer->state == WL_POINTER_BUTTON_STATE_PRESSED); + assert(pointer->button_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&pointer->button_time_timespec, &t1)); + + input_timestamps_destroy(input_ts); + + send_button(client, &t2, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); + assert(pointer->button == BTN_LEFT); + assert(pointer->state == WL_POINTER_BUTTON_STATE_RELEASED); + assert(pointer->button_time_msec == timespec_to_msec(&t2)); + assert(timespec_is_zero(&pointer->button_time_timespec)); +} + +TEST(pointer_timestamps_stop_after_client_releases_wl_pointer) +{ + struct client *client = create_client_with_pointer_focus(100, 100, + 100, 100); + struct pointer *pointer = client->input->pointer; + struct input_timestamps *input_ts = + input_timestamps_create_for_pointer(client); + + send_motion(client, &t1, 150, 150); + assert(pointer->x == 50); + assert(pointer->y == 50); + assert(pointer->motion_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&pointer->motion_time_timespec, &t1)); + + wl_pointer_release(client->input->pointer->wl_pointer); + + /* Set input_timestamp to an arbitrary value (different from t1, t2 + * and 0) and check that it is not changed by sending the event. + * This is preferred over just checking for 0, since 0 is used + * internally for resetting the timestamp after handling an input + * event and checking for it here may lead to false negatives. */ + pointer->input_timestamp = t_other; + send_motion(client, &t2, 175, 175); + assert(timespec_eq(&pointer->input_timestamp, &t_other)); + + input_timestamps_destroy(input_ts); +} diff --git a/tests/presentation-test.c b/tests/presentation-test.c new file mode 100644 index 0000000..5e9d991 --- /dev/null +++ b/tests/presentation-test.c @@ -0,0 +1,250 @@ +/* + * Copyright © 2014 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "shared/helpers.h" +#include "shared/xalloc.h" +#include "shared/timespec-util.h" +#include "weston-test-client-helper.h" +#include "presentation-time-client-protocol.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +static struct wp_presentation * +get_presentation(struct client *client) +{ + struct global *g; + struct global *global_pres = NULL; + struct wp_presentation *pres; + + wl_list_for_each(g, &client->global_list, link) { + if (strcmp(g->interface, wp_presentation_interface.name)) + continue; + + if (global_pres) + assert(0 && "multiple presentation objects"); + + global_pres = g; + } + + assert(global_pres && "no presentation found"); + + assert(global_pres->version == 1); + + pres = wl_registry_bind(client->wl_registry, global_pres->name, + &wp_presentation_interface, 1); + assert(pres); + + return pres; +} + +struct feedback { + struct client *client; + struct wp_presentation_feedback *obj; + + enum { + FB_PENDING = 0, + FB_PRESENTED, + FB_DISCARDED + } result; + + struct wl_output *sync_output; + uint64_t seq; + struct timespec time; + uint32_t refresh_nsec; + uint32_t flags; +}; + +static void +feedback_sync_output(void *data, + struct wp_presentation_feedback *presentation_feedback, + struct wl_output *output) +{ + struct feedback *fb = data; + + assert(fb->result == FB_PENDING); + + if (output) + fb->sync_output = output; +} + +static void +feedback_presented(void *data, + struct wp_presentation_feedback *presentation_feedback, + uint32_t tv_sec_hi, + uint32_t tv_sec_lo, + uint32_t tv_nsec, + uint32_t refresh_nsec, + uint32_t seq_hi, + uint32_t seq_lo, + uint32_t flags) +{ + struct feedback *fb = data; + + assert(fb->result == FB_PENDING); + fb->result = FB_PRESENTED; + fb->seq = ((uint64_t)seq_hi << 32) + seq_lo; + timespec_from_proto(&fb->time, tv_sec_hi, tv_sec_lo, tv_nsec); + fb->refresh_nsec = refresh_nsec; + fb->flags = flags; +} + +static void +feedback_discarded(void *data, + struct wp_presentation_feedback *presentation_feedback) +{ + struct feedback *fb = data; + + assert(fb->result == FB_PENDING); + fb->result = FB_DISCARDED; +} + +static const struct wp_presentation_feedback_listener feedback_listener = { + feedback_sync_output, + feedback_presented, + feedback_discarded +}; + +static struct feedback * +feedback_create(struct client *client, + struct wl_surface *surface, + struct wp_presentation *pres) +{ + struct feedback *fb; + + fb = xzalloc(sizeof *fb); + fb->client = client; + fb->obj = wp_presentation_feedback(pres, surface); + wp_presentation_feedback_add_listener(fb->obj, &feedback_listener, fb); + + return fb; +} + +static void +feedback_wait(struct feedback *fb) +{ + while (fb->result == FB_PENDING) { + assert(wl_display_dispatch(fb->client->wl_display) >= 0); + } +} + +static char * +pflags_to_str(uint32_t flags, char *str, unsigned len) +{ + static const struct { + uint32_t flag; + char sym; + } desc[] = { + { WP_PRESENTATION_FEEDBACK_KIND_VSYNC, 's' }, + { WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK, 'c' }, + { WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION, 'e' }, + { WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY, 'z' }, + }; + unsigned i; + + *str = '\0'; + if (len < ARRAY_LENGTH(desc) + 1) + return str; + + for (i = 0; i < ARRAY_LENGTH(desc); i++) + str[i] = flags & desc[i].flag ? desc[i].sym : '_'; + str[ARRAY_LENGTH(desc)] = '\0'; + + return str; +} + +static void +feedback_print(struct feedback *fb) +{ + char str[10]; + + switch (fb->result) { + case FB_PENDING: + testlog("pending"); + return; + case FB_DISCARDED: + testlog("discarded"); + return; + case FB_PRESENTED: + break; + } + + pflags_to_str(fb->flags, str, sizeof str); + testlog("presented %lld.%09lld, refresh %u us, [%s] seq %" PRIu64, + (long long)fb->time.tv_sec, (long long)fb->time.tv_nsec, + fb->refresh_nsec / 1000, str, fb->seq); +} + +static void +feedback_destroy(struct feedback *fb) +{ + wp_presentation_feedback_destroy(fb->obj); + free(fb); +} + +TEST(test_presentation_feedback_simple) +{ + struct client *client; + struct feedback *fb; + struct wp_presentation *pres; + + client = create_client_and_test_surface(100, 50, 123, 77); + assert(client); + pres = get_presentation(client); + + wl_surface_attach(client->surface->wl_surface, + client->surface->buffer->proxy, 0, 0); + fb = feedback_create(client, client->surface->wl_surface, pres); + wl_surface_damage(client->surface->wl_surface, 0, 0, 100, 100); + wl_surface_commit(client->surface->wl_surface); + + client_roundtrip(client); + + feedback_wait(fb); + + testlog("%s feedback:", __func__); + feedback_print(fb); + testlog("\n"); + + feedback_destroy(fb); +} diff --git a/tests/reference/basic-test-card.png b/tests/reference/basic-test-card.png new file mode 100644 index 0000000000000000000000000000000000000000..027ca852e94d2c58ce735e6a042515a6171b1fe3 GIT binary patch literal 8347 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 G3;P%Myjjfv literal 0 HcmV?d00001 diff --git a/tests/reference/internal-screenshot-bad-00.png b/tests/reference/internal-screenshot-bad-00.png new file mode 100644 index 0000000000000000000000000000000000000000..eb90de6b0934b9a8bae7d98f32f11700a1ba08f9 GIT binary patch literal 4204 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 literal 0 HcmV?d00001 diff --git a/tests/reference/internal-screenshot-good-00.png b/tests/reference/internal-screenshot-good-00.png new file mode 100644 index 0000000000000000000000000000000000000000..728b738e7dc41298744783bdba93063b0a38f21e GIT binary patch literal 733 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fO!OFUg1Ln;{GUSZ^AV&pk+ zfcL*xoc4)?pZ8R^UU&#pHeev(xzxVR*6x=+zu6-;-?H_tv*u|ALx)sJ1-2DB%tsie zrZz|{*u*2i95RhDso|8715ZFCTL+^S(TYleCTRfmubD=vNz`-G2ry|sBgrZ12V>Iv VKn2Bhr*?z1db;|#taD0e0sw)Ws$u{D literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-180_buffer_1-NORMAL-00.png b/tests/reference/output_1-180_buffer_1-NORMAL-00.png new file mode 100644 index 0000000000000000000000000000000000000000..cd60739060c7fd950d4731ded23234190a487d0b GIT binary patch literal 4642 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 N(>2zqyz@BnzW|DDxuF06 literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-180_buffer_2-90-00.png b/tests/reference/output_1-180_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..8aa9a1fc2a2c5dca686931711dec8934d15a7a8b GIT binary patch literal 4869 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-90_buffer_1-NORMAL-00.png b/tests/reference/output_1-90_buffer_1-NORMAL-00.png new file mode 100644 index 0000000000000000000000000000000000000000..f17d21d76a333f328830d7d8373ac0f9a699d904 GIT binary patch literal 4880 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 a!N-kUKUcm;>n4A30Wb|+XyyH<5&r=JBP<*M literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-90_buffer_2-90-00.png b/tests/reference/output_1-90_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..f3bb62c8eed8ac8584595afecc791c53f78f9c17 GIT binary patch literal 4655 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-FLIPPED_180_buffer_2-90-00.png b/tests/reference/output_1-FLIPPED_180_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..381ef746671be79dd6a8f3ff46d90bac4c4ffa6d GIT binary patch literal 4884 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-FLIPPED_270_buffer_1-NORMAL-00.png b/tests/reference/output_1-FLIPPED_270_buffer_1-NORMAL-00.png new file mode 100644 index 0000000000000000000000000000000000000000..91c5cdb925841c6db739bbc15112f2ef8908f34f GIT binary patch literal 4789 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 Z(J`64iF3utvA};Qkby1)T&?XK`!C&;6Sn{W literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-FLIPPED_270_buffer_2-90-00.png b/tests/reference/output_1-FLIPPED_270_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..15333fc17098aa9ef78ab5665d0b195871d5ef20 GIT binary patch literal 4570 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 PV1U+ReYF}D`S!dmQoqg?dU)R|Ob>Gs2=d{>q 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 Dc+|Ba literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-FLIPPED_buffer_2-90-00.png b/tests/reference/output_1-FLIPPED_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..0e32a86a4fc4561d11a05a4df10d10794fb53c70 GIT binary patch literal 4892 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-NORMAL_buffer_1-180-00.png b/tests/reference/output_1-NORMAL_buffer_1-180-00.png new file mode 100644 index 0000000000000000000000000000000000000000..3d3a0653a46f08d3a7e250f453e927698d6fe0de GIT binary patch literal 4674 zcmeHLXH-*bmkvl#0TJO!M--7Fohu+ENE0JXfeTzZL;-0ED7^^SfCQvU35k~~HS``p zArvVQDWQapASuKUS|GsW&iAcZYyQoA-;en->zuXEdCxv?IeR~6KYPCk56q3Z&I_Ff z003N|dxn+(0Fxo({?}P%hTJboyvw+-xtkap0#5%vKB7v~0RWzTkfEM+D2y~8^?)w~ z-cNDC7;vk8yl5oKjxb8)dM{v+ii*>g3v6U^G_4Mtk(BfFd#)o}HotnJ^qY+uJNm|! zb3n+9+>d3+2oW^U+KKaK&5O5LA+c@J_IS1yan>2v6-F*|2prD}i^mOQ5u)coWXV-7ujS%IOesJ@g{N*I^v+?oqo*1~FmzUko zOjbL`mS+laiy`GVu90&%&Y!<<1=!((RY)J?Td^AUp#B*8Kp+qt92|OFVp*j?moB{2 z%f1{s z1;(2zD=Qrx9cQbByVL5G*t=zAW#7DgyHA?qPenaXUfm#JIzK-mFf=f5&&0&U%*+e~nrZUO8}s6fE*N}NUR}LV z3pNIU66#_A-35H4YzqX?$<9tdd!Dch*Ny3HZEbC8QVXpXKT9H!(z%C*hLRE!>+0%$ z&qY@0_6I1Ub}SFMKBQ)*a#Umu^L!NIcX>dP#8Y85p# zHMO+?Cxr7gEJiZxiHV6TEABJBa5y|)E5yde<_-00W@e^V$QE&PGu2E{L7|VL6#B_f zX1%UXWrhc7@8zJfp?j5e$JnnrbHr5v4~-A=ZECnzGKoj6g=7*Ho- z&oDTHAqSc;H_~N()d&Lo68nj|^P0~R#m`=aFJ0|W+2u{_=+K@i0jjGvH#Gdd#(=^X zz7*O(szjYAFZV}QH_EH3NOT%iwUP}0I^V}Yn5@>jc^s}1_e_=ZkxI@p0nPy{ zrS+xw>8tQ6!Oq#)*`=P@F+m~MzoZ3%0s{k=dSYXle)OHcA#C2H0@#rjs3#^R9v>fn zh5lx$hHF^NW%+3MIh*&=CFU!8ySp;#HcB(k>N1%=8h<9M3Uj{cev3k(J`cSL^~P43 z2rZ^sq;g3)2m-?SATu$UIM9jD8$+nC#>`h1D?#{#Ah*{}o;BVsAq=HZA5=8yN$b|d zzW@HwlCp=1DRnv3fd&^_G~Dh7?%+=Fp$psnvrbtztMibwE8aG!N(UMw*J8<9IUPwB zAUxwn@sxF45T9OY^WS4$!2&m^kU9Tj`#|Sa_i^_rZQ*9hQ(qCqXxb4nYpLO|(P(se zKPuT`mvBMIM%PD_yf=gS>TF!UJ~`dUj%(8EhwevCDfCsAzdAFdh7uxmv5NuE_YoRZ`K$)WU8)*p%gC-SKMKXQIIK9gxEBdH+rbU#D$3}XtL=StP@DMh*IwRyE%Kh?r zdt)x%wmPIXnUp`9uUVyaBx4rb;RK!oRe*kp>u_+pj?#KVL&K8db+#@n7vBLeY+W+G zxVU)G#={@8pB&+f1+RoClP2HjH`P%k_E|&I zb+k&&?hfge$6_KPxb_h8T94mc(?8(A2#gH7ynue%C6tI&*!pk^qZDFk{+K6@0raT3 z_g`zaj_6ag#a<3qF+Q9MLg%P4-r#OwZAXV=>oRlZhYv&Mw6rQyCG7mEB{iXKM0evr z*9sjySF>woE=mx2l0%)Im48#@4tj2eJD3b5$5M24z{kW7LD4)fTN%)P^MCs9^GG{Dr zT@*nWXIeL#!Q?2qbc!6v4^&oG(&>@KUEg5{1R^t2(gFGa{!=t6#C>P}iRaAGkHwF$ zgEhs{nBNcrU+n-qr9|{OJb@tj1mBW+xA1oO&ZhzimA+ ih-UAy67al_~Z-O(WR( z5~;1%_XVB96s76C$}BA$D9Iy!m*_I+~lw-Mnwla+p6 z^;7glU9Z11)Z)~SzPj=3kl*m_N@);WBS1e`ao6DOg~`cDnEdfuJE44C6aEGU2EW`$7m;R9BR%m_e2ifJnZ4FiXNUEU z!S7pMsUJQ_X;tRK;n9>8^gwbTL+W?w6wIW%QUyEg=3+_>DuJvWICPR+UXh{9c^sfUkmP!Qzy&I z%S%d1QeA9cN=k<-tM=`Y7q8${!E#z!?F@PM)2&OH(s9FYhrzpVrvWKwRw7 z#l2v)^KKDkUq6j{*=cpFF7M0hf`3w^1s{u>tLw+Et}Y}J+1!kc*+nq;f$?KbOiW~C zWH8{Z^E1EcI*7rSCINYJAWM8u%Ps9!kC_yNtt~ABNwbZAGt9999dfsD%nKHruBBQ( z<7jL9c7O2Kw?w@EUJ=6vMMXubo}&X70x&7pL7t;9(Ea=RgTIsC;tm4sU$8eU7P1b35HnBG?CuhXtH1==Gf~~T4Umh$DW@Hw} zi?5~SRY0;9uF?8M}nD!I>IcQ1Ndo*(Q>_L4DO zYr{dMQJ-%iD|iu;Kd}|J0z~?QfN$4YWRhWlDIGE~mjO&kKLOK>&RdF+61r4e`=5d-4UJif>=<{|ED|nzF{~LPBm#z*Ir5I^9^(UZ3g3&BkoiE(MPWf&{2a;UesEUnUa(h{)BmcK=sYbI1pb${?v$_YlJCpo^%?Qgw4AfHjHM<2mwj&A22 z^j0i>oB}Fuw5ql|T;6MyT&33Is&BtfTurWW=`t!E4}@i{HsN=XPp5y49PRf;pdZbO z@;6%+js?Qr)MncI`Rjk_Lb`OB7rR4lL6h%8Q17!fnj2ZN+l?9?pOonM&u-H1js_X! zhHb4G_&1&A*a|2NE?Tag>(stQ@?s09$xBvZ|`-uE-d>Xb!J#V zm>*5BA+T2LmXDXE7tOQv)Zeu6{l4k!(1_1WO}){M>R!HwCEUDp$=jnnO!SUMfl&S0 zBbYxvF9k1eT7?Mz8FYV@UporZCRJJlU*@d~HSYSI;HV3XFLUqQ8~NHB6o0!<+?Qyc~cu_n%S_NP7G z$30V3g0;0LGURh#6xIA>?ON-?BI&2sDCkQspBUZpNI{n_7QiFDWUcC7?!XKRfMMR% z;T%bGe+4Y?MInZPSj<6unQnjZ{lftzWs>(j1`xvZJE^aFHmDznA$okqI%0)3Ljpq^ zwADN{vm-f5bhD>6d^BPoo}6;}mP=U3*m#Z@jB8-axH!2#3_jhQ?5k*s>84r)mdNf( z+_oPBHWpvX#kDo!Mg2OirZ`Z}Ocr*fJXG8mCb#M5RLqW={((2HyA`W=AfEdJhLLYY z8oEksp7k?1!Ve4-CnW4@&Jzez$K*W{YOa}_n0MlIrn|%F#ZY9sJ;WraDm2%H0+|Xk zpf`Nkg|hXmIWBJiCsq1q$=fL?jEVDLn2AW`bKJ4#Vr9@ca6&%9arMAds*@o7e{b>`e z1gLt`p^5mgNA4!u#S*V80O5n1++ZsNC0KHnc_TL^VI9nU@M-gNPz2(+^4{c)Hp0XN zM@hpkYz%(Skv99qVPWkLZK)33W=sxumGkkt`!7cQ7X^8#s9r<(YFoZ}-?Ro;-Km-s zeyrs)1v^|x;z90XR#!Uf-Cye!w}qb8e+upE>g`I2<1&MLDpN>tyrkGqk zl74yx_oAZ>jzg2&8YXpu?Vm4Xtpr*3Z8Q!cz9l!c=+ocNJay!Yf}nhV?~IH|9NDPo ziol*8J>6Q+$<5fYj-*>rDCri;?T$~O$%io*b%B)$yj$~8f5?{M8ksr!@{ep}*RmtE zAovF`dV~tQ-=7dX_`;;a*^hrAcWx5~1cigWej58={l9GrfSVnw_gR(;kEEdVQGZ)* z?yl_Y?4t29!TLn^l=+jCkgdq*7Y9FJDy>qW7kL$l`V4kgr z^>;YA^MEh>Q}Rwsu9R+nwf}9Q{O^_7|KIyx*_`;lw%S3A`We}%gI}W;8Jj`?kde7z JwZ3cIe*h4`{ht5; literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-NORMAL_buffer_1-270-00.png b/tests/reference/output_1-NORMAL_buffer_1-270-00.png new file mode 100644 index 0000000000000000000000000000000000000000..332b79cae8a0d4c2ab26732c7657f6e49b8223a2 GIT binary patch literal 4877 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%0A`97^} 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 LQy*BT?hyTNC(A*F literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-NORMAL_buffer_1-FLIPPED_90-00.png b/tests/reference/output_1-NORMAL_buffer_1-FLIPPED_90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..bdb129bbeb23e70e0c122af182e32b3e7230ca94 GIT binary patch literal 4914 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 PV1U+ReYF}D`TfRJ?eFh@q{S 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 tfctkEzuXEdCxv?IeR~6KYPCk56q3Z&I_Ff z003N|dxn+(0Fxo({?}P%hTJboyvw+-xtkap0#5%vKB7v~0RWzTkfEM+D2y~8^?)w~ z-cNDC7;vk8yl5oKjxb8)dM{v+ii*>g3v6U^G_4Mtk(BfFd#)o}HotnJ^qY+uJNm|! zb3n+9+>d3+2oW^U+KKaK&5O5LA+c@J_IS1yan>2v6-F*|2prD}i^mOQ5u)coWXV-7ujS%IOesJ@g{N*I^v+?oqo*1~FmzUko zOjbL`mS+laiy`GVu90&%&Y!<<1=!((RY)J?Td^AUp#B*8Kp+qt92|OFVp*j?moB{2 z%f1{s z1;(2zD=Qrx9cQbByVL5G*t=zAW#7DgyHA?qPenaXUfm#JIzK-mFf=f5&&0&U%*+e~nrZUO8}s6fE*N}NUR}LV z3pNIU66#_A-35H4YzqX?$<9tdd!Dch*Ny3HZEbC8QVXpXKT9H!(z%C*hLRE!>+0%$ z&qY@0_6I1Ub}SFMKBQ)*a#Umu^L!NIcX>dP#8Y85p# zHMO+?Cxr7gEJiZxiHV6TEABJBa5y|)E5yde<_-00W@e^V$QE&PGu2E{L7|VL6#B_f zX1%UXWrhc7@8zJfp?j5e$JnnrbHr5v4~-A=ZECnzGKoj6g=7*Ho- z&oDTHAqSc;H_~N()d&Lo68nj|^P0~R#m`=aFJ0|W+2u{_=+K@i0jjGvH#Gdd#(=^X zz7*O(szjYAFZV}QH_EH3NOT%iwUP}0I^V}Yn5@>jc^s}1_e_=ZkxI@p0nPy{ zrS+xw>8tQ6!Oq#)*`=P@F+m~MzoZ3%0s{k=dSYXle)OHcA#C2H0@#rjs3#^R9v>fn zh5lx$hHF^NW%+3MIh*&=CFU!8ySp;#HcB(k>N1%=8h<9M3Uj{cev3k(J`cSL^~P43 z2rZ^sq;g3)2m-?SATu$UIM9jD8$+nC#>`h1D?#{#Ah*{}o;BVsAq=HZA5=8yN$b|d zzW@HwlCp=1DRnv3fd&^_G~Dh7?%+=Fp$psnvrbtztMibwE8aG!N(UMw*J8<9IUPwB zAUxwn@sxF45T9OY^WS4$!2&m^kU9Tj`#|Sa_i^_rZQ*9hQ(qCqXxb4nYpLO|(P(se zKPuT`mvBMIM%PD_yf=gS>TF!UJ~`dUj%(8EhwevCDfCsAzdAFdh7uxmv5NuE_YoRZ`K$)WU8)*p%gC-SKMKXQIIK9gxEBdH+rbU#D$3}XtL=StP@DMh*IwRyE%Kh?r zdt)x%wmPIXnUp`9uUVyaBx4rb;RK!oRe*kp>u_+pj?#KVL&K8db+#@n7vBLeY+W+G zxVU)G#={@8pB&+f1+RoClP2HjH`P%k_E|&I zb+k&&?hfge$6_KPxb_h8T94mc(?8(A2#gH7ynue%C6tI&*!pk^qZDFk{+K6@0raT3 z_g`zaj_6ag#a<3qF+Q9MLg%P4-r#OwZAXV=>oRlZhYv&Mw6rQyCG7mEB{iXKM0evr z*9sjySF>woE=mx2l0%)Im48#@4tj2eJD3b5$5M24z{kW7LD4)fTN%)P^MCs9^GG{Dr zT@*nWXIeL#!Q?2qbc!6v4^&oG(&>@KUEg5{1R^t2(gFGa{!=t6#C>P}iRaAGkHwF$ zgEhs{nBNcrU+n-qr9|{OJb@tj1mBW+xA1oO&ZhzimA+ ih-UAy67al_~Z-O(WR( z5~;1%_XVB96s76C$}BA$D9Iy!m*_I+~lw-Mnwla+p6 z^;7glU9Z11)Z)~SzPj=3kl*m_N@);WBS1e`ao6DOg~`cDnEdfuJE44C6aEGU2EW`$7m;R9BR%m_e2ifJnZ4FiXNUEU z!S7pMsUJQ_X;tRK;n9>8^gwbTL+W?w6wIW%QUyEg=3+_>DuJvWICPR+UXh{9c^sfUkmP!Qzy&I z%S%d1QeA9cN=k<-tM=`Y7q8${!E#z!?F@PM)2&OH(s9FYhrzpVrvWKwRw7 z#l2v)^KKDkUq6j{*=cpFF7M0hf`3w^1s{u>tLw+Et}Y}J+1!kc*+nq;f$?KbOiW~C zWH8{Z^E1EcI*7rSCINYJAWM8u%Ps9!kC_yNtt~ABNwbZAGt9999dfsD%nKHruBBQ( z<7jL9c7O2Kw?w@EUJ=6vMMXubo}&X70x&7pL7t;9(Ea=RgTIsC;tm4sU$8eU7P1b35HnBG?CuhXtH1==Gf~~T4Umh$DW@Hw} zi?5~SRY0;9uF?8M}nD!I>IcQ1Ndo*(Q>_L4DO zYr{dMQJ-%iD|iu;Kd}|J0z~?QfN$4YWRhWlDIGE~mjO&kKLOK>&RdF+61r4e`=5d-4UJif>=<{|ED|nzF{~LPBm#z*Ir5I^9^(UZ3g3&BkoiE(MPWf&{2a;UesEUnUa(h{)BmcK=sYbI1pb${?v$_YlJCpo^%?Qgw4AfHjHM<2mwj&A22 z^j0i>oB}Fuw5ql|T;6MyT&33Is&BtfTurWW=`t!E4}@i{HsN=XPp5y49PRf;pdZbO z@;6%+js?Qr)MncI`Rjk_Lb`OB7rR4lL6h%8Q17!fnj2ZN+l?9?pOonM&u-H1js_X! zhHb4G_&1&A*a|2NE?Tag>(stQ@?s09$xBvZ|`-uE-d>Xb!J#V zm>*5BA+T2LmXDXE7tOQv)Zeu6{l4k!(1_1WO}){M>R!HwCEUDp$=jnnO!SUMfl&S0 zBbYxvF9k1eT7?Mz8FYV@UporZCRJJlU*@d~HSYSI;HV3XFLUqQ8~NHB6o0!<+?Qyc~cu_n%S_NP7G z$30V3g0;0LGURh#6xIA>?ON-?BI&2sDCkQspBUZpNI{n_7QiFDWUcC7?!XKRfMMR% z;T%bGe+4Y?MInZPSj<6unQnjZ{lftzWs>(j1`xvZJE^aFHmDznA$okqI%0)3Ljpq^ zwADN{vm-f5bhD>6d^BPoo}6;}mP=U3*m#Z@jB8-axH!2#3_jhQ?5k*s>84r)mdNf( z+_oPBHWpvX#kDo!Mg2OirZ`Z}Ocr*fJXG8mCb#M5RLqW={((2HyA`W=AfEdJhLLYY z8oEksp7k?1!Ve4-CnW4@&Jzez$K*W{YOa}_n0MlIrn|%F#ZY9sJ;WraDm2%H0+|Xk zpf`Nkg|hXmIWBJiCsq1q$=fL?jEVDLn2AW`bKJ4#Vr9@ca6&%9arMAds*@o7e{b>`e z1gLt`p^5mgNA4!u#S*V80O5n1++ZsNC0KHnc_TL^VI9nU@M-gNPz2(+^4{c)Hp0XN zM@hpkYz%(Skv99qVPWkLZK)33W=sxumGkkt`!7cQ7X^8#s9r<(YFoZ}-?Ro;-Km-s zeyrs)1v^|x;z90XR#!Uf-Cye!w}qb8e+upE>g`I2<1&MLDpN>tyrkGqk zl74yx_oAZ>jzg2&8YXpu?Vm4Xtpr*3Z8Q!cz9l!c=+ocNJay!Yf}nhV?~IH|9NDPo ziol*8J>6Q+$<5fYj-*>rDCri;?T$~O$%io*b%B)$yj$~8f5?{M8ksr!@{ep}*RmtE zAovF`dV~tQ-=7dX_`;;a*^hrAcWx5~1cigWej58={l9GrfSVnw_gR(;kEEdVQGZ)* z?yl_Y?4t29!TLn^l=+jCkgdq*7Y9FJDy>qW7kL$l`V4kgr z^>;YA^MEh>Q}Rwsu9R+nwf}9Q{O^_7|KIyx*_`;lw%S3A`We}%gI}W;8Jj`?kde7z JwZ3cIe*h4`{ht5; literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-NORMAL_buffer_2-90-00.png b/tests/reference/output_1-NORMAL_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..f95cec5709ecd52edaa20187489ed87db1a8e0d7 GIT binary patch literal 4854 zcmeHLXHZjJw~mO2O2STM1nu2^k{1mN8eo)!zX{iG){vP=)MacjF6G}&2#l$~*bJq1KcmjHL%j?hs zclBKtca^1%Igjl%_65rlN(U+n-!T1lwWLwGLGJ$E@-LCwDE*=aCt>NQFxSronLqEV z74p-5m^AoYT_q5}_Nn2029;v8<7FvZpsK2+jd1B3+w|bsmh;NG1~6oRV0}QqUAP}r z9=j45TNQ2xz5ymxrLVZ~Vh--ulv_^0i-f-yP?-lff@gl~=s1;)^HE=bWREeE=BD5o zn+mJ?Fvn{=5LKl*9OQgw0N{F+TG}xplR;c^r_mn--vuouHDHG*!Mip1i5rML4CXBS z_h92JBB8suc)Yln_ifvk;k`-N9e<@?{g>JeJfH^#&7k^UnP`EqsN=>_Tp->IH7hEb z&A;>)S^&VP>j4$yk4v~e?5rxW-3ij6=2`baP=?eWpH7rtnUw}CKQqg?QlWM$eKSXH zb=suMky-U;cTmUJJ>;I9tCwo>8|#`rLr=o0ysZG*&d}eim)?l4hla zY8P^=z$>uD8z84K)4@rY53i7gpac-$+}FgD(4wL|0e*urx^E$Gww{f@ z^wmBV1b7bStlqOI;6eO47JsU2p|z6n+Q!P4YqjrQD?yH7Hcd&Bm9@Io~iQ>TX=;j}$KaX1b$jq^Dh2D7koqOm0Lky2k|FWE<_~I!TBreWJZ4Ezk zFmIyd=Ie^EaO|BZ4^}UH^0e681HD<(npl+z5>1b-C0BBFd#SaJoB3g|Dy5h^bMwv+ z^VZ$8GJRd$(Y2tVkDvWRdKYArlt@|*?7VqotX|Nh;@D%a^P=z~n}hzt>W!HD_cc@d z1*o&9pHN$%#z+A$SY+E)|303`JH)V%vY%~HC^a~96#tYzlVQifv)ZVzam~||mOj+a zVX29a&$OdM+FG^+|P%XEoWKmagyqnnqNl&jwJHxjKIu#RZ z`;7z=rOh$k4DS}dTEWEp@S8>CXI`lTvE(H9YlX+hyjv5WF5L{rHC}YRX`MOrRii8$ znn}zmv16I%-~&O#c+@MNF{%m%%*p!^PMa4Nn)0rwe7D0hFsArqDe58nzX*q)dM&1I zW~|+!tl$n3$TI4TTayXnjlNMMw-9x*)Z$_-&O^i4%qg_WBHwkfHqkb5wl#)odrpyW z-rwkCmU+N#oXg8L{s^Z*?eS#4L_*WP-+29~r(;$UP)M#zpS~{6oihBvws*B5Rv2fI-Nw=6neF$eaS^kqy$R7!z_2_I!_So&*{{U?IBCos@^AU)Q|AKw|W1V1%GLK|f^tl%2 zs^B+*9b1^apRe*2A$u;eIhDjxp_hRk+)WPu&VXgp%=>6iHrzQ&rFUq^P)`qp z#0n<&*#ckGTB^RA-mIJ6WM*dO=%J;h&5`#jN26skB25CFb`EQthZ_~ThabtWO)Lde zG%DFSiC;F?XK4qMFAEE_eGgqsSM@JC;el#lSMf|K_qpukWUpV}!=6I|3Q;Kh(f02m z!#uitjW&T7$t)An%!lxcvhDat-2S)^p0&c*d+Jvqkd3jvWb2RRO_^A0oK>oudc3M1xbo^0A*!m zaX6f#qhmzGL-Vv?rxfYOZQUds1#}!PMwTN+S*!a_c`Hv_YxeF z+#;dZuV07HHCtHWiQePCiLbqpKXtB@61C1yKe=j})mUEN7|P^Xj5zC^85wzIW>#k& zD;vko!NI}8@&}K{Z*Fc92!aR)a$qMXCkF=@u3A7*aklvf(_gn^3cq~|!H<_MFE0-q zKsRoMG!ALrUT;^vX6AWC7a@fqwj4rq`Y2G}0~B%x2M6ow>g;>suoogd9%Qe6(kK+_ zaC15xi9`)!)i~;rKP2f{%BW$yn~>jSdg6nc4XAKzdgMO^1o8>9NII6cQXd=!O3( zf4;0?*m{XK?hFcdcTcC;Twhzm)w-rPmu*bdqKb;Nz;;#Uo%PtXu5+P^`B)yDGJR&p z20L6tbU|it=o075Xw73%M(9XxUY?DS(Sq~ymb}FKv9~To_j0UsMA7RRR2pW>@C@Mg zgE)F}WIR;C?Lf>zM^ySzw6FXlg0snCXbbKXSe_Zi=^2S`XpoVYw*rq04|^>2GO{8Q zWIUI&wX~ud_~qrB8XEjbxqV|LQ73!y@^)M>Nhh1=;6Kd4o<1hcq-(=!gQ0)=`*pXW zxZm3xih(BGCwIK}{usqOAHXu98RVHhrT2!YXEx&~AI#=_6EzQWjwqIG#YzmHoU4{g z4o{LMPFATklaD&<_uZvbIjS#AVOp?50kD1K+-Ttie<+a?U(5w&?Rbo7^@ zT{vTY6bwo({e+oLxT|Y2YNt?#t=e2TY1+0CyUyO~9RSCqZFMYZChJ9A(|p5Q9T7l! zvT>!yI;y?OzGr2A{I+|E`O4sxwV>cQqp)GyfA?pgr^oD0M@QFhTJxA6t0VV2bFUnC z|6I>FceJq^XfO3ctq!OMF(KDU0?H{=J1g0{Tk5-zg7|CAoNcA1b5L+Q{2)epo5c4w z5UDS??POz<2bMfl8_E6zk|xb|&4EtSyH{VdfZE3boAw1b$l0%Mc>x8obRt_?T6Pb& zyr87!1nE#>V9Sxu|I1Kl1#R(UyVM;l?i7Yqb;URD*78v@GvWH?y`DVcii}qtSe$ zB9S11!D6vyC{u8?`@9nRflbyY9@N+a3ky2F@7!#?$JuBUO7HBp0~`)FGBWyN^HTJ| zgMKVF)myEa{@>Q`s^GVO_gsPc-eUPn)7aRU&;I;INpUfwm=zy&J9$QJ_TC$cz5oZM z;FCRallJoTB&01ZFOQ9lMZnoA8yn^N!X@vBL9$!9Ujqk50qWN?qr9XfC3VK1J@)?d z)ZX6yp5Pw|BsPlfE>k*n_>Z4IB|;7R#3fu5)mYl6gM}EWK~GgzfYQ>^>8Yu=Zr+>` zd4$B0dm<^80ABl2ad2>OEL|l~|1F4rVDyXe`$FBEN~_oVJ=)?w)JSc8J|@i*+@);;*z&cs=OkXhU^nNl@gX*5&^1j> zuiG3)pZCM0{QUfgGiYP*J!(~Z``^*)tQR@Y4@GEJDFYGw7&1$im6av6yG_?wqdg1D z%SAJ?@aya4U^K6+AhW8amDL?93eQK#you{F160w7_28AAot>j2|2`(HQ_XQk2b0c@$yCvm1h%wn^rm%T<|9E_#6cLFU>~QgC_-T?HG@h<*{Kc9(Lk`fw07Ei zWJO_NVQDF>YF6eS#I=tlC8wsQW@ct4CRRBPr0wnPb(Md;qVmGc&H5yRLqUZ>oD)U+ zk{M746>EbB!L>6nw)GH!8-t=%etFo$(do7g2}u- zC|$bkXG^IRAuGXueP^$M8VWDBjMcOhF1&Z*AOK`s&@}ias}32 zUS4o`Ik7v#!M1Ot)C@(`d=7^b!tI*@+%(3Uxk|xPhob6ah6@P^v9!#>!K5bv`bW4C=oaIx=;QiJ(K>}rMY^>&$NJGF6pp%9;ak#yG0ekw54#d^}z^RSDmDLRj zlP9n68RS^-k)y1-z7AmMfcvhWR}sAf^M!)e3DOV3OJevfKFd%Ai3RCBt3Hp!Yf%LZ>#KARHU}WX$s%3 zJ<71U2WjwqWhc`@M25`A1}u@g*bP9)U((**>xWv>&8*lO1#B(-C(L% z5f(L@#@O_=MWVrqnd%0ru zFWvlsOCs*Wfv?-F%gL7w7X5x4%t%rDZk{&Jz;${pv9QnyNIagKvN$?y#YHn^RVDH^eAR+)NsbXh38hOAe zQ|U4N*}7i7A=FmS)U@HRJr*HZdy@L_^QSiiQjD50qdl^-_vFPPeWYh|^_*QVC(W>w zA`g2j2~&ApH^{EVC5Ob;n-TY%x)Nz7SBV(#YZGZ0D5@k-$-;j2j|#s4`DM&XT*{!o z&B*UJK?85As|AqT1^fH{w{G!U+PPgpVl{(~c1I48=jvKS4&EgC35!U7c78A*RC9$H zI`!PKBSCL(Gs;<54wehL8ZGHO@ET-CQkKcgRuF+pKkliKlT5CVF>e;Z(VjK z026JRtjDIKU4v9@#9l#Aeh_r2c(CUx4fe}C zCfkQc6#}|6Yt5nCY@#BDu4fE>T?5a2LG-qsV83_`iz;GXV-c)+?57Ls-CjW)zh;%WgXlliCr7pnpG|cc%dbk+eIV1PccT2Xk}turT~tL08<6`<2B2C;?5JDIfBxtEDa5B9z4MJ;zuS58~Xq&EfECSFRk?j`DaVYJUvxF$Ov%E>0i9$tfv$ zXK_rvStyDpUXYW^tE^;>H@%=d*EWn~o+5fK=;CDtWu zKbVFUwZCxTLX{6~tiq%3gH(>cRFy|x?nsqS>WA?=C$YAD-`q-yi#xl!Wt5yAiq&#U zO$}62Qxggu9v%id6%-V7baWUP7{tfNA3Ag>`Yg-Gz0Belkq9rcc=+JKXSxhPRW=4X zo2~zPc6N40C8(AvXN)Z>EG#T4+I*~#;Ampfx7s;5If=*PH#Y-{x2!kjz;q`0@wK%3 z)>X)i^)z0R?>Q}!bn!F_h2m}^kw|%{<So*k>qHV1xPU~|8 zR1`?#wr2+G@991iTIG|T9Ff(IYVYbYUtYwOmmfZIgcqyWsF?eV6uPziP|98g8sL66 zrDQKYD|r6*IobuOwv{2AesWpZ?K+CH#rpoLp`mGs+Qd};2fe<(X=&!+^u3<8y27n1 z;}UsD5(EPA@bIX=$MtzMY)a#MZ||d4=HC?+73t|xOg+po{f9UMs_u_1BECFi8AThv)!>61+a$k(89wldE=mW%pXkYy%0A`97^} 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 PV1U+ReYF}D`TfRJ?eFh@q{S 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 tfctkEU97}*e2K= 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-180_buffer_2-90-00.png b/tests/reference/output_2-180_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..edd425e569c24d9475d72431a9fe9675e355eb11 GIT binary patch literal 5270 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_1-180-00.png b/tests/reference/output_2-90_buffer_1-180-00.png new file mode 100644 index 0000000000000000000000000000000000000000..85579ddd3fcb15fc3a5228f2a2f8a09ae121476f GIT binary patch literal 14282 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_1-270-00.png b/tests/reference/output_2-90_buffer_1-270-00.png new file mode 100644 index 0000000000000000000000000000000000000000..86e4ba0eaa6a35325c1725bebe2fc28ec9d53a04 GIT binary patch literal 14385 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

=!T6s8P@0B0>UQedjg3egSyQ@1BB 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_1-90-00.png b/tests/reference/output_2-90_buffer_1-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..a26f923841a132dc7ef4408e8502bf5ef8839929 GIT binary patch literal 14482 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_1-FLIPPED-00.png b/tests/reference/output_2-90_buffer_1-FLIPPED-00.png new file mode 100644 index 0000000000000000000000000000000000000000..95730615ed7bf8cf10b6f48af9118e230e4c50c6 GIT binary patch literal 14336 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 sdmK4#=Ln+Px# literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_1-FLIPPED_180-00.png b/tests/reference/output_2-90_buffer_1-FLIPPED_180-00.png new file mode 100644 index 0000000000000000000000000000000000000000..ce944d91e5b695476675ea370b295d64b8ede337 GIT binary patch literal 14762 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_1-FLIPPED_270-00.png b/tests/reference/output_2-90_buffer_1-FLIPPED_270-00.png new file mode 100644 index 0000000000000000000000000000000000000000..6ade0281ebb056d1db7095c9feba961d0796e85b GIT binary patch literal 14394 zcmdUWgshxu-i57&82F3jF zju>8}|BO;~FlzDmMdkdiy_Z|#t;kZ5NOwdz6udx29xbkVv^>uODX9 z)@JWXYvqWE>rw>LQa<&TjrCx1A|siYtaj&~Q`3 z$?Dk__|cA^S-sqQ*>O*sYDt!0O}mMl$9Q}1X5GJ)PsE@p#(2hBb!AKAPzbfNQ>5G* z{-nS3wN#SG){BQ^->{!0CnYHrj0Z(X<%zryOn?0330A;aCbv;U$|GfEWz^jP$`5sS zd!IT`F0aXLXS&kwl%1QGOe{usldji*EpdOj6P1m+%qA1Yirfg$_`J3;r#JL0t^EIG z^`?I7hQdWfo@py`)^CtcOzUzaQ~7Owq}p4-`50x2cVBq22_O*x!Zetjr8=8|zjCop z_j7y8Jl!-R+N}_cM7Wre0TQ+MkLREP7z|LpjMj+R5G*>Go?Er(U&Pu$#dzwmyn>Z9 zoJbYwUyz5A5}QJgt$xKK@~ill4IdB zhT)wKwmwJo&;(;mB)e))v2TkPKb}WzK#4>k-=4@5K5)?zu`aB}hcwJ^LdlJpP1gwt zN)oIve#0xAwIL@pS7DtA{H4EajQidx&st`Hk`RKQx*^fJpAYVF=(D@(seOp0V!~>o z_)F2^Avf_D!~}vfAjE(Rdq)=|9*Pyp7Q!2YLdsi0@(GV4++JsyM9}lXAu;gx{q!Yx z>d7_LPRs=6==sq22u6{{#JunjNS*&%$;p@+I(R$_rZ9C?4YXC{U|qC3r8@;S7n$oM z>h$ec#|IUZf(H4-){|oMFco<=ycBw0CcOF9gtbbp>xtARUhQ>Cs#F%7pYVYs7UY_@ zLcNN!-EuagV1quaVQIIRx<~vQQIeZLY?B2-afA~N!3o)xw*T4w`dCCT zNZ5>Amt!Z);XJKyusJ9MLIh(dxnE5tTPnr9=V_;|m2=RZ%+aIT{f8evokwai#)T6f zHEplzZ6q^LnBXvr_9yX&nWMy8Y=(-OHHRc}Q=|~@eYAIU5Htt`#~Q*7moCNIVB~LM zFrUmw924ySmQJaiR?Ra=1Q9au_)-&hU10;WG#oI4zJUmbNI~J!7jWAuCP^aaAN)=~ z&$Q>CIA<75yrHNimw_mf5KR>xK++6T_Dw_49M40y zbzfepQE+s|($bnub2~D|j~Nqob35L+(nk!yl2c8FcE!heHXcg+QR{a1uq&$%RgDYo10L z>)cpbqA9h0z%Ex z>-oMqgoSPnUnV*P!qJ0i7I%zkjys5l7G|oKSVM~Wv}fq&qrtSgJ@$r0^E5PdL`X0c zEfzSapgAr)`B=4EftLwwy1R;Y-IgQ>4uL`wKl~*A8XpIF(ev;d7Ji;RIrhY^M~|Ex z*dK|8R^hj@VzHQEMA$UT9OEp60)i`vRin^_FJNG6M86g29iJ-3;fZnU5GOf|P2$io za#$S7g?<4Spw;n3&2w|e#X|mUv@P=|GVgLvf4|IM)haM}B)x9PC_+=cr{1UNx=-wY z)zv6fwR_#FKFLr+=&e$_%#ZERI?qiM@#w{|4jnCoZ5YWI2Kl5|M=E>CQlg*;e`t-&{Q zUoM8|eL3tS25zc%2iRfvVjx@UT8!%V5NOYm=B{O;Em^CsVyt2ipW2MaJ{7~ieAz}? z(>%^8mRt?(VJu#ZU*d~vg$y6~Wp>;@i#sV$ha|l};dC^VK7ST|#-f)nwy~2DU()s` z!1`%eQsA%ZP0p*kcPZE^wX6kd{MHCA^V`B%Cj{;&1_UbxzRNe9VG8@AN+NdHh8Wd< z__8MnkqP%!%laAy`g;LcXv`feUBKbcb5+GlYS1GOCIbSJZ3|lnPFVN_pnEN z`P*ByBMVMMV=$;$>k)SjxJtUBB(^C!4 zjXxG2+~$uU>=WU!frkh=6LTfK4Wn@Qg;01=b@Ea4Qk;($pFHH5A_0Q*i!>pvn(37} z@~p~4W`OB1;b#9QzOZ@9RRFUSZ!1l6I0>Diq}&%MMOt9LcT5rEhjDqLL^7j4rH`-C zK^jqhmn!A*-kRMx;+Co>CQBn)RG@mgPa!La+_)RzfEe5@xJIP{5?gS0Lhs3u-Z zmKTn}^W8Jf)sJQI6?#H`2JU-Ia|#f!8@x%OHpqU~p5ynPqzmUfr##9_zjO$&M!)OF z);r}}$1DaSu@;<>r`+`ObZ*I3FlP)+nwp`$1vYN=_(;#$lvwqkrf{7n#Cb!z-+;@y z(eg^_drgoybY4|=VI}%KwKFCjd^a0C0@Z6z@rg#X5A|oTH>aS-c*7cK=jgXETmy;- zi6Km1Vi&@XZ}4JaY>FZ@!N+}9#21ixc)CVX0e*-*oH&+t4zd{G#)qVT6=4ajq;pS4 zuIfFQ2!~Ladv;erMFNyKL~b%m64bh5_@-%sGc(c)ZQ8TK?U>EjHb|bCbMS=f(8L97 zXd>E~ZPoJarC&lk5*VH6)B<1Sh{Z*WPQ;{qS$lva;D!AqEk2W+?2J_peM?lX%wn66 zv9^zCo+nxoQ>NZj^K?U3Y&xA;k8u}LOOX5E4Kw1%mtUiw15;SOIRNuWa$0dx5vvEJ z-dHzSVL(aI@Tfm@K>0k>y5S81K;KrH`M~j_oo^`-e4)Dd@L&td7g_npbD!xmOV1uLg@b)`| zGX3R$)!sub%C^D4B$wivN=xtbPRSoykK)Y3Q^)CN)noDxeNXq7WDes`)RFalTYu)n zudlPrX)#MANhHmaEfjgNBOda*M$B$j+{Q}MzQhUnLe%f_rA6|JQ`*Cc*wQI_@M!AK z%1t))rYaqe<2<4}B*gve>#pOZ?$?}QXZiRLtT&GC&%bhT*Yxw@VE%SJBJI!i|81cJ zfvmKT@5*{4xROOpw(w$?i}JO`(%}ex7ExZ>rNyoXQ&%eRQHpc73%gR+M5_qQbeQN& zH4M!sM!cW*lhm*u?{Pwtw5W42Qo?%g7t95Y3q!+!p%)XD-#`3$o=v0tSJcJt z;imn&r5$!;TK({Ewj$3=bIbh+Bg+2^-KS&ihFcItKVB#;C*M|b_NF^GQQ zerUcw|4;goA3jP*0yiHB@_bAt7DJ7$mb+L%oe{4Sl_4vs4IO1&$SGFUi8@s6as0yZ za?7CaYYzOOdeBA&|8>3 zzOPVnbh@^Rb=JiEM9W>V367xOH@_i2{j@c+s`1T9bCnvC;16;ISO+^KnBgmvo|SPs zDGn&RXJYmUGZg-|CibW7YQpX&dqjKTcWC4LUxQw^v76<%)$E`EeCu%djz-{+gpckM z?iVyn^^318k74Q1GMNj`@6Td;=ylLUxTz56&4I{=nvG3k&L`C#a$|qsNVM8_%&{JH zg8^mPJ3qS-h#K53N=!S39rzpT4@_0~Dakyin)@An`SR+xU&{~5V;VcCs3mPA&}|_C zomMTQ1Q>rz>=$Z8S%VJr)$@<4IeMtgyMuX<^N10OfPn!b2%%&UOWb&j1_UQJq(6C_ zSz||Ipt6BbGs2CRRDm^=E;l$qfHwCwJ)MuXxFnlZH%3^LZ`f0O4$6pD4xSe)DOzvwhD49>xv9n&=rKew>U=BA z38JNCbJksBR4$e>j-cc8PKMJo0|PV#uRd!`*yj|^el!hW`h*LIrIYMQ)?pmE>B0+z zX*{Uod9a;%aKh9XLRv&aGP^Fhlyw zj>?REgDi`>F3m(S0Kx(xl6<4ZrcSXPy{4{qg;R&3X}h3>LqN#L%PBnRXL|W&1v6F} zs(qwuxo0=#s;Y6g2Whrcem>pN*T>&o)CG&Ql7z z^<+a%xeW;3Z`_HB_WmrkG^}@!(t2bz&+Q)cmKK@IU@p@Ahi@2dexDcrV|6>rE4=sX zFl&X+C7-Sztb%G@KPjl`kt|;aiXX>)sbHWF13<3pucw3MsOP-AMJh6xghMwpLz!mo zG=Vr458#hkp3PL3=a5#W*9MYSss0i(c|%t#fUvtRsEYn>7wJ ztaUHh<-DauPumd7mHW&lJld39&5?TC!-EaC?KXk|!zqC&zo^mBb5FV3mhHul+G?OB zcwXcD*!c9rp71(C*>fM1DKuea{Pi(*POlG%dhoaIc$#~d+>&qqEJ@)>Uct5*&J>c~ zNkfNqCC*g7#F<2YI(|ZFi1Xoh1x|y4pt0)}85$0q@jyF^bk7sennVN=Dd56FD5ZCC ztABacc61U2N8T#d4E$NT-<@GoD6B^7a9A>J1_V6rcmH;${f* zq$vk-e3R#4I^C}s!vt4TN<2yHQv6}Tuc)PKsXXy4CC8qGa>le;y&fBJ-7S64Ks@20 zcg4K0E4QWYnwmCv)I{a)7XqrlCzCtqzgl+uDknk`+-Sq3xaaLvukQ??_KI5v;mvG4 z6LNv~c~*oy2bEm2OokTJ{>DMxfe^})A2Wk49Jf&{8{e0i@@Dyd8L}E7tQuC`lAzqt zx#oH5q5b_+uMc$+W-WmykwO5zlo!M8fktH{ev0RtO1{!)+K;c(Zv_smv~9P3GLR|? zSyZjgbtQ5_62G7fM?JB|t0u+c!-aC*;g@qxINIwfv0=N`$7GSD$Lc>Yvt(T%W9ajJ zuS32X;$>sNQbekr@_3}AB1j$_=~(Q&vlnGl_kGZp6$-VW$W{3}!{G6bfx_^QkyRUU zHz(1fx3n9gKkp%pfs?(9?R*&7S94WhJ<>ljpCpOcphqP7e^ORmx>g_NLcU&>VY2uvnQ613dtE|7pK_;3MP!-(-kMZ_Vn z;l0@a>wBW|Nv?Dy%B2LJc*OLgJZ+sOst{_ebX7X2_H0V>a8b>f@!GJG_JiC|_Cu1| z;+Mx%bhNAi8}@cj)e~~=6vCK-K=B!4)>$7>9M~ucA@WT~{_+R1k6sHUkNN3j+Df}~ zTq7cLcXOUboU!?nFOnM*5p0rbi-P8U-9_VX9aErljiI=p8xA)*;UjdzYp`&Y6}0;9 zeeq3y&z39WZJ3B&-Xcn9#fAYlSGvpVKF>9~%O)yRZ@aZnbbHzI@mI!}`8BosSrC48 z#&|UJF+W$&=-j#RX^D>_qt#Y4IV^$aF<-6X7y}Ri-@;e&0vR9EL7eMwJDARTG>3BW zhMkP!>}lD4yj>VjyDya_nFRB3z97JTnF+QU9pe+y0GoRz?CG;oopuZzWpjw!cntr) zvXmSs+_X@ zoywQQpGNb6)YVSo^Q8AdyT|3xx^zy=;QIL18{X6x3`9|@dP$aPOq&Ka`dbJaJsjE( zINjVh=%dGJ!$`1%PU-XK&wI_BJv}}B{QR7p4sxHW606k#UI@SYwV_bzi^7R5IGyRd zJNT%lr|0kA*Z%(g9v&X@@_nW}G=h`zJ!LNrO?h4RzsAPKa&mH(mX-o-8jiw@51zV=Ox>r; zRNXwjJUl!sFE39_ObiMNN=;2|ZWh^7w;SnhW!bRcOomy>f`XmZ}0s}Btg1#1{^ zCBI<$jgNJfsBZdtg5Pjv7+D)6{d>GXIq*LtNLp%mn~aQ1NJwbsROrW?Jh8`;EDyNP zs08Y!n?Qn^ii({C@A2cu6+3ecw`cMz!m4U&YP_j!Z??v#N_C403p;L(2A_6Zm^nKi z?h(jM?6WK?NYJ8N72>a&(oLEJ&-zWZrf%27p#Ncym zvD@9seZS9f-@|a;bK?g=Az!AjmXiuJlpeaiRc~3ZWMMDw*cDdw@Hj6}PQJRj;!VYe z3=Iu|(TRY7p!Dr21^twCNMKL2OLN&BV7pzK z+ZxqsXE6idbYsnGO!425wZND2@?>tcahaJzFS*%P7MGW^Rg1j6yj+?W7F#_~H=7cb zWn~%Xa@H%V8NC@Vn7qBcfpNuePbLV&FH9^f@@s2}RnKBVaUfbz^0l+Tl?pU$%*|IX zk2e#QWLXjFY>5whc0Hd!gri(x$N~#l`Tq=EU0q{iW5IMcLtRc^ir_I92DTLk@^4*q z6q{{2rOqRb4GnJ+^j-0sNnJsZb>8iC`W<~|Zgg6HLP@Dqpz%};t~6^e2xf)#t_%ze z08?~-Bna;{BM<&)HD~+2966_<1hM9)tbgU;>Z+;IKrmXZGQmNDr>UvAy1FVOBV+rB zE%imm3_9VByqc?hK-G#i1H*- zC-KbEEuBhWH39?iZ=_~tXJ=(CEs1M}zp~8LAD@`eWJ{c$nfVy}Z*UoRl;OLvCAv_{ zBeuF(!W|Ggy1HbW3*7i{pSv3oi1l@uNa%m=r6zHE!Ypz5{nxKgJ1s^2%g1fNfqHv; z<)fd7`Ch#-H8lkfLNPxRCv5mxtFWjjIXM|b&E|1lULJ5GA*Us33X1QW$3 zwYVg8b(0%L)8a$YQ?z%zY;CvW)x<>>nMxJvdHZ*EcKG=C)Urf9PyUSE+}zkb_517V z@9nLRxR{!nij0f|_ud@Mc5-r3d+`DQ0z2^{?}|ejk|~~ol2RH@H@CDzD*fl8VMExN zxHio2pOL`Xi2qytbYo)!97iBF!QI_?CqR;f!Sz?=XL4@i*8J1c(?D}sSXcm`ir-zV zo-h0R`}l}vk7g4c>YO2(F+iO4H45~FMyF^;%>ZbXr|~`M!luE0@F3G7J_srW1%SuJ z$q9Dndps-@O(W6aaj^95+c$l3D-u~Y!45DQ8<{__Br56a>zkXKcXxLK2u`wmY%KD; zYE+#BmsU%?ZiHZhnn~PDN5qKDU5uC4C5)aDvkQRQ$;ruqKkEAW`~W=n!Tx@dQl*n? z4<9(r{l(VCnwq02UE{?Tx1CK1)a7ux5Xce4q42?j(u;rH3L?Y9MH)Kz*{)#$*O*!O>ALT_rKv^Bv29uc_(pna;b@?d|QS{+G%V{I1Ol&OGenTPJI4 zYrtyRcPB-SqFTezslp+MDQ2)|!$f{2qR`b;?Ey6|((r%F9%rL`&i5Cao15=b>^X<9 zgwZ9cF@5M?cY$yV2uv+6`}g?k%p@u>;01mC`Sa)L&Wv(FT!sHM%L)vsoqaQFm2KGU z((AUEy!dVaUKDo%Q@KMck3mdEm3nTNu4X%4aKeE9WIio8XXz=`R{?E^V7r<5g{QD)Sd66 zR&mq;(!Oa7Biq3CaO}WXzQnTrRc30cVu1!gr%RuX%F2U_2pMgiM|eSYGZhYCI65{| zry$}Go))-qXrYv{-jW}Q7ay2#@sc&)AG&hHxL%!d;-%Y)E!Nb!x}#XaoiiFD{?7mj zlG(Qi*kNSE4+PBo{Jcc)&r`(9+R(kdJ*SS#!p273dQ8X5;i#x6ke1A*ygl{O5BpRJ;eC=*IM=uaT=8T42+Ha|NiP)?TZFSHg9==^JsZ#anbkY!dWnV zZ)ZojXhMFwbn>*N;yFJ*|FdUvGc%1XEpvs}<}kIx9u`-*5Hdx6M(-LFE=JXl{^}lb zkG>|p06FY9OsFJn00jDv_VNs)c+8Ua!G>b$-7RQI+XcWjYzgoR)(p62Y8ngEOh$)> zZf+0!I{{n-t-Z~}PNexJLi6Ow2(p%Qr__EPBu}N$UNau|YjeE-!=X0&LdVzVc(2oQ zy(aY%@1Dt~Gbl+uIbzUD_B5M(VR-KbEuFL{+WnC$Zz@1t3Mwki95x0A2!w3)-**9m z?&QU_?*)R?18R@&@yxa}A>rg3%`Y!a@87=t%UUfB3;_hR1p1epo zp;xyH6ec>25hc6}gu^0Z2A^JIz;XLA=)XKqtL@Lyb9fcRG&$xo=! zViWTM2%aau?sCaVa3G+;l{}@Lpu{*ewIL1!h!%OTdNkZ2W%Y)@aJKYIJI_w5QU25J(*8m=*XH`6Z{( z9+unlkquMxl0l{TEt(!2=`pgprq149xEBZ8;ax!UoLfQ9eJ4j0LBdNc8CN`L9i96a z>Oj@#A759R;xNDepW(R0dEEgj==!#{wyLVChKA{Bx0{kXPmmQskq*k*b>68hk5%k$ zjUfv25pEAI9=>pEdR}xGo+7_x6?>Z9xN(jN?wW@w4!^5hp9J3~FJ1DTi`G^ANpf`7 z&s~8U)Yo0wPm*4vxdY4jomX*PuqV;4>|Txgndu;mK`GvBz!m?!&fb5~UQk=U1E7lg zIj#2XF-Ucw_~`FfV2T%cf~7e%VoNm#igDdhEE!_GxY@vk_5q^2p#fUgmvWE~Um%1k zSaMIRRM#p#CRGP-{wRQ=SJ&72NINUu)Zf2RdFrOKERND`Z-Fb9ViuBb;^$jIrRVD;|GG|>k-1vNh82wkvrr%8^t&S%+XR7aS#r=Z=2}WXXP;UP2{x~r)u_xXqE_r%Bgsjc>Ir^T+uB$pQ z{cjGd{4EDe`SmqX?KR}()lM#G`2kCSO+m=ZD#XgBQ5PuMRc5r0AM-PBgTKA&DhkIU z@%@9b6g2L)E*}txZ0qx8@&fh z+e2hjD#Gy;Ckc~#=_e5(c9P^qM?V&=A_*E?5_3w1x% zZEbC?WN)viMB|R#k&Fky*0i;;2y0~X^Wbk|yU&n@()16kA+P$WO>Dj}{Gh^x(|V+e z^$;KMSV2-Bd9vp=vrrzdRcbV0)Zjtij}`EJA=zU_j(F0=3fnNtTec3YyBKiRdTH0U zwM-lg%}T<97fe%Rx__(Y$ZB9Ke;NxC4YEd0>mUCzE;-fB>^WsDAxN3^D^z)iLbAKy zZ*#F&*tQzW0z@5a3j^+Dv&{__|CX{UY>WE1nqAT-wqz#0wJp54J(6L0@G~i<4A&_~ zHuG=wQ*oGpPi|{d+g^1e zt=Rn3ZRgETw4Sbm$DMX)eX#M!?udtXL_ zmq`2R9S54|{W1&-b7OD(FPJZ2X(23%P+3VUTzS6C5l^`}wV{x=RfAiHYT9HY0L_wL zZNy)_&AfL?C%lzWIsM~+ z86G?YUwStU#bU(Ml`1LlBi6%0kRs3n-r*}=o$y8{2jI}ba3g441Kc3WJ2q#E0KV0{ zEC_mTGzNW?XexD?%w;wbmmY>CpAkI2{f`CTGyeF4z5!AE6p~NCtMz&23&$E4CB;5I zUoBP4;3tMa2cqRa=U~F4cNB63gWG|Kl14fxS zG;;jGwkMI90?&!_NJr7fDJWGk4j=MhA0KA&q3~ozN^9&7cic?V^We5X3mGq}@p;%` zT$VS3&(?v0>&HX>G(=V!dX{Jlf5B|gZ4W+MzsuL-l5r8S(dVC|I?H1izNwHrp$!yJyE3`Yo7g4 zXg1wOt1xNlO40vo1jg6;V}RZT6Wt;Jixxc~qGX&sY$MlRJm;5wjJ*3dT9~4`3cpT> z^u|f`8Vj8~f9!LH?*3(Hr03StmvwhXj!n6A?M?xdlC-XJDT(GtA-?C_0=Oh-IFRw? zS?s!kl+cinS81?B;oril_F*&xdDB1D6z{8hY)tt}@_$F^pu>VAykd(2L_T2A5kb`@ zb7=z&e}CSdn;s;L`!#oWdmB zRmx2~rt8XsG#x#}ok0;256{d+=i}?=kaX>w;(`Rh1{enDE_CDswwyGY(2g?g(jM%w zAnD>XlvwqjeYBm>)c$nf>3l6=i%4o|N#dk@k8~@TpFWT2CB76YLV@-sn{B>_W1LTJV=i#(WqPOBb~n2+eKoJ2Wx6(cNH^76H>alBPe`KOq|lp3=IQcp~N(6T5%@gf#wZfmMl)Sv};4y#e@?4C=NUIn64s{c@I&Z;YgQ zTEg=~v9e_1AcE-(I8y1Ncx_)Dqi4h!{YqBH=w%#QwwVIhKA9Korn1N5m8Y_9X`#CjpzBd z#cIt|X&xUy^HMu&U}$KluWu)q-WUE>`$a3Did|f47R9XAIn`8E&rVMtN1K=0ISHnN zU6z)X4sSd+%tS>^RG3xhl-AbP0tN{X#Oq|_=Ck%^8-NjUS{kz8b@F^$8T*1MuYxXp zIBQa-@m2W<;3`K(vfc>sAE58CZFseJG&Kne2{qQ&Ygf)hGjNya=zZ3*wXj&5tG5Ge z7C5KMwy`Z)fGftv%YXj-0hEZGoE*rzhor{RA>i`*dI4~GQ&aKk&jVhtqFi2eLqh}T z3uZ+CC{?gno|=nb^`)U7d5XV9o!^Yvi+5NoObO_I$b1c~uV3uZrcz{`ST0XMj@@ za$I-|KDoZW*8Hrc?9@DF&BEwpZ{Oo~pgc@rqszmdP$|sEr};VKXFh$RQe6&QW)F)omL0Byl4vT0k&FMJ=_s*#QzdAJ?5k7SQPbp_(il9aX~- z6H8_~vnHu8kif}{ii*IqG^HdaQayQccRA|6-?Crnf4g%Wewlw~YPAKbkE6?*&20ZW zK-xSYAjpmjHRK@O+}L;`;a67M!T)3b-&*0};l_TK1*^h^HYL941W$h#Kh|$)ZFO2| zqrmLi-Y$XWf;PDxWVCX$`a9R;Am?*z#Rd);eVn75?MHFZ?qd0 z;XMx~!SLu{; za&j(q`uhW3Ea9HW@N)&99`IfDC*nRhkYSRfZAEoq|ma@ z@x|e)_xZkqy?xsvZD;>si}UKYf6n>sTkyw^YDe*p{yUYi)b9O{tOLvGv9*UkXR}eI z6E{2a^#}GvA~ygLm*LvYExcK=lU1izppvFF!SwZv|002>t@)Q0dgi(DXWT&0 z0519Q zwmv?}mY)*f7@#w+yJ_xTA1t+JW(w8v7TQ>?Fd;2Fgf~Gl2~mKTOpQ2)7folYOlYCi z+6&Hu#&T*ZadeZ6lvGeih$>R9T&GlvZL{*C(V;aj4@7r!MLJNpxpX$MO4?5)e}K(<(^m@&!{o5WRpF zh4?>31;43s<8$?P0q`_pQ++!}>x01SX6;X_j;V&7&ma2gnS-6M$BCcXwwmsLw$<)e-+& zTnbv;iQQVq^NnM$hGURlHc#itgJT7mwd zPFM*X=X3bD^(!JZfO)hn>xR5_;y#u~*_xGcOsL#dA zd5ht#9>maq_9wRSxt7pmZGo{{@aQ;a9U!0B(WzXj!M+(`m6q zKtu$=Lh@?EiAG+wEwKSm$6sZNMcm1QOUwy34OFUtutb2Iv(4P)$kXD)@*{#=Q*&os^IlcKXAQP3_ zf7}LGS*y`3K->IJ>F{MGv)zcoO>Y^X55bEm{#O&(KwP*BH@;aJ$$Sp*HJ@X@F`6y= z;%cYT|GafsMNO?VlglkCd1DtSo;sJN0|SXl1wc9XT>AM_FXtu5r>_lX8XN?O{hq6Q zO%Tw@^2r-3)?zDxZVx`~yxB2EmDuI*SAJ=w+vqO7+Pu8Hq!My0rY`$0&35@nL3C8J zUBC*71nSWdK#tyCS+UCLHp+wC6hMhBEg3Z%EX~cak%og@4xA5UGPY@l`39RbPWq)y zDcO>4?h{|*-cq35X*mu5M=EZD|y+H!Sy4MALi!Lh^n^Z^g%FAmF%w;h!7l9q-dkKEpdUiIEA$j^!wMZ ztAN~{Ja~F`hUBH_bGDxs*E7=Cpd@g9E&c2R?^kIcXXLUR`7R%e3K0IBot<4=1O*X5 z8bbpEpFG*eX&zxfNdKsQZN9s=2PpIsoBu=cKbKnhxGD_fCF^S#M@s>5t88Tj670s&kxR}?AgOG< zxvEM@lwZ^U;=MS>hHZUIZLKB;DNy?Ab;~?m!P^XSo|C}E`uWrMTnG5|_gyX03@c~; z>%)yI`SP*4dwcCO337QNlK<<=6io`*K(Pj8!VWt2r!nf+K$)@^q%t_+9_f}1^L+dE zLbvlOkqdwPhP)qH3na5zI|u5cHgX=_ou!EaI!HjQvujcP+=TVgL$oiubKrMo^>+w- literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_1-FLIPPED_90-00.png b/tests/reference/output_2-90_buffer_1-FLIPPED_90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..f40570dc51ff377260e279da8d4a3e1651a80cd3 GIT binary patch literal 14446 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_2-FLIPPED-00.png b/tests/reference/output_2-90_buffer_2-FLIPPED-00.png new file mode 100644 index 0000000000000000000000000000000000000000..312448901a19ce9ff4c9c8c80cb87a1053a735fa GIT binary patch literal 5258 zcmb_gc{J4f`=7;9iXls7581M1ZS1#Xi^wk78HFygjNQylmQ=zGgACn}WJ`s{j8T?s zjV=3DF=S~l3>wC7#+`f5?{~iE{Qml$IcJu0p7;BCp7(2cKc7jpzG}kBA;{uu1 zZ-JzQYFKiUe>>F)%*^!?7fCnc*lhXuC`h_gm&DP$p9RjN1AeUdW~5 zTg%omzr$8e)eNRu79^LQ8EtEQCy4hDmXvxNc{I^sK%d%Cn+U=a;1;FuP6oncrR&V>un%!=3&YcbD$pmU`LF{L1(-IrQeJteUd+`nS z%1cHH0t#WkrZ=FP*X2iZg^mE!$Z;*so<&(CG8RF<{;D>=XP$Zbx?BWr7UoZQ^F z6vXtRQ?4wW9ix0dZi1VQ+U$Z#j9IIuUHFjaBU&IM$urvtpHb}FE(3tHfKu11n}lDj zgnkYNRIL&qVju3LrZKj!QAGt&sL1LEqMWfs&p-|5xfk`K&u0Lp7Z(AzaN_Y`Rg{7$ z)HYQ~TqRKdal_7E(<5O1W6=rQ5Hwh7V%u%YjT|jMrm@+^XCjNbEbQs5Iq zVA=9U$l{7N7q<=4bzkG~F6l&#Bpu{QSG} zvtp)hK_PfCz_$gO(Dd>dxKx&**_Jy1Y0dvQ=zc5wZ1X4*q$ z>B-xT=sklR<(Pt9f2jd#jIXSEqVJNG{JJ)GP@LXse`{MA@>5$G`MPvSMy7KsPqqe; zWiuk9;%>{5M|-ov`#9Xs!hAxxU6(@2nQtoHf^{iAUe4&oa*R+z$s4|g?RFt#&h_g9 zg}Yw|2Bv3}=i`3nTvJ_lf$kRkgg0kq5qRq5{)WdOoT9KcPp3OZ{v5LGM54IXV+#?b$(>W zR(&Nb?uGcwIg%Cg_}XnVj;sx(@*5iw|h?raX;SP%h!cFuR=*;Q~V}a{+cc z`-bXIp81Kvyu)N!_WEl)4dbGqHM^}xJsf%F$n!ICuV+GITeOP;KP(^zGT_BN*(?9_ z@0w#Noj>N9uC6&yXY$-L;ts8SXC2=Vd}=2!T%)EtQ-@H#<5@(WYX(a$)$_f-;Kg~i zE=5w|3i0UKv)JZ?*%fD+N){)%3(`xr_wE4ks`kt%!Dz4L40R)_`dE1an0dCt`^a+h zBOIH@$y?-fCyqZI4xoKwFGLijT$o2I^B#kgD=EQubaf-Olw8eYh$hq(uYM;Mcz?R_(uYG6>wq=NFx;A6)49U)3R6VNO z?*~t_|Cj_!fZmY`))!^RO-$ZM`B3UvnAR2BAY|IAL@ZbTCcx~6a8u{Sw%02z`M9Ih-O*$gUQ&g(HFR2KL2 zhsF&wRGT6x)u*HmPT^1Hfu3jfu1({5CBP+gxQ#NT$*!a(+eL}v#duCM$h;y4)RHL( zK<~P1uu8qZj`*DIYWVF#0{_d$ULwqjT=`!#L0;spLFsY^JX_^7jBK?iTK)zUw>#gs zUC>=F`#6vq(qgVaVc6ou&H~4?8fnN#KS}N=Y6T$GL42<`2k{e5<5}c#0L|Nqg6TN{ zJZI~Ce(O~DcI0fR8>)7>JLrJ7DW~&Rt1`${(Eylaxiiu9#4@mqLC{QSb?gq(ZpEV6 zuT+~}DGezVz`$zJfYxN*stouVvJIRf?NxW6a0A-nsKrlKEv-OCfGmpqa=FI}dA~M+ zHE{_II`LFyu<-PIPXTVyUL1`>CYu2mquzD38L*O!4l?sv0!N~;g;h+cvfa@zbhLCj zljFrcN{H4*F+fJB8^%5iyWbYeZz5dGD2{Fsl0&(dg^Sziy=c&rzBo_c^C2COs!2al zE^gcudm;!RQe6rdKjtKz(PYEm$Ty#=F>ZjrJ`c!Th7FJ~IufIP+7jqWl12<#bsz?X zD#!1d`yiM)F4N}+87nIm%OG{Eq!2qLe6v@8Eb?)NV~GHjC|B-N@h)1yn}fBWC)a&$ zGW9qT+h%1YjXt={cY*drmOD;zfA=d2P#?&#jawdYZbu&mHiz3qF>W0eH*=1#R{dSK&E_hERFy=n#Z6i+wXKmz|sko3LU;D z!D@K>wxYGIE!fW9 z9ymWgZz#*Rq-P+I2Fx&Jxfu`uzKYi86xNA2K8k@WueI`8%{2;WVt zB%C5xfA9vh6TUlN;RboQ0W81y3(ooU?3HXlg<~}vtT&JKfrw6oA)1>jIVK@*vrzrk z2^ILy8y;=<`SHZwd?c8i3oub1#9kQlvvJb?qCyb^OW)2wn0xvS0l+vq9kPgh0^ddM z%*RUA{oprP!-Me!&`mqI)?GgbP+ zVuVw$*|Z)P_`T|AUnf_)y~fZL=O>3f6RC1fYSsNdnUP$?_M?VLzp2>H< zVUjM&`bZcG64nkoa`Wa*z;xvIF3E8Qi&VN^M|nv!H0) z=Cz`#DlVtSJM)Mxg*FR@g1a-JLL!8@?^ic|PGOKGS8`xc@w%eyl+_7#UFu35-aB0{ zdp^c8DL-T*pwbm1(6CS#gNPy%fh|QN1{J%}{NYxX>s!QrnJ)SXML zyWi-;qr^$LR}j^?Q+#?0WAH(C5!O?>)XBg6R;x8jIj#hPqb$#3d1R?LCr4VkxxBI>CVk;4q6D19-^=-q z|3PZ&Y|5m}=0e?w@vrN(W@57no%^KG$J+KTdk|w#8EdE2V37qu{_veOPj!QytB2qi z&GUOuR>njZ)U~w0-`gK{ zZ>VYHx#}Kr1V^}`DF-N^5sJr)KWu7Z)y6in(x6$Kwinfwb!-pDhK7c&6AUIUGtgPi zYTU065#M$AY0yYo8b;-=FHK61{vebkV2SIKu{gQamb>hovGhgbH-MF5uwx8UV04M8 zD(s$b<}3=#gQVc{CCJ*I(>>#P)8AKNj~qQoG7!6&v0KX{)mAZ&%HKAE%waBv77?c^ z^d(>)KT3E4DB%So2zy#jK#RH=fIN5@STQ|n)z&vRysYIDGs(L1yCfki_6GDI!O11>Lga4-XsNqm`g}+-TYyaIjsjBpxC>E197Z z92vrQWLlV2zovq(VOPy_cJjNMT6>S%LsV{fQ&M#A<2{wPDjHowp%k2;&EJQvkGdkm zf~dz$npsJ^pNl^+JC|UD9Iwuaip@ij=k$0dURC}$Vf&XwXJAzl>@GjE4VZf$dzc2L z*@03rBLz$t4KQ}pRI9CXCyfh~8bkDV#P@rL9aK-MF`K@51(dti#vQ!FS}h`K(nA5S zr@6s%0|El{9_7Uq-|+R%kwamtAMk;h@wNsJK)lU%Y?Cwxz@lz;{Mi~IeL;ZsqsU5L zA{O^%tfJq(Gy>KBrfw#{hT|VTM8$zj-xUD2pGEX*)k?S?2@P0yk5^iiQ0w$W*943htx z!*f0V;g;)|2*uwk3vc_+VY_nvZ(QzJttXk->oXm)M89TQDt$CHx}G;So?i9!WWooN zbPPb8X6F(X4^<`PYeAfvBF|Gu1A+dY{(q)63fk;3Pct-tN?FBcp1lG6E(aVF3Rr>7)3?=rQaV3m39MGu9 zN|$;sLg-~!X^vc!bZLEH6=L)v!(-g1I>h8ZofM0P@uGN@V?LgwB3wk}fJ%GvZ0bRl zkE6b1AXU%BE8b}6`(D7XNSZ^vf+%d^{-HOLzojXI=vUy<_Mk=5$P6|3k-5y>!@-Yk z*mC*ih~X)^P2L^hy>{1opuD_1wI}q0MHhCX>l|JLKAAyQox3kr442MFjgI-e<`ZHH zW55N3A4B`&+VSLxML z9XO~vN7-*S{5-rkT~v=l7hy7iSHF+z&K z>K)zM+RoC@!bJY96m(;a#cL7$*i%k*FizA$kbAe(%U#s|C-RmGxvE!KdCDpxosrU< zD6Asq7oX?ccT7N`=z=ke1$uaeuf(r-@=&sw&<=}VmDk^LM9w1F7lZg~i#-NLUsc{3 zULUx6jl|B7zbq;_(d;65aOFX5^p?K|ee^l*)2b5#dp^E$%$HKs7d=AKV#XDieZoO+ zMGwJRG4I}G7h-zt?d=VtqM+Oyu4BI1|4x{H`B6q|y*n09mpfEDF$89@yO`j?#K0G7q0G|9v|6k0t+|#Q&XL)(fM*si- literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_2-NORMAL-00.png b/tests/reference/output_2-90_buffer_2-NORMAL-00.png new file mode 100644 index 0000000000000000000000000000000000000000..53fdb7b5e6b81927fbf8248f229a97f667a8e54e GIT binary patch literal 5288 zcmcgwXH-*Lw@m_w7o;di5k!&#HRZ11V2w&$8#0gmLH=%-& zutpeq84o>ZPj8sHxc=j-Tsja(T7UTGUfs@eJ@QVG6;RH-8=x0`E@>loU|;~i$RPH z9ZmE@(lN3EVzu^_Kfrd#*EH%lK_ZxNq1WBkw`>+VZ27GOwA-J=DBg7?rBWuZYjr&~{2o-#D!F%YD*Z8acW$*SI#SkWhX>p*yBfKl)>lsG78+(m^6kseKbHg{ z?#|R4NKU)ZssCIauSb544OsE9ww$oWlP>h)7`4cZ?V?b-$!tB`B zpj*}`Uu#n*_jo@E? zn%=$vKPK$(3%N&gVTg*O6SQFfami9L49)%QmcEPRTmyo$b~W|Xv) ze8)}}->XfS&}-=7MPd54svBu=e$YfUiZ_pHMX~+9+08FYf>mIZCIX+4t@}jLq|dv$ zj>KS8{HkYvK4sCbclp=1Tn~6^=HW)T{0>=*3pn?<9ht@wfmABicelbEG%Tbzz1#&{nWLN%6#MOh7 zap9C6fSLte!%QP_u{p8zf?vlr zB(1JLztl~XX#MV9Z+)*!|9TLK7*C-{KpDCRzxHhNfZ1s@`WxN-tfiPaY2FtD%qSfE_QDDb$-jsweO{^LV(6&aA}SesLo4e5a22keZR^0MFEa6_93 z&Cw1~fUw78^%K5CbqcOcu}ZJ(Ytf|U{zhJpO>`+3 zlHzGnzv69b)bwvf&}IOo(ip%7PO0B=qpU9)pad5M$zzx9wqSB{K0%7CweWZLA#$mmrk5sf(zvy`deL%E<39R|AF`8b;uTZiKOJl;1)9 z4rFQ5*2KtewC>`1|5unzTe-sQr@kO=qGLg4p{jFmmUM2Qa{)&)YDJMQ1)lJSQt3b9 z2Tv_jUXnP#kg(ueCy$#a8X7BtgvNeGG?xHAC#t{MxXl!fO~6%0%)F$vdyFu&ZpI1m zr4=sM2##o<))xN05h3Za{bzo}N~C_J8}H^^8pU~tEWaVqXAt1+<#h=PHqTT|BA-1I z?1=*my*Rg6l@GoONr|Wh-}-udEXJ0uxU@8;n-o`If=VcH=N3G`$lcXD(wHGJXn;2Y z7604Tw>Gc6BvI7#oP{lO`T@RH_2tT$<0>u=0fI?AN=T~4Zhm}36~{f#91$ui_jr%g zkV<2G36MMn#R0pLr57F)6+Wv9Sh5*=pLM_YE4DM`{|3`tJhHml%9a@PxsWD

5w)5#rSQuKu=!?#^J`r-CCf@NumWR824}enV9m2 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 A7XSbN literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_3-FLIPPED_90-00.png b/tests/reference/output_2-90_buffer_3-FLIPPED_90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..fe5507dc639806e744e1b37bcca19dcc52645c80 GIT binary patch literal 5167 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

5w)5#rSQuKu=!?#^J`r-CCf@NumWR824}enV9m2 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 A7XSbN literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-FLIPPED_buffer_1-NORMAL-00.png b/tests/reference/output_2-FLIPPED_buffer_1-NORMAL-00.png new file mode 100644 index 0000000000000000000000000000000000000000..8c456b51434b95d4d02a7a4bceb25ed31c5b8284 GIT binary patch literal 14407 zcmdtJbyQVR_xF2%BaIwFy1S($q`TuFAl=>FjWiMh(x3+sq#J3FZUJcl1p#Rg>3WyX zGu}IXcZ~bT{r7#B4mkt&*?YyD-#OQ3t{tbTu7HC{h6#Z{aFi5fwIC2AS@8K79U1(4 z@gZIw{6Mu(QILf^{QL8+qa+Oip@AsLN@@G$9dsyp8V#o&?NPaZ6wQuPg z*m;^X#bs6Rb>@6-^u>lN1PxXy#RqTrJNy!Si8Txw#u#SXT5T%Xl=jQY*U^c1@L$mK z&~J;rc-18bqd{koBt*iOr3=xGe#AF9EL1Y_;P*ME4K3;Z`T_a1yyuDewipEb6tLrQ zw&Be#bxB(GXMJmzS5WA*bZrTHDXU5;(V{-yi?ziF~-7d=Nbf zx8sLEqNIDq^K!Gi`3;Ng@f)(9NYza{2HZdQK^E&smdT3_#w|Z8{L@d=Nd7 zGS&vyZ!2Y*-}0uC)Nx4WVMIuzkMc>mtwy$%g2kFhgIZGS&G~UeL(q?(i3oP#A_+-i z^YpijX1%tf;g$V<_ZK1#*N?4(F|w|u%_oCJMuBuAQG~fnxQeKESvQi)M|dXiJg#Bh zxpR`A((bq)I}Jha9lz*(_SIs}4gxXiqw4+aqVH>2E43U#Ya^R91bIB_&xnp-S;FjX zVR@d3L@f^ng9NdAmY8gGk+gxm;L~QlkVvXLuq{@TR@7h7vhM~Vry0G9bxwY+o@<_! z=&$t_QOabUKSZxJFmKJ#>;Hyl?>6Yh5&w{*@ zR+`EZ$%1_4>Z^?^iO(~thqcW!AVenvg(xw^iX!S@I;@UuJ{}TGlE^$0dU>9P!mY9B zl5H_)vd>smq`$>UhutBuW7X!Ls!zN~6i&oIRI*(6oHB{IPFT%Y;B0f|$J=c!DYLp_yjyg?6aw)b%oDM&v@j z-dJ7Lpv6lL_KB7#bO;h$Cw7)QuWG&!tp}0Lh(H-+f}vq=d^Wi>{!v6L|ujBmH&dOVsVHAteAk&g?yQJ-dFjJ^P_T#s4v7*eW{b)+oAb z_<}c4SFZ)in3IAHEsZ9W2FMT+4kTL9E~BSCLO$0jarXu95=1}Hut=F#z)Bza$J@Pg zJ>8)BXE+}!l(TUoh#+V@=n;zOg^DUwY~~sfDQYilTtt!#dlta7e>z{l*~hfCnxJ@{u>W3EQ&-X(UZ!O z!$zN0QXG3!OE=<2R2FHQjeb#6@OYDmDjy9SCY*qd1|NdmZNm}$+hf&%#C?FFTbX3; zgHb#p1s#Hnh=7Sm`)T2a#Vvf%OExFOv2@J|rgdr{I<3s^Kv7RD1=QmxLhBltX@I@$gZxA6y zn9oF;+6(_Lk1Z?nfG6sYFw$zT>g1gwEmL{rF>H%Z8NhBypje$(j@$_P`VWrwys*f`W5v z!;FiB((FTua4aeW8KwzED8s`cJtN#+VaT!ZgjH`UIz@>nf7o*yqzr~Hq6Z0Ta)dz@ zlM%deyhHe3-+x;z4B$`+n~f5U8bjW^izkzNgp8^yB6ITtT@J%RgKr#UkNTO@ z%)5;onYAu6`%M*D+ei?USbCpqn*nZ^8DD%sz-sz7C$vYGaJIOCbPv0i{cR)T=Cftp z)8mlQCBpU22MZrO$UdwRGZnX&s~6~cpTeRd7S{~UfM4)##;Yl-_YG!UE+_S!|Tm0i_t^0Whu=pD!7a}U$3*x zZ5}6a8PnrC&2$ z&9|vmYGLZrCCqr`Mmm~p^X8OjLMPzuBbbb;hYO!>n#bxz*Qfcy028&5Fhv*->wK@9 zRIIJJpGdsXz%>WEHHm$1jSnAx@pL_G<}3i;orTD8vwgJA0LQZ1JxP@eZ{7L(tC@HG z-$@$6OK1hYyrDk5j2TnpF{3GC5VNv}V9C7_4IkFXUhIj`VcqK^__JBd>K4s?f|tBz z<0I%n%qsgPEHR9hmp?GomqtSwy*Qac`Dy!CL6P%E6y2zAA$(E4-eC=GWZkA`AD}6) zl>d;BNVU(W^mHR26FJ{%mSDvPa9gdgx3Bf3-G{S2Aw#d%5+AF-2shC+du{O6V z_nBI5Er}&XH$)Ngfh2*?hW;9)W)ZdOGoo%DC{t1mFj7P~$+qZ(@LHw|ClaQXI;vD+ zZMu0MsVAvQM`kwdpj%J$&BPCTH;KN!5{{4rLrM~-v}P7|2}L-48_GE0=iaehGx=7j zT`mo&eIh1tn@<`o8I}k-N4`1%)*^g&;!2JI)a=D1V6=_2ypo0i1cc7*SG7HCPwrXR z*9b<#7GJAV>*bR?gz4vxDdw}aWFe=B&DEf?WMp9TZACbabL>7dNT2SIaiRI}E z^U@oS?G(aCk~r^>e|*Y5J*-O$&yrN7$$CbdIP=sl!`2Teo83H>>w_$FL%xiOdHNcjQW~d@Sk~^7!3%`%E4>{I(rah}nJ< z5JKqfavt@Hwm!u@g;w#wtVSXI{e9xtV}~pB zvGd3nI*_Q@m}|a4s1kNw=gCQ0!fugqv)OnTGJ zk7X`YF9B&XoK3gp=+BBOovvhrzqZpKK^$pd;feUHmj8NgwfvBydT#i3K`iq68`5tZ zr*7^l1KkpP-o7)M%O->1eg9EuCk!|<<^+fMG0~KT3pU+5jBX?-{PGLS0qq{FLh_P+ zm@TyrMHfk!n_M5eCDI0!VB@=;@a2L?q+J7Deb{36#*krZv81ks_x^nahklG1X@vC~ z9l9k!X#zdWvGS}-VP6Te>7r7|&uez}U$2i)(KMHm!yF{fUaDT~m_s-daSY!w7as?+ z$7_qfjmk2wSmTa%5*Zgp(bU&Rra@)_`3spACPa!5?@H@h*1=f$YE}S2n~HZandW`jgR_u?i z)mbXXoqkyc+p}>Zhnx;-R6FDxNobw=6q9T&&15SbZ_icDMpmM5tfy2;GM&`73I9U- zq~{awHX6vIp)6SNCFcIey_cDO?SuTmAnSH|^;4J78RMtYFw%OWwa}#goX+TO^8wck zLzpz-fh@cxFdDi|-De?Ft2fOp%9$jJHtqFk~7e zS+rvOC-1NIsQ!Mym27+HNuZX7cE6wuG<)?cyaGi|K^?{lg)@aJ>2?yIF)OHMlfHYz zk>v14122v(j&68Fe~XWKGwsh8d~(f&5)Wjjf?ym_DK%xCM=Q=G^G)G$;}HJuw@P+Ag<=TwMg;{4Omwrb!}1?{MVAU0|Zo}7yLXA(Kb zBh#`irLPtt+awO|=;J=WFw&9aL>T6zT=Ki+M8<1f1r~HwSFMK&-Rmif70w=&Jss+G zx!s{fJhbN>8G6%NB*-3pR7QC$ZLeM zjE6QPk*(0D>36zRI|Q`MY^FMfE4ZR5zTo`HEM8#JY4*yo3`KTW9}i!&`!KaTP%1f% zf*B5_LEC~PASD+CMlv2AFFZqq_F&F3S>0t5Zt}5bmx1UGMTf*XnqPiBg{H=QrjyO9 zeYu4CsK=}0UKIPK?=t>=zPSLpG{PC7PHPfR3V|_B6=ZEk{zyG~Adw46|{ z$Z01csCUm6X-cWYda9IKNTRE@D>30|9$eUhua1+A*F!S@Ew>Hv#|I6Yi=w}@99k(Q zX8B*#kfHS``QnMmb(h@DfvL!)ld;$73u%M)Nn)&lqy0=|tX@?Xx0ZcG*z+`O?TXz3 zZC`h!w&gsLC)QyuL%Rg}cWo6C3~-_Pz(z8;&&_?^$azzVaQ(9T^MDe+g=l)CEyIYd+kPCn6cMcfN~pdEvErMl=tH#uLmne$cm%PMK&mfdu} z7R#K<5R3na9j9;q>B#s4vqfB{n*X=1QMOm7G=f8gFi})HgfbHf6tS}Phe|GkxH9zI zGsjXxTpA8N!i$<@OhBrKye95f2vm743t2ZK2pSJeBJ#A9CEb;1XIhj_e|P>NwU8)+ zBaB1xa{agv|K0QPT4pNRD_EsWL#OYb3r4DPVVon&*NyT;{wV+77|o0S#@Rf_hAuUB za3g7Bp~;yf+MFcMvkB|4&KLcF&KFNw7EUOadUR8;ty4a$T#R2JWU1u&2{^DJ1AZK? z_~nn?+42@6uC!r>v|&F{s)T-u!CH<{ezl2~>89!W=^B_cI$pFn3|g=~yLaZ$Xuy>z zC@6S(b^GPZm$9+2iHQk+)4Z)<*@P=6ZtPy~6+h7eRVK^YEa&C?{Cs+PdO#6ZR#pxU z4mdeEZ~EKUG^t_ry!|)*Mr;{PO-;JGx?9`Z^^5!0*Fc8P0y>PiUY5@qR1$SmFMi4K zjUzf;{L$*7p)uLr-F(@(b3-C-oe4a#l^+nzeW3+^=dJPNiN}P9B$&yo2|-}W^8O685t=f zBLjLWCME{D`AK`k+xXO*oMRs?+4b%bEZV$Mmz^lV(9m#nbhO2&s;w>1$H&LpyU~a% zbJY3(ZfetzwMMb|gdGX@@#FKeGjAWCL$&&hmqATkL&7hg##-@akK4q=#LUmn6B85L z+S)QRGnbTa(drdAVFE4+6eGR%MN)fM!0$K-F@fdSW(IUThScg+ zcxP&}WvG|D_@u3(l5ky)ubzZ|8lv6T*48GHt?J|D1r|2&;xPE`Y(60&p`-&Z=Ykx7 zAmD|-?kz1X3q$X(6sUvwxw*eCEnzAI4#`3xY=cE6)$y3bfEw)@}H2Yi#uP_AZ{WpZA`f&NL^%Q9#hGMw{<*zHy&m&%oRc3Jx|_ zXvrl3&-}f=e>)#~H$N$Uvp3F!g z%}Am#10Jp>LqqOwuNJ?Pjo-uoAxl^5^xU4<24g)fd}wcOetA`>fG3x_?Vq76m%Sbp z71jTrmYtm)7Z>;XH9=#k4pS>FFhwwCYwP0bf9(k9&xj^7KE|MXdd6yU-NIYz#>PhX z37lTZzJmj2$k+20{eLl_ZusAnPKKP)Xsbg+ssk(f*^>r_N4sSq8-G*EqDVl3WF;4aU&mwR@8i54T`#2A8d# zd@=!#1?jG=yvi)&lI zXi2v^BF^qhldAap(&zMj|1EyT#xq`CV9bRn@?TWI#-sK$P)U=LQMOcgV)x+e}_WyspnMfc)C6m7jFWE0at?P z;2?IzU52J8UKo1)cc&xpqVsAicX)U>pd-YjLtJ#y(Aaq8`*$NFqY7=dsy$RGw~c|< zMqDM=*%X(Z_BoZ6mEacOM!<7JLQE1`>_K2t;?1TYB_*Y#)M+HZ!H6*P^gIO`({;0Q z)OmB*28=79qv{XOB;IDnx^hjY@a4miCtgsRUfUi35tmYa}|kU)GUZI1yjBOYk3J(;)YQrDyZr5DyW$5oTJKwg zKkr-U2DhW4qKe;NZnXIx0d1nFiqhH}29`iTKyY!m0!DqL?wV%E@!L84+j}2WIqJ8{ znwYD%V;RcU0RcIltINxd`g1_8K_Cu4v(4yQnVNFHI5eI-)vl*qk=RFgF7{Vk`>nKk ztEknG&oQ$ndxeDD|FZyrNa2%cCN<@{J!|5{qFEC)i9$E7EriKv#m#W?wcM za8e~VH#G1Yh5Aq^s5QB+wCZpWc|X)7Ojr|pg1IIm)B3qqv~I$t1A@YJPb3hg$Fq7*Oz^#QTJuGq;j=8(DFA5NDlvpxpG%y7JQPq?T%F`<-DELpZm74gG71Dk`J! zwNKh?FJ8R(=P_W>ZeVTV7!CrpwY4X`xDsAIK4ovaak>_#r+ImK-%M^(iw9jMq4`I1 zvpY5g4_CP0ImKO9RNO{>36{dyLM)E%n^5gp>WX*}n<|nanM~CuvVAcnIMde;mi*bK zi1b{Bt2#?(k7M?zwYqxJd!-l8DdYBYUEQDKpS{c)1y0V+#_1jO+ChPij(fvt%pks& zm$PT9Qk7O~&F{6kn3}Hr_+j?)LuiJFBhBghpo~JPOpMy4wB80ns`pjvOo8)ICeY7 zm%*KtuGtAIQ&}MXLmOtso3&DW?v%&OP6o%F?*9E0$%AO9s2~Z@e*X}zq0?hltd*Uc zORCzU27wSC|2qp1c>VX&(zA8ZXn!{CkOiPlGbI{23ah>O9m3mXet~v&ij0AR8Cg^E zMU<5;hkv=tjpeOLUE;4BN+-2GV{CNSqc^CUC4Tqp8ias)+esT68@7y*7VJN& zrP^$Ke0+;e=fHVE##^iB6lxXtl(_;Vd(F)krI@b^Q4PBBhc<>RW498%@<75E@M%!+ zz44!==!6BcFOSLbMUFj^g*_v62@};;8Zol*LG}%27%IaSl%i_>9A@_p4~;&244T4F z#=G?1j-W5<^)mJN=97BoWf03i!|AYIyZD!75)s+h+Gb{EKJ}vPDhn;h&o|c7`@OSc z|5?@hcI9%o2I?7|H8YvdKBh{)6q^W%mB#lb1twhgmzXsR?TQyW+-AV;%67|0?$@+w zEM{ox{g)ns+LKQyvDL|4RBgao?X zy2aArTjMj*QS(HLP9*Z5V4tul;<7;O_I-_Hxr(xM=H`Ke+uCl!vlki-)VzPSeHHMR z2z>c%e%|-;MMh<%K&+{uVc@9~H<3bMSc_m{pR-M)3Y=i|O$Dcx_Mp3vSp-%W08K2qtH68JL>Ey`9sK(yNh=i$-5faZkCZqmM~Gd-TlA;YeG*^s;+=D z2M}3h>MM9_v7)|w`}R%l;_XF|==R~^p<%O&x390@*qnN?76@KgSXfs<_v3wu->ucD zlX1935+tV|;NNUyoeL1WyfI%Mhdq~;NIt`B`%;8CoRc`W!x_IAI`4^+Phf>=?qKvQng4HP>ZmdeDT_lK{4?Jvr8mrcDC?`XivcKPD$vtV;pa_jSn zmR3IIMUf_22KFzx&YH|YzY`#UzoBSa4k2BoT7PzS1-MScyCEoMD&yDoFZ(9HFleOU zsbA=Z=3Ar*hfC??GWf{-BNjV&+4dlMr(v;dfhVQ*=NSD#c)}s8?zj2E7N?|~ej1&<=Cwb3$_XK9mZ;*J2 zbNHCnl~zA(wd}91Qv1hkDh$)>-0AODqH#7k%mZPAxLj(_urlFe?;gEEC)dj^Kv=wkB*HkeUG6PN079^uXeH#@{R0<$DVd~qWuat_W6~BGNdmB&in_; z9rIOE7MTuH@tai*qv&j&8EM~Wh$@5Qw4ru^NYo>E=;N>mlPSx@{1`2$nZ2@jnzubv|^- z|707&n&T(uD#?mdRAw!9`b|-$rlAwwxozD3P&8<%|F*R?nv?YWa4dH(TQpubE}n3L z-V+86m3?mh${2?rc8;_vS}!?k;jU*m2#{7p zEt_^sLmx7d*{5?6#|rx1VcD^5_G~exTQ|8sO!>J!WktcHW24{tAGwDf2=L2M7qr{! z%q}lD?hd08-pvQu9_@0J_wZh<<_kx;6G`gCmJ#0ljc3rO4?XFIGgl_1>+Dc=Tu?ru zX;N|$)JgqD3$oWD76+kO(N&)V+fUGkjYaZ9CQm+;hv0@Dw)E%Qxd+dRO`wDIf=5Ei zV{pQJ^|2h!4G@u(Nu$jP<#4F&W^vf_)dg?8B#M7%i?l!E@7m1^y*U}z2o!y5T|Bc> z^t_OjC&qjsJ@xkEHt+fPTP;*67*wiPVUB;?lIkL1Vzs>N=PYHYE&q{lrZ&Jg{sr@H zl{#XF;wXv?AwXzVR8%|@TENbig@@YnKVO57m5qYkk@JFz z)see|T=VM-;#rd4r7veseC1CIJeB$ZSxa`n{y=q6jjAKS-_neUUj^^uebN>q$CrsNqzrO0FwY5 zh}l@!T>^yd&!0cj(^?%N;pL}Kg)?7{ZF=|v;_Ts3XT&9Vi)_h}ufEn9d~<%V{7HM4 z688vOsaR{54FFQOnAhm$pI^Uzf#eYGD|349>ECZusk^$|&U)1H;Oj=zPO@#8?+hzqvNbCrf*`=QV{ z5xS;%u)n;#3^w>@fLrzG)!siaB-W9Yyi4u#M%;v% zHg`PaYBLj1ako9J^cnoS2$U?wIgBuP-6*3-ePoElq`PzD^0@%z_J>?_vu>b|DeO&#aigi zK{L2pNvN3p#J`UA1KRk~G+3un7YrUuF}aOnc~!ok%lAvbt-#hi`e-l+sCv!&V4OvJ&^LaBne?j|4&D+{^ ze)xv|Fi>mqY{}#G%FWHqI|Z8I(GqmnVLyBLdf18=%m5&bU6=h&0fa5I=FRT4UvS79 z(E(0Wkd>8{E7H2s0FHtKH2GxlaCcXk2@vO}|uLV&9Rw+edc z^)121&Fy&fY1i$)Ir#oLb=WG&{s~aMLb1S)^b2ayM5ijs#nXL#vTPYz<_EHL?s~YO zx)nXOvO<~A&oNpy>kubv0dDQ+Sgy&^ev=b2=svyU1xiE;3WMN?I)H2MZ+}G>!y%xI z1GqzBVJJAK-q+X1gXNKf1i9R(VH!n zhAh+dn|rWwz{vsF6xxn9cbhPt7jvjp&Ug-xE~uS(Js%e?%+Kc}O2|+y)-CVoh-)?{ zsoG=0kJDy*CM0yZ0~qk-&lun%K;eEqZ=u7M$=E+z@m&Lz8CMSvt-+2QY|gDnCl{9x zP>9G-?suoyB%#*fZJg)iNLBRm_CC9~kO;p1d$KVI-~`Y!S69^X-86~IACb!dhyziv zvXZrSER$<2N&>S9H1FbQ?O~?y-V2O)e7w1y_oJ{xLl!P#jCi}fs0alwPO))G*y-fk zb-jHKo7r3S5|QW6!3gXA(RYB~4JviZwb_cbcK$wjXmSS7j{zTJ-Wy=WA3uKl{{8!4 zxf!4UaPi6JT%f=e^}}UMjxpfI1sJim*3ypQ(?d#5vS(L(>l{R@iW?db^iA+5L^x} zE+AkfGdn@z)#v%xz8DdbcPq=quX$|r-&*Sf2k*k{gw*qNEVYivBblp(PkNd*)Dgg#(Y z*P#5+)ZDC6tOaP~p%x3Fj}IKnAZerF5YhBryA+27%yT-ckFg+8!eUdf_GHRk>B5=s zTJS}Q#N7@6+)GS?f(B!Q@AvCrbg7Eyv_We}M@Je=X=PqNYx2_bwTX1DY5|yo~5SoSaNeO&M{`bBEq=v#nP)0BVePC^KH$%dJhBAhhie(Gi-|AvmME zXT?lfInoAr`DB4uAn?F7psmBhN|bG#*q|Cf^#oHFZ^xqcb8G8!etxfmrN*SB$BjXc zUboxzoAljOCTaQ)!3q5fnt?RlUY+jj?11tBAQ=kDR6A$CR3eB@mEc$Apym;JHEIg- zmaB5pyIla`fV`G+9Qr1+6<*%o96cPzNT{l+mgaMLC(Un4C0u22u^6 zg?^&isp-p(XaB7e+uGQ8O{Saom#oOOvMEh^)p9Xljp7F{}sINdea$=3=Fi$KLZW}{0r2s zRGDTL7qgX%LBZcS(A8fl+wuG0DuzAv2s#!mf+Zd?bP+sx`c3vJJ78vXzUEi?a?oM^f+M;4lu3Ln!tWE!~IdrKMJIT}@5O=q4zJ^Jag4YO@I! zB&`1ve~|#j1v1~Q)2pbcM+Z+FlcW=_oZpM?28oB7<&66X+vITi3UiNF>6S0C&rw7X zoi1B+DO66iqj24rydYBp$FwV^_%RH|$kfu@YJ<1Do{Nc#hpZNT1#kXxc#pzvK?p$=Eyr3;94#x~0?oWyY|y5rRu3s*Y|CLxT$wyFT(1%bzB(zU-i$pBxkdFI#0gw!PIanu3HS*?@P@ iBLDAr2krmgC~hAZ<8{z%5}}_Vpef0z%T`I7Mf@*d=<`Pa literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-FLIPPED_buffer_2-90-00.png b/tests/reference/output_2-FLIPPED_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..58206d1eac41b1849c2fe9037a8fcd43afb71197 GIT binary patch literal 5360 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 OfYxn2wNe%9r~d`06IdAl literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-NORMAL_buffer_1-NORMAL-00.png b/tests/reference/output_2-NORMAL_buffer_1-NORMAL-00.png new file mode 100644 index 0000000000000000000000000000000000000000..b1d2a3774b94ca3b1e7604ae5753e614d319ff95 GIT binary patch literal 14482 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 I@#6LW06h`QH2?qr literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-NORMAL_buffer_2-90-00.png b/tests/reference/output_2-NORMAL_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..26b5789d2002a7df71bcd38120ba22327808fd41 GIT binary patch literal 5313 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_3-FLIPPED_270_buffer_1-NORMAL-00.png b/tests/reference/output_3-FLIPPED_270_buffer_1-NORMAL-00.png new file mode 100644 index 0000000000000000000000000000000000000000..878307d7e01de9104ed1faf2efef19703fb31cfd GIT binary patch literal 12107 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

p3eGp`f^+w8~dOhmwz+!}gF$pcy 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_3-NORMAL_buffer_2-90-00.png b/tests/reference/output_3-NORMAL_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..b1133ef66834d5b75086273e8172d058a00711e1 GIT binary patch literal 11702 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 bZlZRDxsLyw_xfQkVgqzE41x7(cCr5p=p_>5 literal 0 HcmV?d00001 diff --git a/tests/reference/subsurface_z_order-00.png b/tests/reference/subsurface_z_order-00.png new file mode 100644 index 0000000000000000000000000000000000000000..581fef61ef57236d4399186fde708ee29f901d48 GIT binary patch literal 122 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKQJ-_N$!f>gTe~DWM4f;<_cq literal 0 HcmV?d00001 diff --git a/tests/reference/subsurface_z_order-01.png b/tests/reference/subsurface_z_order-01.png new file mode 100644 index 0000000000000000000000000000000000000000..0a591811b7de92edc55ed0f34d36fd0f7482b5a6 GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKQJ)^Ngn6WA3%yTz$e62Be3c}kj?Oak;F%K zAnoJn;uuoF`1XvWAcFt{gMsSQ5TQMc{@Yn2et--UNN;g^=&d2bb-5=1NVYmHM5Q(e hbMbSuIw^vTWDrqjS)O+1*~C{Mj;E`i%Q~loCIHZxCsY6c literal 0 HcmV?d00001 diff --git a/tests/reference/subsurface_z_order-02.png b/tests/reference/subsurface_z_order-02.png new file mode 100644 index 0000000000000000000000000000000000000000..737e2f9ccb3e5452a4ab2fd402e5165d8042c4f1 GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKQJ)^Ngn6WA0R5gC&X1Fu+Z$!6vS5kQJRz$e62Be3c}kj?NP2pRtW zf7EdLH;^~g)5S5Qg7NLSPKJg65rz#SK^}^&&(DiEe&7G!Z9}UONCyxk)cOVhfdRWC z7#w`NwX(Sh1b$~XHT~z{;^I2CG!P;J7AA@K{>TzyEW|E`2Mh-8ciLoL)_Z^iJYD@< J);T3K0RT(dRFVJy literal 0 HcmV?d00001 diff --git a/tests/reference/subsurface_z_order-04.png b/tests/reference/subsurface_z_order-04.png new file mode 100644 index 0000000000000000000000000000000000000000..1c415d59c8be04e5fc038a8eb7077444557dd6db GIT binary patch literal 237 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKd>+Z$!6vS5kQJRz$e62Be3c}kj?NP2pRtW zf7EdLH;^~e)5S5Qg7NJ+L&gRM4h945h=W{V3+mPE_4pqgIIMfHPZp#H2oBs-(*S}W zsR0o1!RtRS7YN+*EO0CE0}2|%afoq%h42>!lo?z#HK^!lg}M0I`kX0Q0=;^Jfe$ R7w3ZnJYD@<);T3K0RWzeIwJr8 literal 0 HcmV?d00001 diff --git a/tests/reference/viewport_upscale_solid-00.png b/tests/reference/viewport_upscale_solid-00.png new file mode 100644 index 0000000000000000000000000000000000000000..71f438e79b8d6e1d5a8b89013516a6173a4a7b81 GIT binary patch literal 829 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX5Ps$$$P@Hb9Ck$=lt9;Xep2*t>i(0|V11 zPZ!6KiaBrZY~*YS5O8qx*Kt$M-dU#e;Fs~PvNsQvUo_9GNt}E%bFgnj@Q1m$Q*~61z7~+SUMIeqiu)^>bP0l+XkK&mQXW literal 0 HcmV?d00001 diff --git a/tests/roles-test.c b/tests/roles-test.c new file mode 100644 index 0000000..fbce053 --- /dev/null +++ b/tests/roles-test.c @@ -0,0 +1,155 @@ +/* + * Copyright © 2014 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.logging_scopes = "log,proto,test-harness-plugin"; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +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 struct wl_shell * +get_wl_shell(struct client *client) +{ + struct global *g; + struct global *global = NULL; + struct wl_shell *shell; + + wl_list_for_each(g, &client->global_list, link) { + if (strcmp(g->interface, "wl_shell")) + continue; + + if (global) + assert(0 && "multiple wl_shell objects"); + + global = g; + } + + assert(global && "no wl_shell found"); + + shell = wl_registry_bind(client->wl_registry, global->name, + &wl_shell_interface, 1); + assert(shell); + + return shell; +} + +TEST(test_role_conflict_sub_wlshell) +{ + struct client *client; + struct wl_subcompositor *subco; + struct wl_surface *child; + struct wl_subsurface *sub; + struct wl_shell *shell; + struct wl_shell_surface *shsurf; + + client = create_client_and_test_surface(100, 50, 123, 77); + assert(client); + + subco = get_subcompositor(client); + shell = get_wl_shell(client); + + child = wl_compositor_create_surface(client->wl_compositor); + assert(child); + sub = wl_subcompositor_get_subsurface(subco, child, + client->surface->wl_surface); + assert(sub); + + shsurf = wl_shell_get_shell_surface(shell, child); + assert(shsurf); + + expect_protocol_error(client, &wl_shell_interface, + WL_SHELL_ERROR_ROLE); +} + +TEST(test_role_conflict_wlshell_sub) +{ + struct client *client; + struct wl_subcompositor *subco; + struct wl_surface *child; + struct wl_subsurface *sub; + struct wl_shell *shell; + struct wl_shell_surface *shsurf; + + client = create_client_and_test_surface(100, 50, 123, 77); + assert(client); + + subco = get_subcompositor(client); + shell = get_wl_shell(client); + + child = wl_compositor_create_surface(client->wl_compositor); + assert(child); + shsurf = wl_shell_get_shell_surface(shell, child); + assert(shsurf); + + sub = wl_subcompositor_get_subsurface(subco, child, + client->surface->wl_surface); + assert(sub); + + expect_protocol_error(client, &wl_subcompositor_interface, + WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE); +} diff --git a/tests/setbacklight.c b/tests/setbacklight.c new file mode 100644 index 0000000..3bfc7d1 --- /dev/null +++ b/tests/setbacklight.c @@ -0,0 +1,191 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * 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 "config.h" + +#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/string-test.c b/tests/string-test.c new file mode 100644 index 0000000..5571b52 --- /dev/null +++ b/tests/string-test.c @@ -0,0 +1,88 @@ +/* + * Copyright © 2016 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "shared/string-helpers.h" + +#include "weston-test-client-helper.h" + +TEST(strtol_conversions) +{ + bool ret; + int32_t val = -1; + char *str = NULL; + + str = ""; val = -1; + ret = safe_strtoint(str, &val); + assert(ret == false); + assert(val == -1); + + str = "."; val = -1; + ret = safe_strtoint(str, &val); + assert(ret == false); + assert(val == -1); + + str = "42"; val = -1; + ret = safe_strtoint(str, &val); + assert(ret == true); + assert(val == 42); + + str = "-42"; val = -1; + ret = safe_strtoint(str, &val); + assert(ret == true); + assert(val == -42); + + str = "0042"; val = -1; + ret = safe_strtoint(str, &val); + assert(ret == true); + assert(val == 42); + + str = "x42"; val = -1; + ret = safe_strtoint(str, &val); + assert(ret == false); + assert(val == -1); + + str = "42x"; val = -1; + ret = safe_strtoint(str, &val); + assert(ret == false); + assert(val == -1); + + str = "0x42424242"; val = -1; + ret = safe_strtoint(str, &val); + assert(ret == false); + assert(val == -1); + + str = "424748364789L"; val = -1; + ret = safe_strtoint(str, &val); + assert(ret == false); + assert(val == -1); +} diff --git a/tests/subsurface-shot-test.c b/tests/subsurface-shot-test.c new file mode 100644 index 0000000..4df9d3d --- /dev/null +++ b/tests/subsurface-shot-test.c @@ -0,0 +1,200 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * Copyright © 2016 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static const enum renderer_type renderers[] = { + RENDERER_PIXMAN, + RENDERER_GL, +}; + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness, const enum renderer_type *arg) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.renderer = *arg; + setup.width = 320; + setup.height = 240; + setup.shell = SHELL_TEST_DESKTOP; + setup.logging_scopes = "log,test-harness-plugin"; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, renderers); + +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 int +check_screen(struct client *client, + const char *ref_image, + int ref_seq_no, + const struct rectangle *clip, + int seq_no) +{ + bool match; + + match = verify_screen_content(client, ref_image, ref_seq_no, clip, + seq_no); + + return match ? 0 : -1; +} + +static struct buffer * +surface_commit_color(struct client *client, struct wl_surface *surface, + pixman_color_t *color, int width, int height) +{ + struct buffer *buf; + + buf = create_shm_buffer_a8r8g8b8(client, width, height); + fill_image_with_color(buf->image, color); + wl_surface_attach(surface, buf->proxy, 0, 0); + wl_surface_damage(surface, 0, 0, width, height); + wl_surface_commit(surface); + + return buf; +} + +TEST(subsurface_z_order) +{ + struct client *client; + struct wl_subcompositor *subco; + struct buffer *bufs[5] = { 0 }; + struct wl_surface *surf[5] = { 0 }; + struct wl_subsurface *sub[5] = { 0 }; + struct rectangle clip = { 40, 40, 280, 200 }; + int fail = 0; + unsigned i; + pixman_color_t red; + pixman_color_t blue; + pixman_color_t cyan; + pixman_color_t green; + + color_rgb888(&red, 255, 0, 0); + color_rgb888(&blue, 0, 0, 255); + color_rgb888(&cyan, 0, 255, 255); + color_rgb888(&green, 0, 255, 0); + + client = create_client_and_test_surface(100, 50, 100, 100); + assert(client); + subco = get_subcompositor(client); + + /* move the pointer clearly away from our screenshooting area */ + weston_test_move_pointer(client->test->weston_test, 0, 1, 0, 2, 30); + + /* make the parent surface red */ + surf[0] = client->surface->wl_surface; + bufs[0] = surface_commit_color(client, surf[0], &red, 100, 100); + /* sub[0] is not used */ + + fail += check_screen(client, "subsurface_z_order", 0, &clip, 0); + + /* create a blue sub-surface above red */ + surf[1] = wl_compositor_create_surface(client->wl_compositor); + sub[1] = wl_subcompositor_get_subsurface(subco, surf[1], surf[0]); + bufs[1] = surface_commit_color(client, surf[1], &blue, 100, 100); + + wl_subsurface_set_position(sub[1], 20, 20); + wl_surface_commit(surf[0]); + + fail += check_screen(client, "subsurface_z_order", 1, &clip, 1); + + /* create a cyan sub-surface above blue */ + surf[2] = wl_compositor_create_surface(client->wl_compositor); + sub[2] = wl_subcompositor_get_subsurface(subco, surf[2], surf[1]); + bufs[2] = surface_commit_color(client, surf[2], &cyan, 100, 100); + + wl_subsurface_set_position(sub[2], 20, 20); + wl_surface_commit(surf[1]); + wl_surface_commit(surf[0]); + + fail += check_screen(client, "subsurface_z_order", 2, &clip, 2); + + /* create a green sub-surface above blue, sibling to cyan */ + surf[3] = wl_compositor_create_surface(client->wl_compositor); + sub[3] = wl_subcompositor_get_subsurface(subco, surf[3], surf[1]); + bufs[3] = surface_commit_color(client, surf[3], &green, 100, 100); + + wl_subsurface_set_position(sub[3], -40, 10); + wl_surface_commit(surf[1]); + wl_surface_commit(surf[0]); + + fail += check_screen(client, "subsurface_z_order", 3, &clip, 3); + + /* stack blue below red, which brings also cyan and green below red */ + wl_subsurface_place_below(sub[1], surf[0]); + wl_surface_commit(surf[0]); + + fail += check_screen(client, "subsurface_z_order", 4, &clip, 4); + + assert(fail == 0); + + for (i = 0; i < ARRAY_LENGTH(sub); i++) + if (sub[i]) + wl_subsurface_destroy(sub[i]); + + for (i = 0; i < ARRAY_LENGTH(surf); i++) + if (surf[i]) + wl_surface_destroy(surf[i]); + + for (i = 0; i < ARRAY_LENGTH(bufs); i++) + if (bufs[i]) + buffer_destroy(bufs[i]); +} diff --git a/tests/subsurface-test.c b/tests/subsurface-test.c new file mode 100644 index 0000000..1f9c90c --- /dev/null +++ b/tests/subsurface-test.c @@ -0,0 +1,768 @@ +/* + * Copyright © 2012 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +#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 = create_client_and_test_surface(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 = create_client_and_test_surface(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 = create_client_and_test_surface(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); +} + +TEST(test_subsurface_paradox) +{ + struct client *client; + struct wl_surface *parent; + struct wl_subcompositor *subco; + + client = create_client_and_test_surface(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); + + expect_protocol_error(client, &wl_subcompositor_interface, + WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE); +} + +TEST(test_subsurface_identical_link) +{ + struct client *client; + struct compound_surface com; + + client = create_client_and_test_surface(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); + + expect_protocol_error(client, &wl_subcompositor_interface, + WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE); +} + +TEST(test_subsurface_change_link) +{ + struct client *client; + struct compound_surface com; + struct wl_surface *stranger; + + client = create_client_and_test_surface(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); + + expect_protocol_error(client, &wl_subcompositor_interface, + WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE); +} + +TEST(test_subsurface_nesting) +{ + struct client *client; + struct compound_surface com; + struct wl_surface *stranger; + + client = create_client_and_test_surface(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 = create_client_and_test_surface(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); +} + +TEST(test_subsurface_loop_paradox) +{ + struct client *client; + struct wl_surface *surface[3]; + struct wl_subcompositor *subco; + + client = create_client_and_test_surface(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]); + + expect_protocol_error(client, &wl_subcompositor_interface, + WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE); +} + +TEST(test_subsurface_place_above_nested_parent) +{ + struct client *client; + struct compound_surface com; + struct wl_surface *grandchild; + struct wl_subcompositor *subco; + struct wl_subsurface *sub; + + client = create_client_and_test_surface(100, 50, 123, 77); + assert(client); + + populate_compound_surface(&com, client); + + subco = get_subcompositor(client); + grandchild = wl_compositor_create_surface(client->wl_compositor); + sub = wl_subcompositor_get_subsurface(subco, grandchild, com.child[0]); + + wl_subsurface_place_above(sub, com.child[0]); + + client_roundtrip(client); +} + +TEST(test_subsurface_place_above_grandparent) +{ + struct client *client; + struct compound_surface com; + struct wl_surface *grandchild; + struct wl_subsurface *sub; + struct wl_subcompositor *subco; + + client = create_client_and_test_surface(100, 50, 123, 77); + assert(client); + + populate_compound_surface(&com, client); + + subco = get_subcompositor(client); + grandchild = wl_compositor_create_surface(client->wl_compositor); + sub = wl_subcompositor_get_subsurface(subco, grandchild, com.child[0]); + + /* can't place a subsurface above its grandparent */ + wl_subsurface_place_above(sub, com.parent); + + expect_protocol_error(client, &wl_subsurface_interface, + WL_SUBSURFACE_ERROR_BAD_SURFACE); +} + +TEST(test_subsurface_place_above_great_aunt) +{ + struct client *client; + struct compound_surface com; + struct wl_surface *grandchild; + struct wl_subsurface *sub; + struct wl_subcompositor *subco; + + client = create_client_and_test_surface(100, 50, 123, 77); + assert(client); + + populate_compound_surface(&com, client); + + subco = get_subcompositor(client); + grandchild = wl_compositor_create_surface(client->wl_compositor); + sub = wl_subcompositor_get_subsurface(subco, grandchild, com.child[0]); + + /* can't place a subsurface above its parents' siblings */ + wl_subsurface_place_above(sub, com.child[1]); + + expect_protocol_error(client, &wl_subsurface_interface, + WL_SUBSURFACE_ERROR_BAD_SURFACE); +} + +TEST(test_subsurface_place_above_child) +{ + struct client *client; + struct compound_surface com; + struct wl_surface *grandchild; + struct wl_subcompositor *subco; + + client = create_client_and_test_surface(100, 50, 123, 77); + assert(client); + + populate_compound_surface(&com, client); + + subco = get_subcompositor(client); + grandchild = wl_compositor_create_surface(client->wl_compositor); + wl_subcompositor_get_subsurface(subco, grandchild, com.child[0]); + + /* can't place a subsurface above its own child subsurface */ + wl_subsurface_place_above(com.sub[0], grandchild); + + expect_protocol_error(client, &wl_subsurface_interface, + WL_SUBSURFACE_ERROR_BAD_SURFACE); +} + +TEST(test_subsurface_place_below_nested_parent) +{ + struct client *client; + struct compound_surface com; + struct wl_subcompositor *subco; + struct wl_surface *grandchild; + struct wl_subsurface *sub; + + client = create_client_and_test_surface(100, 50, 123, 77); + assert(client); + + populate_compound_surface(&com, client); + + subco = get_subcompositor(client); + grandchild = wl_compositor_create_surface(client->wl_compositor); + sub = wl_subcompositor_get_subsurface(subco, grandchild, com.child[0]); + + wl_subsurface_place_below(sub, com.child[0]); + + client_roundtrip(client); +} + +TEST(test_subsurface_place_below_grandparent) +{ + struct client *client; + struct compound_surface com; + struct wl_surface *grandchild; + struct wl_subsurface *sub; + struct wl_subcompositor *subco; + + client = create_client_and_test_surface(100, 50, 123, 77); + assert(client); + + populate_compound_surface(&com, client); + + subco = get_subcompositor(client); + grandchild = wl_compositor_create_surface(client->wl_compositor); + sub = wl_subcompositor_get_subsurface(subco, grandchild, com.child[0]); + + /* can't place a subsurface below its grandparent */ + wl_subsurface_place_below(sub, com.parent); + + expect_protocol_error(client, &wl_subsurface_interface, + WL_SUBSURFACE_ERROR_BAD_SURFACE); +} + +TEST(test_subsurface_place_below_great_aunt) +{ + struct client *client; + struct compound_surface com; + struct wl_surface *grandchild; + struct wl_subsurface *sub; + struct wl_subcompositor *subco; + + client = create_client_and_test_surface(100, 50, 123, 77); + assert(client); + + populate_compound_surface(&com, client); + + subco = get_subcompositor(client); + grandchild = wl_compositor_create_surface(client->wl_compositor); + sub = wl_subcompositor_get_subsurface(subco, grandchild, com.child[0]); + + /* can't place a subsurface below its parents' siblings */ + wl_subsurface_place_below(sub, com.child[1]); + + expect_protocol_error(client, &wl_subsurface_interface, + WL_SUBSURFACE_ERROR_BAD_SURFACE); +} + +TEST(test_subsurface_place_below_child) +{ + struct client *client; + struct compound_surface com; + struct wl_surface *grandchild; + struct wl_subcompositor *subco; + + client = create_client_and_test_surface(100, 50, 123, 77); + assert(client); + + populate_compound_surface(&com, client); + + subco = get_subcompositor(client); + grandchild = wl_compositor_create_surface(client->wl_compositor); + wl_subcompositor_get_subsurface(subco, grandchild, com.child[0]); + + /* can't place a subsurface below its own child subsurface */ + wl_subsurface_place_below(com.sub[0], grandchild); + + expect_protocol_error(client, &wl_subsurface_interface, + WL_SUBSURFACE_ERROR_BAD_SURFACE); +} + +TEST(test_subsurface_place_above_stranger) +{ + struct client *client; + struct compound_surface com; + struct wl_surface *stranger; + + client = create_client_and_test_surface(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); + + expect_protocol_error(client, &wl_subsurface_interface, + WL_SUBSURFACE_ERROR_BAD_SURFACE); +} + +TEST(test_subsurface_place_below_stranger) +{ + struct client *client; + struct compound_surface com; + struct wl_surface *stranger; + + client = create_client_and_test_surface(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); + + expect_protocol_error(client, &wl_subsurface_interface, + WL_SUBSURFACE_ERROR_BAD_SURFACE); +} + +TEST(test_subsurface_place_above_foreign) +{ + struct client *client; + struct compound_surface com1; + struct compound_surface com2; + + client = create_client_and_test_surface(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]); + + expect_protocol_error(client, &wl_subsurface_interface, + WL_SUBSURFACE_ERROR_BAD_SURFACE); +} + +TEST(test_subsurface_place_below_foreign) +{ + struct client *client; + struct compound_surface com1; + struct compound_surface com2; + + client = create_client_and_test_surface(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]); + + expect_protocol_error(client, &wl_subsurface_interface, + WL_SUBSURFACE_ERROR_BAD_SURFACE); +} + +TEST(test_subsurface_destroy_protocol) +{ + struct client *client; + struct compound_surface com; + + client = create_client_and_test_surface(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); + /* fallthrough */ + case 10: + SUB_LINK(9, 2); + /* fallthrough */ + case 9: + SUB_LINK(8, 6); + /* fallthrough */ + case 8: + SUB_LINK(7, 6); + /* fallthrough */ + case 7: + SUB_LINK(6, 2); + /* fallthrough */ + case 6: + SUB_LINK(5, 1); + /* fallthrough */ + case 5: + SUB_LINK(4, 3); + /* fallthrough */ + case 4: + SUB_LINK(3, 1); + /* fallthrough */ + case 3: + SUB_LINK(2, 0); + /* fallthrough */ + 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) { + testlog(" [sub %2d]", h); + wl_subsurface_destroy(subs[h]); + subs[h] = NULL; + } else { + testlog(" [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 = create_client_and_test_surface(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); + + testlog("permu"); + + for (i = 0; i < NSTEPS; i++) + testlog(" %2d", per.cnt[i]); + + for (i = 0; i < NSTEPS; i++) + destroy_permu_object(surfs, subs, per.cnt[i]); + + testlog("\n"); + client_roundtrip(client); + + destroy_subsurface_tree(surfs, subs, test_size); + counter++; + } + + client_roundtrip(client); + testlog("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 0000000..cd9b784 --- /dev/null +++ b/tests/surface-global-test.c @@ -0,0 +1,91 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include +#include "libweston-internal.h" +#include "compositor/weston.h" +#include "weston-test-runner.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_plugin(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +PLUGIN_TEST(surface_to_from_global) +{ + /* struct weston_compositor *compositor; */ + struct weston_surface *surface; + struct weston_view *view; + float x, y; + wl_fixed_t fx, fy; + int32_t ix, iy; + + surface = weston_surface_create(compositor); + assert(surface); + view = weston_view_create(surface); + assert(view); + surface->width = 50; + surface->height = 50; + weston_view_set_position(view, 5, 10); + weston_view_update_transform(view); + + weston_view_to_global_float(view, 33, 22, &x, &y); + assert(x == 38 && y == 32); + + weston_view_to_global_float(view, -8, -2, &x, &y); + assert(x == -3 && y == 8); + + weston_view_to_global_fixed(view, 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_view_from_global_float(view, 38, 32, &x, &y); + assert(x == 33 && y == 22); + + weston_view_from_global_float(view, 42, 5, &x, &y); + assert(x == 37 && y == -5); + + weston_view_from_global_fixed(view, 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_view_from_global(view, 0, 0, &ix, &iy); + assert(ix == -5 && iy == -10); + + weston_view_from_global(view, 5, 10, &ix, &iy); + assert(ix == 0 && iy == 0); +} diff --git a/tests/surface-screenshot-test.c b/tests/surface-screenshot-test.c new file mode 100644 index 0000000..3d93ac5 --- /dev/null +++ b/tests/surface-screenshot-test.c @@ -0,0 +1,224 @@ +/* + * Copyright © 2015 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "compositor/weston.h" +#include "shared/file-util.h" +#include "libweston-internal.h" + +static char * +encode_PAM_comment_line(const char *comment) +{ + size_t len = strlen(comment); + char *str = malloc(len + 2); + char *dst = str; + const char *src = comment; + const char *end = src + len; + + *dst++ = '#'; + *dst++ = ' '; + for (; src < end; src++, dst++) { + if (*src == '\n' || !isprint(*src)) + *dst = '_'; + else + *dst = *src; + } + + return str; +} + +/* + * PAM image format: + * http://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format + * RGBA is in byte address order. + * + * ImageMagick 'convert' can convert a PAM image to a more common format. + * To view the image metadata: $ head -n7 image.pam + */ +static int +write_PAM_image_rgba(FILE *fp, int width, int height, + void *pixels, size_t size, const char *comment) +{ + char *str; + int ret; + + assert(size == (size_t)4 * width * height); + + ret = fprintf(fp, "P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 255\n" + "TUPLTYPE RGB_ALPHA\n", width, height); + if (ret < 0) + return -1; + + if (comment) { + str = encode_PAM_comment_line(comment); + ret = fprintf(fp, "%s\n", str); + free(str); + + if (ret < 0) + return -1; + } + + ret = fprintf(fp, "ENDHDR\n"); + if (ret < 0) + return -1; + + if (fwrite(pixels, 1, size, fp) != size) + return -1; + + if (ferror(fp)) + return -1; + + return 0; +} + +static uint32_t +unmult(uint32_t c, uint32_t a) +{ + return (c * 255 + a / 2) / a; +} + +static void +unpremultiply_and_swap_a8b8g8r8_to_PAMrgba(void *pixels, size_t size) +{ + uint32_t *p = pixels; + uint32_t *end; + + for (end = p + size / 4; p < end; p++) { + uint32_t v = *p; + uint32_t a; + + a = (v & 0xff000000) >> 24; + if (a == 0) { + *p = 0; + } else { + uint8_t *dst = (uint8_t *)p; + + dst[0] = unmult((v & 0x000000ff) >> 0, a); + dst[1] = unmult((v & 0x0000ff00) >> 8, a); + dst[2] = unmult((v & 0x00ff0000) >> 16, a); + dst[3] = a; + } + } +} + +static void +trigger_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) +{ + const char *prefix = "surfaceshot-"; + const char *suffix = ".pam"; + char fname[1024]; + struct weston_surface *surface; + struct weston_seat *seat = keyboard->seat; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + int width, height; + char desc[512]; + void *pixels; + const size_t bytespp = 4; /* PIXMAN_a8b8g8r8 */ + size_t sz; + int ret; + FILE *fp; + + if (!pointer || !pointer->focus) + return; + + surface = pointer->focus->surface; + + weston_surface_get_content_size(surface, &width, &height); + + if (!surface->get_label || + surface->get_label(surface, desc, sizeof(desc)) < 0) + snprintf(desc, sizeof(desc), "(unknown)"); + + weston_log("surface screenshot of %p: '%s', %dx%d\n", + surface, desc, width, height); + + sz = width * bytespp * height; + if (sz == 0) { + weston_log("no content for %p\n", surface); + return; + } + + pixels = malloc(sz); + if (!pixels) { + weston_log("%s: failed to malloc %zu B\n", __func__, sz); + return; + } + + ret = weston_surface_copy_content(surface, pixels, sz, + 0, 0, width, height); + if (ret < 0) { + weston_log("shooting surface %p failed\n", surface); + goto out; + } + + unpremultiply_and_swap_a8b8g8r8_to_PAMrgba(pixels, sz); + + fp = file_create_dated(NULL, prefix, suffix, fname, sizeof(fname)); + if (!fp) { + const char *msg; + + switch (errno) { + case ETIME: + msg = "failure in datetime formatting"; + break; + default: + msg = strerror(errno); + } + + weston_log("Cannot open '%s*%s' for writing: %s\n", + prefix, suffix, msg); + goto out; + } + + ret = write_PAM_image_rgba(fp, width, height, pixels, sz, desc); + if (fclose(fp) != 0 || ret < 0) + weston_log("writing surface %p screenshot failed.\n", surface); + else + weston_log("successfully shot surface %p into '%s'\n", + surface, fname); + +out: + free(pixels); +} + +WL_EXPORT int +wet_module_init(struct weston_compositor *ec, + int *argc, char *argv[]) +{ + weston_compositor_add_debug_binding(ec, KEY_H, trigger_binding, ec); + + return 0; +} diff --git a/tests/surface-test.c b/tests/surface-test.c new file mode 100644 index 0000000..fca74c9 --- /dev/null +++ b/tests/surface-test.c @@ -0,0 +1,71 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include +#include "compositor/weston.h" +#include "weston-test-runner.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_plugin(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +PLUGIN_TEST(surface_transform) +{ + /* struct weston_compositor *compositor; */ + struct weston_surface *surface; + struct weston_view *view; + float x, y; + + surface = weston_surface_create(compositor); + assert(surface); + view = weston_view_create(surface); + assert(view); + surface->width = 200; + surface->height = 200; + weston_view_set_position(view, 100, 100); + weston_view_update_transform(view); + weston_view_to_global_float(view, 20, 20, &x, &y); + + fprintf(stderr, "20,20 maps to %f, %f\n", x, y); + assert(x == 120 && y == 120); + + weston_view_set_position(view, 150, 300); + weston_view_update_transform(view); + weston_view_to_global_float(view, 50, 40, &x, &y); + assert(x == 200 && y == 340); +} diff --git a/tests/text-test.c b/tests/text-test.c new file mode 100644 index 0000000..84988f3 --- /dev/null +++ b/tests/text-test.c @@ -0,0 +1,234 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include "weston-test-client-helper.h" +#include "text-input-unstable-v1-client-protocol.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +struct text_input_state { + int activated; + int deactivated; +}; + +static void +text_input_commit_string(void *data, + struct zwp_text_input_v1 *text_input, + uint32_t serial, + const char *text) +{ +} + +static void +text_input_preedit_string(void *data, + struct zwp_text_input_v1 *text_input, + uint32_t serial, + const char *text, + const char *commit) +{ +} + +static void +text_input_delete_surrounding_text(void *data, + struct zwp_text_input_v1 *text_input, + int32_t index, + uint32_t length) +{ +} + +static void +text_input_cursor_position(void *data, + struct zwp_text_input_v1 *text_input, + int32_t index, + int32_t anchor) +{ +} + +static void +text_input_preedit_styling(void *data, + struct zwp_text_input_v1 *text_input, + uint32_t index, + uint32_t length, + uint32_t style) +{ +} + +static void +text_input_preedit_cursor(void *data, + struct zwp_text_input_v1 *text_input, + int32_t index) +{ +} + +static void +text_input_modifiers_map(void *data, + struct zwp_text_input_v1 *text_input, + struct wl_array *map) +{ +} + +static void +text_input_keysym(void *data, + struct zwp_text_input_v1 *text_input, + uint32_t serial, + uint32_t time, + uint32_t sym, + uint32_t state, + uint32_t modifiers) +{ +} + +static void +text_input_enter(void *data, + struct zwp_text_input_v1 *text_input, + struct wl_surface *surface) + +{ + struct text_input_state *state = data; + + testlog("%s\n", __FUNCTION__); + + state->activated += 1; +} + +static void +text_input_leave(void *data, + struct zwp_text_input_v1 *text_input) +{ + struct text_input_state *state = data; + + state->deactivated += 1; +} + +static void +text_input_input_panel_state(void *data, + struct zwp_text_input_v1 *text_input, + uint32_t state) +{ +} + +static void +text_input_language(void *data, + struct zwp_text_input_v1 *text_input, + uint32_t serial, + const char *language) +{ +} + +static void +text_input_text_direction(void *data, + struct zwp_text_input_v1 *text_input, + uint32_t serial, + uint32_t direction) +{ +} + +static const struct zwp_text_input_v1_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 zwp_text_input_manager_v1 *factory; + struct zwp_text_input_v1 *text_input; + struct text_input_state state; + + client = create_client_and_test_surface(100, 100, 100, 100); + assert(client); + + factory = NULL; + wl_list_for_each(global, &client->global_list, link) { + if (strcmp(global->interface, "zwp_text_input_manager_v1") == 0) + factory = wl_registry_bind(client->wl_registry, + global->name, + &zwp_text_input_manager_v1_interface, 1); + } + + assert(factory); + + memset(&state, 0, sizeof state); + text_input = zwp_text_input_manager_v1_create_text_input(factory); + zwp_text_input_v1_add_listener(text_input, + &text_input_listener, + &state); + + /* Make sure our test surface has keyboard focus. */ + weston_test_activate_surface(client->test->weston_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. */ + zwp_text_input_v1_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. */ + zwp_text_input_v1_deactivate(text_input, client->input->wl_seat); + client_roundtrip(client); + assert(state.activated == 1 && state.deactivated == 1); + + /* Activate test model again. */ + zwp_text_input_v1_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. */ + weston_test_activate_surface(client->test->weston_test, NULL); + client_roundtrip(client); + assert(state.activated == 2 && state.deactivated == 2); +} diff --git a/tests/timespec-test.c b/tests/timespec-test.c new file mode 100644 index 0000000..fa1e6a1 --- /dev/null +++ b/tests/timespec-test.c @@ -0,0 +1,308 @@ +/* + * Copyright © 2016 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shared/timespec-util.h" + +#include "shared/helpers.h" +#include "zunitc/zunitc.h" + +ZUC_TEST(timespec_test, timespec_sub) +{ + struct timespec a, b, r; + + a.tv_sec = 1; + a.tv_nsec = 1; + b.tv_sec = 0; + b.tv_nsec = 2; + timespec_sub(&r, &a, &b); + ZUC_ASSERT_EQ(r.tv_sec, 0); + ZUC_ASSERT_EQ(r.tv_nsec, NSEC_PER_SEC - 1); +} + +ZUC_TEST(timespec_test, timespec_to_nsec) +{ + struct timespec a; + + a.tv_sec = 4; + a.tv_nsec = 4; + ZUC_ASSERT_EQ(timespec_to_nsec(&a), (NSEC_PER_SEC * 4ULL) + 4); +} + +ZUC_TEST(timespec_test, timespec_to_usec) +{ + struct timespec a; + + a.tv_sec = 4; + a.tv_nsec = 4000; + ZUC_ASSERT_EQ(timespec_to_usec(&a), (4000000ULL) + 4); +} + +ZUC_TEST(timespec_test, timespec_to_msec) +{ + struct timespec a; + + a.tv_sec = 4; + a.tv_nsec = 4000000; + ZUC_ASSERT_EQ(timespec_to_msec(&a), (4000ULL) + 4); +} + +ZUC_TEST(timespec_test, timespec_to_proto) +{ + struct timespec a; + uint32_t tv_sec_hi; + uint32_t tv_sec_lo; + uint32_t tv_nsec; + + a.tv_sec = 0; + a.tv_nsec = 0; + timespec_to_proto(&a, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + ZUC_ASSERT_EQ(0, tv_sec_hi); + ZUC_ASSERT_EQ(0, tv_sec_lo); + ZUC_ASSERT_EQ(0, tv_nsec); + + a.tv_sec = 1234; + a.tv_nsec = NSEC_PER_SEC - 1; + timespec_to_proto(&a, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + ZUC_ASSERT_EQ(0, tv_sec_hi); + ZUC_ASSERT_EQ(1234, tv_sec_lo); + ZUC_ASSERT_EQ(NSEC_PER_SEC - 1, tv_nsec); + + a.tv_sec = (time_t)0x7000123470005678LL; + a.tv_nsec = 1; + timespec_to_proto(&a, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + ZUC_ASSERT_EQ((uint64_t)a.tv_sec >> 32, tv_sec_hi); + ZUC_ASSERT_EQ(0x70005678, tv_sec_lo); + ZUC_ASSERT_EQ(1, tv_nsec); +} + +ZUC_TEST(timespec_test, millihz_to_nsec) +{ + ZUC_ASSERT_EQ(millihz_to_nsec(60000), 16666666); +} + +ZUC_TEST(timespec_test, timespec_add_nsec) +{ + struct timespec a, r; + + a.tv_sec = 0; + a.tv_nsec = NSEC_PER_SEC - 1; + timespec_add_nsec(&r, &a, 1); + ZUC_ASSERT_EQ(1, r.tv_sec); + ZUC_ASSERT_EQ(0, r.tv_nsec); + + timespec_add_nsec(&r, &a, 2); + ZUC_ASSERT_EQ(1, r.tv_sec); + ZUC_ASSERT_EQ(1, r.tv_nsec); + + timespec_add_nsec(&r, &a, (NSEC_PER_SEC * 2ULL)); + ZUC_ASSERT_EQ(2, r.tv_sec); + ZUC_ASSERT_EQ(NSEC_PER_SEC - 1, r.tv_nsec); + + timespec_add_nsec(&r, &a, (NSEC_PER_SEC * 2ULL) + 2); + ZUC_ASSERT_EQ(r.tv_sec, 3); + ZUC_ASSERT_EQ(r.tv_nsec, 1); + + a.tv_sec = 1; + a.tv_nsec = 1; + timespec_add_nsec(&r, &a, -2); + ZUC_ASSERT_EQ(r.tv_sec, 0); + ZUC_ASSERT_EQ(r.tv_nsec, NSEC_PER_SEC - 1); + + a.tv_nsec = 0; + timespec_add_nsec(&r, &a, -NSEC_PER_SEC); + ZUC_ASSERT_EQ(0, r.tv_sec); + ZUC_ASSERT_EQ(0, r.tv_nsec); + + a.tv_nsec = 0; + timespec_add_nsec(&r, &a, -NSEC_PER_SEC + 1); + ZUC_ASSERT_EQ(0, r.tv_sec); + ZUC_ASSERT_EQ(1, r.tv_nsec); + + a.tv_nsec = 50; + timespec_add_nsec(&r, &a, (-NSEC_PER_SEC * 10ULL)); + ZUC_ASSERT_EQ(-9, r.tv_sec); + ZUC_ASSERT_EQ(50, r.tv_nsec); + + r.tv_sec = 4; + r.tv_nsec = 0; + timespec_add_nsec(&r, &r, NSEC_PER_SEC + 10ULL); + ZUC_ASSERT_EQ(5, r.tv_sec); + ZUC_ASSERT_EQ(10, r.tv_nsec); + + timespec_add_nsec(&r, &r, (NSEC_PER_SEC * 3ULL) - 9ULL); + ZUC_ASSERT_EQ(8, r.tv_sec); + ZUC_ASSERT_EQ(1, r.tv_nsec); + + timespec_add_nsec(&r, &r, (NSEC_PER_SEC * 7ULL) + (NSEC_PER_SEC - 1ULL)); + ZUC_ASSERT_EQ(16, r.tv_sec); + ZUC_ASSERT_EQ(0, r.tv_nsec); +} + +ZUC_TEST(timespec_test, timespec_add_msec) +{ + struct timespec a, r; + + a.tv_sec = 1000; + a.tv_nsec = 1; + timespec_add_msec(&r, &a, 2002); + ZUC_ASSERT_EQ(1002, r.tv_sec); + ZUC_ASSERT_EQ(2000001, r.tv_nsec); +} + +ZUC_TEST(timespec_test, timespec_sub_to_nsec) +{ + struct timespec a, b; + + a.tv_sec = 1000; + a.tv_nsec = 1; + b.tv_sec = 1; + b.tv_nsec = 2; + ZUC_ASSERT_EQ((999LL * NSEC_PER_SEC) - 1, timespec_sub_to_nsec(&a, &b)); +} + +ZUC_TEST(timespec_test, timespec_sub_to_msec) +{ + struct timespec a, b; + + a.tv_sec = 1000; + a.tv_nsec = 2000000L; + b.tv_sec = 2; + b.tv_nsec = 1000000L; + ZUC_ASSERT_EQ((998 * 1000) + 1, timespec_sub_to_msec(&a, &b)); +} + +ZUC_TEST(timespec_test, timespec_from_nsec) +{ + struct timespec a; + + timespec_from_nsec(&a, 0); + ZUC_ASSERT_EQ(0, a.tv_sec); + ZUC_ASSERT_EQ(0, a.tv_nsec); + + timespec_from_nsec(&a, NSEC_PER_SEC - 1); + ZUC_ASSERT_EQ(0, a.tv_sec); + ZUC_ASSERT_EQ(NSEC_PER_SEC - 1, a.tv_nsec); + + timespec_from_nsec(&a, NSEC_PER_SEC); + ZUC_ASSERT_EQ(1, a.tv_sec); + ZUC_ASSERT_EQ(0, a.tv_nsec); + + timespec_from_nsec(&a, (5LL * NSEC_PER_SEC) + 1); + ZUC_ASSERT_EQ(5, a.tv_sec); + ZUC_ASSERT_EQ(1, a.tv_nsec); +} + +ZUC_TEST(timespec_test, timespec_from_usec) +{ + struct timespec a; + + timespec_from_usec(&a, 0); + ZUC_ASSERT_EQ(0, a.tv_sec); + ZUC_ASSERT_EQ(0, a.tv_nsec); + + timespec_from_usec(&a, 999999); + ZUC_ASSERT_EQ(0, a.tv_sec); + ZUC_ASSERT_EQ(999999 * 1000, a.tv_nsec); + + timespec_from_usec(&a, 1000000); + ZUC_ASSERT_EQ(1, a.tv_sec); + ZUC_ASSERT_EQ(0, a.tv_nsec); + + timespec_from_usec(&a, 5000001); + ZUC_ASSERT_EQ(5, a.tv_sec); + ZUC_ASSERT_EQ(1000, a.tv_nsec); +} + +ZUC_TEST(timespec_test, timespec_from_msec) +{ + struct timespec a; + + timespec_from_msec(&a, 0); + ZUC_ASSERT_EQ(0, a.tv_sec); + ZUC_ASSERT_EQ(0, a.tv_nsec); + + timespec_from_msec(&a, 999); + ZUC_ASSERT_EQ(0, a.tv_sec); + ZUC_ASSERT_EQ(999 * 1000000, a.tv_nsec); + + timespec_from_msec(&a, 1000); + ZUC_ASSERT_EQ(1, a.tv_sec); + ZUC_ASSERT_EQ(0, a.tv_nsec); + + timespec_from_msec(&a, 5001); + ZUC_ASSERT_EQ(5, a.tv_sec); + ZUC_ASSERT_EQ(1000000, a.tv_nsec); +} + +ZUC_TEST(timespec_test, timespec_from_proto) +{ + struct timespec a; + + timespec_from_proto(&a, 0, 0, 0); + ZUC_ASSERT_EQ(0, a.tv_sec); + ZUC_ASSERT_EQ(0, a.tv_nsec); + + timespec_from_proto(&a, 0, 1234, 9999); + ZUC_ASSERT_EQ(1234, a.tv_sec); + ZUC_ASSERT_EQ(9999, a.tv_nsec); + + timespec_from_proto(&a, 0x1234, 0x5678, 1); + ZUC_ASSERT_EQ((time_t)0x0000123400005678LL, a.tv_sec); + ZUC_ASSERT_EQ(1, a.tv_nsec); +} + +ZUC_TEST(timespec_test, timespec_is_zero) +{ + struct timespec zero = { 0 }; + struct timespec non_zero_sec = { .tv_sec = 1, .tv_nsec = 0 }; + struct timespec non_zero_nsec = { .tv_sec = 0, .tv_nsec = 1 }; + + ZUC_ASSERT_TRUE(timespec_is_zero(&zero)); + ZUC_ASSERT_FALSE(timespec_is_zero(&non_zero_nsec)); + ZUC_ASSERT_FALSE(timespec_is_zero(&non_zero_sec)); +} + +ZUC_TEST(timespec_test, timespec_eq) +{ + struct timespec a = { .tv_sec = 2, .tv_nsec = 1 }; + struct timespec b = { .tv_sec = -1, .tv_nsec = 2 }; + + ZUC_ASSERT_TRUE(timespec_eq(&a, &a)); + ZUC_ASSERT_TRUE(timespec_eq(&b, &b)); + + ZUC_ASSERT_FALSE(timespec_eq(&a, &b)); + ZUC_ASSERT_FALSE(timespec_eq(&b, &a)); +} diff --git a/tests/touch-test.c b/tests/touch-test.c new file mode 100644 index 0000000..b2133bd --- /dev/null +++ b/tests/touch-test.c @@ -0,0 +1,135 @@ +/* + * Copyright © 2017 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include + +#include "input-timestamps-helper.h" +#include "shared/timespec-util.h" +#include "weston-test-client-helper.h" +#include "wayland-server-protocol.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +static const struct timespec t1 = { .tv_sec = 1, .tv_nsec = 1000001 }; +static const struct timespec t2 = { .tv_sec = 2, .tv_nsec = 2000001 }; +static const struct timespec t3 = { .tv_sec = 3, .tv_nsec = 3000001 }; +static const struct timespec t_other = { .tv_sec = 123, .tv_nsec = 456 }; + +static struct client * +create_touch_test_client(void) +{ + struct client *cl = create_client_and_test_surface(0, 0, 100, 100); + assert(cl); + return cl; +} + +static void +send_touch(struct client *client, const struct timespec *time, + uint32_t touch_type) +{ + uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; + + timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + weston_test_send_touch(client->test->weston_test, tv_sec_hi, tv_sec_lo, + tv_nsec, 1, 1, 1, touch_type); + client_roundtrip(client); +} + +TEST(touch_events) +{ + struct client *client = create_touch_test_client(); + struct touch *touch = client->input->touch; + struct input_timestamps *input_ts = + input_timestamps_create_for_touch(client); + + send_touch(client, &t1, WL_TOUCH_DOWN); + assert(touch->down_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&touch->down_time_timespec, &t1)); + + send_touch(client, &t2, WL_TOUCH_MOTION); + assert(touch->motion_time_msec == timespec_to_msec(&t2)); + assert(timespec_eq(&touch->motion_time_timespec, &t2)); + + send_touch(client, &t3, WL_TOUCH_UP); + assert(touch->up_time_msec == timespec_to_msec(&t3)); + assert(timespec_eq(&touch->up_time_timespec, &t3)); + + input_timestamps_destroy(input_ts); +} + +TEST(touch_timestamps_stop_after_input_timestamps_object_is_destroyed) +{ + struct client *client = create_touch_test_client(); + struct touch *touch = client->input->touch; + struct input_timestamps *input_ts = + input_timestamps_create_for_touch(client); + + send_touch(client, &t1, WL_TOUCH_DOWN); + assert(touch->down_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&touch->down_time_timespec, &t1)); + + input_timestamps_destroy(input_ts); + + send_touch(client, &t2, WL_TOUCH_UP); + assert(touch->up_time_msec == timespec_to_msec(&t2)); + assert(timespec_is_zero(&touch->up_time_timespec)); +} + +TEST(touch_timestamps_stop_after_client_releases_wl_touch) +{ + struct client *client = create_touch_test_client(); + struct touch *touch = client->input->touch; + struct input_timestamps *input_ts = + input_timestamps_create_for_touch(client); + + send_touch(client, &t1, WL_TOUCH_DOWN); + assert(touch->down_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&touch->down_time_timespec, &t1)); + + wl_touch_release(client->input->touch->wl_touch); + + /* Set input_timestamp to an arbitrary value (different from t1, t2 + * and 0) and check that it is not changed by sending the event. + * This is preferred over just checking for 0, since 0 is used + * internally for resetting the timestamp after handling an input + * event and checking for it here may lead to false negatives. */ + touch->input_timestamp = t_other; + send_touch(client, &t2, WL_TOUCH_UP); + assert(timespec_eq(&touch->input_timestamp, &t_other)); + + input_timestamps_destroy(input_ts); +} diff --git a/tests/vertex-clip-test.c b/tests/vertex-clip-test.c new file mode 100644 index 0000000..ce876ce --- /dev/null +++ b/tests/vertex-clip-test.c @@ -0,0 +1,223 @@ +/* + * Copyright © 2013 Sam Spilsbury + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include "weston-test-runner.h" + +#include "shared/helpers.h" +#include "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, + float *vertices_x, + float *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; + float vertices_x[8]; + float 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; + float vertices_x[8]; + float 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/viewporter-shot-test.c b/tests/viewporter-shot-test.c new file mode 100644 index 0000000..b4f389c --- /dev/null +++ b/tests/viewporter-shot-test.c @@ -0,0 +1,89 @@ +/* + * Copyright 2014, 2016, 2020 Collabora, Ltd. + * Copyright 2020 Zodiac Inflight Innovations + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static const enum renderer_type renderers[] = { + RENDERER_PIXMAN, + RENDERER_GL, +}; + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness, + const enum renderer_type *renderer) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.renderer = *renderer; + setup.shell = SHELL_TEST_DESKTOP; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, renderers); + + +TEST(viewport_upscale_solid) +{ + struct client *client; + struct wp_viewport *viewport; + pixman_color_t color; + const int width = 256; + const int height = 100; + bool match; + + color_rgb888(&color, 255, 128, 0); + + client = create_client(); + client->surface = create_test_surface(client); + viewport = client_create_viewport(client); + + client->surface->buffer = create_shm_buffer_a8r8g8b8(client, 2, 2); + fill_image_with_color(client->surface->buffer->image, &color); + + /* Needs output scale != buffer scale to hit bilinear filter. */ + wl_surface_set_buffer_scale(client->surface->wl_surface, 2); + + wp_viewport_set_destination(viewport, width, height); + client->surface->width = width; + client->surface->height = height; + + move_client(client, 19, 19); + + match = verify_screen_content(client, "viewport_upscale_solid", 0, + NULL, 0); + assert(match); + + wp_viewport_destroy(viewport); + client_destroy(client); +} diff --git a/tests/viewporter-test.c b/tests/viewporter-test.c new file mode 100644 index 0000000..68e9364 --- /dev/null +++ b/tests/viewporter-test.c @@ -0,0 +1,519 @@ +/* + * Copyright © 2014, 2016 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "shared/helpers.h" +#include "shared/xalloc.h" +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +static void +set_source(struct wp_viewport *vp, int x, int y, int w, int h) +{ + wp_viewport_set_source(vp, wl_fixed_from_int(x), wl_fixed_from_int(y), + wl_fixed_from_int(w), wl_fixed_from_int(h)); +} + +TEST(test_viewporter_double_create) +{ + struct wp_viewporter *viewporter; + struct client *client; + + client = create_client_and_test_surface(100, 50, 123, 77); + + viewporter = bind_to_singleton_global(client, + &wp_viewporter_interface, 1); + wp_viewporter_get_viewport(viewporter, client->surface->wl_surface); + wp_viewporter_get_viewport(viewporter, client->surface->wl_surface); + + expect_protocol_error(client, &wp_viewporter_interface, + WP_VIEWPORTER_ERROR_VIEWPORT_EXISTS); +} + +struct bad_source_rect_args { + int x, y, w, h; +}; + +static const struct bad_source_rect_args bad_source_rect_args[] = { + { -5, 0, 20, 10 }, + { 0, -5, 20, 10 }, + { 5, 6, 0, 10 }, + { 5, 6, 20, 0 }, + { 5, 6, -20, 10 }, + { 5, 6, 20, -10 }, + { -1, -1, 20, 10 }, + { 5, 6, -1, -1 }, +}; + +TEST_P(test_viewporter_bad_source_rect, bad_source_rect_args) +{ + const struct bad_source_rect_args *args = data; + struct client *client; + struct wp_viewport *vp; + + client = create_client_and_test_surface(100, 50, 123, 77); + + vp = client_create_viewport(client); + + testlog("wp_viewport.set_source x=%d, y=%d, w=%d, h=%d\n", + args->x, args->y, args->w, args->h); + set_source(vp, args->x, args->y, args->w, args->h); + + expect_protocol_error(client, &wp_viewport_interface, + WP_VIEWPORT_ERROR_BAD_VALUE); +} + +TEST(test_viewporter_unset_source_rect) +{ + struct client *client; + struct wp_viewport *vp; + + client = create_client_and_test_surface(100, 50, 123, 77); + + vp = client_create_viewport(client); + set_source(vp, -1, -1, -1, -1); + wl_surface_commit(client->surface->wl_surface); + + client_roundtrip(client); +} + +struct bad_destination_args { + int w, h; +}; + +static const struct bad_destination_args bad_destination_args[] = { + { 0, 10 }, + { 20, 0 }, + { -20, 10 }, + { -1, 10 }, + { 20, -10 }, + { 20, -1 }, +}; + +TEST_P(test_viewporter_bad_destination_size, bad_destination_args) +{ + const struct bad_destination_args *args = data; + struct client *client; + struct wp_viewport *vp; + + client = create_client_and_test_surface(100, 50, 123, 77); + + vp = client_create_viewport(client); + + testlog("wp_viewport.set_destination w=%d, h=%d\n", args->w, args->h); + wp_viewport_set_destination(vp, args->w, args->h); + + expect_protocol_error(client, &wp_viewport_interface, + WP_VIEWPORT_ERROR_BAD_VALUE); +} + +TEST(test_viewporter_unset_destination_size) +{ + struct client *client; + struct wp_viewport *vp; + + client = create_client_and_test_surface(100, 50, 123, 77); + + vp = client_create_viewport(client); + wp_viewport_set_destination(vp, -1, -1); + wl_surface_commit(client->surface->wl_surface); + + client_roundtrip(client); +} + +struct nonint_destination_args { + wl_fixed_t w, h; +}; + +static const struct nonint_destination_args nonint_destination_args[] = { +#define F(i,f) ((i) * 256 + (f)) + { F(20, 0), F(10, 1) }, + { F(20, 0), F(10, -1) }, + { F(20, 1), F(10, 0) }, + { F(20, -1), F(10, 0) }, + { F(20, 128), F(10, 128) }, +#undef F +}; + +TEST_P(test_viewporter_non_integer_destination_size, nonint_destination_args) +{ + const struct nonint_destination_args *args = data; + struct client *client; + struct wp_viewport *vp; + + client = create_client_and_test_surface(100, 50, 123, 77); + + vp = client_create_viewport(client); + + testlog("non-integer size w=%f, h=%f\n", + wl_fixed_to_double(args->w), wl_fixed_to_double(args->h)); + wp_viewport_set_source(vp, 5, 6, args->w, args->h); + wp_viewport_set_destination(vp, -1, -1); + wl_surface_commit(client->surface->wl_surface); + + expect_protocol_error(client, &wp_viewport_interface, + WP_VIEWPORT_ERROR_BAD_SIZE); +} + +struct source_buffer_args { + wl_fixed_t x, y; + wl_fixed_t w, h; + int buffer_scale; + enum wl_output_transform buffer_transform; +}; + +static int +get_surface_width(struct surface *surface, + int buffer_scale, + enum wl_output_transform buffer_transform) +{ + switch (buffer_transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + return surface->width / buffer_scale; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + return surface->height / buffer_scale; + } + + return -1; +} + +static int +get_surface_height(struct surface *surface, + int buffer_scale, + enum wl_output_transform buffer_transform) +{ + switch (buffer_transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + return surface->height / buffer_scale; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + return surface->width / buffer_scale; + } + + return -1; +} + +static void +setup_source_vs_buffer(struct client *client, + const struct source_buffer_args *args) +{ + struct wl_surface *surf; + struct wp_viewport *vp; + + surf = client->surface->wl_surface; + vp = client_create_viewport(client); + + testlog("surface %dx%d\n", + get_surface_width(client->surface, + args->buffer_scale, args->buffer_transform), + get_surface_height(client->surface, + args->buffer_scale, args->buffer_transform)); + testlog("source x=%f, y=%f, w=%f, h=%f; " + "buffer scale=%d, transform=%d\n", + wl_fixed_to_double(args->x), wl_fixed_to_double(args->y), + wl_fixed_to_double(args->w), wl_fixed_to_double(args->h), + args->buffer_scale, args->buffer_transform); + + wl_surface_set_buffer_scale(surf, args->buffer_scale); + wl_surface_set_buffer_transform(surf, args->buffer_transform); + wl_surface_attach(surf, client->surface->buffer->proxy, 0, 0); + wp_viewport_set_source(vp, args->x, args->y, args->w, args->h); + wp_viewport_set_destination(vp, 99, 99); + wl_surface_commit(surf); +} + +/* buffer dimensions */ +#define WIN_W 124 +#define WIN_H 78 + +/* source rect base size */ +#define SRC_W 20 +#define SRC_H 10 + +/* margin */ +#define MRG 10 +/* epsilon of wl_fixed_t */ +#define EPS 1 + +TEST(test_viewporter_source_buffer_params) +{ + const int max_scale = 2; + + /* buffer_scale requirement */ + assert(WIN_W % max_scale == 0); + assert(WIN_H % max_scale == 0); + + /* source rect must fit inside regardless of scale and transform */ + assert(SRC_W < WIN_W / max_scale); + assert(SRC_H < WIN_H / max_scale); + assert(SRC_W < WIN_H / max_scale); + assert(SRC_H < WIN_W / max_scale); + + /* If buffer scale was ignored, source rect should be inside instead */ + assert(WIN_W / max_scale + SRC_W + MRG < WIN_W); + assert(WIN_H / max_scale + SRC_H + MRG < WIN_H); + assert(WIN_W / max_scale + SRC_H + MRG < WIN_W); + assert(WIN_H / max_scale + SRC_W + MRG < WIN_H); +} + +static const struct source_buffer_args bad_source_buffer_args[] = { +#define F(i) ((i) * 256) + +/* Flush right-top, but epsilon too far right. */ + { F(WIN_W - SRC_W) + EPS, F(0), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, + { F(WIN_W - SRC_W), F(0), F(SRC_W) + EPS, F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, +/* Flush left-bottom, but epsilon too far down. */ + { F(0), F(WIN_H - SRC_H) + EPS, F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, + { F(0), F(WIN_H - SRC_H), F(SRC_W), F(SRC_H) + EPS, 1, WL_OUTPUT_TRANSFORM_NORMAL }, +/* Completely outside on the right. */ + { F(WIN_W + MRG), F(0), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, +/* Competely outside on the bottom. */ + { F(0), F(WIN_H + MRG), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, + +/* + * buffer_scale=2, so the surface size will be halved. + * If buffer_scale was not taken into account, these would all be inside. + * These are the same as above, but adapted to buffer_scale=2. + */ + { F(WIN_W / 2 - SRC_W) + EPS, F(0), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, + { F(WIN_W / 2 - SRC_W), F(0), F(SRC_W) + EPS, F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, + + { F(0), F(WIN_H / 2 - SRC_H) + EPS, F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, + { F(0), F(WIN_H / 2 - SRC_H), F(SRC_W), F(SRC_H) + EPS, 2, WL_OUTPUT_TRANSFORM_NORMAL }, + + { F(WIN_W / 2 + MRG), F(0), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, + + { F(0), F(WIN_H / 2 + MRG), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, + +/* Exceeding bottom-right corner by epsilon: */ +/* non-dimension-swapping transforms */ + { F(WIN_W - SRC_W), F(WIN_H - SRC_H), F(SRC_W), F(SRC_H) + EPS, 1, WL_OUTPUT_TRANSFORM_FLIPPED_180 }, + { F(WIN_W - SRC_W), F(WIN_H - SRC_H) + EPS, F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_FLIPPED }, + { F(WIN_W - SRC_W), F(WIN_H - SRC_H), F(SRC_W) + EPS, F(SRC_H), 1, WL_OUTPUT_TRANSFORM_180 }, + +/* dimension-swapping transforms */ + { F(WIN_H - SRC_W) + EPS, F(WIN_W - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_90 }, + { F(WIN_H - SRC_W), F(WIN_W - SRC_H) + EPS, F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_270 }, + { F(WIN_H - SRC_W), F(WIN_W - SRC_H), F(SRC_W) + EPS, F(SRC_H), 1, WL_OUTPUT_TRANSFORM_FLIPPED_90 }, + { F(WIN_H - SRC_W), F(WIN_W - SRC_H), F(SRC_W), F(SRC_H) + EPS, 1, WL_OUTPUT_TRANSFORM_FLIPPED_270 }, + +/* non-dimension-swapping transforms, buffer_scale=2 */ + { F(WIN_W / 2 - SRC_W), F(WIN_H / 2 - SRC_H) + EPS, F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_FLIPPED_180 }, + { F(WIN_W / 2 - SRC_W), F(WIN_H / 2 - SRC_H), F(SRC_W) + EPS, F(SRC_H), 2, WL_OUTPUT_TRANSFORM_FLIPPED }, + { F(WIN_W / 2 - SRC_W), F(WIN_H / 2 - SRC_H), F(SRC_W), F(SRC_H) + EPS, 2, WL_OUTPUT_TRANSFORM_180 }, + +/* dimension-swapping transforms, buffer_scale=2 */ + { F(WIN_H / 2 - SRC_W) + EPS, F(WIN_W / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_90 }, + { F(WIN_H / 2 - SRC_W), F(WIN_W / 2 - SRC_H) + EPS, F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_270 }, + { F(WIN_H / 2 - SRC_W), F(WIN_W / 2 - SRC_H), F(SRC_W) + EPS, F(SRC_H), 2, WL_OUTPUT_TRANSFORM_FLIPPED_90 }, + { F(WIN_H / 2 - SRC_W), F(WIN_W / 2 - SRC_H), F(SRC_W), F(SRC_H) + EPS, 2, WL_OUTPUT_TRANSFORM_FLIPPED_270 }, + +#undef F +}; + +TEST_P(test_viewporter_source_outside_buffer, bad_source_buffer_args) +{ + const struct source_buffer_args *args = data; + struct client *client; + + client = create_client_and_test_surface(100, 50, WIN_W, WIN_H); + setup_source_vs_buffer(client, args); + + expect_protocol_error(client, &wp_viewport_interface, + WP_VIEWPORT_ERROR_OUT_OF_BUFFER); +} + +static const struct source_buffer_args good_source_buffer_args[] = { +#define F(i) ((i) * 256) + +/* top-left, top-right, bottom-left, and bottom-right corner */ + { F(0), F(0), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, + { F(WIN_W - SRC_W), F(0), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, + { F(0), F(WIN_H - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, + { F(WIN_W - SRC_W), F(WIN_H - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, + +/* buffer_scale=2, so the surface size will be halved */ + { F(0), F(0), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, + { F(WIN_W / 2 - SRC_W), F(0), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, + { F(0), F(WIN_H / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, + { F(WIN_W / 2 - SRC_W), F(WIN_H / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, + +/* with half pixel offset */ + { F(WIN_W / 2 - SRC_W) + 128, F(WIN_H / 2 - SRC_H) + 128, F(SRC_W) - 128, F(SRC_H) - 128, 2, WL_OUTPUT_TRANSFORM_NORMAL }, + +/* Flushed to bottom-right corner: */ +/* non-dimension-swapping transforms */ + { F(WIN_W - SRC_W), F(WIN_H - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_FLIPPED_180 }, + { F(WIN_W - SRC_W), F(WIN_H - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_FLIPPED }, + { F(WIN_W - SRC_W), F(WIN_H - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_180 }, + +/* dimension-swapping transforms */ + { F(WIN_H - SRC_W), F(WIN_W - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_90 }, + { F(WIN_H - SRC_W), F(WIN_W - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_270 }, + { F(WIN_H - SRC_W), F(WIN_W - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_FLIPPED_90 }, + { F(WIN_H - SRC_W), F(WIN_W - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_FLIPPED_270 }, + +/* non-dimension-swapping transforms, buffer_scale=2 */ + { F(WIN_W / 2 - SRC_W), F(WIN_H / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_FLIPPED_180 }, + { F(WIN_W / 2 - SRC_W), F(WIN_H / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_FLIPPED }, + { F(WIN_W / 2 - SRC_W), F(WIN_H / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_180 }, + +/* dimension-swapping transforms, buffer_scale=2 */ + { F(WIN_H / 2 - SRC_W), F(WIN_W / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_90 }, + { F(WIN_H / 2 - SRC_W), F(WIN_W / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_270 }, + { F(WIN_H / 2 - SRC_W), F(WIN_W / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_FLIPPED_90 }, + { F(WIN_H / 2 - SRC_W), F(WIN_W / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_FLIPPED_270 }, + +#undef F +}; + +TEST_P(test_viewporter_source_inside_buffer, good_source_buffer_args) +{ + const struct source_buffer_args *args = data; + struct client *client; + + client = create_client_and_test_surface(100, 50, WIN_W, WIN_H); + setup_source_vs_buffer(client, args); + client_roundtrip(client); +} + +#undef WIN_W +#undef WIN_H +#undef SRC_W +#undef SRC_H +#undef MRG +#undef EPS + +TEST(test_viewporter_outside_null_buffer) +{ + struct client *client; + struct wp_viewport *vp; + struct wl_surface *surf; + + client = create_client_and_test_surface(100, 50, 123, 77); + surf = client->surface->wl_surface; + + /* If buffer is NULL, does not matter what the source rect is. */ + vp = client_create_viewport(client); + wl_surface_attach(surf, NULL, 0, 0); + set_source(vp, 1000, 1000, 20, 10); + wp_viewport_set_destination(vp, 99, 99); + wl_surface_commit(surf); + client_roundtrip(client); + + /* Try again, with all old values. */ + wl_surface_commit(surf); + client_roundtrip(client); + + /* Try once more with old NULL buffer. */ + set_source(vp, 1200, 1200, 20, 10); + wl_surface_commit(surf); + client_roundtrip(client); + + /* When buffer comes back, source rect matters again. */ + wl_surface_attach(surf, client->surface->buffer->proxy, 0, 0); + wl_surface_commit(surf); + expect_protocol_error(client, &wp_viewport_interface, + WP_VIEWPORT_ERROR_OUT_OF_BUFFER); +} + +TEST(test_viewporter_no_surface_set_source) +{ + struct client *client; + struct wp_viewport *vp; + + client = create_client_and_test_surface(100, 50, 123, 77); + vp = client_create_viewport(client); + wl_surface_destroy(client->surface->wl_surface); + client->surface->wl_surface = NULL; + + /* But the wl_surface does not exist anymore. */ + set_source(vp, 1000, 1000, 20, 10); + + expect_protocol_error(client, &wp_viewport_interface, + WP_VIEWPORT_ERROR_NO_SURFACE); +} + +TEST(test_viewporter_no_surface_set_destination) +{ + struct client *client; + struct wp_viewport *vp; + + client = create_client_and_test_surface(100, 50, 123, 77); + vp = client_create_viewport(client); + wl_surface_destroy(client->surface->wl_surface); + client->surface->wl_surface = NULL; + + /* But the wl_surface does not exist anymore. */ + wp_viewport_set_destination(vp, 99, 99); + + expect_protocol_error(client, &wp_viewport_interface, + WP_VIEWPORT_ERROR_NO_SURFACE); +} + +TEST(test_viewporter_no_surface_destroy) +{ + struct client *client; + struct wp_viewport *vp; + + client = create_client_and_test_surface(100, 50, 123, 77); + vp = client_create_viewport(client); + wl_surface_destroy(client->surface->wl_surface); + client->surface->wl_surface = NULL; + + /* But the wl_surface does not exist anymore. */ + wp_viewport_destroy(vp); + + client_roundtrip(client); +} diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c new file mode 100644 index 0000000..94b93ea --- /dev/null +++ b/tests/weston-test-client-helper.c @@ -0,0 +1,1899 @@ +/* + * Copyright © 2012 Intel Corporation + * Copyright © 2015 Samsung Electronics Co., Ltd + * Copyright 2016, 2017 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test-config.h" +#include "shared/os-compatibility.h" +#include "shared/xalloc.h" +#include +#include "weston-test-client-helper.h" + +#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 +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; +} + +int +frame_callback_wait_nofail(struct client *client, int *done) +{ + while (!*done) { + if (wl_display_dispatch(client->wl_display) < 0) + return 0; + } + + return 1; +} + +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; + weston_test_move_surface(client->test->weston_test, surface->wl_surface, + surface->x, surface->y); + /* The attach here is necessary because commit() will call configure + * only on surfaces newly attached, and the one that sets the surface + * position is the configure. */ + wl_surface_attach(surface->wl_surface, surface->buffer->proxy, 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; + + if (wl_surface) + pointer->focus = wl_surface_get_user_data(wl_surface); + else + pointer->focus = NULL; + + pointer->x = wl_fixed_to_int(x); + pointer->y = wl_fixed_to_int(y); + + testlog("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; + + testlog("test-client: got pointer leave, surface %p\n", + wl_surface ? wl_surface_get_user_data(wl_surface) : NULL); +} + +static void +pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, + uint32_t time_msec, 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); + pointer->motion_time_msec = time_msec; + pointer->motion_time_timespec = pointer->input_timestamp; + pointer->input_timestamp = (struct timespec) { 0 }; + + testlog("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_msec, uint32_t button, + uint32_t state) +{ + struct pointer *pointer = data; + + pointer->button = button; + pointer->state = state; + pointer->button_time_msec = time_msec; + pointer->button_time_timespec = pointer->input_timestamp; + pointer->input_timestamp = (struct timespec) { 0 }; + + testlog("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_msec, uint32_t axis, wl_fixed_t value) +{ + struct pointer *pointer = data; + + pointer->axis = axis; + pointer->axis_value = wl_fixed_to_double(value); + pointer->axis_time_msec = time_msec; + pointer->axis_time_timespec = pointer->input_timestamp; + pointer->input_timestamp = (struct timespec) { 0 }; + + testlog("test-client: got pointer axis %u %f\n", + axis, wl_fixed_to_double(value)); +} + +static void +pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) +{ + testlog("test-client: got pointer frame\n"); +} + +static void +pointer_handle_axis_source(void *data, struct wl_pointer *wl_pointer, + uint32_t source) +{ + testlog("test-client: got pointer axis source %u\n", source); +} + +static void +pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer, + uint32_t time_msec, uint32_t axis) +{ + struct pointer *pointer = data; + + pointer->axis = axis; + pointer->axis_stop_time_msec = time_msec; + pointer->axis_stop_time_timespec = pointer->input_timestamp; + pointer->input_timestamp = (struct timespec) { 0 }; + + testlog("test-client: got pointer axis stop %u\n", axis); +} + +static void +pointer_handle_axis_discrete(void *data, struct wl_pointer *wl_pointer, + uint32_t axis, int32_t value) +{ + testlog("test-client: got pointer axis discrete %u %d\n", axis, value); +} + +static const struct wl_pointer_listener pointer_listener = { + pointer_handle_enter, + pointer_handle_leave, + pointer_handle_motion, + pointer_handle_button, + pointer_handle_axis, + pointer_handle_frame, + pointer_handle_axis_source, + pointer_handle_axis_stop, + pointer_handle_axis_discrete, +}; + +static void +keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int fd, uint32_t size) +{ + close(fd); + + testlog("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; + + if (wl_surface) + keyboard->focus = wl_surface_get_user_data(wl_surface); + else + keyboard->focus = NULL; + + testlog("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; + + testlog("test-client: got keyboard leave, surface %p\n", + wl_surface ? wl_surface_get_user_data(wl_surface) : NULL); +} + +static void +keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t time_msec, uint32_t key, + uint32_t state) +{ + struct keyboard *keyboard = data; + + keyboard->key = key; + keyboard->state = state; + keyboard->key_time_msec = time_msec; + keyboard->key_time_timespec = keyboard->input_timestamp; + keyboard->input_timestamp = (struct timespec) { 0 }; + + testlog("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; + + testlog("test-client: got keyboard modifiers %u %u %u %u\n", + mods_depressed, mods_latched, mods_locked, group); +} + +static void +keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard, + int32_t rate, int32_t delay) +{ + struct keyboard *keyboard = data; + + keyboard->repeat_info.rate = rate; + keyboard->repeat_info.delay = delay; + + testlog("test-client: got keyboard repeat_info %d %d\n", rate, delay); +} + +static const struct wl_keyboard_listener keyboard_listener = { + keyboard_handle_keymap, + keyboard_handle_enter, + keyboard_handle_leave, + keyboard_handle_key, + keyboard_handle_modifiers, + keyboard_handle_repeat_info, +}; + +static void +touch_handle_down(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time_msec, + struct wl_surface *surface, int32_t id, + wl_fixed_t x_w, wl_fixed_t y_w) +{ + struct touch *touch = data; + + touch->down_x = wl_fixed_to_int(x_w); + touch->down_y = wl_fixed_to_int(y_w); + touch->id = id; + touch->down_time_msec = time_msec; + touch->down_time_timespec = touch->input_timestamp; + touch->input_timestamp = (struct timespec) { 0 }; + + testlog("test-client: got touch down %d %d, surf: %p, id: %d\n", + touch->down_x, touch->down_y, surface, id); +} + +static void +touch_handle_up(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time_msec, int32_t id) +{ + struct touch *touch = data; + touch->up_id = id; + touch->up_time_msec = time_msec; + touch->up_time_timespec = touch->input_timestamp; + touch->input_timestamp = (struct timespec) { 0 }; + + testlog("test-client: got touch up, id: %d\n", id); +} + +static void +touch_handle_motion(void *data, struct wl_touch *wl_touch, + uint32_t time_msec, int32_t id, + wl_fixed_t x_w, wl_fixed_t y_w) +{ + struct touch *touch = data; + touch->x = wl_fixed_to_int(x_w); + touch->y = wl_fixed_to_int(y_w); + touch->motion_time_msec = time_msec; + touch->motion_time_timespec = touch->input_timestamp; + touch->input_timestamp = (struct timespec) { 0 }; + + testlog("test-client: got touch motion, %d %d, id: %d\n", + touch->x, touch->y, id); +} + +static void +touch_handle_frame(void *data, struct wl_touch *wl_touch) +{ + struct touch *touch = data; + + ++touch->frame_no; + + testlog("test-client: got touch frame (%d)\n", touch->frame_no); +} + +static void +touch_handle_cancel(void *data, struct wl_touch *wl_touch) +{ + struct touch *touch = data; + + ++touch->cancel_no; + + testlog("test-client: got touch cancel (%d)\n", touch->cancel_no); +} + +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 +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); + + testlog("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; + + testlog("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 +}; + +static struct buffer * +create_shm_buffer(struct client *client, int width, int height, + pixman_format_code_t format, uint32_t wlfmt) +{ + struct wl_shm *shm = client->wl_shm; + struct buffer *buf; + size_t stride_bytes; + struct wl_shm_pool *pool; + int fd; + void *data; + size_t bytes_pp; + + assert(width > 0); + assert(height > 0); + + buf = xzalloc(sizeof *buf); + + bytes_pp = PIXMAN_FORMAT_BPP(format) / 8; + stride_bytes = width * bytes_pp; + /* round up to multiple of 4 bytes for Pixman */ + stride_bytes = (stride_bytes + 3) & ~3u; + assert(stride_bytes / bytes_pp >= (unsigned)width); + + buf->len = stride_bytes * height; + assert(buf->len / stride_bytes == (unsigned)height); + + fd = os_create_anonymous_file(buf->len); + assert(fd >= 0); + + data = mmap(NULL, buf->len, 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, buf->len); + buf->proxy = wl_shm_pool_create_buffer(pool, 0, width, height, + stride_bytes, wlfmt); + wl_shm_pool_destroy(pool); + close(fd); + + buf->image = pixman_image_create_bits(format, width, height, + data, stride_bytes); + + assert(buf->proxy); + assert(buf->image); + + return buf; +} + +struct buffer * +create_shm_buffer_a8r8g8b8(struct client *client, int width, int height) +{ + assert(client->has_argb); + + return create_shm_buffer(client, width, height, + PIXMAN_a8r8g8b8, WL_SHM_FORMAT_ARGB8888); +} + +void +buffer_destroy(struct buffer *buf) +{ + void *pixels; + + pixels = pixman_image_get_data(buf->image); + + if (buf->proxy) { + wl_buffer_destroy(buf->proxy); + assert(munmap(pixels, buf->len) == 0); + } + + assert(pixman_image_unref(buf->image)); + + free(buf); +} + +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 weston_test *weston_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); + + testlog("test-client: got global pointer %d %d\n", + test->pointer_x, test->pointer_y); +} + +static void +test_handle_capture_screenshot_done(void *data, struct weston_test *weston_test) +{ + struct test *test = data; + + testlog("Screenshot has been captured\n"); + test->buffer_copy_done = 1; +} + +static const struct weston_test_listener test_listener = { + test_handle_pointer_position, + test_handle_capture_screenshot_done, +}; + +static void +input_destroy(struct input *inp) +{ + if (inp->pointer) { + wl_pointer_release(inp->pointer->wl_pointer); + free(inp->pointer); + } + if (inp->keyboard) { + wl_keyboard_release(inp->keyboard->wl_keyboard); + free(inp->keyboard); + } + if (inp->touch) { + wl_touch_release(inp->touch->wl_touch); + free(inp->touch); + } + wl_list_remove(&inp->link); + wl_seat_release(inp->wl_seat); + free(inp->seat_name); + free(inp); +} + +static void +input_update_devices(struct input *input) +{ + struct pointer *pointer; + struct keyboard *keyboard; + struct touch *touch; + + struct wl_seat *seat = input->wl_seat; + enum wl_seat_capability caps = input->caps; + + 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; + } + + if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !input->touch) { + touch = xzalloc(sizeof *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); + input->touch = touch; + } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && input->touch) { + wl_touch_destroy(input->touch->wl_touch); + free(input->touch); + input->touch = NULL; + } +} + +static void +seat_handle_capabilities(void *data, struct wl_seat *seat, + enum wl_seat_capability caps) +{ + struct input *input = data; + + input->caps = caps; + + /* we will create/update the devices only with the right (test) seat. + * If we haven't discovered which seat is the test seat, just + * store capabilities and bail out */ + if (input->seat_name && strcmp(input->seat_name, "test-seat") == 0) + input_update_devices(input); + + testlog("test-client: got seat %p capabilities: %x\n", input, caps); +} + +static void +seat_handle_name(void *data, struct wl_seat *seat, const char *name) +{ + struct input *input = data; + + input->seat_name = strdup(name); + assert(input->seat_name && "No memory"); + + /* We only update the devices and set client input for the test seat */ + if (strcmp(name, "test-seat") == 0) { + assert(!input->client->input && + "Multiple test seats detected!"); + + input_update_devices(input); + input->client->input = input; + } + + testlog("test-client: got seat %p name: \'%s\'\n", input, name); +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, + seat_handle_name, +}; + +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 void +output_handle_scale(void *data, + struct wl_output *wl_output, + int scale) +{ + struct output *output = data; + + output->scale = scale; +} + +static void +output_handle_done(void *data, + struct wl_output *wl_output) +{ + struct output *output = data; + + output->initialized = 1; +} + +static const struct wl_output_listener output_listener = { + output_handle_geometry, + output_handle_mode, + output_handle_done, + output_handle_scale, +}; + +static void +output_destroy(struct output *output) +{ + assert(wl_proxy_get_version((struct wl_proxy *)output->wl_output) >= 3); + wl_output_release(output->wl_output); + wl_list_remove(&output->link); + free(output); +} + +static void +handle_global(void *data, struct wl_registry *registry, + uint32_t id, const char *interface, uint32_t version) +{ + struct client *client = data; + struct output *output; + struct test *test; + struct global *global; + struct input *input; + + 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); + + /* We deliberately bind all globals with the maximum (advertised) + * version, because this test suite must be kept up-to-date with + * Weston. We must always implement at least the version advertised + * by Weston. This is not ok for normal clients, but it is ok in + * this test suite. + */ + + if (strcmp(interface, "wl_compositor") == 0) { + client->wl_compositor = + wl_registry_bind(registry, id, + &wl_compositor_interface, version); + } else if (strcmp(interface, "wl_seat") == 0) { + input = xzalloc(sizeof *input); + input->client = client; + input->global_name = global->name; + input->wl_seat = + wl_registry_bind(registry, id, + &wl_seat_interface, version); + wl_seat_add_listener(input->wl_seat, &seat_listener, input); + wl_list_insert(&client->inputs, &input->link); + } else if (strcmp(interface, "wl_shm") == 0) { + client->wl_shm = + wl_registry_bind(registry, id, + &wl_shm_interface, version); + 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, version); + wl_output_add_listener(output->wl_output, + &output_listener, output); + wl_list_insert(&client->output_list, &output->link); + client->output = output; + } else if (strcmp(interface, "weston_test") == 0) { + test = xzalloc(sizeof *test); + test->weston_test = + wl_registry_bind(registry, id, + &weston_test_interface, version); + weston_test_add_listener(test->weston_test, &test_listener, test); + client->test = test; + } else if (strcmp(interface, "wl_drm") == 0) { + client->has_wl_drm = true; + } +} + +static struct global * +client_find_global_with_name(struct client *client, uint32_t name) +{ + struct global *global; + + wl_list_for_each(global, &client->global_list, link) { + if (global->name == name) + return global; + } + + return NULL; +} + +static struct input * +client_find_input_with_name(struct client *client, uint32_t name) +{ + struct input *input; + + wl_list_for_each(input, &client->inputs, link) { + if (input->global_name == name) + return input; + } + + return NULL; +} + +static void +global_destroy(struct global *global) +{ + wl_list_remove(&global->link); + free(global->interface); + free(global); +} + +static void +handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) +{ + struct client *client = data; + struct global *global; + struct input *input; + + global = client_find_global_with_name(client, name); + assert(global && "Request to remove unknown global"); + + if (strcmp(global->interface, "wl_seat") == 0) { + input = client_find_input_with_name(client, name); + if (input) { + if (client->input == input) + client->input = NULL; + input_destroy(input); + } + } + + /* XXX: handle wl_output */ + + global_destroy(global); +} + +static const struct wl_registry_listener registry_listener = { + handle_global, + handle_global_remove, +}; + +void +skip(const char *fmt, ...) +{ + va_list argp; + + va_start(argp, fmt); + vfprintf(stderr, fmt, argp); + va_end(argp); + + /* automake tests uses exit code 77. weston-test-runner will see + * this and use it, and then weston-test's sigchld handler (in the + * weston process) will use that as an exit status, which is what + * ninja will see in the end. */ + exit(77); +} + +void +expect_protocol_error(struct client *client, + const struct wl_interface *intf, + uint32_t code) +{ + int err; + uint32_t errcode, failed = 0; + const struct wl_interface *interface; + unsigned int id; + + /* if the error has not come yet, make it happen */ + wl_display_roundtrip(client->wl_display); + + err = wl_display_get_error(client->wl_display); + + assert(err && "Expected protocol error but nothing came"); + assert(err == EPROTO && "Expected protocol error but got local error"); + + errcode = wl_display_get_protocol_error(client->wl_display, + &interface, &id); + + /* check error */ + if (errcode != code) { + testlog("Should get error code %d but got %d\n", code, errcode); + failed = 1; + } + + /* this should be definitely set */ + assert(interface); + + if (strcmp(intf->name, interface->name) != 0) { + testlog("Should get interface '%s' but got '%s'\n", + intf->name, interface->name); + failed = 1; + } + + if (failed) { + testlog("Expected other protocol error\n"); + abort(); + } + + /* all OK */ + testlog("Got expected protocol error on '%s' (object id: %d) " + "with code %d\n", interface->name, id, errcode); +} + +static void +log_handler(const char *fmt, va_list args) +{ + fprintf(stderr, "libwayland: "); + vfprintf(stderr, fmt, args); +} + +struct client * +create_client(void) +{ + struct client *client; + + 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); + wl_list_init(&client->inputs); + wl_list_init(&client->output_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); + + /* this roundtrip makes sure we have all globals and we bound to them */ + client_roundtrip(client); + /* this roundtrip makes sure we got all wl_shm.format and wl_seat.* + * events */ + client_roundtrip(client); + + /* must have WL_SHM_FORMAT_ARGB32 */ + assert(client->has_argb); + + /* must have weston_test interface */ + assert(client->test); + + /* must have an output */ + assert(client->output); + + /* the output must be initialized */ + assert(client->output->initialized == 1); + + /* must have seat set */ + assert(client->input); + + return client; +} + +struct surface * +create_test_surface(struct client *client) +{ + struct surface *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); + + wl_surface_set_user_data(surface->wl_surface, surface); + + return surface; +} + +void +surface_destroy(struct surface *surface) +{ + if (surface->wl_surface) + wl_surface_destroy(surface->wl_surface); + if (surface->buffer) + buffer_destroy(surface->buffer); + free(surface); +} + +struct client * +create_client_and_test_surface(int x, int y, int width, int height) +{ + struct client *client; + struct surface *surface; + pixman_color_t color = { 16384, 16384, 16384, 16384 }; /* uint16_t */ + pixman_image_t *solid; + + client = create_client(); + + /* initialize the client surface */ + surface = create_test_surface(client); + client->surface = surface; + + surface->width = width; + surface->height = height; + surface->buffer = create_shm_buffer_a8r8g8b8(client, width, height); + + solid = pixman_image_create_solid_fill(&color); + pixman_image_composite32(PIXMAN_OP_SRC, + solid, /* src */ + NULL, /* mask */ + surface->buffer->image, /* dst */ + 0, 0, /* src x,y */ + 0, 0, /* mask x,y */ + 0, 0, /* dst x,y */ + width, height); + pixman_image_unref(solid); + + move_client(client, x, y); + + return client; +} + +void +client_destroy(struct client *client) +{ + if (client->surface) + surface_destroy(client->surface); + + while (!wl_list_empty(&client->inputs)) { + input_destroy(container_of(client->inputs.next, + struct input, link)); + } + + while (!wl_list_empty(&client->output_list)) { + output_destroy(container_of(client->output_list.next, + struct output, link)); + } + + while (!wl_list_empty(&client->global_list)) { + global_destroy(container_of(client->global_list.next, + struct global, link)); + } + + if (client->test) { + weston_test_destroy(client->test->weston_test); + free(client->test); + } + + if (client->wl_shm) + wl_shm_destroy(client->wl_shm); + if (client->wl_compositor) + wl_compositor_destroy(client->wl_compositor); + if (client->wl_registry) + wl_registry_destroy(client->wl_registry); + + client_roundtrip(client); + + if (client->wl_display) + wl_display_disconnect(client->wl_display); + free(client); +} + +static const char* +output_path(void) +{ + char *path = getenv("WESTON_TEST_OUTPUT_PATH"); + + if (!path) + return "."; + + return path; +} + +char* +screenshot_output_filename(const char *basename, uint32_t seq) +{ + char *filename; + + if (asprintf(&filename, "%s/%s-%02d.png", + output_path(), basename, seq) < 0) + return NULL; + return filename; +} + +static const char* +reference_path(void) +{ + char *path = getenv("WESTON_TEST_REFERENCE_PATH"); + + if (!path) + return WESTON_TEST_REFERENCE_PATH; + return path; +} + +char* +screenshot_reference_filename(const char *basename, uint32_t seq) +{ + char *filename; + + if (asprintf(&filename, "%s/%s-%02d.png", + reference_path(), basename, seq) < 0) + return NULL; + return filename; +} + +char * +image_filename(const char *basename) +{ + char *filename; + + if (asprintf(&filename, "%s/%s.png", reference_path(), basename) < 0) + assert(0); + return filename; +} + +struct format_map_entry { + cairo_format_t cairo; + pixman_format_code_t pixman; +}; + +static const struct format_map_entry format_map[] = { + { CAIRO_FORMAT_ARGB32, PIXMAN_a8r8g8b8 }, + { CAIRO_FORMAT_RGB24, PIXMAN_x8r8g8b8 }, + { CAIRO_FORMAT_A8, PIXMAN_a8 }, + { CAIRO_FORMAT_RGB16_565, PIXMAN_r5g6b5 }, +}; + +static pixman_format_code_t +format_cairo2pixman(cairo_format_t fmt) +{ + unsigned i; + + for (i = 0; i < ARRAY_LENGTH(format_map); i++) + if (format_map[i].cairo == fmt) + return format_map[i].pixman; + + assert(0 && "unknown Cairo pixel format"); +} + +static cairo_format_t +format_pixman2cairo(pixman_format_code_t fmt) +{ + unsigned i; + + for (i = 0; i < ARRAY_LENGTH(format_map); i++) + if (format_map[i].pixman == fmt) + return format_map[i].cairo; + + assert(0 && "unknown Pixman pixel format"); +} + +/** + * Validate range + * + * \param r Range to validate or NULL. + * \return The given range, or {0, 0} for NULL. + * + * Will abort if range is invalid, that is a > b. + */ +static struct range +range_get(const struct range *r) +{ + if (!r) + return (struct range){ 0, 0 }; + + assert(r->a <= r->b); + return *r; +} + +/** + * Compute the ROI for image comparisons + * + * \param img_a An image. + * \param img_b Another image. + * \param clip_rect Explicit ROI, or NULL for using the whole + * image area. + * + * \return The region of interest (ROI) that is guaranteed to be inside both + * images. + * + * If clip_rect is given, it must fall inside of both images. + * If clip_rect is NULL, the images must be of the same size. + * If any precondition is violated, this function aborts with an error. + * + * The ROI is given as pixman_box32_t, where x2,y2 are non-inclusive. + */ +static pixman_box32_t +image_check_get_roi(pixman_image_t *img_a, pixman_image_t *img_b, + const struct rectangle *clip_rect) +{ + int width_a; + int width_b; + int height_a; + int height_b; + pixman_box32_t box; + + width_a = pixman_image_get_width(img_a); + height_a = pixman_image_get_height(img_a); + + width_b = pixman_image_get_width(img_b); + height_b = pixman_image_get_height(img_b); + + if (clip_rect) { + box.x1 = clip_rect->x; + box.y1 = clip_rect->y; + box.x2 = clip_rect->x + clip_rect->width; + box.y2 = clip_rect->y + clip_rect->height; + } else { + box.x1 = 0; + box.y1 = 0; + box.x2 = max(width_a, width_b); + box.y2 = max(height_a, height_b); + } + + assert(box.x1 >= 0); + assert(box.y1 >= 0); + assert(box.x2 > box.x1); + assert(box.y2 > box.y1); + assert(box.x2 <= width_a); + assert(box.x2 <= width_b); + assert(box.y2 <= height_a); + assert(box.y2 <= height_b); + + return box; +} + +struct image_iterator { + char *data; + int stride; /* bytes */ +}; + +static void +image_iter_init(struct image_iterator *it, pixman_image_t *image) +{ + pixman_format_code_t fmt; + + it->stride = pixman_image_get_stride(image); + it->data = (void *)pixman_image_get_data(image); + + fmt = pixman_image_get_format(image); + assert(PIXMAN_FORMAT_BPP(fmt) == 32); +} + +static uint32_t * +image_iter_get_row(struct image_iterator *it, int y) +{ + return (uint32_t *)(it->data + y * it->stride); +} + +struct pixel_diff_stat { + struct pixel_diff_stat_channel { + int min_diff; + int max_diff; + } ch[4]; +}; + +static void +testlog_pixel_diff_stat(const struct pixel_diff_stat *stat) +{ + int i; + + testlog("Image difference statistics:\n"); + for (i = 0; i < 4; i++) { + testlog("\tch %d: [%d, %d]\n", + i, stat->ch[i].min_diff, stat->ch[i].max_diff); + } +} + +static bool +fuzzy_match_pixels(uint32_t pix_a, uint32_t pix_b, + const struct range *fuzz, + struct pixel_diff_stat *stat) +{ + bool ret = true; + int shift; + int i; + + for (shift = 0, i = 0; i < 4; shift += 8, i++) { + int val_a = (pix_a >> shift) & 0xffu; + int val_b = (pix_b >> shift) & 0xffu; + int d = val_b - val_a; + + stat->ch[i].min_diff = min(stat->ch[i].min_diff, d); + stat->ch[i].max_diff = max(stat->ch[i].max_diff, d); + + if (d < fuzz->a || d > fuzz->b) + ret = false; + } + + return ret; +} + +/** + * Test if a given region within two images are pixel-identical + * + * Returns true if the two images pixel-wise identical, and false otherwise. + * + * \param img_a First image. + * \param img_b Second image. + * \param clip_rect The region of interest, or NULL for comparing the whole + * images. + * \param prec Per-channel allowed difference, or NULL for identical match + * required. + * + * This function hard-fails if clip_rect is not inside both images. If clip_rect + * is given, the images do not have to match in size, otherwise size mismatch + * will be a hard failure. + * + * The per-pixel, per-channel difference is computed as img_b - img_a which is + * required to be in the range [prec->a, prec->b] inclusive. The difference is + * signed. All four channels are compared the same way, without any special + * meaning on alpha channel. + */ +bool +check_images_match(pixman_image_t *img_a, pixman_image_t *img_b, + const struct rectangle *clip_rect, const struct range *prec) +{ + struct range fuzz = range_get(prec); + struct pixel_diff_stat diffstat = {}; + struct image_iterator it_a; + struct image_iterator it_b; + pixman_box32_t box; + int x, y; + uint32_t *pix_a; + uint32_t *pix_b; + + box = image_check_get_roi(img_a, img_b, clip_rect); + + image_iter_init(&it_a, img_a); + image_iter_init(&it_b, img_b); + + for (y = box.y1; y < box.y2; y++) { + pix_a = image_iter_get_row(&it_a, y) + box.x1; + pix_b = image_iter_get_row(&it_b, y) + box.x1; + + for (x = box.x1; x < box.x2; x++) { + if (!fuzzy_match_pixels(*pix_a, *pix_b, + &fuzz, &diffstat)) + return false; + + pix_a++; + pix_b++; + } + } + + return true; +} + +/** + * Tint a color + * + * \param src Source pixel as x8r8g8b8. + * \param add The tint as x8r8g8b8, x8 must be zero; r8, g8 and b8 must be + * no greater than 0xc0 to avoid overflow to another channel. + * \return The tinted pixel color as x8r8g8b8, x8 guaranteed to be 0xff. + * + * The source pixel RGB values are divided by 4, and then the tint is added. + * To achieve colors outside of the range of src, a tint color channel must be + * at least 0x40. (0xff / 4 = 0x3f, 0xff - 0x3f = 0xc0) + */ +static uint32_t +tint(uint32_t src, uint32_t add) +{ + uint32_t v; + + v = ((src & 0xfcfcfcfc) >> 2) | 0xff000000; + + return v + add; +} + +/** + * Create a visualization of image differences. + * + * \param img_a First image, which is used as the basis for the output. + * \param img_b Second image. + * \param clip_rect The region of interest, or NULL for comparing the whole + * images. + * \param prec Per-channel allowed difference, or NULL for identical match + * required. + * \return A new image with the differences highlighted. + * + * Regions outside of the region of interest are shaded with black, matching + * pixels are shaded with green, and differing pixels are shaded with + * bright red. + * + * This function hard-fails if clip_rect is not inside both images. If clip_rect + * is given, the images do not have to match in size, otherwise size mismatch + * will be a hard failure. + * + * The per-pixel, per-channel difference is computed as img_b - img_a which is + * required to be in the range [prec->a, prec->b] inclusive. The difference is + * signed. All four channels are compared the same way, without any special + * meaning on alpha channel. + */ +pixman_image_t * +visualize_image_difference(pixman_image_t *img_a, pixman_image_t *img_b, + const struct rectangle *clip_rect, + const struct range *prec) +{ + struct range fuzz = range_get(prec); + struct pixel_diff_stat diffstat = {}; + pixman_image_t *diffimg; + pixman_image_t *shade; + struct image_iterator it_a; + struct image_iterator it_b; + struct image_iterator it_d; + int width; + int height; + pixman_box32_t box; + int x, y; + uint32_t *pix_a; + uint32_t *pix_b; + uint32_t *pix_d; + pixman_color_t shade_color = { 0, 0, 0, 32768 }; + + width = pixman_image_get_width(img_a); + height = pixman_image_get_height(img_a); + box = image_check_get_roi(img_a, img_b, clip_rect); + + diffimg = pixman_image_create_bits_no_clear(PIXMAN_x8r8g8b8, + width, height, NULL, 0); + + /* Fill diffimg with a black-shaded copy of img_a, and then fill + * the clip_rect area with original img_a. + */ + shade = pixman_image_create_solid_fill(&shade_color); + pixman_image_composite32(PIXMAN_OP_SRC, img_a, shade, diffimg, + 0, 0, 0, 0, 0, 0, width, height); + pixman_image_unref(shade); + pixman_image_composite32(PIXMAN_OP_SRC, img_a, NULL, diffimg, + box.x1, box.y1, 0, 0, box.x1, box.y1, + box.x2 - box.x1, box.y2 - box.y1); + + image_iter_init(&it_a, img_a); + image_iter_init(&it_b, img_b); + image_iter_init(&it_d, diffimg); + + for (y = box.y1; y < box.y2; y++) { + pix_a = image_iter_get_row(&it_a, y) + box.x1; + pix_b = image_iter_get_row(&it_b, y) + box.x1; + pix_d = image_iter_get_row(&it_d, y) + box.x1; + + for (x = box.x1; x < box.x2; x++) { + if (fuzzy_match_pixels(*pix_a, *pix_b, + &fuzz, &diffstat)) + *pix_d = tint(*pix_d, 0x00008000); /* green */ + else + *pix_d = tint(*pix_d, 0x00c00000); /* red */ + + pix_a++; + pix_b++; + pix_d++; + } + } + + testlog_pixel_diff_stat(&diffstat); + + return diffimg; +} + +/** + * Write an image into a PNG file. + * + * \param image The image. + * \param fname The name and path for the file. + * + * \returns true if successfully saved file; false otherwise. + * + * \note Only image formats directly supported by Cairo are accepted, not all + * Pixman formats. + */ +bool +write_image_as_png(pixman_image_t *image, const char *fname) +{ + cairo_surface_t *cairo_surface; + cairo_status_t status; + cairo_format_t fmt; + + fmt = format_pixman2cairo(pixman_image_get_format(image)); + + cairo_surface = cairo_image_surface_create_for_data( + (void *)pixman_image_get_data(image), + fmt, + pixman_image_get_width(image), + pixman_image_get_height(image), + pixman_image_get_stride(image)); + + status = cairo_surface_write_to_png(cairo_surface, fname); + if (status != CAIRO_STATUS_SUCCESS) { + testlog("Failed to save image '%s': %s\n", fname, + cairo_status_to_string(status)); + + return false; + } + + cairo_surface_destroy(cairo_surface); + + return true; +} + +static pixman_image_t * +image_convert_to_a8r8g8b8(pixman_image_t *image) +{ + pixman_image_t *ret; + int width; + int height; + + if (pixman_image_get_format(image) == PIXMAN_a8r8g8b8) + return pixman_image_ref(image); + + width = pixman_image_get_width(image); + height = pixman_image_get_height(image); + + ret = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8, width, height, + NULL, 0); + assert(ret); + + pixman_image_composite32(PIXMAN_OP_SRC, image, NULL, ret, + 0, 0, 0, 0, 0, 0, width, height); + + return ret; +} + +static void +destroy_cairo_surface(pixman_image_t *image, void *data) +{ + cairo_surface_t *surface = data; + + cairo_surface_destroy(surface); +} + +/** + * Load an image from a PNG file + * + * Reads a PNG image from disk using the given filename (and path) + * and returns as a Pixman image. Use pixman_image_unref() to free it. + * + * The returned image is always in PIXMAN_a8r8g8b8 format. + * + * @returns Pixman image, or NULL in case of error. + */ +pixman_image_t * +load_image_from_png(const char *fname) +{ + pixman_image_t *image; + pixman_image_t *converted; + cairo_format_t cairo_fmt; + pixman_format_code_t pixman_fmt; + cairo_surface_t *reference_cairo_surface; + cairo_status_t status; + int width; + int height; + int stride; + void *data; + + reference_cairo_surface = cairo_image_surface_create_from_png(fname); + cairo_surface_flush(reference_cairo_surface); + status = cairo_surface_status(reference_cairo_surface); + if (status != CAIRO_STATUS_SUCCESS) { + testlog("Could not open %s: %s\n", fname, + cairo_status_to_string(status)); + cairo_surface_destroy(reference_cairo_surface); + return NULL; + } + + cairo_fmt = cairo_image_surface_get_format(reference_cairo_surface); + pixman_fmt = format_cairo2pixman(cairo_fmt); + + width = cairo_image_surface_get_width(reference_cairo_surface); + height = cairo_image_surface_get_height(reference_cairo_surface); + stride = cairo_image_surface_get_stride(reference_cairo_surface); + data = cairo_image_surface_get_data(reference_cairo_surface); + + /* The Cairo surface will own the data, so we keep it around. */ + image = pixman_image_create_bits_no_clear(pixman_fmt, + width, height, data, stride); + assert(image); + + pixman_image_set_destroy_function(image, destroy_cairo_surface, + reference_cairo_surface); + + converted = image_convert_to_a8r8g8b8(image); + pixman_image_unref(image); + + return converted; +} + +/** + * Take screenshot of a single output + * + * Requests a screenshot from the server of the output that the + * client appears on. This implies that the compositor goes through an output + * repaint to provide the screenshot before this function returns. This + * function is therefore both a server roundtrip and a wait for a repaint. + * + * @returns A new buffer object, that should be freed with buffer_destroy(). + */ +struct buffer * +capture_screenshot_of_output(struct client *client) +{ + struct buffer *buffer; + + buffer = create_shm_buffer_a8r8g8b8(client, + client->output->width, + client->output->height); + + client->test->buffer_copy_done = 0; + weston_test_capture_screenshot(client->test->weston_test, + client->output->wl_output, + buffer->proxy); + while (client->test->buffer_copy_done == 0) + if (wl_display_dispatch(client->wl_display) < 0) + break; + + /* FIXME: Document somewhere the orientation the screenshot is taken + * and how the clip coords are interpreted, in case of scaling/transform. + * If we're using read_pixels() just make sure it is documented somewhere. + * Protocol docs in the XML, comparison function docs in Doxygen style. + */ + + return buffer; +} + +static void +write_visual_diff(pixman_image_t *ref_image, + struct buffer *shot, + const struct rectangle *clip, + const char *test_name, + int seq_no, + const struct range *fuzz) +{ + char *fname; + char *ext_test_name; + pixman_image_t *diff; + int ret; + + ret = asprintf(&ext_test_name, "%s-diff", test_name); + assert(ret >= 0); + + fname = screenshot_output_filename(ext_test_name, seq_no); + diff = visualize_image_difference(ref_image, shot->image, clip, fuzz); + write_image_as_png(diff, fname); + + pixman_image_unref(diff); + free(fname); + free(ext_test_name); +} + +/** + * Take a screenshot and verify its contents + * + * Takes a screenshot and writes the image into a PNG file named with + * get_test_name() and seq_no. Compares the contents to the given reference + * image over the given clip rectangle, reports whether they match to the + * test log, and if they do not match writes a visual diff into a PNG file. + * + * The compositor output size and the reference image size must both contain + * the clip rectangle. + * + * This function uses the pixel value allowed fuzz approriate for GL-renderer + * with 8 bits per channel data. + * + * \param client The client, for connecting to the compositor. + * \param ref_image The reference image file basename, without sequence number + * and .png suffix. + * \param ref_seq_no The reference image sequence number. + * \param clip The region of interest, or NULL for comparing the whole + * images. + * \param seq_no Test sequence number, for writing output files. + * \return True if the screen contents matches the reference image, + * false otherwise. + * + * For bootstrapping, ref_image can be NULL or the file can be missing. + * In that case the screenshot file is written but no comparison is performed, + * and false is returned. + */ +bool +verify_screen_content(struct client *client, + const char *ref_image, + int ref_seq_no, + const struct rectangle *clip, + int seq_no) +{ + const char *test_name = get_test_name(); + const struct range gl_fuzz = { -3, 4 }; + struct buffer *shot; + pixman_image_t *ref = NULL; + char *ref_fname = NULL; + char *shot_fname; + bool match; + + shot = capture_screenshot_of_output(client); + assert(shot); + shot_fname = screenshot_output_filename(test_name, seq_no); + write_image_as_png(shot->image, shot_fname); + + if (ref_image) { + ref_fname = screenshot_reference_filename(ref_image, ref_seq_no); + ref = load_image_from_png(ref_fname); + } + + if (ref) { + match = check_images_match(ref, shot->image, clip, &gl_fuzz); + testlog("Verify reference image %s vs. shot %s: %s\n", + ref_fname, shot_fname, match ? "PASS" : "FAIL"); + + if (!match) { + write_visual_diff(ref, shot, clip, + test_name, seq_no, &gl_fuzz); + } + + pixman_image_unref(ref); + } else { + testlog("No reference image, shot %s: FAIL\n", shot_fname); + match = false; + } + + free(ref_fname); + buffer_destroy(shot); + free(shot_fname); + + return match; +} + +/** + * Create a wl_buffer from a PNG file + * + * Loads the named PNG file from the directory of reference images, + * creates a wl_buffer with scale times the image dimensions in pixels, + * and copies the image content into the buffer using nearest-neighbor filter. + * + * \param client The client, for the Wayland connection. + * \param basename The PNG file name without .png suffix. + * \param scale Upscaling factor >= 1. + */ +struct buffer * +client_buffer_from_image_file(struct client *client, + const char *basename, + int scale) +{ + struct buffer *buf; + char *fname; + pixman_image_t *img; + int buf_w, buf_h; + pixman_transform_t scaling; + + assert(scale >= 1); + + fname = image_filename(basename); + img = load_image_from_png(fname); + free(fname); + assert(img); + + buf_w = scale * pixman_image_get_width(img); + buf_h = scale * pixman_image_get_height(img); + buf = create_shm_buffer_a8r8g8b8(client, buf_w, buf_h); + + pixman_transform_init_scale(&scaling, + pixman_fixed_1 / scale, + pixman_fixed_1 / scale); + pixman_image_set_transform(img, &scaling); + pixman_image_set_filter(img, PIXMAN_FILTER_NEAREST, NULL, 0); + + pixman_image_composite32(PIXMAN_OP_SRC, + img, /* src */ + NULL, /* mask */ + buf->image, /* dst */ + 0, 0, /* src x,y */ + 0, 0, /* mask x,y */ + 0, 0, /* dst x,y */ + buf_w, buf_h); + pixman_image_unref(img); + + return buf; +} + +/** + * Bind to a singleton global in wl_registry + * + * \param client Client whose registry and globals to use. + * \param iface The Wayland interface to look for. + * \param version The version to bind the interface with. + * \return A struct wl_proxy, which you need to cast to the proper type. + * + * Asserts that the global being searched for is a singleton and is found. + * + * Binds with the exact version given, does not take compositor interface + * version into account. + */ +void * +bind_to_singleton_global(struct client *client, + const struct wl_interface *iface, + int version) +{ + struct global *tmp; + struct global *g = NULL; + struct wl_proxy *proxy; + + wl_list_for_each(tmp, &client->global_list, link) { + if (strcmp(tmp->interface, iface->name)) + continue; + + assert(!g && "multiple singleton objects"); + g = tmp; + } + + assert(g && "singleton not found"); + + proxy = wl_registry_bind(client->wl_registry, g->name, iface, version); + assert(proxy); + + return proxy; +} + +/** + * Create a wp_viewport for the client surface + * + * \param client The client->surface to use. + * \return A fresh viewport object. + */ +struct wp_viewport * +client_create_viewport(struct client *client) +{ + struct wp_viewporter *viewporter; + struct wp_viewport *viewport; + + viewporter = bind_to_singleton_global(client, + &wp_viewporter_interface, 1); + viewport = wp_viewporter_get_viewport(viewporter, + client->surface->wl_surface); + assert(viewport); + wp_viewporter_destroy(viewporter); + + return viewport; +} + +/** + * Fill the image with the given color + * + * \param image The image to write to. + * \param color The color to use. + */ +void +fill_image_with_color(pixman_image_t *image, pixman_color_t *color) +{ + pixman_image_t *solid; + int width; + int height; + + width = pixman_image_get_width(image); + height = pixman_image_get_height(image); + + solid = pixman_image_create_solid_fill(color); + pixman_image_composite32(PIXMAN_OP_SRC, + solid, /* src */ + NULL, /* mask */ + image, /* dst */ + 0, 0, /* src x,y */ + 0, 0, /* mask x,y */ + 0, 0, /* dst x,y */ + width, height); + pixman_image_unref(solid); +} + +/** + * Convert 8-bit RGB to opaque Pixman color + * + * \param tmp Pixman color struct to fill in. + * \param r Red value, 0 - 255. + * \param g Green value, 0 - 255. + * \param b Blue value, 0 - 255. + * \return tmp + */ +pixman_color_t * +color_rgb888(pixman_color_t *tmp, uint8_t r, uint8_t g, uint8_t b) +{ + tmp->alpha = 65535; + tmp->red = (r << 8) + r; + tmp->green = (g << 8) + g; + tmp->blue = (b << 8) + b; + + return tmp; +} diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h new file mode 100644 index 0000000..bda330e --- /dev/null +++ b/tests/weston-test-client-helper.h @@ -0,0 +1,287 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _WESTON_TEST_CLIENT_HELPER_H_ +#define _WESTON_TEST_CLIENT_HELPER_H_ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include "weston-test-runner.h" +#include "weston-test-client-protocol.h" +#include "viewporter-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; + /* the seat that is actually used for input events */ + struct input *input; + /* server can have more wl_seats. We need keep them all until we + * find the one that we need. After that, the others + * will be destroyed, so this list will have the length of 1. + * If some day in the future we will need the other seats, + * we can just keep them here. */ + struct wl_list inputs; + struct output *output; + struct surface *surface; + int has_argb; + struct wl_list global_list; + bool has_wl_drm; + struct wl_list output_list; /* struct output::link */ +}; + +struct global { + uint32_t name; + char *interface; + uint32_t version; + struct wl_list link; +}; + +struct test { + struct weston_test *weston_test; + int pointer_x; + int pointer_y; + uint32_t n_egl_buffers; + int buffer_copy_done; +}; + +struct input { + struct client *client; + uint32_t global_name; + struct wl_seat *wl_seat; + struct pointer *pointer; + struct keyboard *keyboard; + struct touch *touch; + char *seat_name; + enum wl_seat_capability caps; + struct wl_list link; +}; + +struct pointer { + struct wl_pointer *wl_pointer; + struct surface *focus; + int x; + int y; + uint32_t button; + uint32_t state; + uint32_t axis; + double axis_value; + uint32_t motion_time_msec; + uint32_t button_time_msec; + uint32_t axis_time_msec; + uint32_t axis_stop_time_msec; + struct timespec input_timestamp; + struct timespec motion_time_timespec; + struct timespec button_time_timespec; + struct timespec axis_time_timespec; + struct timespec axis_stop_time_timespec; +}; + +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 { + int rate; + int delay; + } repeat_info; + uint32_t key_time_msec; + struct timespec input_timestamp; + struct timespec key_time_timespec; +}; + +struct touch { + struct wl_touch *wl_touch; + int down_x; + int down_y; + int x; + int y; + int id; + int up_id; /* id of last wl_touch.up event */ + int frame_no; + int cancel_no; + uint32_t down_time_msec; + uint32_t up_time_msec; + uint32_t motion_time_msec; + struct timespec input_timestamp; + struct timespec down_time_timespec; + struct timespec up_time_timespec; + struct timespec motion_time_timespec; +}; + +struct output { + struct wl_output *wl_output; + struct wl_list link; /* struct client::output_list */ + int x; + int y; + int width; + int height; + int scale; + int initialized; +}; + +struct buffer { + struct wl_buffer *proxy; + size_t len; + pixman_image_t *image; +}; + +struct surface { + struct wl_surface *wl_surface; + struct output *output; /* not owned */ + int x; + int y; + int width; + int height; + struct buffer *buffer; +}; + +struct rectangle { + int x; + int y; + int width; + int height; +}; + +struct range { + int a; + int b; +}; + +struct client * +create_client(void); + +void +client_destroy(struct client *client); + +struct surface * +create_test_surface(struct client *client); + +void +surface_destroy(struct surface *surface); + +struct client * +create_client_and_test_surface(int x, int y, int width, int height); + +struct buffer * +create_shm_buffer_a8r8g8b8(struct client *client, int width, int height); + +void +buffer_destroy(struct buffer *buf); + +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); + +int +frame_callback_wait_nofail(struct client *client, int *done); + +#define frame_callback_wait(c, d) assert(frame_callback_wait_nofail((c), (d))) + +void +skip(const char *fmt, ...); + +void +expect_protocol_error(struct client *client, + const struct wl_interface *intf, uint32_t code); + +char * +screenshot_output_filename(const char *basename, uint32_t seq); + +char * +screenshot_reference_filename(const char *basename, uint32_t seq); + +char * +image_filename(const char *basename); + +bool +check_images_match(pixman_image_t *img_a, pixman_image_t *img_b, + const struct rectangle *clip, + const struct range *prec); + +pixman_image_t * +visualize_image_difference(pixman_image_t *img_a, pixman_image_t *img_b, + const struct rectangle *clip_rect, + const struct range *prec); + +bool +write_image_as_png(pixman_image_t *image, const char *fname); + +pixman_image_t * +load_image_from_png(const char *fname); + +struct buffer * +capture_screenshot_of_output(struct client *client); + +bool +verify_screen_content(struct client *client, + const char *ref_image, + int ref_seq_no, + const struct rectangle *clip, + int seq_no); + +struct buffer * +client_buffer_from_image_file(struct client *client, + const char *basename, + int scale); + +void * +bind_to_singleton_global(struct client *client, + const struct wl_interface *iface, + int version); + +struct wp_viewport * +client_create_viewport(struct client *client); + +void +fill_image_with_color(pixman_image_t *image, pixman_color_t *color); + +pixman_color_t * +color_rgb888(pixman_color_t *tmp, uint8_t r, uint8_t g, uint8_t b); + +#endif diff --git a/tests/weston-test-desktop-shell.c b/tests/weston-test-desktop-shell.c new file mode 100644 index 0000000..f3d881b --- /dev/null +++ b/tests/weston-test-desktop-shell.c @@ -0,0 +1,235 @@ +/* + * Copyright © 2010-2012 Intel Corporation + * Copyright © 2011-2012 Collabora, Ltd. + * Copyright © 2013 Raspberry Pi Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compositor/weston.h" +#include +#include "shared/helpers.h" +#include + +struct desktest_shell { + struct wl_listener compositor_destroy_listener; + struct weston_desktop *desktop; + struct weston_layer background_layer; + struct weston_surface *background_surface; + struct weston_view *background_view; + struct weston_layer layer; + struct weston_view *view; +}; + +static void +desktop_surface_added(struct weston_desktop_surface *desktop_surface, + void *shell) +{ + struct desktest_shell *dts = shell; + + assert(!dts->view); + + dts->view = weston_desktop_surface_create_view(desktop_surface); + + assert(dts->view); +} + +static void +desktop_surface_removed(struct weston_desktop_surface *desktop_surface, + void *shell) +{ + struct desktest_shell *dts = shell; + + assert(dts->view); + + weston_desktop_surface_unlink_view(dts->view); + weston_view_destroy(dts->view); + dts->view = NULL; +} + +static void +desktop_surface_committed(struct weston_desktop_surface *desktop_surface, + int32_t sx, int32_t sy, void *shell) +{ + struct desktest_shell *dts = shell; + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + struct weston_geometry geometry = + weston_desktop_surface_get_geometry(desktop_surface); + + assert(dts->view); + + if (weston_surface_is_mapped(surface)) + return; + + surface->is_mapped = true; + weston_layer_entry_insert(&dts->layer.view_list, &dts->view->layer_link); + weston_view_set_position(dts->view, 0 - geometry.x, 0 - geometry.y); + weston_view_update_transform(dts->view); + dts->view->is_mapped = true; +} + +static void +desktop_surface_move(struct weston_desktop_surface *desktop_surface, + struct weston_seat *seat, uint32_t serial, void *shell) +{ +} + +static void +desktop_surface_resize(struct weston_desktop_surface *desktop_surface, + struct weston_seat *seat, uint32_t serial, + enum weston_desktop_surface_edge edges, void *shell) +{ +} + +static void +desktop_surface_fullscreen_requested(struct weston_desktop_surface *desktop_surface, + bool fullscreen, + struct weston_output *output, void *shell) +{ +} + +static void +desktop_surface_maximized_requested(struct weston_desktop_surface *desktop_surface, + bool maximized, void *shell) +{ +} + +static void +desktop_surface_minimized_requested(struct weston_desktop_surface *desktop_surface, + void *shell) +{ +} + +static void +desktop_surface_ping_timeout(struct weston_desktop_client *desktop_client, + void *shell) +{ +} + +static void +desktop_surface_pong(struct weston_desktop_client *desktop_client, + void *shell) +{ +} + +static const struct weston_desktop_api shell_desktop_api = { + .struct_size = sizeof(struct weston_desktop_api), + .surface_added = desktop_surface_added, + .surface_removed = desktop_surface_removed, + .committed = desktop_surface_committed, + .move = desktop_surface_move, + .resize = desktop_surface_resize, + .fullscreen_requested = desktop_surface_fullscreen_requested, + .maximized_requested = desktop_surface_maximized_requested, + .minimized_requested = desktop_surface_minimized_requested, + .ping_timeout = desktop_surface_ping_timeout, + .pong = desktop_surface_pong, +}; + +static void +shell_destroy(struct wl_listener *listener, void *data) +{ + struct desktest_shell *dts; + + dts = container_of(listener, struct desktest_shell, + compositor_destroy_listener); + + weston_desktop_destroy(dts->desktop); + weston_view_destroy(dts->background_view); + weston_surface_destroy(dts->background_surface); + free(dts); +} + +WL_EXPORT int +wet_shell_init(struct weston_compositor *ec, + int *argc, char *argv[]) +{ + struct desktest_shell *dts; + + dts = zalloc(sizeof *dts); + if (!dts) + return -1; + + if (!weston_compositor_add_destroy_listener_once(ec, + &dts->compositor_destroy_listener, + shell_destroy)) { + free(dts); + return 0; + } + + weston_layer_init(&dts->layer, ec); + weston_layer_init(&dts->background_layer, ec); + + weston_layer_set_position(&dts->layer, + WESTON_LAYER_POSITION_NORMAL); + weston_layer_set_position(&dts->background_layer, + WESTON_LAYER_POSITION_BACKGROUND); + + dts->background_surface = weston_surface_create(ec); + if (dts->background_surface == NULL) + goto out_free; + + dts->background_view = weston_view_create(dts->background_surface); + if (dts->background_view == NULL) + goto out_surface; + + weston_surface_set_color(dts->background_surface, 0.16, 0.32, 0.48, 1.); + pixman_region32_fini(&dts->background_surface->opaque); + pixman_region32_init_rect(&dts->background_surface->opaque, 0, 0, 2000, 2000); + pixman_region32_fini(&dts->background_surface->input); + pixman_region32_init_rect(&dts->background_surface->input, 0, 0, 2000, 2000); + + weston_surface_set_size(dts->background_surface, 2000, 2000); + weston_view_set_position(dts->background_view, 0, 0); + weston_layer_entry_insert(&dts->background_layer.view_list, &dts->background_view->layer_link); + weston_view_update_transform(dts->background_view); + + dts->desktop = weston_desktop_create(ec, &shell_desktop_api, dts); + if (dts->desktop == NULL) + goto out_view; + + return 0; + +out_view: + weston_view_destroy(dts->background_view); + +out_surface: + weston_surface_destroy(dts->background_surface); + +out_free: + wl_list_remove(&dts->compositor_destroy_listener.link); + free(dts); + + return -1; +} diff --git a/tests/weston-test-fixture-compositor.c b/tests/weston-test-fixture-compositor.c new file mode 100644 index 0000000..0c9855f --- /dev/null +++ b/tests/weston-test-fixture-compositor.c @@ -0,0 +1,427 @@ +/* + * Copyright 2019 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "shared/helpers.h" +#include "weston-test-fixture-compositor.h" +#include "weston.h" +#include "test-config.h" + +struct prog_args { + int argc; + char **argv; + char **saved; + int alloc; +}; + +static void +prog_args_init(struct prog_args *p) +{ + memset(p, 0, sizeof(*p)); +} + +static void +prog_args_take(struct prog_args *p, char *arg) +{ + assert(arg); + + if (p->argc == p->alloc) { + p->alloc += 10; + p->argv = realloc(p->argv, sizeof(char *) * p->alloc); + assert(p->argv); + } + + p->argv[p->argc++] = arg; +} + +/* + * The program to be executed will trample on argv, hence we need a copy to + * be able to free all our args. + */ +static void +prog_args_save(struct prog_args *p) +{ + assert(p->saved == NULL); + + p->saved = calloc(p->argc, sizeof(char *)); + assert(p->saved); + + memcpy(p->saved, p->argv, sizeof(char *) * p->argc); +} + +static void +prog_args_fini(struct prog_args *p) +{ + int i; + + assert(p->saved); + + for (i = 0; i < p->argc; i++) + free(p->saved[i]); + free(p->saved); + free(p->argv); + prog_args_init(p); +} + +static char * +get_lock_path(void) +{ + const char *env_path, *suffix; + char *lock_path; + + suffix = "weston-test-suite-drm-lock"; + env_path = getenv("XDG_RUNTIME_DIR"); + if (!env_path) { + fprintf(stderr, "Failed to compute lock file path. " \ + "XDG_RUNTIME_DIR is not set.\n"); + return NULL; + } + + if (asprintf(&lock_path, "%s/%s", env_path, suffix) == -1) + return NULL; + + return lock_path; +} + +/* + * DRM-backend tests need to be run sequentially, since there can only be + * one user at a time with master status in a DRM KMS device. Since the + * test suite runs the tests in parallel, there's a mechanism to assure + * only one DRM-backend test is running at a time: tests of this type keep + * waiting until they acquire a lock (which is hold until they end). + * + * Returns -1 in failure and fd in success. + */ +static int +wait_for_lock(void) +{ + char *lock_path; + int fd; + + lock_path = get_lock_path(); + if (!lock_path) + goto err_path; + + fd = open(lock_path, O_RDWR | O_CLOEXEC | O_CREAT, 00700); + if (fd == -1) { + fprintf(stderr, "Could not open lock file %s: %s\n", lock_path, strerror(errno)); + goto err_open; + } + fprintf(stderr, "Waiting for lock on %s...\n", lock_path); + + /* The call is blocking, so we don't need a 'while'. Also, as we + * have a timeout for each test, this won't get stuck waiting. */ + if (flock(fd, LOCK_EX) == -1) { + fprintf(stderr, "Could not lock %s: %s\n", lock_path, strerror(errno)); + goto err_lock; + } + fprintf(stderr, "Lock %s acquired.\n", lock_path); + free(lock_path); + + return fd; + +err_lock: + close(fd); +err_open: + free(lock_path); +err_path: + return -1; +} + +/** Initialize part of compositor setup + * + * \param setup The variable to initialize. + * \param testset_name Value for testset_name member. + * + * \ingroup testharness_private + */ +void +compositor_setup_defaults_(struct compositor_setup *setup, + const char *testset_name) +{ + *setup = (struct compositor_setup) { + .backend = WESTON_BACKEND_HEADLESS, + .renderer = RENDERER_NOOP, + .shell = SHELL_DESKTOP, + .xwayland = false, + .width = 320, + .height = 240, + .scale = 1, + .transform = WL_OUTPUT_TRANSFORM_NORMAL, + .config_file = NULL, + .extra_module = NULL, + .logging_scopes = NULL, + .testset_name = testset_name, + }; +} + +static const char * +backend_to_str(enum weston_compositor_backend b) +{ + static const char * const names[] = { + [WESTON_BACKEND_DRM] = "drm-backend.so", + [WESTON_BACKEND_FBDEV] = "fbdev-backend.so", + [WESTON_BACKEND_HEADLESS] = "headless-backend.so", + [WESTON_BACKEND_RDP] = "rdp-backend.so", + [WESTON_BACKEND_WAYLAND] = "wayland-backend.so", + [WESTON_BACKEND_X11] = "X11-backend.so", + }; + assert(b >= 0 && b < ARRAY_LENGTH(names)); + return names[b]; +} + +static const char * +renderer_to_arg(enum weston_compositor_backend b, enum renderer_type r) +{ + static const char * const headless_names[] = { + [RENDERER_NOOP] = NULL, + [RENDERER_PIXMAN] = "--use-pixman", + [RENDERER_GL] = "--use-gl", + }; + static const char * const drm_names[] = { + [RENDERER_PIXMAN] = "--use-pixman", + [RENDERER_GL] = NULL, + }; + + switch (b) { + case WESTON_BACKEND_HEADLESS: + assert(r >= RENDERER_NOOP && r <= RENDERER_GL); + return headless_names[r]; + case WESTON_BACKEND_DRM: + assert(r >= RENDERER_PIXMAN && r <= RENDERER_GL); + return drm_names[r]; + default: + assert(0 && "renderer_to_str() does not know the backend"); + } + + return NULL; +} + +static const char * +shell_to_str(enum shell_type t) +{ + static const char * const names[] = { + [SHELL_TEST_DESKTOP] = "weston-test-desktop-shell.so", + [SHELL_DESKTOP] = "desktop-shell.so", + [SHELL_FULLSCREEN] = "fullscreen-shell.so", + [SHELL_IVI] = "ivi-shell.so", + }; + assert(t >= 0 && t < ARRAY_LENGTH(names)); + return names[t]; +} + +static const char * +transform_to_str(enum wl_output_transform t) +{ + static const char * const names[] = { + [WL_OUTPUT_TRANSFORM_NORMAL] = "normal", + [WL_OUTPUT_TRANSFORM_90] = "rotate-90", + [WL_OUTPUT_TRANSFORM_180] = "rotate-180", + [WL_OUTPUT_TRANSFORM_270] = "rotate-270", + [WL_OUTPUT_TRANSFORM_FLIPPED] = "flipped", + [WL_OUTPUT_TRANSFORM_FLIPPED_90] = "flipped-rotate-90", + [WL_OUTPUT_TRANSFORM_FLIPPED_180] = "flipped-rotate-180", + [WL_OUTPUT_TRANSFORM_FLIPPED_270] = "flipped-rotate-270", + }; + + assert(t < ARRAY_LENGTH(names) && names[t]); + return names[t]; +} + +/** Execute compositor + * + * Manufactures the compositor command line and calls wet_main(). + * + * Returns RESULT_SKIP if the given setup contains features that were disabled + * in the build, e.g. GL-renderer or DRM-backend. + * + * \ingroup testharness_private + */ +int +execute_compositor(const struct compositor_setup *setup, + struct wet_testsuite_data *data) +{ + struct prog_args args; + char *tmp; + const char *ctmp, *drm_device; + int ret, lock_fd = -1; + + if (setenv("WESTON_MODULE_MAP", WESTON_MODULE_MAP, 0) < 0 || + setenv("WESTON_DATA_DIR", WESTON_DATA_DIR, 0) < 0) { + fprintf(stderr, "Error: environment setup failed.\n"); + return RESULT_HARD_ERROR; + } + +#ifndef BUILD_DRM_COMPOSITOR + if (setup->backend == WESTON_BACKEND_DRM) { + fprintf(stderr, "DRM-backend required but not built, skipping.\n"); + return RESULT_SKIP; + } +#endif + +#ifndef BUILD_FBDEV_COMPOSITOR + if (setup->backend == WESTON_BACKEND_FBDEV) { + fprintf(stderr, "fbdev-backend required but not built, skipping.\n"); + return RESULT_SKIP; + } +#endif + +#ifndef BUILD_RDP_COMPOSITOR + if (setup->backend == WESTON_BACKEND_RDP) { + fprintf(stderr, "RDP-backend required but not built, skipping.\n"); + return RESULT_SKIP; + } +#endif + +#ifndef BUILD_WAYLAND_COMPOSITOR + if (setup->backend == WESTON_BACKEND_WAYLAND) { + fprintf(stderr, "wayland-backend required but not built, skipping.\n"); + return RESULT_SKIP; + } +#endif + +#ifndef BUILD_X11_COMPOSITOR + if (setup->backend == WESTON_BACKEND_X11) { + fprintf(stderr, "X11-backend required but not built, skipping.\n"); + return RESULT_SKIP; + } +#endif + +#ifndef ENABLE_EGL + if (setup->renderer == RENDERER_GL) { + fprintf(stderr, "GL-renderer required but not built, skipping.\n"); + return RESULT_SKIP; + } +#endif + +#if !TEST_GL_RENDERER + if (setup->renderer == RENDERER_GL) { + fprintf(stderr, "GL-renderer disabled for tests, skipping.\n"); + return RESULT_SKIP; + } +#endif + + prog_args_init(&args); + + /* argv[0] */ + asprintf(&tmp, "weston-%s", setup->testset_name); + prog_args_take(&args, tmp); + + asprintf(&tmp, "--backend=%s", backend_to_str(setup->backend)); + prog_args_take(&args, tmp); + + if (setup->backend == WESTON_BACKEND_DRM) { + + drm_device = getenv("WESTON_TEST_SUITE_DRM_DEVICE"); + if (!drm_device) { + fprintf(stderr, "Skipping DRM-backend tests because " \ + "WESTON_TEST_SUITE_DRM_DEVICE is not set. " \ + "See test suite documentation to learn how " \ + "to run them.\n"); + return RESULT_SKIP; + } + asprintf(&tmp, "--drm-device=%s", drm_device); + prog_args_take(&args, tmp); + + prog_args_take(&args, strdup("--seat=weston-test-seat")); + + prog_args_take(&args, strdup("--continue-without-input")); + + lock_fd = wait_for_lock(); + if (lock_fd == -1) + return RESULT_FAIL; + } + + asprintf(&tmp, "--socket=%s", setup->testset_name); + prog_args_take(&args, tmp); + + asprintf(&tmp, "--modules=%s%s%s", TESTSUITE_PLUGIN_PATH, + setup->extra_module ? "," : "", + setup->extra_module ? setup->extra_module : ""); + prog_args_take(&args, tmp); + + if (setup->backend != WESTON_BACKEND_DRM && + setup->backend != WESTON_BACKEND_FBDEV) { + asprintf(&tmp, "--width=%d", setup->width); + prog_args_take(&args, tmp); + + asprintf(&tmp, "--height=%d", setup->height); + prog_args_take(&args, tmp); + } + + if (setup->scale != 1) { + asprintf(&tmp, "--scale=%d", setup->scale); + prog_args_take(&args, tmp); + } + + if (setup->transform != WL_OUTPUT_TRANSFORM_NORMAL) { + asprintf(&tmp, "--transform=%s", + transform_to_str(setup->transform)); + prog_args_take(&args, tmp); + } + + if (setup->config_file) { + asprintf(&tmp, "--config=%s", setup->config_file); + prog_args_take(&args, tmp); + } else { + prog_args_take(&args, strdup("--no-config")); + } + + ctmp = renderer_to_arg(setup->backend, setup->renderer); + if (ctmp) + prog_args_take(&args, strdup(ctmp)); + + asprintf(&tmp, "--shell=%s", shell_to_str(setup->shell)); + prog_args_take(&args, tmp); + + if (setup->logging_scopes) { + asprintf(&tmp, "--logger-scopes=%s", setup->logging_scopes); + prog_args_take(&args, tmp); + } + + if (setup->xwayland) + prog_args_take(&args, strdup("--xwayland")); + + wet_testsuite_data_set(data); + prog_args_save(&args); + ret = wet_main(args.argc, args.argv); + + prog_args_fini(&args); + + /* We acquired a lock (if this is a DRM-backend test) and now we can + * close its fd and release it, as it has already been run. */ + if (lock_fd != -1) + close(lock_fd); + + return ret; +} diff --git a/tests/weston-test-fixture-compositor.h b/tests/weston-test-fixture-compositor.h new file mode 100644 index 0000000..fd8d0e5 --- /dev/null +++ b/tests/weston-test-fixture-compositor.h @@ -0,0 +1,134 @@ +/* + * Copyright 2019 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_TEST_FIXTURE_COMPOSITOR_H +#define WESTON_TEST_FIXTURE_COMPOSITOR_H + +#include +#include + +#include "weston-testsuite-data.h" + +/** Weston renderer type + * + * \sa compositor_setup + * \ingroup testharness + */ +enum renderer_type { + /** Dummy renderer that does nothing. */ + RENDERER_NOOP = 0, + /** Pixman-renderer */ + RENDERER_PIXMAN, + /** GL-renderer */ + RENDERER_GL +}; + +/** Weston shell plugin + * + * \sa compositor_setup + * \ingroup testharness + */ +enum shell_type { + /** Desktop test-shell with predictable window placement and + * no helper clients */ + SHELL_TEST_DESKTOP = 0, + /** The full desktop shell. */ + SHELL_DESKTOP, + /** The ivi-shell. */ + SHELL_IVI, + /** The fullscreen-shell. */ + SHELL_FULLSCREEN +}; + +/** Weston compositor configuration + * + * This structure determines the Weston compositor command line arguments. + * You should always use compositor_setup_defaults() to initialize this, then + * override any members you need with assignments. + * + * \ingroup testharness + */ +struct compositor_setup { + /** The backend to use. */ + enum weston_compositor_backend backend; + /** The renderer to use. */ + enum renderer_type renderer; + /** The shell plugin to use. */ + enum shell_type shell; + /** Whether to enable xwayland support. */ + bool xwayland; + /** Default output width. */ + unsigned width; + /** Default output height. */ + unsigned height; + /** Default output scale. */ + int scale; + /** Default output transform, one of WL_OUTPUT_TRANSFORM_*. */ + enum wl_output_transform transform; + /** The absolute path to \c weston.ini to use, + * or NULL for \c --no-config . */ + const char *config_file; + /** Full path to an extra plugin to load, or NULL for none. */ + const char *extra_module; + /** Debug scopes for the compositor log, + * or NULL for compositor defaults. */ + const char *logging_scopes; + /** The name of this test program, used as a unique identifier. */ + const char *testset_name; +}; + +void +compositor_setup_defaults_(struct compositor_setup *setup, + const char *testset_name); + +/** Initialize compositor setup to defaults + * + * \param s The variable to initialize. + * + * The defaults are: + * - backend: headless + * - renderer: noop + * - shell: desktop shell + * - xwayland: no + * - width: 320 + * - height: 240 + * - scale: 1 + * - transform: WL_OUTPUT_TRANSFORM_NORMAL + * - config_file: none + * - extra_module: none + * - logging_scopes: compositor defaults + * - testset_name: the test name from meson.build + * + * \ingroup testharness + */ +#define compositor_setup_defaults(s) do {\ + compositor_setup_defaults_(s, THIS_TEST_NAME); \ +} while (0) + +int +execute_compositor(const struct compositor_setup *setup, + struct wet_testsuite_data *data); + +#endif /* WESTON_TEST_FIXTURE_COMPOSITOR_H */ diff --git a/tests/weston-test-runner.c b/tests/weston-test-runner.c new file mode 100644 index 0000000..8e92170 --- /dev/null +++ b/tests/weston-test-runner.c @@ -0,0 +1,620 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "weston-test-runner.h" +#include "weston-testsuite-data.h" +#include "shared/string-helpers.h" + +/** + * \defgroup testharness Test harness + * \defgroup testharness_private Test harness private + */ + +extern const struct weston_test_entry __start_test_section, __stop_test_section; + +struct weston_test_run_info { + char name[512]; + int fixture_nr; +}; + +static const struct weston_test_run_info *test_run_info_; + +/** Get the test name string with counter + * + * \return The test name with fixture number \c -f%%d added. For an array + * driven test, e.g. defined with TEST_P(), the name has also a \c -e%%d + * suffix to indicate the array element number. + * + * This is only usable from code paths inside TEST(), TEST_P(), PLUGIN_TEST() + * etc. defined functions. + * + * \ingroup testharness + */ +const char * +get_test_name(void) +{ + return test_run_info_->name; +} + +/** Get the current fixture index + * + * Returns the current fixture index which can be used directly as an index + * into the array passed as an argument to DECLARE_FIXTURE_SETUP_WITH_ARG(). + * + * This is only usable from code paths inside TEST(), TEST_P(), PLUGIN_TEST() + * etc. defined functions. + * + * \ingroup testharness + */ +int +get_test_fixture_index(void) +{ + return test_run_info_->fixture_nr - 1; +} + +/** Print into test log + * + * This is exactly like printf() except the output goes to the test log, + * which is at stderr. + * + * \param fmt printf format string + * + * \ingroup testharness + */ +void +testlog(const char *fmt, ...) +{ + va_list argp; + + va_start(argp, fmt); + vfprintf(stderr, fmt, argp); + va_end(argp); +} + +static const struct weston_test_entry * +find_test(const char *name) +{ + const struct weston_test_entry *t; + + for (t = &__start_test_section; t < &__stop_test_section; t++) + if (strcmp(t->name, name) == 0) + return t; + + return NULL; +} + +static enum test_result_code +run_test(int fixture_nr, const struct weston_test_entry *t, void *data, + int iteration) +{ + struct weston_test_run_info info; + + if (data) { + snprintf(info.name, sizeof(info.name), "%s-f%02d-e%02d", + t->name, fixture_nr, iteration); + } else { + snprintf(info.name, sizeof(info.name), "%s-f%02d", + t->name, fixture_nr); + } + info.fixture_nr = fixture_nr; + + test_run_info_ = &info; + t->run(data); + test_run_info_ = NULL; + + /* + * XXX: We should return t->run(data); but that requires changing + * the function signature and stop using assert() in tests. + * https://gitlab.freedesktop.org/wayland/weston/issues/311 + */ + return RESULT_OK; +} + +static void +list_tests(void) +{ + const struct fixture_setup_array *fsa; + const struct weston_test_entry *t; + + fsa = fixture_setup_array_get_(); + + printf("Fixture setups: %d\n", fsa->n_elements); + + for (t = &__start_test_section; t < &__stop_test_section; t++) { + printf(" %s\n", t->name); + if (t->n_elements > 1) + printf(" with array of %d cases\n", t->n_elements); + } +} + +struct weston_test_harness { + int32_t fixt_ind; + char *chosen_testname; + int32_t case_ind; + + struct wet_testsuite_data data; +}; + +typedef void (*weston_test_cb)(struct wet_testsuite_data *suite_data, + const struct weston_test_entry *t, + const void *test_data, + int iteration); + +static void +for_each_test_case(struct wet_testsuite_data *data, weston_test_cb cb) +{ + unsigned i; + + for (i = 0; i < data->tests_count; i++) { + const struct weston_test_entry *t = &data->tests[i]; + const void *current_test_data = t->table_data; + int elem; + int elem_end; + + if (data->case_index == -1) { + elem = 0; + elem_end = t->n_elements; + } else { + elem = data->case_index; + elem_end = elem + 1; + } + + for (; elem < elem_end; elem++) { + current_test_data = (char *)t->table_data + + elem * t->element_size; + cb(data, t, current_test_data, elem); + } + } +} + +static const char * +result_to_str(enum test_result_code ret) +{ + static const char *names[] = { + [RESULT_FAIL] = "fail", + [RESULT_HARD_ERROR] = "hard error", + [RESULT_OK] = "ok", + [RESULT_SKIP] = "skip", + }; + + assert(ret >= 0 && ret < ARRAY_LENGTH(names)); + return names[ret]; +} + +static void +run_case(struct wet_testsuite_data *suite_data, + const struct weston_test_entry *t, + const void *test_data, + int iteration) +{ + enum test_result_code ret; + const char *fail = ""; + const char *skip = ""; + int fixture_nr = suite_data->fixture_iteration + 1; + int iteration_nr = iteration + 1; + + testlog("*** Run fixture %d, %s/%d\n", + fixture_nr, t->name, iteration_nr); + + if (suite_data->type == TEST_TYPE_PLUGIN) { + ret = run_test(fixture_nr, t, suite_data->compositor, + iteration); + } else { + ret = run_test(fixture_nr, t, (void *)test_data, iteration); + } + + switch (ret) { + case RESULT_OK: + suite_data->passed++; + break; + case RESULT_FAIL: + case RESULT_HARD_ERROR: + suite_data->failed++; + fail = "not "; + break; + case RESULT_SKIP: + suite_data->skipped++; + skip = " # SKIP"; + break; + } + + testlog("*** Result fixture %d, %s/%d: %s\n", + fixture_nr, t->name, iteration_nr, result_to_str(ret)); + + suite_data->counter++; + printf("%sok %d fixture %d %s/%d%s\n", fail, suite_data->counter, + fixture_nr, t->name, iteration_nr, skip); +} + +/* This function might run in a new thread */ +static void +testsuite_run(struct wet_testsuite_data *data) +{ + for_each_test_case(data, run_case); +} + +static void +count_case(struct wet_testsuite_data *suite_data, + const struct weston_test_entry *t, + const void *test_data, + int iteration) +{ + suite_data->total++; +} + +static void +tap_plan(struct wet_testsuite_data *data, int count_fixtures) +{ + data->total = 0; + for_each_test_case(data, count_case); + + printf("1..%d\n", data->total * count_fixtures); +} + +static void +skip_case(struct wet_testsuite_data *suite_data, + const struct weston_test_entry *t, + const void *test_data, + int iteration) +{ + int fixture_nr = suite_data->fixture_iteration + 1; + int iteration_nr = iteration + 1; + + suite_data->counter++; + printf("ok %d fixture %d %s/%d # SKIP fixture\n", suite_data->counter, + fixture_nr, t->name, iteration_nr); +} + +static void +tap_skip_fixture(struct wet_testsuite_data *data) +{ + for_each_test_case(data, skip_case); +} + +static void +help(const char *exe) +{ + printf( + "Usage: %s [options] [testname [index]]\n" + "\n" + "This is a Weston test suite executable that runs some tests.\n" + "Options:\n" + " -f, --fixture N Run only fixture index N. Indices start from 1.\n" + " -h, --help Print this help and exit with success.\n" + " -l, --list List all tests in this executable and exit with success.\n" + "testname: Optional; name of the test to execute instead of all tests.\n" + "index: Optional; for a multi-case test, run the given case only.\n", + exe); +} + +static void +parse_command_line(struct weston_test_harness *harness, int argc, char **argv) +{ + int c; + static const struct option opts[] = { + { "fixture", required_argument, NULL, 'f' }, + { "help", no_argument, NULL, 'h' }, + { "list", no_argument, NULL, 'l' }, + { 0, 0, NULL, 0 } + }; + + while ((c = getopt_long(argc, argv, "f:hl", opts, NULL)) != -1) { + switch (c) { + case 'f': + if (!safe_strtoint(optarg, &harness->fixt_ind)) { + fprintf(stderr, + "Error: '%s' does not look like a number (command line).\n", + optarg); + exit(RESULT_HARD_ERROR); + } + harness->fixt_ind--; /* convert base-1 to base 0 */ + break; + case 'h': + help(argv[0]); + exit(RESULT_OK); + case 'l': + list_tests(); + exit(RESULT_OK); + case 0: + break; + default: + exit(RESULT_HARD_ERROR); + } + } + + if (optind < argc) + harness->chosen_testname = argv[optind++]; + + if (optind < argc) { + if (!safe_strtoint(argv[optind], &harness->case_ind)) { + fprintf(stderr, + "Error: '%s' does not look like a number (command line).\n", + argv[optind]); + exit(RESULT_HARD_ERROR); + } + harness->case_ind--; /* convert base-1 to base 0 */ + optind++; + } + + if (optind < argc) { + fprintf(stderr, "Unexpected extra arguments given (command line).\n\n"); + help(argv[0]); + exit(RESULT_HARD_ERROR); + } +} + +static struct weston_test_harness * +weston_test_harness_create(int argc, char **argv) +{ + const struct fixture_setup_array *fsa; + struct weston_test_harness *harness; + + harness = zalloc(sizeof(*harness)); + assert(harness); + + harness->fixt_ind = -1; + harness->case_ind = -1; + parse_command_line(harness, argc, argv); + + fsa = fixture_setup_array_get_(); + if (harness->fixt_ind < -1 || harness->fixt_ind >= fsa->n_elements) { + fprintf(stderr, + "Error: fixture index %d (command line) is invalid for this program.\n", + harness->fixt_ind + 1); + exit(RESULT_HARD_ERROR); + } + + if (harness->chosen_testname) { + const struct weston_test_entry *t; + + t = find_test(harness->chosen_testname); + if (!t) { + fprintf(stderr, + "Error: test '%s' not found (command line).\n", + harness->chosen_testname); + exit(RESULT_HARD_ERROR); + } + + if (harness->case_ind < -1 || + harness->case_ind >= t->n_elements) { + fprintf(stderr, + "Error: case index %d (command line) is invalid for this test.\n", + harness->case_ind + 1); + exit(RESULT_HARD_ERROR); + } + + harness->data.tests = t; + harness->data.tests_count = 1; + harness->data.case_index = harness->case_ind; + } else { + harness->data.tests = &__start_test_section; + harness->data.tests_count = + &__stop_test_section - &__start_test_section; + harness->data.case_index = -1; + } + + harness->data.run = testsuite_run; + + return harness; +} + +static void +weston_test_harness_destroy(struct weston_test_harness *harness) +{ + free(harness); +} + +static enum test_result_code +counts_to_result(const struct wet_testsuite_data *data) +{ + /* RESULT_SKIP is reserved for fixture setup itself skipping everything */ + if (data->total == data->passed + data->skipped) + return RESULT_OK; + return RESULT_FAIL; +} + +/** Execute all tests as client tests + * + * \param harness The test harness context. + * \param setup The compositor configuration. + * + * Initializes the compositor with the given setup and executes the compositor. + * The compositor creates a new thread where all tests in the test program are + * serially executed. Once the thread finishes, the compositor returns from its + * event loop and cleans up. + * + * Returns RESULT_SKIP if the requested compositor features, e.g. GL-renderer, + * are not built. + * + * \sa DECLARE_FIXTURE_SETUP(), DECLARE_FIXTURE_SETUP_WITH_ARG() + * \ingroup testharness + */ +enum test_result_code +weston_test_harness_execute_as_client(struct weston_test_harness *harness, + const struct compositor_setup *setup) +{ + struct wet_testsuite_data *data = &harness->data; + + data->type = TEST_TYPE_CLIENT; + return execute_compositor(setup, data); +} + +/** Execute all tests as plugin tests + * + * \param harness The test harness context. + * \param setup The compositor configuration. + * + * Initializes the compositor with the given setup and executes the compositor. + * The compositor executes all tests in the test program serially from an idle + * handler, then returns from its event loop and cleans up. + * + * Returns RESULT_SKIP if the requested compositor features, e.g. GL-renderer, + * are not built. + * + * \sa DECLARE_FIXTURE_SETUP(), DECLARE_FIXTURE_SETUP_WITH_ARG() + * \ingroup testharness + */ +enum test_result_code +weston_test_harness_execute_as_plugin(struct weston_test_harness *harness, + const struct compositor_setup *setup) +{ + struct wet_testsuite_data *data = &harness->data; + + data->type = TEST_TYPE_PLUGIN; + return execute_compositor(setup, data); +} + +/** Execute all tests as standalone tests + * + * \param harness The test harness context. + * + * Executes all tests in the test program serially without any further setup, + * particularly without any compositor instance created. + * + * \sa DECLARE_FIXTURE_SETUP(), DECLARE_FIXTURE_SETUP_WITH_ARG() + * \ingroup testharness + */ +enum test_result_code +weston_test_harness_execute_standalone(struct weston_test_harness *harness) +{ + struct wet_testsuite_data *data = &harness->data; + + data->type = TEST_TYPE_STANDALONE; + data->run(data); + + return RESULT_OK; +} + +/** Fixture data array getter method + * + * DECLARE_FIXTURE_SETUP_WITH_ARG() overrides this in test programs. + * The default implementation has no data and makes the tests run once. + * + * \ingroup testharness + */ +__attribute__((weak)) const struct fixture_setup_array * +fixture_setup_array_get_(void) +{ + /* A dummy fixture without a data array. */ + static const struct fixture_setup_array default_fsa = { + .array = NULL, + .element_size = 0, + .n_elements = 1, + }; + + return &default_fsa; +} + +/** Fixture setup function + * + * DECLARE_FIXTURE_SETUP() and DECLARE_FIXTURE_SETUP_WITH_ARG() override + * this in test programs. + * The default implementation just calls + * weston_test_harness_execute_standalone(). + * + * \ingroup testharness + */ +__attribute__((weak)) enum test_result_code +fixture_setup_run_(struct weston_test_harness *harness, const void *arg_) +{ + return weston_test_harness_execute_standalone(harness); +} + +static void +fixture_report(const struct wet_testsuite_data *d, enum test_result_code ret) +{ + int fixture_nr = d->fixture_iteration + 1; + + testlog("--- Fixture %d %s: passed %d, skipped %d, failed %d, total %d\n", + fixture_nr, result_to_str(ret), + d->passed, d->skipped, d->failed, d->total); +} + +int +main(int argc, char *argv[]) +{ + struct weston_test_harness *harness; + enum test_result_code ret; + enum test_result_code result = RESULT_OK; + const struct fixture_setup_array *fsa; + const char *array_data; + int fi; + int fi_end; + + harness = weston_test_harness_create(argc, argv); + + fsa = fixture_setup_array_get_(); + array_data = fsa->array; + + if (harness->fixt_ind == -1) { + fi = 0; + fi_end = fsa->n_elements; + } else { + fi = harness->fixt_ind; + fi_end = fi + 1; + } + + tap_plan(&harness->data, fi_end - fi); + testlog("Iterating through %d fixtures.\n", fi_end - fi); + + for (; fi < fi_end; fi++) { + const void *arg = array_data + fi * fsa->element_size; + + testlog("--- Fixture %d...\n", fi + 1); + harness->data.fixture_iteration = fi; + harness->data.passed = 0; + harness->data.skipped = 0; + harness->data.failed = 0; + + ret = fixture_setup_run_(harness, arg); + fixture_report(&harness->data, ret); + + if (ret == RESULT_SKIP) { + tap_skip_fixture(&harness->data); + continue; + } + + if (ret != RESULT_OK && result != RESULT_HARD_ERROR) + result = ret; + else if (counts_to_result(&harness->data) != RESULT_OK) + result = RESULT_FAIL; + } + + weston_test_harness_destroy(harness); + + return result; +} diff --git a/tests/weston-test-runner.h b/tests/weston-test-runner.h new file mode 100644 index 0000000..9e545cd --- /dev/null +++ b/tests/weston-test-runner.h @@ -0,0 +1,259 @@ +/* + * Copyright © 2012 Intel Corporation + * Copyright © 2013 Sam Spilsbury + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _WESTON_TEST_RUNNER_H_ +#define _WESTON_TEST_RUNNER_H_ + +#include "config.h" + +#include + +#include +#include "shared/helpers.h" +#include "weston-test-fixture-compositor.h" +#include "weston-testsuite-data.h" + +#ifdef NDEBUG +#error "Tests must not be built with NDEBUG defined, they rely on assert()." +#endif + +/** Test program entry + * + * Each invocation of TEST(), TEST_P(), or PLUGIN_TEST() will create one + * more weston_test_entry in a custom named section in the final binary. + * Iterating through the section then allows to iterate through all + * the defined tests. + * + * \ingroup testharness_private + */ +struct weston_test_entry { + const char *name; + void (*run)(void *); + const void *table_data; + size_t element_size; + int n_elements; +} __attribute__ ((aligned (32))); + +#define TEST_BEGIN(name, arg) \ + static void name(arg) + +#define TEST_COMMON(func, name, data, size, n_elem) \ + static void func(void *); \ + \ + const struct weston_test_entry test##name \ + __attribute__ ((used, section ("test_section"))) = \ + { \ + #name, func, data, size, n_elem \ + }; + +#define NO_ARG_TEST(name) \ + TEST_COMMON(wrap##name, name, NULL, 0, 1) \ + static void name(void); \ + static void wrap##name(void *data) \ + { \ + (void) data; \ + name(); \ + } \ + \ + TEST_BEGIN(name, void) + +#define ARG_TEST(name, test_data) \ + TEST_COMMON(name, name, test_data, \ + sizeof(test_data[0]), \ + ARRAY_LENGTH(test_data)) \ + TEST_BEGIN(name, void *data) \ + +/** Add a test with no parameters + * + * This defines one test as a new function. Use this macro in place of the + * function signature and put the function body after this. + * + * \param name Name for the test, must be a valid function name. + * + * \ingroup testharness + */ +#define TEST(name) NO_ARG_TEST(name) + +/** Add an array driven test with a parameter + * + * This defines an array of tests as a new function. Use this macro in place + * of the function signature and put the function body after this. The function + * will be executed once for each element in \c data_array, passing the + * element as the argument void *data to the function. + * + * This macro is not usable if fixture setup is using + * weston_test_harness_execute_as_plugin(). + * + * \param name Name for the test, must be a valid function name. + * \param data_array A static const array of any type. The length will be + * recorded automatically. + * + * \ingroup testharness + */ +#define TEST_P(name, data_array) ARG_TEST(name, data_array) + +/** Add a test with weston_compositor argument + * + * This defines one test as a new function. Use this macro in place of the + * function signature and put the function body after this. The function + * will have one argument struct weston_compositor *compositor. + * + * This macro is only usable if fixture setup is using + * weston_test_harness_execute_as_plugin(). + * + * \param name Name for the test, must be a valid function name. + * + * \ingroup testharness + */ +#define PLUGIN_TEST(name) \ + TEST_COMMON(wrap##name, name, NULL, 0, 1) \ + static void name(struct weston_compositor *); \ + static void wrap##name(void *compositor) \ + { \ + name(compositor); \ + } \ + TEST_BEGIN(name, struct weston_compositor *compositor) + +void +testlog(const char *fmt, ...) WL_PRINTF(1, 2); + +const char * +get_test_name(void); + +int +get_test_fixture_index(void); + +/** Fixture setup array record + * + * Helper to store the attributes of the data array passed in to + * DECLARE_FIXTURE_SETUP_WITH_ARG(). + * + * \ingroup testharness_private + */ +struct fixture_setup_array { + const void *array; + size_t element_size; + int n_elements; +}; + +const struct fixture_setup_array * +fixture_setup_array_get_(void); + +/** Test harness context + * + * \ingroup testharness + */ +struct weston_test_harness; + +enum test_result_code +fixture_setup_run_(struct weston_test_harness *harness, const void *arg_); + +/** Register a fixture setup function + * + * This registers the given (preferably static) function to be used for setting + * up any fixtures you might need. The function must have the signature: + * + * \code + * enum test_result_code func_(struct weston_test_harness *harness) + * \endcode + * + * The function must call one of weston_test_harness_execute_standalone(), + * weston_test_harness_execute_as_plugin() or + * weston_test_harness_execute_as_client() passing in the \c harness argument, + * and return the return value from that call. The function can also return a + * test_result_code on its own if it does not want to run the tests, + * e.g. RESULT_SKIP or RESULT_HARD_ERROR. + * + * The function will be called once to run all tests. + * + * \param func_ The function to be used as fixture setup. + * + * \ingroup testharness + */ +#define DECLARE_FIXTURE_SETUP(func_) \ + enum test_result_code \ + fixture_setup_run_(struct weston_test_harness *harness, \ + const void *arg_) \ + { \ + return func_(harness); \ + } + +/** Register a fixture setup function with a data array + * + * This registers the given (preferably static) function to be used for setting + * up any fixtures you might need. The function must have the signature: + * + * \code + * enum test_result_code func_(struct weston_test_harness *harness, typeof(array_[0]) *arg) + * \endcode + * + * The function must call one of weston_test_harness_execute_standalone(), + * weston_test_harness_execute_as_plugin() or + * weston_test_harness_execute_as_client() passing in the \c harness argument, + * and return the return value from that call. The function can also return a + * test_result_code on its own if it does not want to run the tests, + * e.g. RESULT_SKIP or RESULT_HARD_ERROR. + * + * The function will be called once with each element of the array pointed to + * by \c arg, so that all tests would be repeated for each element in turn. + * + * \param func_ The function to be used as fixture setup. + * \param array_ A static const array of arbitrary type. + * + * \ingroup testharness + */ +#define DECLARE_FIXTURE_SETUP_WITH_ARG(func_, array_) \ + const struct fixture_setup_array * \ + fixture_setup_array_get_(void) \ + { \ + static const struct fixture_setup_array arr = { \ + .array = array_, \ + .element_size = sizeof(array_[0]), \ + .n_elements = ARRAY_LENGTH(array_) \ + }; \ + return &arr; \ + } \ + \ + enum test_result_code \ + fixture_setup_run_(struct weston_test_harness *harness, \ + const void *arg_) \ + { \ + typeof(array_[0]) *arg = arg_; \ + return func_(harness, arg); \ + } + +enum test_result_code +weston_test_harness_execute_as_client(struct weston_test_harness *harness, + const struct compositor_setup *setup); + +enum test_result_code +weston_test_harness_execute_as_plugin(struct weston_test_harness *harness, + const struct compositor_setup *setup); + +enum test_result_code +weston_test_harness_execute_standalone(struct weston_test_harness *harness); + +#endif diff --git a/tests/weston-test.c b/tests/weston-test.c new file mode 100644 index 0000000..e3a0cb6 --- /dev/null +++ b/tests/weston-test.c @@ -0,0 +1,849 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "backend.h" +#include "libweston-internal.h" +#include "compositor/weston.h" +#include "weston-test-server-protocol.h" +#include "weston.h" +#include "weston-testsuite-data.h" + +#include "shared/helpers.h" +#include "shared/timespec-util.h" + +#define MAX_TOUCH_DEVICES 32 + +struct weston_test { + struct weston_compositor *compositor; + struct wl_listener destroy_listener; + + struct weston_log_scope *log; + + struct weston_layer layer; + struct weston_seat seat; + struct weston_touch_device *touch_device[MAX_TOUCH_DEVICES]; + int nr_touch_devices; + bool is_seat_initialized; + + pthread_t client_thread; + struct wl_event_source *client_source; +}; + +struct weston_test_surface { + struct weston_surface *surface; + struct weston_view *view; + int32_t x, y; + struct weston_test *test; +}; + +static void +touch_device_add(struct weston_test *test) +{ + char buf[128]; + int i = test->nr_touch_devices; + + assert(i < MAX_TOUCH_DEVICES); + assert(!test->touch_device[i]); + + snprintf(buf, sizeof buf, "test-touch-device-%d", i); + + test->touch_device[i] = weston_touch_create_touch_device( + test->seat.touch_state, buf, NULL, NULL); + test->nr_touch_devices++; +} + +static void +touch_device_remove(struct weston_test *test) +{ + int i = test->nr_touch_devices - 1; + + assert(i >= 0); + assert(test->touch_device[i]); + weston_touch_device_destroy(test->touch_device[i]); + test->touch_device[i] = NULL; + --test->nr_touch_devices; +} + +static int +test_seat_init(struct weston_test *test) +{ + assert(!test->is_seat_initialized && + "Trying to add already added test seat"); + + /* create our own seat */ + weston_seat_init(&test->seat, test->compositor, "test-seat"); + test->is_seat_initialized = true; + + /* add devices */ + weston_seat_init_pointer(&test->seat); + if (weston_seat_init_keyboard(&test->seat, NULL) < 0) + return -1; + weston_seat_init_touch(&test->seat); + touch_device_add(test); + + return 0; +} + +static void +test_seat_release(struct weston_test *test) +{ + while (test->nr_touch_devices > 0) + touch_device_remove(test); + + assert(test->is_seat_initialized && + "Trying to release already released test seat"); + test->is_seat_initialized = false; + weston_seat_release(&test->seat); + memset(&test->seat, 0, sizeof test->seat); +} + +static struct weston_seat * +get_seat(struct weston_test *test) +{ + return &test->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 = weston_seat_get_pointer(seat); + + weston_test_send_pointer_position(resource, pointer->x, pointer->y); +} + +static void +test_surface_committed(struct weston_surface *surface, int32_t sx, int32_t sy) +{ + struct weston_test_surface *test_surface = surface->committed_private; + struct weston_test *test = test_surface->test; + + if (wl_list_empty(&test_surface->view->layer_link.link)) + weston_layer_entry_insert(&test->layer.view_list, + &test_surface->view->layer_link); + + weston_view_set_position(test_surface->view, + test_surface->x, test_surface->y); + + weston_view_update_transform(test_surface->view); + + test_surface->surface->is_mapped = true; + test_surface->view->is_mapped = true; +} + +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; + + test_surface = surface->committed_private; + if (!test_surface) { + test_surface = malloc(sizeof *test_surface); + if (!test_surface) { + wl_resource_post_no_memory(resource); + return; + } + + test_surface->view = weston_view_create(surface); + if (!test_surface->view) { + wl_resource_post_no_memory(resource); + free(test_surface); + return; + } + + surface->committed_private = test_surface; + surface->committed = test_surface_committed; + } + + 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, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, + 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 = weston_seat_get_pointer(seat); + struct weston_pointer_motion_event event = { 0 }; + struct timespec time; + + event = (struct weston_pointer_motion_event) { + .mask = WESTON_POINTER_MOTION_REL, + .dx = wl_fixed_to_double(wl_fixed_from_int(x) - pointer->x), + .dy = wl_fixed_to_double(wl_fixed_from_int(y) - pointer->y), + }; + + timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); + + notify_motion(seat, &time, &event); + + notify_pointer_position(test, resource); +} + +static void +send_button(struct wl_client *client, struct wl_resource *resource, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, + int32_t button, uint32_t state) +{ + struct timespec time; + + struct weston_test *test = wl_resource_get_user_data(resource); + struct weston_seat *seat = get_seat(test); + + timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); + + notify_button(seat, &time, button, state); +} + +static void +send_axis(struct wl_client *client, struct wl_resource *resource, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, + uint32_t axis, wl_fixed_t value) +{ + struct weston_test *test = wl_resource_get_user_data(resource); + struct weston_seat *seat = get_seat(test); + struct timespec time; + struct weston_pointer_axis_event axis_event; + + timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); + axis_event.axis = axis; + axis_event.value = wl_fixed_to_double(value); + axis_event.has_discrete = false; + axis_event.discrete = 0; + + notify_axis(seat, &time, &axis_event); +} + +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; + struct weston_keyboard *keyboard; + + seat = get_seat(test); + keyboard = weston_seat_get_keyboard(seat); + if (surface) { + weston_seat_set_keyboard_focus(seat, surface); + notify_keyboard_focus_in(seat, &keyboard->keys, + STATE_UPDATE_AUTOMATIC); + } + else { + notify_keyboard_focus_out(seat); + weston_seat_set_keyboard_focus(seat, surface); + } +} + +static void +send_key(struct wl_client *client, struct wl_resource *resource, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, + 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); + struct timespec time; + + timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); + + notify_key(seat, &time, key, state, STATE_UPDATE_AUTOMATIC); +} + +static void +device_release(struct wl_client *client, + struct wl_resource *resource, const char *device) +{ + struct weston_test *test = wl_resource_get_user_data(resource); + struct weston_seat *seat = get_seat(test); + + if (strcmp(device, "pointer") == 0) { + weston_seat_release_pointer(seat); + } else if (strcmp(device, "keyboard") == 0) { + weston_seat_release_keyboard(seat); + } else if (strcmp(device, "touch") == 0) { + touch_device_remove(test); + weston_seat_release_touch(seat); + } else if (strcmp(device, "seat") == 0) { + test_seat_release(test); + } else { + assert(0 && "Unsupported device"); + } +} + +static void +device_add(struct wl_client *client, + struct wl_resource *resource, const char *device) +{ + struct weston_test *test = wl_resource_get_user_data(resource); + struct weston_seat *seat = get_seat(test); + + if (strcmp(device, "pointer") == 0) { + weston_seat_init_pointer(seat); + } else if (strcmp(device, "keyboard") == 0) { + weston_seat_init_keyboard(seat, NULL); + } else if (strcmp(device, "touch") == 0) { + weston_seat_init_touch(seat); + touch_device_add(test); + } else if (strcmp(device, "seat") == 0) { + test_seat_init(test); + } else { + assert(0 && "Unsupported device"); + } +} + +enum weston_test_screenshot_outcome { + WESTON_TEST_SCREENSHOT_SUCCESS, + WESTON_TEST_SCREENSHOT_NO_MEMORY, + WESTON_TEST_SCREENSHOT_BAD_BUFFER + }; + +typedef void (*weston_test_screenshot_done_func_t)(void *data, + enum weston_test_screenshot_outcome outcome); + +struct test_screenshot { + struct weston_compositor *compositor; + struct wl_global *global; + struct wl_client *client; + struct weston_process process; + struct wl_listener destroy_listener; +}; + +struct test_screenshot_frame_listener { + struct wl_listener listener; + struct weston_buffer *buffer; + struct weston_output *output; + weston_test_screenshot_done_func_t done; + void *data; +}; + +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 +test_screenshot_frame_notify(struct wl_listener *listener, void *data) +{ + struct test_screenshot_frame_listener *l = + container_of(listener, + struct test_screenshot_frame_listener, listener); + struct weston_output *output = l->output; + struct weston_compositor *compositor = output->compositor; + int32_t stride; + uint8_t *pixels, *d, *s; + + weston_output_disable_planes_decr(output); + 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) { + l->done(l->data, WESTON_TEST_SCREENSHOT_NO_MEMORY); + free(l); + return; + } + + /* FIXME: Needs to handle output transformations */ + + 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); + + wl_shm_buffer_begin_access(l->buffer->shm_buffer); + + /* XXX: It would be nice if we used Pixman to do all this rather + * than our own implementation + */ + 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; + } + + wl_shm_buffer_end_access(l->buffer->shm_buffer); + + l->done(l->data, WESTON_TEST_SCREENSHOT_SUCCESS); + free(pixels); + free(l); +} + +static bool +weston_test_screenshot_shoot(struct weston_output *output, + struct weston_buffer *buffer, + weston_test_screenshot_done_func_t done, + void *data) +{ + struct test_screenshot_frame_listener *l; + + /* Get the shm buffer resource the client created */ + if (!wl_shm_buffer_get(buffer->resource)) { + done(data, WESTON_TEST_SCREENSHOT_BAD_BUFFER); + return false; + } + + 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); + + /* Verify buffer is big enough */ + if (buffer->width < output->current_mode->width || + buffer->height < output->current_mode->height) { + done(data, WESTON_TEST_SCREENSHOT_BAD_BUFFER); + return false; + } + + /* allocate the frame listener */ + l = malloc(sizeof *l); + if (l == NULL) { + done(data, WESTON_TEST_SCREENSHOT_NO_MEMORY); + return false; + } + + /* Set up the listener */ + l->buffer = buffer; + l->output = output; + l->done = done; + l->data = data; + l->listener.notify = test_screenshot_frame_notify; + wl_signal_add(&output->frame_signal, &l->listener); + + /* Fire off a repaint */ + weston_output_disable_planes_incr(output); + weston_output_schedule_repaint(output); + + return true; +} + +static void +capture_screenshot_done(void *data, enum weston_test_screenshot_outcome outcome) +{ + struct wl_resource *resource = data; + + switch (outcome) { + case WESTON_TEST_SCREENSHOT_SUCCESS: + weston_test_send_capture_screenshot_done(resource); + break; + case WESTON_TEST_SCREENSHOT_NO_MEMORY: + wl_resource_post_no_memory(resource); + break; + default: + break; + } +} + + +/** + * Grabs a snapshot of the screen. + */ +static void +capture_screenshot(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *output_resource, + struct wl_resource *buffer_resource) +{ + struct weston_output *output = + weston_head_from_resource(output_resource)->output; + struct weston_buffer *buffer = + weston_buffer_from_resource(buffer_resource); + + if (buffer == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + weston_test_screenshot_shoot(output, buffer, + capture_screenshot_done, resource); +} + +static void +send_touch(struct wl_client *client, struct wl_resource *resource, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, + int32_t touch_id, wl_fixed_t x, wl_fixed_t y, uint32_t touch_type) +{ + struct weston_test *test = wl_resource_get_user_data(resource); + struct weston_touch_device *device = test->touch_device[0]; + struct timespec time; + + assert(device); + + timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); + + notify_touch(device, &time, touch_id, wl_fixed_to_double(x), + wl_fixed_to_double(y), touch_type); +} + +static const struct weston_test_interface test_implementation = { + move_surface, + move_pointer, + send_button, + send_axis, + activate_surface, + send_key, + device_release, + device_add, + capture_screenshot, + send_touch, +}; + +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, &weston_test_interface, 1, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, + &test_implementation, test, NULL); + + notify_pointer_position(test, resource); +} + +static void +client_thread_cleanup(void *data_) +{ + struct wet_testsuite_data *data = data_; + + close(data->thread_event_pipe); + data->thread_event_pipe = -1; +} + +static void * +client_thread_routine(void *data_) +{ + struct wet_testsuite_data *data = data_; + + pthread_setname_np(pthread_self(), "client"); + pthread_cleanup_push(client_thread_cleanup, data); + data->run(data); + pthread_cleanup_pop(true); + + return NULL; +} + +static void +client_thread_join(struct weston_test *test) +{ + assert(test->client_source); + + pthread_join(test->client_thread, NULL); + wl_event_source_remove(test->client_source); + test->client_source = NULL; + + weston_log_scope_printf(test->log, "Test thread reaped.\n"); +} + +static int +handle_client_thread_event(int fd, uint32_t mask, void *data_) +{ + struct weston_test *test = data_; + + weston_log_scope_printf(test->log, + "Received thread event mask 0x%x\n", mask); + + if (mask != WL_EVENT_HANGUP) + weston_log("%s: unexpected event %u\n", __func__, mask); + + client_thread_join(test); + weston_compositor_exit(test->compositor); + + return 0; +} + +static int +create_client_thread(struct weston_test *test, struct wet_testsuite_data *data) +{ + struct wl_event_loop *loop; + int pipefd[2] = { -1, -1 }; + sigset_t saved; + sigset_t blocked; + int ret; + + weston_log_scope_printf(test->log, "Creating a thread for running tests...\n"); + + if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) < 0) { + weston_log("Creating pipe for a client thread failed: %s\n", + strerror(errno)); + return -1; + } + + loop = wl_display_get_event_loop(test->compositor->wl_display); + test->client_source = wl_event_loop_add_fd(loop, pipefd[0], + WL_EVENT_READABLE, + handle_client_thread_event, + test); + close(pipefd[0]); + + if (!test->client_source) { + weston_log("Adding client thread fd to event loop failed.\n"); + goto out_pipe; + } + + data->thread_event_pipe = pipefd[1]; + + /* Ensure we don't accidentally get signals to the thread. */ + sigfillset(&blocked); + sigdelset(&blocked, SIGSEGV); + sigdelset(&blocked, SIGFPE); + sigdelset(&blocked, SIGILL); + sigdelset(&blocked, SIGCONT); + sigdelset(&blocked, SIGSYS); + if (pthread_sigmask(SIG_BLOCK, &blocked, &saved) != 0) + goto out_source; + + ret = pthread_create(&test->client_thread, NULL, + client_thread_routine, data); + + pthread_sigmask(SIG_SETMASK, &saved, NULL); + + if (ret != 0) { + weston_log("Creating client thread failed: %s (%d)\n", + strerror(ret), ret); + goto out_source; + } + + return 0; + +out_source: + data->thread_event_pipe = -1; + wl_event_source_remove(test->client_source); + test->client_source = NULL; + +out_pipe: + close(pipefd[1]); + + return -1; +} + +static void +idle_launch_testsuite(void *test_) +{ + struct weston_test *test = test_; + struct wet_testsuite_data *data = wet_testsuite_data_get(); + + if (!data) + return; + + switch (data->type) { + case TEST_TYPE_CLIENT: + if (create_client_thread(test, data) < 0) { + weston_log("Error: creating client thread for test suite failed.\n"); + weston_compositor_exit_with_code(test->compositor, + RESULT_HARD_ERROR); + } + break; + + case TEST_TYPE_PLUGIN: + data->compositor = test->compositor; + weston_log_scope_printf(test->log, + "Running tests from idle handler...\n"); + data->run(data); + weston_compositor_exit(test->compositor); + break; + + case TEST_TYPE_STANDALONE: + weston_log("Error: unknown test internal type %d.\n", + data->type); + weston_compositor_exit_with_code(test->compositor, + RESULT_HARD_ERROR); + } +} + +static void +handle_compositor_destroy(struct wl_listener *listener, + void *weston_compositor) +{ + struct weston_test *test; + + test = wl_container_of(listener, test, destroy_listener); + + if (test->client_source) { + weston_log_scope_printf(test->log, "Cancelling client thread...\n"); + pthread_cancel(test->client_thread); + client_thread_join(test); + } + + if (test->is_seat_initialized) + test_seat_release(test); + + wl_list_remove(&test->layer.view_list.link); + wl_list_remove(&test->layer.link); + + weston_log_scope_destroy(test->log); + free(test); +} + +WL_EXPORT int +wet_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; + + if (!weston_compositor_add_destroy_listener_once(ec, + &test->destroy_listener, + handle_compositor_destroy)) { + free(test); + return 0; + } + + test->compositor = ec; + weston_layer_init(&test->layer, ec); + weston_layer_set_position(&test->layer, WESTON_LAYER_POSITION_CURSOR - 1); + + test->log = weston_compositor_add_log_scope(ec, "test-harness-plugin", + "weston-test plugin's own actions", + NULL, NULL, NULL); + + if (wl_global_create(ec->wl_display, &weston_test_interface, 1, + test, bind_test) == NULL) + goto out_free; + + if (test_seat_init(test) == -1) + goto out_free; + + loop = wl_display_get_event_loop(ec->wl_display); + wl_event_loop_add_idle(loop, idle_launch_testsuite, test); + + return 0; + +out_free: + wl_list_remove(&test->destroy_listener.link); + free(test); + return -1; +} diff --git a/tests/weston-testsuite-data.h b/tests/weston-testsuite-data.h new file mode 100644 index 0000000..06c35bd --- /dev/null +++ b/tests/weston-testsuite-data.h @@ -0,0 +1,88 @@ +/* + * Copyright 2019 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WESTON_TESTSUITE_DATA_H +#define WESTON_TESTSUITE_DATA_H + +/** Standard return codes + * + * Both Autotools and Meson use these codes as test program exit codes + * to denote the test result for the whole process. + * + * \ingroup testharness + */ +enum test_result_code { + RESULT_OK = 0, + RESULT_SKIP = 77, + RESULT_FAIL = 1, + RESULT_HARD_ERROR = 99, +}; + +struct weston_test; +struct weston_compositor; + +/** Weston test types + * + * \sa weston_test_harness_execute_standalone + * weston_test_harness_execute_as_plugin + * weston_test_harness_execute_as_client + * + * \ingroup testharness_private + */ +enum test_type { + TEST_TYPE_STANDALONE, + TEST_TYPE_PLUGIN, + TEST_TYPE_CLIENT, +}; + +/** Test harness specific data for running tests + * + * \ingroup testharness_private + */ +struct wet_testsuite_data { + void (*run)(struct wet_testsuite_data *); + + /* test definitions */ + const struct weston_test_entry *tests; + unsigned tests_count; + int case_index; + enum test_type type; + struct weston_compositor *compositor; + + /* client thread control */ + int thread_event_pipe; + + /* informational run state */ + int fixture_iteration; + + /* test counts */ + unsigned counter; + unsigned passed; + unsigned skipped; + unsigned failed; + unsigned total; +}; + +#endif /* WESTON_TESTSUITE_DATA_H */ diff --git a/tests/xwayland-test.c b/tests/xwayland-test.c new file mode 100644 index 0000000..665f6ea --- /dev/null +++ b/tests/xwayland-test.c @@ -0,0 +1,119 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * xwayland-test: Confirm that we can map a window and we're running + * under Xwayland, not just X. + * + * This is done in steps: + * 1) Confirm that the WL_SURFACE_ID atom exists + * 2) Confirm that the window manager's name is "Weston WM" + * 3) Make sure we can map a window + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "weston-test-runner.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.xwayland = true; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +TEST(xwayland_client_test) +{ + Display *display; + Window window, root, *support; + XEvent event; + int screen, status, actual_format; + unsigned long nitems, bytes; + Atom atom, type_atom, actual_type; + char *wm_name; + + if (access(XSERVER_PATH, X_OK) != 0) + exit(77); + + display = XOpenDisplay(NULL); + if (!display) + exit(EXIT_FAILURE); + + atom = XInternAtom(display, "WL_SURFACE_ID", True); + assert(atom != None); + + atom = XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", True); + assert(atom != None); + + screen = DefaultScreen(display); + root = RootWindow(display, screen); + + status = XGetWindowProperty(display, root, atom, 0L, ~0L, + False, XA_WINDOW, &actual_type, + &actual_format, &nitems, &bytes, + (void *)&support); + assert(status == Success); + + atom = XInternAtom(display, "_NET_WM_NAME", True); + assert(atom != None); + type_atom = XInternAtom(display, "UTF8_STRING", True); + assert(atom != None); + status = XGetWindowProperty(display, *support, atom, 0L, BUFSIZ, + False, type_atom, &actual_type, + &actual_format, &nitems, &bytes, + (void *)&wm_name); + assert(status == Success); + assert(nitems); + assert(strcmp("Weston WM", wm_name) == 0); + free(support); + free(wm_name); + + window = XCreateSimpleWindow(display, root, 100, 100, 300, 300, 1, + BlackPixel(display, screen), + WhitePixel(display, screen)); + XSelectInput(display, window, ExposureMask); + XMapWindow(display, window); + + alarm(4); + while (1) { + XNextEvent(display, &event); + if (event.type == Expose) + break; + } + + XCloseDisplay(display); +} diff --git a/tools/zunitc/doc/zunitc.dox b/tools/zunitc/doc/zunitc.dox new file mode 100644 index 0000000..6fc6858 --- /dev/null +++ b/tools/zunitc/doc/zunitc.dox @@ -0,0 +1,220 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** +@page zunitc zunitc + +- @ref zunitc_overview + - @ref zunitc_overview_return +- @ref zunitc_execution + - @ref zunitc_execution_commandline + - @ref zunitc_execution_matching + - @ref zunitc_execution_wildcards + - @ref zunitc_execution_repeat + - @ref zunitc_execution_randomize +- @ref zunitc_fixtures +- @ref zunitc_functions + +@section zunitc_overview Overview + +A simple test framework in plain C suitable for basic unit and integration +testing. + +The main rationale for creating this framework was to have a simple to use +testing framework with tests implemented in C using common patterns and under a +compatible license. The structure of the test code and macro use is intended to +follow common patterns established by frameworks such as Boost Test and Google +Test. + + +To get started, one or more tests should be defined via ZUC_TEST() and/or +ZUC_TEST_F(), which set up automatic test registration via gcc extensions. +To actually execute tests, ZUC_RUN_TESTS() should be called. + + +Tests can use several ZUC_ASSERT_* or ZUC_ASSERTG_* checks to validate +conditions. The ZUC_ASSERT_* ones upon failure will mark the current test +as failing and immediately execute a return. On the other hand, the +ZUC_ASSERTG_* tests will mark the current test as failed and then execute a +'goto' targeting the specified label. See @ref zunitc_overview_return for more +detail. + +The set of fatal test checks are + +- ZUC_ASSERT_TRUE() +- ZUC_ASSERT_FALSE() +- ZUC_ASSERT_NULL() +- ZUC_ASSERT_NOT_NULL() +- ZUC_ASSERT_EQ() +- ZUC_ASSERT_NE() +- ZUC_ASSERT_LT() +- ZUC_ASSERT_LE() +- ZUC_ASSERT_GT() +- ZUC_ASSERT_GE() +- ZUC_ASSERT_STREQ() +- ZUC_ASSERT_STRNE() + +and + +- ZUC_ASSERTG_TRUE() +- ZUC_ASSERTG_FALSE() +- ZUC_ASSERTG_NULL() +- ZUC_ASSERTG_NOT_NULL() +- ZUC_ASSERTG_EQ() +- ZUC_ASSERTG_NE() +- ZUC_ASSERTG_LT() +- ZUC_ASSERTG_LE() +- ZUC_ASSERTG_GT() +- ZUC_ASSERTG_GE() +- ZUC_ASSERTG_STREQ() +- ZUC_ASSERTG_STRNE() + +Unconditional test values for logging and termination are +- ZUC_SKIP() +- ZUC_FATAL() + +Unconditional message logging for failure cases only is +- ZUC_TRACEPOINT() + +@subsection zunitc_overview_return Test Termination and Return + +One key point where this framework differs from some others (especially many +common ad hoc test programs) is that it does not use assert() nor exceptions. + +- does not use assert() +- can not use throw + +One main implication of this is that when a check fails the current function +will be terminated via a 'return' statement and control passed back to the +calling function. If the check fails in an individual ZUC_TEST() or ZUC_TEST_F() +test function then control is returned to the framework and the current test is +deemed completed (either successfully or with failure). + +On the other hand, if a check fails that is being executed in a function called +by an individual test, then control will stay in the current test. In order to +successfully exit the current test a follow-up check needs to be done after +calling functions that might cause a failure. + +@code{.c} +... + + /* Call a function that might contain ZUC_ASSERT_* use. */ + some_helper_function(); + + /* Stop the current test if something failed. */ + ZUC_ASSERT_FALSE(zuc_has_failure()); + + /* execution will only reach this point if no failures occurred. */ + +... +@endcode + +Use of the ZUC_ASSERTG_*() variants can help in certain situations as check +failure will trigger a 'goto' statement as opposed to a general 'return'. + +@code{.c} +... + + /* Call a function that might contain ZUC_ASSERT_* use. */ + some_helper_function(); + + /* Stop the current test if something failed. */ + ZUC_ASSERTG_FALSE(zuc_has_failure(), err); + + /* execution will only reach this point if no failures occurred. */ +... + +err: + free(str_a); +} +... +@endcode + +@section zunitc_execution Controlling Execution + +To control execution, the various zuc_set_* functions can be called +before invoking ZUC_RUN_TESTS(). + +@subsection zunitc_execution_commandline Command-line Parameters + +The current implementation defers processing of command-line parameters +to the main application hosting the testing. It is possible that a +helper function to process certain parameters may be added. + +@subsection zunitc_execution_matching Matching Patterns for Tests + +The function zuc_set_filter() can be used to specify a pattern for +matching or excluding tests from a run. The general form is + match1[:match2[:match3..n]][:-exclude1[:exclude2[:exclude3..n]]] + +@subsection zunitc_execution_wildcards Matching Wildcards + +Wildcards can be used in the match/exclude patterns and recognize the +following two special characters: +- '*' matches any number of characters including zero. +- '?' matches any single character. + +This pattern matching is consistent with other testing frameworks and +has been chosen in order to make it easier for developers to move +between different testing frameworks. + +Calling zuc_list_tests() after zuc_set_filter() can be done to show the +effects of the matching without needing to actually run tests. + +@subsection zunitc_execution_repeat Repeating Tests + +Setting the repeat count higher than 1 ( via zuc_set_repeat() ) will +cause the tests to be executed several times in a row. This can be +useful for stress testing, checking for leaks, etc. + +@subsection zunitc_execution_randomize Randomizing Tests + +Test ordering can be randomized by setting a non-zero positive value to +zuc_set_random(). Setting it to 1 will cause the framework to pick a +random seed based on the time. A value greater than 1 will be taken as a +random seed itself. And setting it to 0 will disable randomization and +allow the tests to be executed in their natural ordering. + +@section zunitc_fixtures Fixtures + +Per-suite and per-test setup and teardown fixtures can be implemented by +defining an instance of struct zuc_fixture and using it as the first +parameter to ZUC_TEST_F(). + +@section zunitc_functions Functions + +- ZUC_TEST() +- ZUC_TEST_F() +- ZUC_RUN_TESTS() +- zuc_cleanup() +- zuc_list_tests() +- zuc_set_filter() +- zuc_set_random() +- zuc_set_spawn() +- zuc_set_output_junit() +- zuc_has_skip() +- zuc_has_failure() + +*/ diff --git a/tools/zunitc/inc/zunitc/zunitc.h b/tools/zunitc/inc/zunitc/zunitc.h new file mode 100644 index 0000000..16b211b --- /dev/null +++ b/tools/zunitc/inc/zunitc/zunitc.h @@ -0,0 +1,743 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef Z_UNIT_C_H +#define Z_UNIT_C_H + +#include +#include +#include +#include + +#include "zunitc/zunitc_impl.h" + +#if !__GNUC__ +#error Framework currently requires gcc or compatible compiler. +#endif + +#if INTPTR_MAX < INT_MAX +#error Odd platform requires rework of value type from intptr_t to custom. +#endif + +/** + * @file + * Simple unit test framework declarations. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page zunitc + */ + +/** + * Structure to use when defining a test fixture. + * @note likely pending refactoring as use cases are refined. + * @see ZUC_TEST_F() + */ +struct zuc_fixture { + /** + * Initial optional seed data to pass to setup functions and/or tests. + */ + const void *data; + + /** + * Per-suite setup called before invoking any of the tests + * contained in the suite. + * + * @return a pointer to test data, or NULL. + */ + void *(*set_up_test_case)(const void *data); + + /** + * Per-suite tear-down called after invoking all of the tests + * contained in the suite. + * + * @param data pointer returned from the setup function. + */ + void (*tear_down_test_case)(void *data); + + /** + * Setup called before running each of the tests in the suite. + * + * @param data optional data from suite setup, or NULL. + * @return a pointer to test data, or NULL. + */ + void *(*set_up)(void *data); + + /** + * Tear-down called after running each of the tests in the suite. + * + * @param data pointer returned from the setup function. + */ + void (*tear_down)(void *data); +}; + +/** + * Process exit code to mark skipped tests, consistent with + * automake tests. + */ +#define ZUC_EXIT_SKIP 77 + +/** + * Accesses the test executable program name. + * This version will include any full or partial path used to + * launch the executable. + * + * @note This depends on zuc_initialize() having been called. + * + * @return the name of the running program. + * The caller should not free nor hold this pointer. It will not stay + * valid across calls to zuc_initialize() or zuc_cleanup(). + * @see zuc_get_program_basename() + */ +const char * +zuc_get_program_name(void); + +/** + * Accesses the test executable program name in trimmed format. + * If the program is launched via a partial or full path, this + * version trims to return only the basename. + * + * @note This depends on zuc_initialize() having been called. + * + * @return the name of the running program. + * The caller should not free nor hold this pointer. It will not stay + * valid across calls to zuc_initialize() or zuc_cleanup(). + * @see zuc_get_program_name() + */ +const char * +zuc_get_program_basename(void); + +/** + * Initializes the test framework and consumes any known command-line + * parameters from the list. + * The exception is 'h/help' which will be left in place for follow-up + * processing by the hosting app if so desired. + * + * @param argc pointer to argc value to read and possibly change. + * @param argv array of parameter pointers to read and possibly change. + * @param help_flagged if non-NULL will be set to true if the user + * specifies the help flag (and framework help has been output). + * @return EXIT_SUCCESS upon success setting or help, EXIT_FAILURE otherwise. + */ +int zuc_initialize(int *argc, char *argv[], bool *help_flagged); + +/** + * Runs all tests that have been registered. + * Expected return values include EXIT_FAILURE if any errors or failures + * have occurred, ::ZUC_EXIT_SKIP if no failures have occurred but at least + * one test reported skipped, otherwise EXIT_SUCCESS if nothing of note + * was recorded. + * + * @note for consistency with other frameworks and to allow for additional + * cleanup to be added later this is implemented as a wrapper macro. + * + * @return expected exit status - normally EXIT_SUCCESS, ::ZUC_EXIT_SKIP, + * or EXIT_FAILURE. + * Normally an application can use this value directly in calling exit(), + * however there could be cases where some additional processing such as + * resource cleanup or library shutdown that a program might want to do + * first. + */ +#define ZUC_RUN_TESTS() \ + zucimpl_run_tests() + +/** + * Clears the test system in preparation for application shutdown. + */ +void +zuc_cleanup(void); + +/** + * Displays all known tests. + * The list returned is affected by any filtering in place. + * + * @see zuc_set_filter() + */ +void +zuc_list_tests(void); + +/** + * Sets the filter string to use for tests. + * The format is a series of patterns separated by a colon, with wildcards + * and an optional flag for negative matching. For wildcards, the '*' + * character will match any sequence and the '?' character will match any + * single character. + * The '-' character at the start of a pattern marks the end of the + * patterns required to match and the beginning of patterns that names + * must not match. + * Defaults to use all tests. + * + * @param filter the filter string to apply to tests. + */ +void +zuc_set_filter(const char *filter); + +/** + * Trigger specific failure/signal upon test failures; useful when + * running under a debugger. + * Currently this is implemented to raise a SIGABRT signal when any + * failure is reported. + * Defaults to false. + * + * @param break_on_failure true to cause a break when tests fail, false to + * allow normal operation upon failures. + */ +void +zuc_set_break_on_failure(bool break_on_failure); + +/** + * Sets the number of times to repeat the tests. + * Any number higher than 1 will cause the tests to be repeated the + * specified number of times. + * Defaults to 1/no repeating. + * + * @param repeat number of times to repeat the tests. + */ +void +zuc_set_repeat(int repeat); + +/** + * Randomizes the order in which tests are executed. + * A value of 0 (the default) means tests are executed in their natural + * ordering. A value of 1 will pick a random seed based on the time to + * use for running tests in a pseudo-random order. A value greater than 1 + * will be used directly for the initial seed. + * + * If the tests are also repeated, the seed will be incremented for each + * subsequent run. + * Defaults to 0/not randomize. + * + * @param random 0|1|seed value. + * @see zuc_set_repeat() + */ +void +zuc_set_random(int random); + +/** + * Controls whether or not to run the tests as forked child processes. + * Defaults to true. + * + * @param spawn true to spawn each test in a forked child process, + * false to run tests directly. + */ +void +zuc_set_spawn(bool spawn); + +/** + * Enables output in the JUnit XML format. + * Defaults to false. + * + * @param enable true to generate JUnit XML output, false to disable. + */ +void +zuc_set_output_junit(bool enable); + +/** + * Defines a test case that can be registered to run. + * + * @param tcase name to use as the containing test case. + * @param test name used for the test under a given test case. + */ +#define ZUC_TEST(tcase, test) \ + static void zuctest_##tcase##_##test(void); \ + \ + const struct zuc_registration zzz_##tcase##_##test \ + __attribute__ ((used, section ("zuc_tsect"))) = \ + { \ + #tcase, #test, 0, \ + zuctest_##tcase##_##test, \ + 0 \ + }; \ + \ + static void zuctest_##tcase##_##test(void) + +/** + * Defines a test case that can be registered to run along with setup/teardown + * support per-test and/or per test case. + * + * @note This defines a test that *uses* a fixture, it does not + * actually define a test fixture itself. + * + * @param tcase name to use as the containing test case/fixture. + * The name used must represent a test fixture instance. It also + * must not duplicate any name used in a non-fixture ZUC_TEST() + * test. + * @note the test case name must be the name of a fixture struct + * to be passed to the test. + * @param test name used for the test under a given test case. + * @param param name for the fixture data pointer. + * @see struct zuc_fixture + */ +#define ZUC_TEST_F(tcase, test, param) \ + static void zuctest_##tcase##_##test(void *param); \ + \ + const struct zuc_registration zzz_##tcase##_##test \ + __attribute__ ((used, section ("zuc_tsect"))) = \ + { \ + #tcase, #test, &tcase, \ + 0, \ + zuctest_##tcase##_##test \ + }; \ + \ + static void zuctest_##tcase##_##test(void *param) + + +/** + * Returns true if the currently executing test has encountered any skips. + * + * @return true if there is currently a test executing and it has + * encountered any skips. + * @see zuc_has_failure + * @see ZUC_SKIP() + */ +bool +zuc_has_skip(void); + +/** + * Returns true if the currently executing test has encountered any failures. + * + * @return true if there is currently a test executing and it has + * encountered any failures. + * @see zuc_has_skip + */ +bool +zuc_has_failure(void); + +/** + * Marks the running test as skipped without marking it as failed, and returns + * from the current function. + * + * For details on return and test termination see @ref zunitc_overview_return. + * + * @param message the message to log as to why the test has been skipped. + */ +#define ZUC_SKIP(message) \ + do { \ + zucimpl_terminate(__FILE__, __LINE__, false, false, #message); \ + return; \ + } \ + while (0) + +/** + * Marks the running test as failed and returns from the current function. + * + * For details on return and test termination see @ref zunitc_overview_return. + * + * @param message the message to log as to why the test has failed. + */ +#define ZUC_FATAL(message) \ + do { \ + zucimpl_terminate(__FILE__, __LINE__, true, true, #message); \ + return; \ + } \ + while (0) + +/** + * Marks the current test as failed with a fatal issue, but does not + * immediately return from the current function. ZUC_FATAL() is normally + * preferred, but when further cleanup is needed, or the current function + * needs to return a value, this macro may be required. + * + * @param message the message to log as to why the test has failed. + * @see ZUC_FATAL() + */ +#define ZUC_MARK_FATAL(message) \ + do { \ + zucimpl_terminate(__FILE__, __LINE__, true, true, #message); \ + } \ + while (0) + +/** + * Creates a message that will be processed in the case of failure. + * If the test encounters any failures (fatal or non-fatal) then these + * messages are included in output. Otherwise they are discarded at the + * end of the test run. + * + * @param message the format string style message. + */ +#define ZUC_TRACEPOINT(message, ...) \ + zucimpl_tracepoint(__FILE__, __LINE__, message, ##__VA_ARGS__); + +/** + * Internal use macro for ASSERT implementation. + * Should not be used directly in code. + */ +#define ZUCIMPL_ASSERT(opcode, valtype, lhs, rhs) \ + do { \ + if (zucimpl_expect_pred2(__FILE__, __LINE__, \ + (opcode), (valtype), true, \ + (intptr_t)(lhs), (intptr_t)(rhs), \ + #lhs, #rhs)) { \ + return; \ + } \ + } \ + while (0) + +/** + * Internal use macro for ASSERT with Goto implementation. + * Should not be used directly in code. + */ +#define ZUCIMPL_ASSERTG(label, opcode, valtype, lhs, rhs) \ + do { \ + if (zucimpl_expect_pred2(__FILE__, __LINE__, \ + (opcode), (valtype), true, \ + (intptr_t)(lhs), (intptr_t)(rhs), \ + #lhs, #rhs)) { \ + goto label; \ + } \ + } \ + while (0) + +/** + * Verifies that the specified expression is true, marks the test as failed + * and exits the current function via 'return' if it is not. + * + * For details on return and test termination see @ref zunitc_overview_return. + * + * @param condition the expression that is expected to be true. + * @note it is far better to use a more specific check when possible + * (e.g. ZUC_ASSERT_EQ(), ZUC_ASSERT_NE(), etc.) + * @see ZUC_ASSERTG_TRUE() + */ +#define ZUC_ASSERT_TRUE(condition) \ + ZUCIMPL_ASSERT(ZUC_OP_TRUE, ZUC_VAL_INT, condition, 0) + +/** + * Verifies that the specified expression is false, marks the test as + * failed and exits the current function via 'return' if it is not. + * + * For details on return and test termination see @ref zunitc_overview_return. + * + * @param condition the expression that is expected to be false. + * @note it is far better to use a more specific check when possible + * (e.g. ZUC_ASSERT_EQ(), ZUC_ASSERT_NE(), etc.) + * @see ZUC_ASSERTG_FALSE() + */ +#define ZUC_ASSERT_FALSE(condition) \ + ZUCIMPL_ASSERT(ZUC_OP_FALSE, ZUC_VAL_INT, condition, 0) + +/** + * Verifies that the specified expression is NULL, marks the test as failed + * and exits the current function via 'return' if it is not. + * + * For details on return and test termination see @ref zunitc_overview_return. + * + * @param condition the expression that is expected to be a NULL pointer. + * @see ZUC_ASSERTG_NULL() + */ +#define ZUC_ASSERT_NULL(condition) \ + ZUCIMPL_ASSERT(ZUC_OP_NULL, ZUC_VAL_PTR, condition, 0) + +/** + * Verifies that the specified expression is non-NULL, marks the test as + * failed and exits the current function via 'return' if it is not. + * + * For details on return and test termination see @ref zunitc_overview_return. + * + * @param condition the expression that is expected to be a non-NULL pointer. + * @see ZUC_ASSERTG_NOT_NULL() + */ +#define ZUC_ASSERT_NOT_NULL(condition) \ + ZUCIMPL_ASSERT(ZUC_OP_NOT_NULL, ZUC_VAL_PTR, condition, 0) + +/** + * Verifies that the values of the specified expressions match, marks the + * test as failed and exits the current function via 'return' if they do not. + * + * For details on return and test termination see @ref zunitc_overview_return. + * + * @param expected the value the result should hold. + * @param actual the actual value seen in testing. + * @see ZUC_ASSERTG_EQ() + */ +#define ZUC_ASSERT_EQ(expected, actual) \ + ZUCIMPL_ASSERT(ZUC_OP_EQ, ZUC_VAL_INT, expected, actual) + +/** + * Verifies that the values of the specified expressions differ, marks the + * test as failed and exits the current function via 'return' if they do not. + * + * For details on return and test termination see @ref zunitc_overview_return. + * + * @param expected the value the result should not hold. + * @param actual the actual value seen in testing. + * @see ZUC_ASSERTG_NE() + */ +#define ZUC_ASSERT_NE(expected, actual) \ + ZUCIMPL_ASSERT(ZUC_OP_NE, ZUC_VAL_INT, expected, actual) + +/** + * Verifies that the value of the first expression is less than the value + * of the second expression, marks the test as failed and exits the current + * function via 'return' if it is not. + * + * For details on return and test termination see @ref zunitc_overview_return. + * + * @param lesser the expression whose value should be lesser than the other. + * @param greater the expression whose value should be greater than the other. + * @see ZUC_ASSERTG_LT() + */ +#define ZUC_ASSERT_LT(lesser, greater) \ + ZUCIMPL_ASSERT(ZUC_OP_LT, ZUC_VAL_INT, lesser, greater) + +/** + * Verifies that the value of the first expression is less than or equal + * to the value of the second expression, marks the test as failed and + * exits the current function via 'return' if it is not. + * + * For details on return and test termination see @ref zunitc_overview_return. + * + * @param lesser the expression whose value should be lesser than or equal to + * the other. + * @param greater the expression whose value should be greater than or equal to + * the other. + * @see ZUC_ASSERTG_LE() + */ +#define ZUC_ASSERT_LE(lesser, greater) \ + ZUCIMPL_ASSERT(ZUC_OP_LE, ZUC_VAL_INT, lesser, greater) + +/** + * Verifies that the value of the first expression is greater than the + * value of the second expression, marks the test as failed and exits the + * current function via 'return' if it is not. + * + * For details on return and test termination see @ref zunitc_overview_return. + * + * @param greater the expression whose value should be greater than the other. + * @param lesser the expression whose value should be lesser than the other. + * @see ZUC_ASSERTG_GT() + */ +#define ZUC_ASSERT_GT(greater, lesser) \ + ZUCIMPL_ASSERT(ZUC_OP_GT, ZUC_VAL_INT, greater, lesser) + +/** + * Verifies that the value of the first expression is greater than or equal + * to the value of the second expression, marks the test as failed and exits + * the current function via 'return' if it is not. + * + * For details on return and test termination see @ref zunitc_overview_return. + * + * @param greater the expression whose value should be greater than or equal to + * the other. + * @param lesser the expression whose value should be lesser than or equal to + * the other. + * @see ZUC_ASSERTG_GE() + */ +#define ZUC_ASSERT_GE(greater, lesser) \ + ZUCIMPL_ASSERT(ZUC_OP_GE, ZUC_VAL_INT, greater, lesser) + +/** + * Verifies that the values of the specified expressions match when + * compared as null-terminated C-style strings, marks the test as failed + * and exits the current function via 'return' if they do not. + * + * For details on return and test termination see @ref zunitc_overview_return. + * + * @param expected the value the result should hold. + * @param actual the actual value seen in testing. + * @see ZUC_ASSERTG_STREQ() + */ +#define ZUC_ASSERT_STREQ(expected, actual) \ + ZUCIMPL_ASSERT(ZUC_OP_EQ, ZUC_VAL_CSTR, expected, actual) + +/** + * Verifies that the values of the specified expressions differ when + * compared as null-terminated C-style strings, marks the test as failed + * and exits the current function via 'return' if they do not. + * + * For details on return and test termination see @ref zunitc_overview_return. + * + * @param expected the value the result should not hold. + * @param actual the actual value seen in testing. + * @see ZUC_ASSERTG_STRNE() + */ +#define ZUC_ASSERT_STRNE(expected, actual) \ + ZUCIMPL_ASSERT(ZUC_OP_NE, ZUC_VAL_CSTR, expected, actual) + +/** + * Verifies that the specified expression is true, marks the test as failed + * and interrupts the current execution via a 'goto' if it is not. + * + * @param condition the expression that is expected to be true. + * @note it is far better to use a more specific check when possible + * (e.g. ZUC_ASSERTG_EQ(), ZUC_ASSERTG_NE(), etc.) + * @param label the target for 'goto' if the assertion fails. + * @see ZUC_ASSERT_TRUE() + */ +#define ZUC_ASSERTG_TRUE(condition, label) \ + ZUCIMPL_ASSERTG(label, ZUC_OP_TRUE, ZUC_VAL_INT, condition, 0) + +/** + * Verifies that the specified expression is false, marks the test as + * failed and interrupts the current execution via a 'goto' if it is not. + * + * @param condition the expression that is expected to be false. + * @note it is far better to use a more specific check when possible + * (e.g. ZUC_ASSERTG_EQ(), ZUC_ASSERTG_NE(), etc.) + * @param label the target for 'goto' if the assertion fails. + * @see ZUC_ASSERT_FALSE() + */ +#define ZUC_ASSERTG_FALSE(condition, label) \ + ZUCIMPL_ASSERTG(label, ZUC_OP_FALSE, ZUC_VAL_INT, condition, 0) + +/** + * Verifies that the specified expression is NULL, marks the test as failed + * and interrupts the current execution via a 'goto' if it is not. + * + * @param condition the expression that is expected to be a NULL pointer. + * @param label the target for 'goto' if the assertion fails. + * @see ZUC_ASSERT_NULL() + */ +#define ZUC_ASSERTG_NULL(condition, label) \ + ZUCIMPL_ASSERTG(label, ZUC_OP_NULL, ZUC_VAL_PTR, condition, 0) + +/** + * Verifies that the specified expression is non-NULL, marks the test as + * failed and interrupts the current execution via a 'goto' if it is not. + * + * @param condition the expression that is expected to be a non-NULL pointer. + * @param label the target for 'goto' if the assertion fails. + * @see ZUC_ASSERT_NOT_NULL() + */ +#define ZUC_ASSERTG_NOT_NULL(condition, label) \ + ZUCIMPL_ASSERTG(label, ZUC_OP_NOT_NULL, ZUC_VAL_PTR, condition, 0) + +/** + * Verifies that the values of the specified expressions match, marks the + * test as failed and interrupts the current execution via a 'goto' if they + * do not. + * + * @param expected the value the result should hold. + * @param actual the actual value seen in testing. + * @param label the target for 'goto' if the assertion fails. + * @see ZUC_ASSERT_EQ() + */ +#define ZUC_ASSERTG_EQ(expected, actual, label) \ + ZUCIMPL_ASSERTG(label, ZUC_OP_EQ, ZUC_VAL_INT, expected, actual) + +/** + * Verifies that the values of the specified expressions differ, marks the + * test as failed and interrupts the current execution via a 'goto' if they + * do not. + * + * @param expected the value the result should not hold. + * @param actual the actual value seen in testing. + * @param label the target for 'goto' if the assertion fails. + * @see ZUC_ASSERT_NE() + */ +#define ZUC_ASSERTG_NE(expected, actual, label) \ + ZUCIMPL_ASSERTG(label, ZUC_OP_NE, ZUC_VAL_INT, expected, actual) + +/** + * Verifies that the value of the first expression is less than the value + * of the second expression, marks the test as failed and interrupts the + * current execution via a 'goto' if it is not. + * + * @param lesser the expression whose value should be lesser than the other. + * @param greater the expression whose value should be greater than the other. + * @param label the target for 'goto' if the assertion fails. + * @see ZUC_ASSERT_LT() + */ +#define ZUC_ASSERTG_LT(lesser, greater, label) \ + ZUCIMPL_ASSERTG(label, ZUC_OP_LT, ZUC_VAL_INT, lesser, greater) + +/** + * Verifies that the value of the first expression is less than or equal + * to the value of the second expression, marks the test as failed and + * interrupts the current execution via a 'goto' if it is not. + * + * @param lesser the expression whose value should be lesser than or equal to + * the other. + * @param greater the expression whose value should be greater than or equal to + * the other. + * @param label the target for 'goto' if the assertion fails. + * @see ZUC_ASSERT_LE() + */ +#define ZUC_ASSERTG_LE(lesser, greater, label) \ + ZUCIMPL_ASSERTG(label, ZUC_OP_LE, ZUC_VAL_INT, lesser, greater) + +/** + * Verifies that the value of the first expression is greater than the + * value of the second expression, marks the test as failed and interrupts the + * current execution via a 'goto' if it is not. + * + * @param greater the expression whose value should be greater than the other. + * @param lesser the expression whose value should be lesser than the other. + * @param label the target for 'goto' if the assertion fails. + * @see ZUC_ASSERT_GT() + */ +#define ZUC_ASSERTG_GT(greater, lesser, label) \ + ZUCIMPL_ASSERTG(label, ZUC_OP_GT, ZUC_VAL_INT, greater, lesser) + +/** + * Verifies that the value of the first expression is greater than or equal + * to the value of the second expression, marks the test as failed and + * interrupts the current execution via a 'goto' if it is not. + * + * @param greater the expression whose value should be greater than or equal to + * the other. + * @param lesser the expression whose value should be lesser than or equal to + * the other. + * @param label the target for 'goto' if the assertion fails. + * @see ZUC_ASSERT_GE() + */ +#define ZUC_ASSERTG_GE(greater, lesser, label) \ + ZUCIMPL_ASSERTG(label, ZUC_OP_GE, ZUC_VAL_INT, greater, lesser) + +/** + * Verifies that the values of the specified expressions match when + * compared as null-terminated C-style strings, marks the test as failed + * and interrupts the current execution via a 'goto' if they do not. + * + * @param expected the value the result should hold. + * @param actual the actual value seen in testing. + * @param label the target for 'goto' if the assertion fails. + * @see ZUC_ASSERT_STREQ() + */ +#define ZUC_ASSERTG_STREQ(expected, actual, label) \ + ZUCIMPL_ASSERTG(label, ZUC_OP_EQ, ZUC_VAL_CSTR, expected, actual) + +/** + * Verifies that the values of the specified expressions differ when + * compared as null-terminated C-style strings, marks the test as failed + * and interrupts the current execution via a 'goto' if they do not. + * + * @param expected the value the result should not hold. + * @param actual the actual value seen in testing. + * @param label the target for 'goto' if the assertion fails. + * @see ZUC_ASSERT_STRNE() + */ +#define ZUC_ASSERTG_STRNE(expected, actual, label) \ + ZUCIMPL_ASSERTG(label, ZUC_OP_NE, ZUC_VAL_CSTR, expected, actual) + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* Z_UNIT_C_H */ diff --git a/tools/zunitc/inc/zunitc/zunitc_impl.h b/tools/zunitc/inc/zunitc/zunitc_impl.h new file mode 100644 index 0000000..f053368 --- /dev/null +++ b/tools/zunitc/inc/zunitc/zunitc_impl.h @@ -0,0 +1,105 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef Z_UNIT_C_IMPL_H +#define Z_UNIT_C_IMPL_H + +/** + * @file + * Internal details to bridge the public API - should not be used + * directly in user code. + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum zuc_check_op +{ + ZUC_OP_TRUE, + ZUC_OP_FALSE, + ZUC_OP_NULL, + ZUC_OP_NOT_NULL, + ZUC_OP_EQ, + ZUC_OP_NE, + ZUC_OP_GE, + ZUC_OP_GT, + ZUC_OP_LE, + ZUC_OP_LT, + ZUC_OP_TERMINATE, + ZUC_OP_TRACEPOINT +}; + +enum zuc_check_valtype +{ + ZUC_VAL_INT, + ZUC_VAL_CSTR, + ZUC_VAL_PTR, +}; + +typedef void (*zucimpl_test_fn)(void); + +typedef void (*zucimpl_test_fn_f)(void *); + +/** + * Internal use structure for automatic test case registration. + * Should not be used directly in code. + */ +struct zuc_registration { + const char *tcase; /**< Name of the test case. */ + const char *test; /**< Name of the specific test. */ + const struct zuc_fixture* fxt; /**< Optional fixture for test/case. */ + zucimpl_test_fn fn; /**< function implementing base test. */ + zucimpl_test_fn_f fn_f; /**< function implementing test with + fixture. */ +} __attribute__ ((aligned (32))); + + +int +zucimpl_run_tests(void); + +void +zucimpl_terminate(char const *file, int line, + bool fail, bool fatal, const char *msg); + +int +zucimpl_tracepoint(char const *file, int line, const char *fmt, ...) + __attribute__ ((format (printf, 3, 4))); + +int +zucimpl_expect_pred2(char const *file, int line, + enum zuc_check_op, enum zuc_check_valtype valtype, + bool fatal, + intptr_t lhs, intptr_t rhs, + const char *lhs_str, const char* rhs_str); + +#ifdef __cplusplus +} +#endif + +#endif /* Z_UNIT_C_IMPL_H */ diff --git a/tools/zunitc/src/main.c b/tools/zunitc/src/main.c new file mode 100644 index 0000000..8179807 --- /dev/null +++ b/tools/zunitc/src/main.c @@ -0,0 +1,58 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +/* + * Common main() for test programs. + */ + +#include +#include + +#include "zunitc/zunitc.h" + +int +main(int argc, char* argv[]) +{ + bool helped = false; + int rc = zuc_initialize(&argc, argv, &helped); + + if ((rc == EXIT_SUCCESS) && !helped) { + /* Stop if any unrecognized parameters were encountered. */ + if (argc > 1) { + printf("%s: unrecognized option '%s'\n", + argv[0], argv[1]); + printf("Try '%s --help' for more information.\n", + argv[0]); + rc = EXIT_FAILURE; + } else { + rc = ZUC_RUN_TESTS(); + } + } + + zuc_cleanup(); + return rc; +} diff --git a/tools/zunitc/src/zuc_base_logger.c b/tools/zunitc/src/zuc_base_logger.c new file mode 100644 index 0000000..4b54344 --- /dev/null +++ b/tools/zunitc/src/zuc_base_logger.c @@ -0,0 +1,401 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include "zuc_base_logger.h" + +#include +#include +#include +#include +#include +#include + +#include "zuc_event_listener.h" +#include "zuc_types.h" + +#include + +/* a few sequences for rudimentary ANSI graphics. */ +#define CSI_GRN "\x1b[0;32m" +#define CSI_RED "\x1b[0;31m" +#define CSI_YLW "\x1b[0;33m" +#define CSI_RST "\x1b[m" + +/** + * Logical mappings of style levels. + */ +enum style_level { + STYLE_GOOD, + STYLE_WARN, + STYLE_BAD +}; + +/** + * Structure for internal context. + */ +struct base_data { + bool use_color; +}; + +/** + * Prints a formatted string with optional ANSI coloring. + * + * @param use_color true to colorize the output, false to output normally. + * @param slevel the logical type to color for. + * @param fmt the format string to print with. + */ +static void +styled_printf(bool use_color, enum style_level slevel, const char *fmt, ...); + +static void +destroy(void *data); + +static void +pre_run(void *data, int pass_count, int pass_num, int seed, const char *filter); + +static void +run_started(void *data, int live_case_count, + int live_test_count, int disabled_count); + +static void +run_ended(void *data, int case_count, struct zuc_case **cases, + int live_case_count, int live_test_count, int total_passed, + int total_failed, int total_disabled, long total_elapsed); + +static void +case_started(void *data, struct zuc_case *test_case, int live_test_count, + int disabled_count); + +static void +case_ended(void *data, struct zuc_case *test_case); + +static void +test_started(void *data, struct zuc_test *test); + +static void +test_ended(void *data, struct zuc_test *test); + +static void +check_triggered(void *data, char const *file, int line, + enum zuc_fail_state state, enum zuc_check_op op, + enum zuc_check_valtype valtype, + intptr_t val1, intptr_t val2, + const char *expr1, const char *expr2); + +struct zuc_event_listener * +zuc_base_logger_create(void) +{ + struct zuc_event_listener *listener = + zalloc(sizeof(struct zuc_event_listener)); + + listener->data = zalloc(sizeof(struct base_data)); + listener->destroy = destroy; + listener->pre_run = pre_run; + listener->run_started = run_started; + listener->run_ended = run_ended; + listener->case_started = case_started; + listener->case_ended = case_ended; + listener->test_started = test_started; + listener->test_ended = test_ended; + listener->check_triggered = check_triggered; + + return listener; +} + +void +styled_printf(bool use_color, enum style_level slevel, const char *fmt, ...) +{ + va_list argp; + + if (use_color) + switch (slevel) { + case STYLE_GOOD: + printf(CSI_GRN); + break; + case STYLE_WARN: + printf(CSI_YLW); + break; + case STYLE_BAD: + printf(CSI_RED); + break; + default: + break; + } + + va_start(argp, fmt); + vprintf(fmt, argp); + va_end(argp); + + if (use_color) + printf(CSI_RST); +} + +void +destroy(void *data) +{ + free(data); +} + +void +pre_run(void *data, int pass_count, int pass_num, int seed, const char *filter) +{ + struct base_data *bdata = data; + + bdata->use_color = isatty(fileno(stdout)) + && getenv("TERM") && strcmp(getenv("TERM"), "dumb"); + + if (pass_count > 1) + printf("\nRepeating all tests (iteration %d) . . .\n\n", + pass_num); + + if (filter && filter[0]) + styled_printf(bdata->use_color, STYLE_WARN, + "Note: test filter = %s\n", + filter); + + if (seed > 0) + styled_printf(bdata->use_color, STYLE_WARN, + "Note: Randomizing tests' orders" + " with a seed of %u .\n", + seed); +} + +void +run_started(void *data, int live_case_count, + int live_test_count, int disabled_count) +{ + struct base_data *bdata = data; + + styled_printf(bdata->use_color, STYLE_GOOD, "[==========]"); + printf(" Running %d %s from %d test %s.\n", + live_test_count, + (live_test_count == 1) ? "test" : "tests", + live_case_count, + (live_case_count == 1) ? "case" : "cases"); +} + +void +run_ended(void *data, int case_count, struct zuc_case **cases, + int live_case_count, int live_test_count, int total_passed, + int total_failed, int total_disabled, long total_elapsed) +{ + struct base_data *bdata = data; + styled_printf(bdata->use_color, STYLE_GOOD, "[==========]"); + printf(" %d %s from %d test %s ran. (%ld ms)\n", + live_test_count, + (live_test_count == 1) ? "test" : "tests", + live_case_count, + (live_case_count == 1) ? "case" : "cases", + total_elapsed); + + if (total_passed) { + styled_printf(bdata->use_color, STYLE_GOOD, "[ PASSED ]"); + printf(" %d %s.\n", total_passed, + (total_passed == 1) ? "test" : "tests"); + } + + if (total_failed) { + int case_num; + styled_printf(bdata->use_color, STYLE_BAD, "[ FAILED ]"); + printf(" %d %s, listed below:\n", + total_failed, (total_failed == 1) ? "test" : "tests"); + + for (case_num = 0; case_num < case_count; ++case_num) { + int i; + for (i = 0; i < cases[case_num]->test_count; ++i) { + struct zuc_test *curr = + cases[case_num]->tests[i]; + if (curr->failed || curr->fatal) { + styled_printf(bdata->use_color, + STYLE_BAD, + "[ FAILED ]"); + printf(" %s.%s\n", + cases[case_num]->name, + curr->name); + } + } + } + } + + if (total_failed || total_disabled) + printf("\n"); + + if (total_failed) + printf(" %d FAILED %s\n", + total_failed, + (total_failed == 1) ? "TEST" : "TESTS"); + + if (total_disabled) + styled_printf(bdata->use_color, STYLE_WARN, + " YOU HAVE %d DISABLED %s\n", + total_disabled, + (total_disabled == 1) ? "TEST" : "TESTS"); +} + +void +case_started(void *data, struct zuc_case *test_case, int live_test_count, + int disabled_count) +{ + struct base_data *bdata = data; + styled_printf(bdata->use_color, STYLE_GOOD, "[----------]"); + printf(" %d %s from %s.\n", + live_test_count, + (live_test_count == 1) ? "test" : "tests", + test_case->name); + +} + +void +case_ended(void *data, struct zuc_case *test_case) +{ + struct base_data *bdata = data; + styled_printf(bdata->use_color, STYLE_GOOD, "[----------]"); + printf(" %d %s from %s (%ld ms)\n", + test_case->test_count, + (test_case->test_count == 1) ? "test" : "tests", + test_case->name, + test_case->elapsed); + printf("\n"); +} + +void +test_started(void *data, struct zuc_test *test) +{ + struct base_data *bdata = data; + styled_printf(bdata->use_color, STYLE_GOOD, "[ RUN ]"); + printf(" %s.%s\n", test->test_case->name, test->name); +} + +void +test_ended(void *data, struct zuc_test *test) +{ + struct base_data *bdata = data; + if (test->failed || test->fatal) { + styled_printf(bdata->use_color, STYLE_BAD, "[ FAILED ]"); + printf(" %s.%s (%ld ms)\n", + test->test_case->name, test->name, test->elapsed); + } else { + styled_printf(bdata->use_color, STYLE_GOOD, "[ OK ]"); + printf(" %s.%s (%ld ms)\n", + test->test_case->name, test->name, test->elapsed); + } +} + +const char * +zuc_get_opstr(enum zuc_check_op op) +{ + switch (op) { + case ZUC_OP_EQ: + return "="; + case ZUC_OP_NE: + return "!="; + case ZUC_OP_GE: + return ">="; + case ZUC_OP_GT: + return ">"; + case ZUC_OP_LE: + return "<="; + case ZUC_OP_LT: + return "<"; + default: + return "???"; + } +} + +void +check_triggered(void *data, char const *file, int line, + enum zuc_fail_state state, enum zuc_check_op op, + enum zuc_check_valtype valtype, + intptr_t val1, intptr_t val2, + const char *expr1, const char *expr2) +{ + switch (op) { + case ZUC_OP_TRUE: + printf("%s:%d: error: Value of: %s\n", file, line, expr1); + printf(" Actual: false\n"); + printf("Expected: true\n"); + break; + case ZUC_OP_FALSE: + printf("%s:%d: error: Value of: %s\n", file, line, expr1); + printf(" Actual: true\n"); + printf("Expected: false\n"); + break; + case ZUC_OP_NULL: + printf("%s:%d: error: Value of: %s\n", file, line, expr1); + printf(" Actual: %p\n", (void *)val1); + printf("Expected: %p\n", NULL); + break; + case ZUC_OP_NOT_NULL: + printf("%s:%d: error: Value of: %s\n", file, line, expr1); + printf(" Actual: %p\n", (void *)val1); + printf("Expected: not %p\n", NULL); + break; + case ZUC_OP_EQ: + if (valtype == ZUC_VAL_CSTR) { + printf("%s:%d: error: Value of: %s\n", file, line, + expr2); + printf(" Actual: %s\n", (const char *)val2); + printf("Expected: %s\n", expr1); + printf("Which is: %s\n", (const char *)val1); + } else { + printf("%s:%d: error: Value of: %s\n", file, line, + expr2); + printf(" Actual: %"PRIdPTR"\n", val2); + printf("Expected: %s\n", expr1); + printf("Which is: %"PRIdPTR"\n", val1); + } + break; + case ZUC_OP_NE: + if (valtype == ZUC_VAL_CSTR) { + printf("%s:%d: error: ", file, line); + printf("Expected: (%s) %s (%s), actual: %s == %s\n", + expr1, zuc_get_opstr(op), expr2, + (char *)val1, (char *)val2); + } else { + printf("%s:%d: error: ", file, line); + printf("Expected: (%s) %s (%s), actual: %"PRIdPTR" vs " + "%"PRIdPTR"\n", expr1, zuc_get_opstr(op), expr2, + val1, val2); + } + break; + case ZUC_OP_TERMINATE: { + char const *level = (val1 == 0) ? "error" + : (val1 == 1) ? "warning" + : "note"; + printf("%s:%d: %s: %s\n", file, line, level, expr1); + break; + } + case ZUC_OP_TRACEPOINT: + printf("%s:%d: note: %s\n", file, line, expr1); + break; + default: + printf("%s:%d: error: ", file, line); + printf("Expected: (%s) %s (%s), actual: %"PRIdPTR" vs " + "%"PRIdPTR"\n", expr1, zuc_get_opstr(op), expr2, val1, + val2); + } +} diff --git a/tools/zunitc/src/zuc_base_logger.h b/tools/zunitc/src/zuc_base_logger.h new file mode 100644 index 0000000..80b108e --- /dev/null +++ b/tools/zunitc/src/zuc_base_logger.h @@ -0,0 +1,38 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZUC_BASE_LOGGER_H +#define ZUC_BASE_LOGGER_H + +struct zuc_event_listener; + +/** + * Creates a new logger that outputs data to console in the default + * format. + */ +struct zuc_event_listener * +zuc_base_logger_create(void); + +#endif /* ZUC_BASE_LOGGER_H */ diff --git a/tools/zunitc/src/zuc_collector.c b/tools/zunitc/src/zuc_collector.c new file mode 100644 index 0000000..035c9ce --- /dev/null +++ b/tools/zunitc/src/zuc_collector.c @@ -0,0 +1,427 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include "zuc_collector.h" + +#include +#include +#include +#include + +#include +#include "zuc_event_listener.h" +#include "zunitc/zunitc_impl.h" + +#include +#include +#include + +/** + * @file + * General handling of collecting events during testing to pass back to + * main tracking of fork()'d tests. + * + * @note implementation of zuc_process_message() is included here so that + * all child->parent IPC is in a single source file for easier maintenance + * and updating. + */ + +/** + * Internal data struct for processing. + */ +struct collector_data +{ + int *fd; /**< file descriptor to output to. */ + struct zuc_test *test; /**< current test, or NULL. */ +}; + +/** + * Stores an int32_t into the given buffer. + * + * @param ptr the buffer to store to. + * @param val the value to store. + * @return a pointer to the position in the buffer after the stored value. + */ +static char * +pack_int32(char *ptr, int32_t val); + +/** + * Stores an intptr_t into the given buffer. + * + * @param ptr the buffer to store to. + * @param val the value to store. + * @return a pointer to the position in the buffer after the stored value. + */ +static char * +pack_intptr_t(char *ptr, intptr_t val); + +/** + * Extracts a int32_t from the given buffer. + * + * @param ptr the buffer to extract from. + * @param val the value to set. + * @return a pointer to the position in the buffer after the extracted + * value. + */ +static char const * +unpack_int32(char const *ptr, int32_t *val); + +/** + * Extracts a intptr_t from the given buffer. + * + * @param ptr the buffer to extract from. + * @param val the value to set. + * @return a pointer to the position in the buffer after the extracted + * value. + */ +static char const * +unpack_intptr_t(char const *ptr, intptr_t *val); + +/** + * Extracts a length-prefixed string from the given buffer. + * + * @param ptr the buffer to extract from. + * @param str the value to set. + * @return a pointer to the position in the buffer after the extracted + * value. + */ +static char const * +unpack_string(char const *ptr, char **str); + +/** + * Extracts an event from the given buffer. + * + * @param ptr the buffer to extract from. + * @param len the length of the given buffer. + * @return an event that was packed in the buffer + */ +static struct zuc_event * +unpack_event(char const *ptr, int32_t len); + +/** + * Handles an event by either attaching it directly or sending it over IPC + * as needed. + */ +static void +store_event(struct collector_data *cdata, + enum zuc_event_type event_type, char const *file, int line, + enum zuc_fail_state state, enum zuc_check_op op, + enum zuc_check_valtype valtype, + intptr_t val1, intptr_t val2, const char *expr1, const char *expr2); + +static void +destroy(void *data); + +static void +test_started(void *data, struct zuc_test *test); + +static void +test_ended(void *data, struct zuc_test *test); + +static void +check_triggered(void *data, char const *file, int line, + enum zuc_fail_state state, enum zuc_check_op op, + enum zuc_check_valtype valtype, + intptr_t val1, intptr_t val2, + const char *expr1, const char *expr2); + +static void +collect_event(void *data, char const *file, int line, const char *expr1); + +struct zuc_event_listener * +zuc_collector_create(int *pipe_fd) +{ + struct zuc_event_listener *listener = + zalloc(sizeof(struct zuc_event_listener)); + + listener->data = zalloc(sizeof(struct collector_data)); + ((struct collector_data *)listener->data)->fd = pipe_fd; + listener->destroy = destroy; + listener->test_started = test_started; + listener->test_ended = test_ended; + listener->check_triggered = check_triggered; + listener->collect_event = collect_event; + + return listener; +} + +char * +pack_int32(char *ptr, int32_t val) +{ + memcpy(ptr, &val, sizeof(val)); + return ptr + sizeof(val); +} + +char * +pack_intptr_t(char *ptr, intptr_t val) +{ + memcpy(ptr, &val, sizeof(val)); + return ptr + sizeof(val); +} + +static char * +pack_cstr(char *ptr, intptr_t val, int len) +{ + if (val == 0) { /* a null pointer */ + ptr = pack_int32(ptr, -1); + } else { + ptr = pack_int32(ptr, len); + memcpy(ptr, (const char *)val, len); + ptr += len; + } + return ptr; +} + +void +destroy(void *data) +{ + free(data); +} + +void +test_started(void *data, struct zuc_test *test) +{ + struct collector_data *cdata = data; + cdata->test = test; +} + +void +test_ended(void *data, struct zuc_test *test) +{ + struct collector_data *cdata = data; + cdata->test = NULL; +} + +void +check_triggered(void *data, char const *file, int line, + enum zuc_fail_state state, enum zuc_check_op op, + enum zuc_check_valtype valtype, + intptr_t val1, intptr_t val2, + const char *expr1, const char *expr2) +{ + struct collector_data *cdata = data; + if (op != ZUC_OP_TRACEPOINT) + store_event(cdata, ZUC_EVENT_IMMEDIATE, file, line, state, op, + valtype, + val1, val2, expr1, expr2); +} + +void +collect_event(void *data, char const *file, int line, const char *expr1) +{ + struct collector_data *cdata = data; + store_event(cdata, ZUC_EVENT_DEFERRED, file, line, ZUC_CHECK_OK, + ZUC_OP_TRACEPOINT, ZUC_VAL_INT, + 0, 0, expr1, ""); +} + +void +store_event(struct collector_data *cdata, + enum zuc_event_type event_type, char const *file, int line, + enum zuc_fail_state state, enum zuc_check_op op, + enum zuc_check_valtype valtype, + intptr_t val1, intptr_t val2, const char *expr1, const char *expr2) +{ + struct zuc_event *event = zalloc(sizeof(*event)); + event->file = strdup(file); + event->line = line; + event->state = state; + event->op = op; + event->valtype = valtype; + event->val1 = val1; + event->val2 = val2; + if (valtype == ZUC_VAL_CSTR) { + if (val1) + event->val1 = (intptr_t)strdup((const char *)val1); + if (val2) + event->val2 = (intptr_t)strdup((const char *)val2); + } + event->expr1 = strdup(expr1); + event->expr2 = strdup(expr2); + + zuc_attach_event(cdata->test, event, event_type, false); + + if (*cdata->fd == -1) { + } else { + /* Need to pass it back */ + int sent; + int count; + int expr1_len = strlen(expr1); + int expr2_len = strlen(expr2); + int val1_len = + ((valtype == ZUC_VAL_CSTR) && val1) ? + strlen((char *)val1) : 0; + int val2_len = + ((valtype == ZUC_VAL_CSTR) && val2) ? + strlen((char *)val2) : 0; + int file_len = strlen(file); + int len = (sizeof(int32_t) * 9) + file_len + + (sizeof(intptr_t) * 2) + + ((valtype == ZUC_VAL_CSTR) ? + (sizeof(int32_t) * 2) + val1_len + val2_len : 0) + + expr1_len + expr2_len; + char *buf = zalloc(len); + + char *ptr = pack_int32(buf, len - 4); + ptr = pack_int32(ptr, event_type); + ptr = pack_int32(ptr, file_len); + memcpy(ptr, file, file_len); + ptr += file_len; + ptr = pack_int32(ptr, line); + ptr = pack_int32(ptr, state); + ptr = pack_int32(ptr, op); + ptr = pack_int32(ptr, valtype); + if (valtype == ZUC_VAL_CSTR) { + ptr = pack_cstr(ptr, val1, val1_len); + ptr = pack_cstr(ptr, val2, val2_len); + } else { + ptr = pack_intptr_t(ptr, val1); + ptr = pack_intptr_t(ptr, val2); + } + + ptr = pack_int32(ptr, expr1_len); + if (expr1_len) { + memcpy(ptr, expr1, expr1_len); + ptr += expr1_len; + } + ptr = pack_int32(ptr, expr2_len); + if (expr2_len) { + memcpy(ptr, expr2, expr2_len); + ptr += expr2_len; + } + + + sent = 0; + while (sent < len) { + count = write(*cdata->fd, buf, len); + if (count == -1) + break; + sent += count; + } + + free(buf); + } +} + +char const * +unpack_int32(char const *ptr, int32_t *val) +{ + memcpy(val, ptr, sizeof(*val)); + return ptr + sizeof(*val); +} + +char const * +unpack_intptr_t(char const *ptr, intptr_t *val) +{ + memcpy(val, ptr, sizeof(*val)); + return ptr + sizeof(*val); +} + +char const * +unpack_string(char const *ptr, char **str) +{ + int32_t len = 0; + ptr = unpack_int32(ptr, &len); + *str = zalloc(len + 1); + if (len) + memcpy(*str, ptr, len); + ptr += len; + return ptr; +} + +static char const * +unpack_cstr(char const *ptr, char **str) +{ + int32_t len = 0; + ptr = unpack_int32(ptr, &len); + if (len < 0) { + *str = NULL; + } else { + *str = zalloc(len + 1); + if (len) + memcpy(*str, ptr, len); + ptr += len; + } + return ptr; +} + +struct zuc_event * +unpack_event(char const *ptr, int32_t len) +{ + int32_t val = 0; + struct zuc_event *evt = zalloc(sizeof(*evt)); + char const *tmp = unpack_string(ptr, &evt->file); + tmp = unpack_int32(tmp, &evt->line); + + tmp = unpack_int32(tmp, &val); + evt->state = val; + tmp = unpack_int32(tmp, &val); + evt->op = val; + tmp = unpack_int32(tmp, &val); + evt->valtype = val; + + if (evt->valtype == ZUC_VAL_CSTR) { + char *ptr = NULL; + tmp = unpack_cstr(tmp, &ptr); + evt->val1 = (intptr_t)ptr; + tmp = unpack_cstr(tmp, &ptr); + evt->val2 = (intptr_t)ptr; + } else { + tmp = unpack_intptr_t(tmp, &evt->val1); + tmp = unpack_intptr_t(tmp, &evt->val2); + } + + tmp = unpack_string(tmp, &evt->expr1); + tmp = unpack_string(tmp, &evt->expr2); + + return evt; +} + +int +zuc_process_message(struct zuc_test *test, int fd) +{ + char buf[4] = {}; + int got = read(fd, buf, 4); + if (got == 4) { + enum zuc_event_type event_type = ZUC_EVENT_IMMEDIATE; + int32_t val = 0; + int32_t len = 0; + const char *tmp = NULL; + char *raw = NULL; + unpack_int32(buf, &len); + raw = zalloc(len); + got = read(fd, raw, len); + + tmp = unpack_int32(raw, &val); + event_type = val; + + struct zuc_event *evt = unpack_event(tmp, len - (tmp - raw)); + zuc_attach_event(test, evt, event_type, true); + free(raw); + } + return got; +} diff --git a/tools/zunitc/src/zuc_collector.h b/tools/zunitc/src/zuc_collector.h new file mode 100644 index 0000000..56f8a5f --- /dev/null +++ b/tools/zunitc/src/zuc_collector.h @@ -0,0 +1,58 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZUC_COLLECTOR_H +#define ZUC_COLLECTOR_H + +struct zuc_event_listener; +struct zuc_test; + +/** + * Creates a new instance of an event collector that will attach events + * to the current test directly or via connection from child to parent. + * + * @param pipe_fd pointer to the file descriptor to use for communication if + * needed. If the value is -1 the events will be attached directly to the + * current test. Otherwise events will be passed back via IPC over this + * pipe with the expectation that the payload will be handled in the parent + * process via zuc_process_message(). + * @return a new collector instance. + * @see zuc_process_message() + */ +struct zuc_event_listener * +zuc_collector_create(int *pipe_fd); + +/** + * Reads events from the given pipe and processes them. + * + * @param test the currently active test to attach events for. + * @param pipe_fd the file descriptor of the pipe to read from. + * @return a positive value if a message was received, 0 if the end has + * been reached and -1 if an error has occurred. + */ +int +zuc_process_message(struct zuc_test *test, int pipe_fd); + +#endif /* ZUC_COLLECTOR_H */ diff --git a/tools/zunitc/src/zuc_context.h b/tools/zunitc/src/zuc_context.h new file mode 100644 index 0000000..609f34b --- /dev/null +++ b/tools/zunitc/src/zuc_context.h @@ -0,0 +1,58 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZUC_CONTEXT_H +#define ZUC_CONTEXT_H + +#include "zuc_types.h" + +struct zuc_slinked; + +/** + * Internal context for processing. + * Collecting data members here minimizes use of globals. + */ +struct zuc_context { + int case_count; + struct zuc_case **cases; + + bool fatal; + int repeat; + int random; + unsigned int seed; + bool spawn; + bool break_on_failure; + bool output_tap; + bool output_junit; + int fds[2]; + char *filter; + + struct zuc_slinked *listeners; + + struct zuc_case *curr_case; + struct zuc_test *curr_test; +}; + +#endif /* ZUC_CONTEXT_H */ diff --git a/tools/zunitc/src/zuc_event.h b/tools/zunitc/src/zuc_event.h new file mode 100644 index 0000000..ccb2f7b --- /dev/null +++ b/tools/zunitc/src/zuc_event.h @@ -0,0 +1,86 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZUC_EVENT_H +#define ZUC_EVENT_H + +#include + +#include "zunitc/zunitc_impl.h" + +/** + * + */ +enum zuc_event_type +{ + ZUC_EVENT_IMMEDIATE, + ZUC_EVENT_DEFERRED +}; + +/** + * Status enum for posted events. + */ +enum zuc_fail_state +{ + ZUC_CHECK_OK, /**< no problem. */ + ZUC_CHECK_SKIP, /**< runtime skip directive encountered. */ + ZUC_CHECK_FAIL, /**< non-fatal check fails. */ + ZUC_CHECK_FATAL, /**< fatal assertion/check fails. */ + ZUC_CHECK_ERROR /**< internal level problem. */ +}; + +/** + * Record of an event that occurs during testing such as assert macro + * failures. + */ +struct zuc_event +{ + char *file; + int32_t line; + enum zuc_fail_state state; + enum zuc_check_op op; + enum zuc_check_valtype valtype; + intptr_t val1; + intptr_t val2; + char *expr1; + char *expr2; + + struct zuc_event *next; +}; + +/** + * Attaches an event to the specified test. + * + * @param test the test to attach to. + * @param event the event to attach. + * @param event_type of event (immediate or deferred) to attach. + * @param transferred true if the event has been processed elsewhere and + * is being transferred for storage, false otherwise. + */ +void +zuc_attach_event(struct zuc_test *test, struct zuc_event *event, + enum zuc_event_type event_type, bool transferred); + +#endif /* ZUC_EVENT_H */ diff --git a/tools/zunitc/src/zuc_event_listener.h b/tools/zunitc/src/zuc_event_listener.h new file mode 100644 index 0000000..41c5fbd --- /dev/null +++ b/tools/zunitc/src/zuc_event_listener.h @@ -0,0 +1,174 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZUC_EVENT_HANDLER_H +#define ZUC_EVENT_HANDLER_H + +#include + +#include "zuc_context.h" +#include "zuc_event.h" + +struct zuc_test; +struct zuc_case; + +/** + * Interface to allow components to process testing events as they occur. + * + * Event listeners that will stream output as testing progresses are often + * named "*_logger" whereas those that produce their output upon test run + * completion are named "*_reporter". + */ +struct zuc_event_listener { + /** + * User data pointer. + */ + void *data; + + /** + * Destructor. + * @param data the user data associated with this instance. + */ + void (*destroy)(void *data); + + /** + * Handler for simple pre-run state. + * + * @param pass_count total number of expected test passes. + * @param pass_num current pass iteration number. + * @param seed random seed being used, or 0 for no randomization. + * @param filter filter string used for tests, or NULL/blank for none. + */ + void (*pre_run)(void *data, + int pass_count, + int pass_num, + int seed, + const char *filter); + + /** + * Handler for test runs starting. + * + * @param data the user data associated with this instance. + * @param live_case_count number of active test cases in this run. + * @param live_test_count number of active tests in this run. + * @param disabled_count number of disabled tests in this run. + */ + void (*run_started)(void *data, + int live_case_count, + int live_test_count, + int disabled_count); + + /** + * Handler for test runs ending. + * + * @param data the user data associated with this instance. + */ + void (*run_ended)(void *data, + int case_count, + struct zuc_case** cases, + int live_case_count, + int live_test_count, + int total_passed, + int total_failed, + int total_disabled, + long total_elapsed); + + /** + * Handler for test case starting. + * + * @param data the user data associated with this instance. + */ + void (*case_started)(void *data, + struct zuc_case *test_case, + int live_test_count, + int disabled_count); + + /** + * Handler for test case ending. + * + * @param data the user data associated with this instance. + */ + void (*case_ended)(void *data, + struct zuc_case *test_case); + + /** + * Handler for test starting. + * + * @param data the user data associated with this instance. + */ + void (*test_started)(void *data, + struct zuc_test *test); + + /** + * Handler for test ending. + * + * @param data the user data associated with this instance. + */ + void (*test_ended)(void *data, + struct zuc_test *test); + + /** + * Handler for disabled test notification. + * + * @param data the user data associated with this instance. + */ + void (*test_disabled)(void *data, + struct zuc_test *test); + + /** + * Handler for check/assertion fired due to failure, warning, etc. + * + * @param data the user data associated with this instance. + */ + void (*check_triggered)(void *data, + char const *file, + int line, + enum zuc_fail_state state, + enum zuc_check_op op, + enum zuc_check_valtype valtype, + intptr_t val1, + intptr_t val2, + const char *expr1, + const char *expr2); + + /** + * Handler for tracepoints and such that may be displayed later. + * + * @param data the user data associated with this instance. + */ + void (*collect_event)(void *data, + char const *file, + int line, + const char *expr1); +}; + +/** + * Registers an event listener instance to be called. + */ +void +zuc_add_event_listener(struct zuc_event_listener *event_listener); + + +#endif /* ZUC_EVENT_HANDLER_H */ diff --git a/tools/zunitc/src/zuc_junit_reporter.c b/tools/zunitc/src/zuc_junit_reporter.c new file mode 100644 index 0000000..42acad9 --- /dev/null +++ b/tools/zunitc/src/zuc_junit_reporter.c @@ -0,0 +1,479 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include "zuc_junit_reporter.h" + +#if ENABLE_JUNIT_XML + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "zuc_event_listener.h" +#include "zuc_types.h" + +#include + +/** + * Hardcoded output name. + * @todo follow-up with refactoring to avoid filename hardcoding. + * Will allow for better testing in parallel etc. in general. + */ +#define XML_FNAME "test_detail.xml" + +#define ISO_8601_FORMAT "%Y-%m-%dT%H:%M:%SZ" + +#if LIBXML_VERSION >= 20904 +#define STRPRINTF_CAST +#else +#define STRPRINTF_CAST BAD_CAST +#endif + +/** + * Internal data. + */ +struct junit_data +{ + int fd; + time_t begin; +}; + +#define MAX_64BIT_STRLEN 20 + +static void +set_attribute(xmlNodePtr node, const char *name, int value) +{ + xmlChar scratch[MAX_64BIT_STRLEN + 1] = {}; + xmlStrPrintf(scratch, sizeof(scratch), STRPRINTF_CAST "%d", value); + xmlSetProp(node, BAD_CAST name, scratch); +} + +/** + * Output the given event. + * + * @param parent the parent node to add new content to. + * @param event the event to write out. + */ +static void +emit_event(xmlNodePtr parent, struct zuc_event *event) +{ + char *msg = NULL; + + switch (event->op) { + case ZUC_OP_TRUE: + if (asprintf(&msg, "%s:%d: error: Value of: %s\n" + " Actual: false\n" + "Expected: true\n", event->file, event->line, + event->expr1) < 0) { + msg = NULL; + } + break; + case ZUC_OP_FALSE: + if (asprintf(&msg, "%s:%d: error: Value of: %s\n" + " Actual: true\n" + "Expected: false\n", event->file, event->line, + event->expr1) < 0) { + msg = NULL; + } + break; + case ZUC_OP_NULL: + if (asprintf(&msg, "%s:%d: error: Value of: %s\n" + " Actual: %p\n" + "Expected: %p\n", event->file, event->line, + event->expr1, (void *)event->val1, NULL) < 0) { + msg = NULL; + } + break; + case ZUC_OP_NOT_NULL: + if (asprintf(&msg, "%s:%d: error: Value of: %s\n" + " Actual: %p\n" + "Expected: not %p\n", event->file, event->line, + event->expr1, (void *)event->val1, NULL) < 0) { + msg = NULL; + } + break; + case ZUC_OP_EQ: + if (event->valtype == ZUC_VAL_CSTR) { + if (asprintf(&msg, "%s:%d: error: Value of: %s\n" + " Actual: %s\n" + "Expected: %s\n" + "Which is: %s\n", + event->file, event->line, event->expr2, + (char *)event->val2, event->expr1, + (char *)event->val1) < 0) { + msg = NULL; + } + } else { + if (asprintf(&msg, "%s:%d: error: Value of: %s\n" + " Actual: %"PRIdPTR"\n" + "Expected: %s\n" + "Which is: %"PRIdPTR"\n", + event->file, event->line, event->expr2, + event->val2, event->expr1, + event->val1) < 0) { + msg = NULL; + } + } + break; + case ZUC_OP_NE: + if (event->valtype == ZUC_VAL_CSTR) { + if (asprintf(&msg, "%s:%d: error: " + "Expected: (%s) %s (%s)," + " actual: %s == %s\n", + event->file, event->line, + event->expr1, zuc_get_opstr(event->op), + event->expr2, (char *)event->val1, + (char *)event->val2) < 0) { + msg = NULL; + } + } else { + if (asprintf(&msg, "%s:%d: error: " + "Expected: (%s) %s (%s)," + " actual: %"PRIdPTR" vs %"PRIdPTR"\n", + event->file, event->line, + event->expr1, zuc_get_opstr(event->op), + event->expr2, event->val1, + event->val2) < 0) { + msg = NULL; + } + } + break; + case ZUC_OP_TERMINATE: + { + char const *level = (event->val1 == 0) ? "error" + : (event->val1 == 1) ? "warning" + : "note"; + if (asprintf(&msg, "%s:%d: %s: %s\n", + event->file, event->line, level, + event->expr1) < 0) { + msg = NULL; + } + break; + } + case ZUC_OP_TRACEPOINT: + if (asprintf(&msg, "%s:%d: note: %s\n", + event->file, event->line, event->expr1) < 0) { + msg = NULL; + } + break; + default: + if (asprintf(&msg, "%s:%d: error: " + "Expected: (%s) %s (%s), actual: %"PRIdPTR" vs " + "%"PRIdPTR"\n", + event->file, event->line, + event->expr1, zuc_get_opstr(event->op), + event->expr2, event->val1, event->val2) < 0) { + msg = NULL; + } + } + + if ((event->op == ZUC_OP_TERMINATE) && (event->val1 > 1)) { + xmlNewChild(parent, NULL, BAD_CAST "skipped", NULL); + } else { + xmlNodePtr node = xmlNewChild(parent, NULL, + BAD_CAST "failure", NULL); + + if (msg) { + xmlSetProp(node, BAD_CAST "message", BAD_CAST msg); + } + xmlSetProp(node, BAD_CAST "type", BAD_CAST ""); + if (msg) { + xmlNodePtr cdata = xmlNewCDataBlock(node->doc, + BAD_CAST msg, + strlen(msg)); + xmlAddChild(node, cdata); + } + } + + free(msg); +} + +/** + * Formats a time in milliseconds to the normal JUnit elapsed form, or + * NULL if there is a problem. + * The caller should release this with free() + * + * @return the formatted time string upon success, NULL otherwise. + */ +static char * +as_duration(long ms) +{ + char *str = NULL; + + if (asprintf(&str, "%1.3f", ms / 1000.0) < 0) { + str = NULL; + } else { + /* + * Special case to match behavior of standard JUnit output + * writers. Assumption is certain readers might have + * limitations, etc. so it is best to keep 100% identical + * output. + */ + if (!strcmp("0.000", str)) { + free(str); + str = strdup("0"); + } + } + return str; +} + +/** + * Returns the status string for the tests (run/notrun). + * + * @param test the test to check status of. + * @return the status string. + */ +static char const * +get_test_status(struct zuc_test *test) +{ + if (test->disabled || test->skipped) + return "notrun"; + else + return "run"; +} + +/** + * Output the given test. + * + * @param parent the parent node to add new content to. + * @param test the test to write out. + */ +static void +emit_test(xmlNodePtr parent, struct zuc_test *test) +{ + char *time_str = as_duration(test->elapsed); + xmlNodePtr node = xmlNewChild(parent, NULL, BAD_CAST "testcase", NULL); + + xmlSetProp(node, BAD_CAST "name", BAD_CAST test->name); + xmlSetProp(node, BAD_CAST "status", BAD_CAST get_test_status(test)); + + if (time_str) { + xmlSetProp(node, BAD_CAST "time", BAD_CAST time_str); + + free(time_str); + time_str = NULL; + } + + xmlSetProp(node, BAD_CAST "classname", BAD_CAST test->test_case->name); + + if ((test->failed || test->fatal || test->skipped) && test->events) { + struct zuc_event *evt; + for (evt = test->events; evt; evt = evt->next) + emit_event(node, evt); + } +} + +/** + * Output the given test case. + * + * @param parent the parent node to add new content to. + * @param test_case the test case to write out. + */ +static void +emit_case(xmlNodePtr parent, struct zuc_case *test_case) +{ + int i; + int skipped = 0; + int disabled = 0; + int failures = 0; + xmlNodePtr node = NULL; + char *time_str = as_duration(test_case->elapsed); + + for (i = 0; i < test_case->test_count; ++i) { + if (test_case->tests[i]->disabled ) + disabled++; + if (test_case->tests[i]->skipped ) + skipped++; + if (test_case->tests[i]->failed + || test_case->tests[i]->fatal ) + failures++; + } + + node = xmlNewChild(parent, NULL, BAD_CAST "testsuite", NULL); + xmlSetProp(node, BAD_CAST "name", BAD_CAST test_case->name); + + set_attribute(node, "tests", test_case->test_count); + set_attribute(node, "failures", failures); + set_attribute(node, "disabled", disabled); + set_attribute(node, "skipped", skipped); + + if (time_str) { + xmlSetProp(node, BAD_CAST "time", BAD_CAST time_str); + free(time_str); + time_str = NULL; + } + + for (i = 0; i < test_case->test_count; ++i) + emit_test(node, test_case->tests[i]); +} + +/** + * Formats a time in milliseconds to the full ISO-8601 date/time string + * format, or NULL if there is a problem. + * The caller should release this with free() + * + * @return the formatted time string upon success, NULL otherwise. + */ +static char * +as_iso_8601(time_t const *t) +{ + char *result = NULL; + char buf[32] = {}; + struct tm when; + + if (gmtime_r(t, &when) != NULL) + if (strftime(buf, sizeof(buf), ISO_8601_FORMAT, &when)) + result = strdup(buf); + + return result; +} + + +static void +run_started(void *data, int live_case_count, int live_test_count, + int disabled_count) +{ + struct junit_data *jdata = data; + + jdata->begin = time(NULL); + jdata->fd = open(XML_FNAME, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +} + +static void +run_ended(void *data, int case_count, struct zuc_case **cases, + int live_case_count, int live_test_count, int total_passed, + int total_failed, int total_disabled, long total_elapsed) +{ + int i; + long time = 0; + char *time_str = NULL; + char *timestamp = NULL; + xmlNodePtr root = NULL; + xmlDocPtr doc = NULL; + xmlChar *xmlchars = NULL; + int xmlsize = 0; + struct junit_data *jdata = data; + + for (i = 0; i < case_count; ++i) + time += cases[i]->elapsed; + + time_str = as_duration(time); + timestamp = as_iso_8601(&jdata->begin); + + /* here would be where to add errors? */ + + doc = xmlNewDoc(BAD_CAST "1.0"); + root = xmlNewNode(NULL, BAD_CAST "testsuites"); + xmlDocSetRootElement(doc, root); + + set_attribute(root, "tests", live_test_count); + set_attribute(root, "failures", total_failed); + set_attribute(root, "disabled", total_disabled); + + if (timestamp) { + xmlSetProp(root, BAD_CAST "timestamp", BAD_CAST timestamp); + free(timestamp); + timestamp = NULL; + } + + if (time_str) { + xmlSetProp(root, BAD_CAST "time", BAD_CAST time_str); + free(time_str); + time_str = NULL; + } + + xmlSetProp(root, BAD_CAST "name", BAD_CAST "AllTests"); + + for (i = 0; i < case_count; ++i) { + emit_case(root, cases[i]); + } + + xmlDocDumpFormatMemoryEnc(doc, &xmlchars, &xmlsize, "UTF-8", 1); + dprintf(jdata->fd, "%s", (char *) xmlchars); + xmlFree(xmlchars); + xmlchars = NULL; + xmlFreeDoc(doc); + + if ((jdata->fd != fileno(stdout)) + && (jdata->fd != fileno(stderr)) + && (jdata->fd != -1)) { + close(jdata->fd); + jdata->fd = -1; + } +} + +static void +destroy(void *data) +{ + xmlCleanupParser(); + + free(data); +} + +struct zuc_event_listener * +zuc_junit_reporter_create(void) +{ + struct zuc_event_listener *listener = + zalloc(sizeof(struct zuc_event_listener)); + + struct junit_data *data = zalloc(sizeof(struct junit_data)); + data->fd = -1; + + listener->data = data; + listener->destroy = destroy; + listener->run_started = run_started; + listener->run_ended = run_ended; + + return listener; +} + +#else /* ENABLE_JUNIT_XML */ + +#include +#include "zuc_event_listener.h" + +/* + * Simple stub version if junit output (including libxml2 support) has + * been disabled. + * Will return NULL to cause failures as calling this when the #define + * has not been enabled is an invalid scenario. + */ + +struct zuc_event_listener * +zuc_junit_reporter_create(void) +{ + return NULL; +} + +#endif /* ENABLE_JUNIT_XML */ diff --git a/tools/zunitc/src/zuc_junit_reporter.h b/tools/zunitc/src/zuc_junit_reporter.h new file mode 100644 index 0000000..66f3c7b --- /dev/null +++ b/tools/zunitc/src/zuc_junit_reporter.h @@ -0,0 +1,38 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZUC_JUNIT_REPORTER_H +#define ZUC_JUNIT_REPORTER_H + +struct zuc_event_listener; + +/** + * Creates an instance of a reporter that will write data in the JUnit + * XML format. + */ +struct zuc_event_listener * +zuc_junit_reporter_create(void); + +#endif /* ZUC_JUNIT_REPORTER_H */ diff --git a/tools/zunitc/src/zuc_types.h b/tools/zunitc/src/zuc_types.h new file mode 100644 index 0000000..4ed9342 --- /dev/null +++ b/tools/zunitc/src/zuc_types.h @@ -0,0 +1,80 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZUC_TYPES_H +#define ZUC_TYPES_H + +#include "zunitc/zunitc_impl.h" + +struct zuc_case; + +/** + * Represents a specific test. + */ +struct zuc_test +{ + int order; + struct zuc_case *test_case; + zucimpl_test_fn fn; + zucimpl_test_fn_f fn_f; + char *name; + int disabled; + int skipped; + int failed; + int fatal; + long elapsed; + struct zuc_event *events; + struct zuc_event *deferred; +}; + +/** + * Represents a test case that can hold a collection of tests. + */ +struct zuc_case +{ + int order; + char *name; + const struct zuc_fixture* fxt; + int disabled; + int skipped; + int failed; + int fatal; + int passed; + long elapsed; + int test_count; + struct zuc_test **tests; +}; + +/** + * Returns a human-readable version of the comparison opcode. + * + * @param op the opcode to get a string version of. + * @return a human-readable string of the opcode. + * (This value should not be freed) + */ +const char * +zuc_get_opstr(enum zuc_check_op op); + +#endif /* ZUC_TYPES_H */ diff --git a/tools/zunitc/src/zunitc_impl.c b/tools/zunitc/src/zunitc_impl.c new file mode 100644 index 0000000..395bdd7 --- /dev/null +++ b/tools/zunitc/src/zunitc_impl.c @@ -0,0 +1,1578 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "zunitc/zunitc_impl.h" +#include "zunitc/zunitc.h" + +#include "zuc_base_logger.h" +#include "zuc_collector.h" +#include "zuc_context.h" +#include "zuc_event_listener.h" +#include "zuc_junit_reporter.h" + +#include +#include "shared/helpers.h" +#include + +/* + * If CLOCK_MONOTONIC is present on the system it will give us reliable + * results under certain edge conditions that normally require manual + * admin actions to trigger. If not, CLOCK_REALTIME is a reasonable + * fallback. + */ +#if _POSIX_MONOTONIC_CLOCK +static const clockid_t TARGET_TIMER = CLOCK_MONOTONIC; +#else +static const clockid_t TARGET_TIMER = CLOCK_REALTIME; +#endif + +static char const DISABLED_PREFIX[] = "DISABLED_"; + +#define MS_PER_SEC 1000L +#define NANO_PER_MS 1000000L + +/** + * Simple single-linked list structure. + */ +struct zuc_slinked { + void *data; + struct zuc_slinked *next; +}; + +static struct zuc_context g_ctx = { + .case_count = 0, + .cases = NULL, + + .fatal = false, + .repeat = 0, + .random = 0, + .spawn = true, + .break_on_failure = false, + .fds = {-1, -1}, + + .listeners = NULL, + + .curr_case = NULL, + .curr_test = NULL, +}; + +static char *g_progname = NULL; +static char *g_progbasename = NULL; + +typedef int (*comp_pred2)(intptr_t lhs, intptr_t rhs); + +static bool +test_has_skip(struct zuc_test *test) +{ + return test->skipped; +} + +static bool +test_has_failure(struct zuc_test *test) +{ + return test->fatal || test->failed; +} + +bool +zuc_has_skip(void) +{ + return g_ctx.curr_test ? + test_has_skip(g_ctx.curr_test) : false; +} + +bool +zuc_has_failure(void) +{ + return g_ctx.curr_test ? + test_has_failure(g_ctx.curr_test) : false; +} + +void +zuc_set_filter(const char *filter) +{ + g_ctx.filter = strdup(filter); +} + +void +zuc_set_repeat(int repeat) +{ + g_ctx.repeat = repeat; +} + +void +zuc_set_random(int random) +{ + g_ctx.random = random; +} + +void +zuc_set_spawn(bool spawn) +{ + g_ctx.spawn = spawn; +} + +void +zuc_set_break_on_failure(bool break_on_failure) +{ + g_ctx.break_on_failure = break_on_failure; +} + +void +zuc_set_output_junit(bool enable) +{ + g_ctx.output_junit = enable; +} + +const char * +zuc_get_program_name(void) +{ + return g_progname; +} + +const char * +zuc_get_program_basename(void) +{ + return g_progbasename; +} + +static struct zuc_test * +create_test(int order, zucimpl_test_fn fn, zucimpl_test_fn_f fn_f, + char const *case_name, char const *test_name, + struct zuc_case *parent) +{ + struct zuc_test *test = zalloc(sizeof(struct zuc_test)); + ZUC_ASSERTG_NOT_NULL(test, out); + test->order = order; + test->fn = fn; + test->fn_f = fn_f; + test->name = strdup(test_name); + if ((!fn && !fn_f) || + (strncmp(DISABLED_PREFIX, + test_name, sizeof(DISABLED_PREFIX) - 1) == 0)) + test->disabled = 1; + + test->test_case = parent; + +out: + return test; +} + +static int +compare_regs(const void *lhs, const void *rhs) +{ + int rc = strcmp((*(struct zuc_registration **)lhs)->tcase, + (*(struct zuc_registration **)rhs)->tcase); + if (rc == 0) + rc = strcmp((*(struct zuc_registration **)lhs)->test, + (*(struct zuc_registration **)rhs)->test); + + return rc; +} + +/* gcc-specific markers for auto test case registration: */ +extern const struct zuc_registration __start_zuc_tsect; +extern const struct zuc_registration __stop_zuc_tsect; + +static void +register_tests(void) +{ + size_t case_count = 0; + size_t count = &__stop_zuc_tsect - &__start_zuc_tsect; + size_t i; + int idx = 0; + const char *last_name = NULL; + void **array = zalloc(sizeof(void *) * count); + ZUC_ASSERT_NOT_NULL(array); + for (i = 0; i < count; ++i) + array[i] = (void *)(&__start_zuc_tsect + i); + + qsort(array, count, sizeof(array[0]), compare_regs); + + /* Count transitions to get number of test cases. */ + last_name = NULL; + for (i = 0; i < count; ++i) { + const struct zuc_registration *reg = + (const struct zuc_registration *)array[i]; + if (!last_name || (strcmp(last_name, reg->tcase))) { + last_name = reg->tcase; + case_count++; + } + } + + /* Create test case data items. */ + struct zuc_case **case_array = + zalloc(sizeof(struct zuc_case *) * case_count); + ZUC_ASSERT_NOT_NULL(case_array); + struct zuc_case *last_case = NULL; + size_t case_num = 0; + for (i = 0; i < count; ++i) { + const struct zuc_registration *reg = + (const struct zuc_registration *)array[i]; + if (!last_case || (strcmp(last_case->name, reg->tcase))) { + last_case = zalloc(sizeof(struct zuc_case)); + ZUC_ASSERT_NOT_NULL(last_case); + last_case->order = count; + last_case->name = strdup(reg->tcase); + last_case->fxt = reg->fxt; + last_case->test_count = i; + if (case_num > 0) { + int tcount = i + - case_array[case_num - 1]->test_count; + case_array[case_num - 1]->test_count = tcount; + } + case_array[case_num++] = last_case; + } + } + case_array[case_count - 1]->test_count = count + - case_array[case_count - 1]->test_count; + + /* Reserve space for tests per test case. */ + for (case_num = 0; case_num < case_count; ++case_num) { + case_array[case_num]->tests = + zalloc(case_array[case_num]->test_count + * sizeof(struct zuc_test *)); + ZUC_ASSERT_NOT_NULL(case_array[case_num]->tests); + } + + last_name = NULL; + case_num = -1; + for (i = 0; i < count; ++i) { + const struct zuc_registration *reg = + (const struct zuc_registration *)array[i]; + int order = count - (1 + (reg - &__start_zuc_tsect)); + + if (!last_name || (strcmp(last_name, reg->tcase))) { + last_name = reg->tcase; + case_num++; + idx = 0; + } + if (order < case_array[case_num]->order) + case_array[case_num]->order = order; + case_array[case_num]->tests[idx] = + create_test(order, reg->fn, reg->fn_f, + reg->tcase, reg->test, + case_array[case_num]); + + if (case_array[case_num]->fxt != reg->fxt) + printf("%s:%d: error: Mismatched fixtures for '%s'\n", + __FILE__, __LINE__, case_array[case_num]->name); + + idx++; + } + free(array); + + g_ctx.case_count = case_count; + g_ctx.cases = case_array; +} + +static int +compare_case_order(const void *lhs, const void *rhs) +{ + return (*(struct zuc_case **)lhs)->order + - (*(struct zuc_case **)rhs)->order; +} + +static int +compare_test_order(const void *lhs, const void *rhs) +{ + return (*(struct zuc_test **)lhs)->order + - (*(struct zuc_test **)rhs)->order; +} + +static void +order_cases(int count, struct zuc_case **cases) +{ + int i; + qsort(cases, count, sizeof(*cases), compare_case_order); + for (i = 0; i < count; ++i) { + qsort(cases[i]->tests, cases[i]->test_count, + sizeof(*cases[i]->tests), compare_test_order); + } +} + +static void +free_events(struct zuc_event **events) +{ + struct zuc_event *evt = *events; + *events = NULL; + while (evt) { + struct zuc_event *old = evt; + evt = evt->next; + free(old->file); + if (old->valtype == ZUC_VAL_CSTR) { + free((void *)old->val1); + free((void *)old->val2); + } + free(old->expr1); + free(old->expr2); + free(old); + } +} + +static void +free_test(struct zuc_test *test) +{ + free(test->name); + free_events(&test->events); + free_events(&test->deferred); + free(test); +} + +static void +free_test_case(struct zuc_case *test_case) +{ + int i; + free(test_case->name); + for (i = test_case->test_count - 1; i >= 0; --i) { + free_test(test_case->tests[i]); + test_case->tests[i] = NULL; + } + free(test_case->tests); + free(test_case); +} + +/** + * A very simple matching that is compatible with the algorithm used in + * Google Test. + * + * @param wildcard sequence of '?', '*' or normal characters to match. + * @param str string to check for matching. + * @return true if the wildcard matches the entire string, false otherwise. + */ +static bool +wildcard_matches(char const *wildcard, char const *str) +{ + switch (*wildcard) { + case '\0': + return !*str; + case '?': + return *str && wildcard_matches(wildcard + 1, str + 1); + case '*': + return (*str && wildcard_matches(wildcard, str + 1)) + || wildcard_matches(wildcard + 1, str); + default: + return (*wildcard == *str) + && wildcard_matches(wildcard + 1, str + 1); + }; +} + +static char** +segment_str(char *str) +{ + int count = 1; + char **parts = NULL; + char *saved = NULL; + char *tok = NULL; + int i = 0; + for (i = 0; str[i]; ++i) + if (str[i] == ':') + count++; + parts = zalloc(sizeof(char*) * (count + 1)); + ZUC_ASSERTG_NOT_NULL(parts, out); + tok = strtok_r(str, ":", &saved); + i = 0; + parts[i++] = tok; + while (tok) { + tok = strtok_r(NULL, ":", &saved); + parts[i++] = tok; + } +out: + return parts; +} + +static void +filter_cases(int *count, struct zuc_case **cases, char const *filter) +{ + int i = 0; + int j = 0; + int num_pos = 0; + int negative = -1; + char *buf = strdup(filter); + char **parts = segment_str(buf); + + for (i = 0; parts[i]; ++i) { + if (parts[i][0] == '-') { + parts[i]++; + negative = i; + break; + } + num_pos++; + } + + for (i = 0; i < *count; ++i) { + /* Walk backwards for easier pruning. */ + for (j = cases[i]->test_count - 1; j >= 0; --j) { + int x; + bool keep = num_pos == 0; + char *name = NULL; + struct zuc_test *test = cases[i]->tests[j]; + if (asprintf(&name, "%s.%s", cases[i]->name, + test->name) < 0) { + printf("%s:%d: error: %d\n", __FILE__, __LINE__, + errno); + exit(EXIT_FAILURE); + } + for (x = 0; (x < num_pos) && !keep; ++x) + keep = wildcard_matches(parts[x], name); + if (keep && (negative >= 0)) + for (x = negative; parts[x] && keep; ++x) + keep &= !wildcard_matches(parts[x], + name); + if (!keep) { + int w; + free_test(test); + for (w = j + 1; w < cases[i]->test_count; w++) + cases[i]->tests[w - 1] = + cases[i]->tests[w]; + cases[i]->test_count--; + } + + free(name); + } + } + free(parts); + parts = NULL; + free(buf); + buf = NULL; + + /* Prune any cases with no more tests. */ + for (i = *count - 1; i >= 0; --i) { + if (cases[i]->test_count < 1) { + free_test_case(cases[i]); + for (j = i + 1; j < *count; ++j) + cases[j - 1] = cases[j]; + cases[*count - 1] = NULL; + (*count)--; + } + } +} + +static unsigned int +get_seed_from_time(void) +{ + time_t sec = time(NULL); + unsigned int seed = (unsigned int) sec % 100000; + if (seed < 2) + seed = 2; + + return seed; +} + +static void +initialize(void) +{ + static bool init = false; + if (init) + return; + + init = true; + register_tests(); + if (g_ctx.fatal) + return; + + if (g_ctx.random > 1) { + g_ctx.seed = g_ctx.random; + } else if (g_ctx.random == 1) { + g_ctx.seed = get_seed_from_time(); + } + + if (g_ctx.case_count) { + order_cases(g_ctx.case_count, g_ctx.cases); + if (g_ctx.filter && g_ctx.filter[0]) + filter_cases(&g_ctx.case_count, g_ctx.cases, + g_ctx.filter); + } +} + +int +zuc_initialize(int *argc, char *argv[], bool *help_flagged) +{ + int rc = EXIT_FAILURE; + bool opt_help = false; + bool opt_nofork = false; + bool opt_list = false; + int opt_repeat = 0; + int opt_random = 0; + bool opt_break_on_failure = false; + bool opt_junit = false; + char *opt_filter = NULL; + + char *help_param = NULL; + int argc_in = *argc; + + const struct weston_option options[] = { + { WESTON_OPTION_BOOLEAN, "zuc-nofork", 0, &opt_nofork }, + { WESTON_OPTION_BOOLEAN, "zuc-list-tests", 0, &opt_list }, + { WESTON_OPTION_INTEGER, "zuc-repeat", 0, &opt_repeat }, + { WESTON_OPTION_INTEGER, "zuc-random", 0, &opt_random }, + { WESTON_OPTION_BOOLEAN, "zuc-break-on-failure", 0, + &opt_break_on_failure }, +#if ENABLE_JUNIT_XML + { WESTON_OPTION_BOOLEAN, "zuc-output-xml", 0, &opt_junit }, +#endif + { WESTON_OPTION_STRING, "zuc-filter", 0, &opt_filter }, + }; + + /* + *If a test binary is linked to our libzunitcmain it might want + * to access the program 'name' from argv[0] + */ + free(g_progname); + g_progname = NULL; + free(g_progbasename); + g_progbasename = NULL; + if ((*argc > 0) && argv) { + char *path = NULL; + char *base = NULL; + + g_progname = strdup(argv[0]); + + /* basename() might modify the input, so needs a writeable + * string. + * It also may return a statically allocated buffer subject to + * being overwritten so needs to be dup'd. + */ + path = strdup(g_progname); + base = basename(path); + g_progbasename = strdup(base); + free(path); + } else { + g_progname = strdup(""); + printf("%s:%d: warning: No valid argv[0] for initialization\n", + __FILE__, __LINE__); + } + + + initialize(); + if (g_ctx.fatal) + return EXIT_FAILURE; + + if (help_flagged) + *help_flagged = false; + + { + /* Help param will be a special case and need restoring. */ + int i = 0; + char **argv_in = NULL; + const struct weston_option help_options[] = { + { WESTON_OPTION_BOOLEAN, "help", 'h', &opt_help }, + }; + argv_in = zalloc(sizeof(char *) * argc_in); + if (!argv_in) { + printf("%s:%d: error: alloc failed.\n", + __FILE__, __LINE__); + return EXIT_FAILURE; + } + for (i = 0; i < argc_in; ++i) + argv_in[i] = argv[i]; + + parse_options(help_options, ARRAY_LENGTH(help_options), + argc, argv); + if (*argc < argc_in) { + for (i = 1; (i < argc_in) && !help_param; ++i) { + bool found = false; + int j = 0; + for (j = 0; (j < *argc) && !found; ++j) + found = (argv[j] == argv_in[i]); + + if (!found) + help_param = argv_in[i]; + } + } + free(argv_in); + } + + parse_options(options, ARRAY_LENGTH(options), argc, argv); + + if (help_param && (*argc < argc_in)) + argv[(*argc)++] = help_param; + + if (opt_filter) { + zuc_set_filter(opt_filter); + free(opt_filter); + } + + if (opt_help) { + printf("Usage: %s [OPTIONS]\n" + " --zuc-break-on-failure\n" + " --zuc-filter=FILTER\n" + " --zuc-list-tests\n" + " --zuc-nofork\n" +#if ENABLE_JUNIT_XML + " --zuc-output-xml\n" +#endif + " --zuc-random=N [0|1|]\n" + " --zuc-repeat=N\n" + " --help\n", + argv[0]); + if (help_flagged) + *help_flagged = true; + rc = EXIT_SUCCESS; + } else if (opt_list) { + zuc_list_tests(); + rc = EXIT_FAILURE; + } else { + zuc_set_repeat(opt_repeat); + zuc_set_random(opt_random); + zuc_set_spawn(!opt_nofork); + zuc_set_break_on_failure(opt_break_on_failure); + zuc_set_output_junit(opt_junit); + rc = EXIT_SUCCESS; + } + + return rc; +} + +static void +dispatch_pre_run(struct zuc_context *ctx, int pass_count, int pass_num, + int seed, const char *filter) +{ + struct zuc_slinked *curr; + for (curr = ctx->listeners; curr; curr = curr->next) { + struct zuc_event_listener *listener = curr->data; + if (listener->pre_run) + listener->pre_run(listener->data, + pass_count, + pass_num, + seed, + filter); + } +} + +static void +dispatch_run_started(struct zuc_context *ctx, int live_case_count, + int live_test_count, int disabled_count) +{ + struct zuc_slinked *curr; + for (curr = ctx->listeners; curr; curr = curr->next) { + struct zuc_event_listener *listener = curr->data; + if (listener->run_started) + listener->run_started(listener->data, + live_case_count, + live_test_count, + disabled_count); + } +} + +static void +dispatch_run_ended(struct zuc_context *ctx, + int live_case_count, int live_test_count, int total_passed, + int total_failed, int total_disabled, long total_elapsed) +{ + struct zuc_slinked *curr; + for (curr = ctx->listeners; curr; curr = curr->next) { + struct zuc_event_listener *listener = curr->data; + if (listener->run_ended) + listener->run_ended(listener->data, + ctx->case_count, + ctx->cases, + live_case_count, + live_test_count, + total_passed, + total_failed, + total_disabled, + total_elapsed); + } +} + +static void +dispatch_case_started(struct zuc_context *ctx,struct zuc_case *test_case, + int live_test_count, int disabled_count) +{ + struct zuc_slinked *curr; + for (curr = ctx->listeners; curr; curr = curr->next) { + struct zuc_event_listener *listener = curr->data; + if (listener->case_started) + listener->case_started(listener->data, + test_case, + live_test_count, + disabled_count); + } +} + +static void +dispatch_case_ended(struct zuc_context *ctx, struct zuc_case *test_case) +{ + struct zuc_slinked *curr; + for (curr = ctx->listeners; curr; curr = curr->next) { + struct zuc_event_listener *listener = curr->data; + if (listener->case_ended) + listener->case_ended(listener->data, test_case); + } +} + +static void +dispatch_test_started(struct zuc_context *ctx, struct zuc_test *test) +{ + struct zuc_slinked *curr; + for (curr = ctx->listeners; curr; curr = curr->next) { + struct zuc_event_listener *listener = curr->data; + if (listener->test_started) + listener->test_started(listener->data, test); + } +} + +static void +dispatch_test_ended(struct zuc_context *ctx, struct zuc_test *test) +{ + struct zuc_slinked *curr; + for (curr = ctx->listeners; curr; curr = curr->next) { + struct zuc_event_listener *listener = curr->data; + if (listener->test_ended) + listener->test_ended(listener->data, test); + } +} + +static void +dispatch_test_disabled(struct zuc_context *ctx, struct zuc_test *test) +{ + struct zuc_slinked *curr; + for (curr = ctx->listeners; curr; curr = curr->next) { + struct zuc_event_listener *listener = curr->data; + if (listener->test_disabled) + listener->test_disabled(listener->data, test); + } +} + +static void +dispatch_check_triggered(struct zuc_context *ctx, char const *file, int line, + enum zuc_fail_state state, enum zuc_check_op op, + enum zuc_check_valtype valtype, + intptr_t val1, intptr_t val2, + const char *expr1, const char *expr2) +{ + struct zuc_slinked *curr; + for (curr = ctx->listeners; curr; curr = curr->next) { + struct zuc_event_listener *listener = curr->data; + if (listener->check_triggered) + listener->check_triggered(listener->data, + file, line, + state, op, valtype, + val1, val2, + expr1, expr2); + } +} + +static void +dispatch_collect_event(struct zuc_context *ctx, char const *file, int line, + const char *expr1) +{ + struct zuc_slinked *curr; + for (curr = ctx->listeners; curr; curr = curr->next) { + struct zuc_event_listener *listener = curr->data; + if (listener->collect_event) + listener->collect_event(listener->data, + file, line, expr1); + } +} + +static void +migrate_deferred_events(struct zuc_test *test, bool transferred) +{ + struct zuc_event *evt = test->deferred; + if (!evt) + return; + + test->deferred = NULL; + if (test->events) { + struct zuc_event *old = test->events; + while (old->next) + old = old->next; + old->next = evt; + } else { + test->events = evt; + } + while (evt && !transferred) { + dispatch_check_triggered(&g_ctx, + evt->file, evt->line, + evt->state, evt->op, + evt->valtype, + evt->val1, evt->val2, + evt->expr1, evt->expr2); + evt = evt->next; + } +} + +static void +mark_single_failed(struct zuc_test *test, enum zuc_fail_state state) +{ + switch (state) { + case ZUC_CHECK_OK: + /* no internal state to change */ + break; + case ZUC_CHECK_SKIP: + if (test) + test->skipped = 1; + break; + case ZUC_CHECK_FAIL: + if (test) + test->failed = 1; + break; + case ZUC_CHECK_ERROR: + case ZUC_CHECK_FATAL: + if (test) + test->fatal = 1; + break; + } + + if (g_ctx.break_on_failure) + raise(SIGABRT); + +} + +static void +mark_failed(struct zuc_test *test, enum zuc_fail_state state) +{ + if (!test && g_ctx.curr_test) + test = g_ctx.curr_test; + + if (test) { + mark_single_failed(test, state); + } else if (g_ctx.curr_case) { + /* In setup or tear-down of test suite */ + int i; + for (i = 0; i < g_ctx.curr_case->test_count; ++i) + mark_single_failed(g_ctx.curr_case->tests[i], state); + } + if ((state == ZUC_CHECK_FATAL) || (state == ZUC_CHECK_ERROR)) + g_ctx.fatal = true; +} + +void +zuc_attach_event(struct zuc_test *test, struct zuc_event *event, + enum zuc_event_type event_type, bool transferred) +{ + if (!test) { + /* + * consider adding events directly to the case. + * would be for use during per-suite setup and teardown. + */ + printf("%s:%d: error: No current test.\n", __FILE__, __LINE__); + } else if (event_type == ZUC_EVENT_DEFERRED) { + if (test->deferred) { + struct zuc_event *curr = test->deferred; + while (curr->next) + curr = curr->next; + curr->next = event; + } else { + test->deferred = event; + } + } else { + if (test) + migrate_deferred_events(test, transferred); + + if (test->events) { + struct zuc_event *curr = test->events; + while (curr->next) + curr = curr->next; + curr->next = event; + } else { + test->events = event; + } + mark_failed(test, event->state); + } +} + +void +zuc_add_event_listener(struct zuc_event_listener *event_listener) +{ + if (!event_listener) /* ensure null entries are not added */ + return; + + if (!g_ctx.listeners) { + g_ctx.listeners = zalloc(sizeof(struct zuc_slinked)); + ZUC_ASSERT_NOT_NULL(g_ctx.listeners); + g_ctx.listeners->data = event_listener; + } else { + struct zuc_slinked *curr = g_ctx.listeners; + while (curr->next) + curr = curr->next; + curr->next = zalloc(sizeof(struct zuc_slinked)); + ZUC_ASSERT_NOT_NULL(curr->next); + curr->next->data = event_listener; + } +} + + +void +zuc_cleanup(void) +{ + int i; + + free(g_ctx.filter); + g_ctx.filter = 0; + for (i = 0; i < 2; ++i) + if (g_ctx.fds[i] != -1) { + close(g_ctx.fds[i]); + g_ctx.fds[i] = -1; + } + + if (g_ctx.listeners) { + struct zuc_slinked *curr = g_ctx.listeners; + while (curr) { + struct zuc_slinked *old = curr; + struct zuc_event_listener *listener = curr->data; + if (listener->destroy) + listener->destroy(listener->data); + free(listener); + curr = curr->next; + free(old); + } + g_ctx.listeners = NULL; + } + + for (i = g_ctx.case_count - 1; i >= 0; --i) { + free_test_case(g_ctx.cases[i]); + g_ctx.cases[i] = NULL; + } + free(g_ctx.cases); + g_ctx.cases = NULL; + + free(g_progname); + g_progname = NULL; + free(g_progbasename); + g_progbasename = NULL; +} + +static void +shuffle_cases(int count, struct zuc_case **cases, + unsigned int seed) +{ + int i; + unsigned int rseed = seed; + for (i = 0; i < count; ++i) { + int j; + for (j = cases[i]->test_count - 1; j > 0 ; --j) { + int val = rand_r(&rseed); + int b = ((val / (double)RAND_MAX) * j + 0.5); + if (j != b) { + struct zuc_test *tmp = cases[i]->tests[j]; + cases[i]->tests[j] = cases[i]->tests[b]; + cases[i]->tests[b] = tmp; + } + } + } + + for (i = count - 1; i > 0; --i) { + int val = rand_r(&rseed); + int j = ((val / (double)RAND_MAX) * i + 0.5); + + if (i != j) { + struct zuc_case *tmp = cases[i]; + cases[i] = cases[j]; + cases[j] = tmp; + } + } +} + +void +zuc_list_tests(void) +{ + int i; + int j; + initialize(); + if (g_ctx.fatal) + return; + for (i = 0; i < g_ctx.case_count; ++i) { + printf("%s.\n", g_ctx.cases[i]->name); + for (j = 0; j < g_ctx.cases[i]->test_count; ++j) { + printf(" %s\n", g_ctx.cases[i]->tests[j]->name); + } + } +} + +static void +spawn_test(struct zuc_test *test, void *test_data, + void (*cleanup_fn)(void *data), void *cleanup_data) +{ + pid_t pid = -1; + + if (!test || (!test->fn && !test->fn_f)) + return; + + if (pipe2(g_ctx.fds, O_CLOEXEC)) { + printf("%s:%d: error: Unable to create pipe: %d\n", + __FILE__, __LINE__, errno); + mark_failed(test, ZUC_CHECK_ERROR); + return; + } + + fflush(NULL); /* important. avoid duplication of output */ + pid = fork(); + switch (pid) { + case -1: /* Error forking */ + printf("%s:%d: error: Problem with fork: %d\n", + __FILE__, __LINE__, errno); + mark_failed(test, ZUC_CHECK_ERROR); + close(g_ctx.fds[0]); + g_ctx.fds[0] = -1; + close(g_ctx.fds[1]); + g_ctx.fds[1] = -1; + break; + case 0: { /* child */ + int rc = EXIT_SUCCESS; + close(g_ctx.fds[0]); + g_ctx.fds[0] = -1; + + if (test->fn_f) + test->fn_f(test_data); + else + test->fn(); + + if (test_has_failure(test)) + rc = EXIT_FAILURE; + else if (test_has_skip(test)) + rc = ZUC_EXIT_SKIP; + + /* Avoid confusing memory tools like valgrind */ + if (cleanup_fn) + cleanup_fn(cleanup_data); + + zuc_cleanup(); + exit(rc); + } + default: { /* parent */ + ssize_t rc = 0; + siginfo_t info = {}; + + close(g_ctx.fds[1]); + g_ctx.fds[1] = -1; + + do { + rc = zuc_process_message(g_ctx.curr_test, + g_ctx.fds[0]); + } while (rc > 0); + close(g_ctx.fds[0]); + g_ctx.fds[0] = -1; + + if (waitid(P_ALL, 0, &info, WEXITED)) { + printf("%s:%d: error: waitid failed. (%d)\n", + __FILE__, __LINE__, errno); + mark_failed(test, ZUC_CHECK_ERROR); + } else { + switch (info.si_code) { + case CLD_EXITED: { + int exit_code = info.si_status; + switch(exit_code) { + case EXIT_SUCCESS: + break; + case ZUC_EXIT_SKIP: + if (!test_has_skip(g_ctx.curr_test) && + !test_has_failure(g_ctx.curr_test)) + ZUC_SKIP("Child exited SKIP"); + break; + default: + /* unexpected failure */ + if (!test_has_failure(g_ctx.curr_test)) + ZUC_ASSERT_EQ(0, exit_code); + } + break; + } + case CLD_KILLED: + case CLD_DUMPED: + printf("%s:%d: error: signaled: %d\n", + __FILE__, __LINE__, info.si_status); + mark_failed(test, ZUC_CHECK_ERROR); + break; + } + } + } + } +} + +static void +run_single_test(struct zuc_test *test,const struct zuc_fixture *fxt, + void *case_data, bool spawn) +{ + long elapsed = 0; + struct timespec begin; + struct timespec end; + void *test_data = NULL; + void *cleanup_data = NULL; + void (*cleanup_fn)(void *data) = NULL; + memset(&begin, 0, sizeof(begin)); + memset(&end, 0, sizeof(end)); + + g_ctx.curr_test = test; + dispatch_test_started(&g_ctx, test); + + cleanup_fn = fxt ? fxt->tear_down : NULL; + cleanup_data = NULL; + + if (fxt && fxt->set_up) { + test_data = fxt->set_up(case_data); + cleanup_data = test_data; + } else { + test_data = case_data; + } + + clock_gettime(TARGET_TIMER, &begin); + + /* Need to re-check these, as fixtures might have changed test state. */ + if (!test->fatal && !test->skipped) { + if (spawn) { + spawn_test(test, test_data, + cleanup_fn, cleanup_data); + } else { + if (test->fn_f) + test->fn_f(test_data); + else + test->fn(); + } + } + + clock_gettime(TARGET_TIMER, &end); + + elapsed = (end.tv_sec - begin.tv_sec) * MS_PER_SEC; + if (end.tv_sec != begin.tv_sec) { + elapsed -= (begin.tv_nsec) / NANO_PER_MS; + elapsed += (end.tv_nsec) / NANO_PER_MS; + } else { + elapsed += (end.tv_nsec - begin.tv_nsec) / NANO_PER_MS; + } + test->elapsed = elapsed; + + if (cleanup_fn) + cleanup_fn(cleanup_data); + + if (test->deferred) { + if (test_has_failure(test)) + migrate_deferred_events(test, false); + else + free_events(&test->deferred); + } + + dispatch_test_ended(&g_ctx, test); + + g_ctx.curr_test = NULL; +} + +static void +run_single_case(struct zuc_case *test_case) +{ + int count_live = test_case->test_count - test_case->disabled; + g_ctx.curr_case = test_case; + if (count_live) { + int i = 0; + const struct zuc_fixture *fxt = test_case->fxt; + void *case_data = fxt ? (void *)fxt->data : NULL; + + dispatch_case_started(&g_ctx, test_case, + count_live, test_case->disabled); + + if (fxt && fxt->set_up_test_case) + case_data = fxt->set_up_test_case(fxt->data); + + for (i = 0; i < test_case->test_count; ++i) { + struct zuc_test *curr = test_case->tests[i]; + if (curr->disabled) { + dispatch_test_disabled(&g_ctx, curr); + } else { + run_single_test(curr, fxt, case_data, + g_ctx.spawn); + if (curr->skipped) + test_case->skipped++; + if (curr->failed) + test_case->failed++; + if (curr->fatal) + test_case->fatal++; + if (!curr->failed && !curr->fatal) + test_case->passed++; + test_case->elapsed += curr->elapsed; + } + } + + if (fxt && fxt->tear_down_test_case) + fxt->tear_down_test_case(case_data); + + dispatch_case_ended(&g_ctx, test_case); + } + g_ctx.curr_case = NULL; +} + +static void +reset_test_values(struct zuc_case **cases, int case_count) +{ + int i; + for (i = 0; i < case_count; ++i) { + int j; + cases[i]->disabled = 0; + cases[i]->skipped = 0; + cases[i]->failed = 0; + cases[i]->fatal = 0; + cases[i]->passed = 0; + cases[i]->elapsed = 0; + for (j = 0; j < cases[i]->test_count; ++j) { + struct zuc_test *test = cases[i]->tests[j]; + if (test->disabled) + cases[i]->disabled++; + test->skipped = 0; + test->failed = 0; + test->fatal = 0; + test->elapsed = 0; + + free_events(&test->events); + free_events(&test->deferred); + } + } +} + +static int +run_single_pass(void) +{ + long total_elapsed = 0; + int total_passed = 0; + int total_failed = 0; + int total_skipped = 0; + int live_case_count = 0; + int live_test_count = 0; + int disabled_test_count = 0; + int i; + + reset_test_values(g_ctx.cases, g_ctx.case_count); + for (i = 0; i < g_ctx.case_count; ++i) { + int live = g_ctx.cases[i]->test_count + - g_ctx.cases[i]->disabled; + if (live) { + live_test_count += live; + live_case_count++; + } + if (g_ctx.cases[i]->disabled) + disabled_test_count++; + } + + dispatch_run_started(&g_ctx, live_case_count, live_test_count, + disabled_test_count); + + for (i = 0; i < g_ctx.case_count; ++i) { + run_single_case(g_ctx.cases[i]); + total_failed += g_ctx.cases[i]->test_count + - (g_ctx.cases[i]->passed + g_ctx.cases[i]->disabled); + total_passed += g_ctx.cases[i]->passed; + total_elapsed += g_ctx.cases[i]->elapsed; + total_skipped += g_ctx.cases[i]->skipped; + } + + dispatch_run_ended(&g_ctx, live_case_count, live_test_count, + total_passed, total_failed, disabled_test_count, + total_elapsed); + + if (total_failed) + return EXIT_FAILURE; + else if (total_skipped) + return ZUC_EXIT_SKIP; + else + return EXIT_SUCCESS; +} + +int +zucimpl_run_tests(void) +{ + int rc = EXIT_SUCCESS; + int i; + int limit = g_ctx.repeat > 0 ? g_ctx.repeat : 1; + + initialize(); + if (g_ctx.fatal) + return EXIT_FAILURE; + + if (g_ctx.listeners == NULL) { + zuc_add_event_listener(zuc_collector_create(&(g_ctx.fds[1]))); + zuc_add_event_listener(zuc_base_logger_create()); + if (g_ctx.output_junit) + zuc_add_event_listener(zuc_junit_reporter_create()); + } + + if (g_ctx.case_count < 1) { + printf("%s:%d: error: Setup error: test tree is empty\n", + __FILE__, __LINE__); + rc = EXIT_FAILURE; + } + + for (i = 0; (i < limit) && (g_ctx.case_count > 0); ++i) { + int pass_code = EXIT_SUCCESS; + dispatch_pre_run(&g_ctx, limit, i + 1, + (g_ctx.random > 0) ? g_ctx.seed : 0, + g_ctx.filter); + + order_cases(g_ctx.case_count, g_ctx.cases); + if (g_ctx.random > 0) + shuffle_cases(g_ctx.case_count, g_ctx.cases, + g_ctx.seed); + + pass_code = run_single_pass(); + if (pass_code == EXIT_FAILURE) + rc = EXIT_FAILURE; + else if ((pass_code == ZUC_EXIT_SKIP) && (rc == EXIT_SUCCESS)) + rc = ZUC_EXIT_SKIP; + + g_ctx.seed++; + } + + return rc; +} + +int +zucimpl_tracepoint(char const *file, int line, char const *fmt, ...) +{ + int rc = -1; + va_list argp; + char *msg = NULL; + + + va_start(argp, fmt); + rc = vasprintf(&msg, fmt, argp); + if (rc == -1) { + msg = NULL; + } + va_end(argp); + + dispatch_collect_event(&g_ctx, + file, line, + msg); + + free(msg); + + return rc; +} + +void +zucimpl_terminate(char const *file, int line, + bool fail, bool fatal, const char *msg) +{ + enum zuc_fail_state state = ZUC_CHECK_SKIP; + int level = 2; + if (fail && fatal) { + state = ZUC_CHECK_FATAL; + level = 0; + } else if (fail && !fatal) { + state = ZUC_CHECK_FAIL; + level = 0; + } + + mark_failed(g_ctx.curr_test, state); + + if ((state != ZUC_CHECK_OK) && g_ctx.curr_test) + migrate_deferred_events(g_ctx.curr_test, false); + + dispatch_check_triggered(&g_ctx, + file, line, + state, + ZUC_OP_TERMINATE, ZUC_VAL_INT, + level, 0, + msg, ""); +} + +static void +validate_types(enum zuc_check_op op, enum zuc_check_valtype valtype) +{ + bool is_valid = true; + + switch (op) { + case ZUC_OP_NULL: + case ZUC_OP_NOT_NULL: + is_valid = is_valid && (valtype == ZUC_VAL_PTR); + break; + default: + ; /* all rest OK */ + } + + switch (valtype) { + case ZUC_VAL_CSTR: + is_valid = is_valid && ((op == ZUC_OP_EQ) || (op == ZUC_OP_NE)); + break; + default: + ; /* all rest OK */ + } + + if (!is_valid) + printf("%s:%d: warning: Unexpected op+type %d/%d.\n", + __FILE__, __LINE__, op, valtype); +} + +static int +pred2_unknown(intptr_t lhs, intptr_t rhs) +{ + return 0; +} + +static int +pred2_true(intptr_t lhs, intptr_t rhs) +{ + return lhs; +} + +static int +pred2_false(intptr_t lhs, intptr_t rhs) +{ + return !lhs; +} + +static int +pred2_eq(intptr_t lhs, intptr_t rhs) +{ + return lhs == rhs; +} + +static int +pred2_streq(intptr_t lhs, intptr_t rhs) +{ + int status = 0; + const char *lhptr = (const char *)lhs; + const char *rhptr = (const char *)rhs; + + if (!lhptr && !rhptr) + status = 1; + else if (lhptr && rhptr) + status = strcmp(lhptr, rhptr) == 0; + + return status; +} + +static int +pred2_ne(intptr_t lhs, intptr_t rhs) +{ + return lhs != rhs; +} + +static int +pred2_strne(intptr_t lhs, intptr_t rhs) +{ + int status = 0; + const char *lhptr = (const char *)lhs; + const char *rhptr = (const char *)rhs; + + if (lhptr != rhptr) { + if (!lhptr || !rhptr) + status = 1; + else + status = strcmp(lhptr, rhptr) != 0; + } + + return status; +} + +static int +pred2_ge(intptr_t lhs, intptr_t rhs) +{ + return lhs >= rhs; +} + +static int +pred2_gt(intptr_t lhs, intptr_t rhs) +{ + return lhs > rhs; +} + +static int +pred2_le(intptr_t lhs, intptr_t rhs) +{ + return lhs <= rhs; +} + +static int +pred2_lt(intptr_t lhs, intptr_t rhs) +{ + return lhs < rhs; +} + +static comp_pred2 +get_pred2(enum zuc_check_op op, enum zuc_check_valtype valtype) +{ + switch (op) { + case ZUC_OP_TRUE: + return pred2_true; + case ZUC_OP_FALSE: + return pred2_false; + case ZUC_OP_NULL: + return pred2_false; + case ZUC_OP_NOT_NULL: + return pred2_true; + case ZUC_OP_EQ: + if (valtype == ZUC_VAL_CSTR) + return pred2_streq; + else + return pred2_eq; + case ZUC_OP_NE: + if (valtype == ZUC_VAL_CSTR) + return pred2_strne; + else + return pred2_ne; + case ZUC_OP_GE: + return pred2_ge; + case ZUC_OP_GT: + return pred2_gt; + case ZUC_OP_LE: + return pred2_le; + case ZUC_OP_LT: + return pred2_lt; + default: + return pred2_unknown; + } +} + +int +zucimpl_expect_pred2(char const *file, int line, + enum zuc_check_op op, enum zuc_check_valtype valtype, + bool fatal, + intptr_t lhs, intptr_t rhs, + const char *lhs_str, const char* rhs_str) +{ + enum zuc_fail_state state = fatal ? ZUC_CHECK_FATAL : ZUC_CHECK_FAIL; + comp_pred2 pred = get_pred2(op, valtype); + int failed = !pred(lhs, rhs); + + validate_types(op, valtype); + + if (failed) { + mark_failed(g_ctx.curr_test, state); + + if (g_ctx.curr_test) + migrate_deferred_events(g_ctx.curr_test, false); + + dispatch_check_triggered(&g_ctx, + file, line, + fatal ? ZUC_CHECK_FATAL + : ZUC_CHECK_FAIL, + op, valtype, + lhs, rhs, + lhs_str, rhs_str); + } + return failed; +} diff --git a/tools/zunitc/test/fixtures_test.c b/tools/zunitc/test/fixtures_test.c new file mode 100644 index 0000000..04a0ba9 --- /dev/null +++ b/tools/zunitc/test/fixtures_test.c @@ -0,0 +1,106 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +/** + * Tests of fixtures. + */ + +#include +#include +#include +#include + +#include "zunitc/zunitc.h" + + + +/* Use a simple string for a simplistic fixture */ +static struct zuc_fixture fixture_minimal = { + .data = "for all good men to", +}; + +ZUC_TEST_F(fixture_minimal, just_as_is, data) +{ + const char *str = data; + ZUC_ASSERT_NOT_NULL(str); + + ZUC_ASSERT_EQ(0, strcmp("for all good men to", str)); +} + +/* + * Not important what or how data is manipulated, just that this function + * does something non-transparent to it. + */ +static void * +setup_test_config(void *data) +{ + int i; + const char *str = data; + char *upper = NULL; + ZUC_ASSERTG_NOT_NULL(data, out); + + upper = strdup(str); + ZUC_ASSERTG_NOT_NULL(upper, out); + + for (i = 0; upper[i]; ++i) + upper[i] = (char)toupper(upper[i]); + +out: + return upper; +} + +static void +teardown_test_config(void *data) +{ + ZUC_ASSERT_NOT_NULL(data); + free(data); +} + +static struct zuc_fixture fixture_data0 = { + .data = "Now is the time", + .set_up = setup_test_config, + .tear_down = teardown_test_config +}; + +ZUC_TEST_F(fixture_data0, base, data) +{ + const char *str = data; + ZUC_ASSERT_NOT_NULL(str); + + ZUC_ASSERT_EQ(0, strcmp("NOW IS THE TIME", str)); +} + +/* Use the same fixture for a second test. */ +ZUC_TEST_F(fixture_data0, no_lower, data) +{ + int i; + const char *str = data; + ZUC_ASSERT_NOT_NULL(str); + + for (i = 0; str[i]; ++i) + ZUC_ASSERT_EQ(0, islower(str[i])); +} diff --git a/tools/zunitc/test/zunitc_test.c b/tools/zunitc/test/zunitc_test.c new file mode 100644 index 0000000..8000aa8 --- /dev/null +++ b/tools/zunitc/test/zunitc_test.c @@ -0,0 +1,464 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +/* + * A simple file to show tests being setup and run. + */ + +#include +#include +#include +#include + +#include "zunitc/zunitc.h" + +/* + * The SKIP and FAIL sets of tests are those that will cause 'make check' + * to fail so are disabled by default. They can be re-enabled when working + * on the test framework itself. + */ + +/* #define ENABLE_FAIL_TESTS */ +/* #define ENABLE_SKIP_TESTS */ + +ZUC_TEST(base_test, math_is_sane) +{ + ZUC_ASSERT_EQ(4, 2 + 2); +} + +ZUC_TEST(base_test, math_is_hard) +{ + ZUC_TRACEPOINT("Tracepoint here."); + + ZUC_TRACEPOINT("Checking %d", 4); + +#ifdef ENABLE_FAIL_TESTS + + ZUC_ASSERT_EQ(5, 2 + 2); + ZUC_TRACEPOINT("flip %1.3f", 3.1415927); /* not seen */ +#endif +} + +#ifdef ENABLE_FAIL_TESTS +ZUC_TEST(base_test, tracepoint_after_assert) +{ + ZUC_TRACEPOINT("Should be seen in output"); + + ZUC_ASSERT_EQ(5, 2 + 2); + + ZUC_TRACEPOINT("Should NOT be seen in output"); +} +#endif + +#ifdef ENABLE_FAIL_TESTS +ZUC_TEST(base_test, math_is_more_hard) +{ + ZUC_ASSERT_EQ(5, 2 + 2); +} + +ZUC_TEST(base_test, math_is_more_hard2) +{ + ZUC_ASSERT_EQ(7, 9); +} +#endif + +ZUC_TEST(base_test, time_counted) +{ + ZUC_TRACEPOINT("Never seen"); + + ZUC_TRACEPOINT("Sleepy Time %d", 10000 * 5); + ZUC_ASSERT_EQ(0, usleep(10000 * 5)); /* 50ms to show up in reporting */ +} + +ZUC_TEST(other_test, math_monkey) +{ + ZUC_ASSERT_TRUE(1); + ZUC_ASSERT_TRUE(3); + ZUC_ASSERT_FALSE(0); + + ZUC_ASSERT_TRUE(1); + ZUC_ASSERT_TRUE(3); + ZUC_ASSERT_FALSE(0); + + ZUC_ASSERT_EQ(5, 2 + 3); + ZUC_ASSERT_EQ(5, 2 + 3); + + int b = 9; + ZUC_ASSERT_NE(1, 2); + ZUC_ASSERT_NE(b, b + 2); + + ZUC_ASSERT_NE(1, 2); + ZUC_ASSERT_NE(b, b + 1); + + ZUC_ASSERT_LT(1, 2); + ZUC_ASSERT_LT(1, 3); + + ZUC_ASSERT_LE(1, 2); + ZUC_ASSERT_LE(1, 3); + + ZUC_ASSERT_LE(1, 1); + ZUC_ASSERT_LE(1, 1); + + ZUC_ASSERT_GT(2, 1); + ZUC_ASSERT_GT(3, 1); + + ZUC_ASSERT_GE(1, 1); + ZUC_ASSERT_GE(1, 1); + + ZUC_ASSERT_GE(2, 1); + ZUC_ASSERT_GE(3, 1); +} + +static void +force_fatal_failure(void) +{ +#ifdef ENABLE_FAIL_TESTS + bool expected_to_fail_here = true; + ZUC_ASSERT_FALSE(expected_to_fail_here); + + ZUC_FATAL("Should never reach here"); + ZUC_ASSERT_NE(1, 1); +#endif +} + +#ifdef ENABLE_FAIL_TESTS +ZUC_TEST(infrastructure, fail_keeps_testing) +{ + ZUC_FATAL("Should always reach here"); + ZUC_ASSERT_NE(1, 1); /* in case ZUC_FATAL doesn't work. */ +} +#endif + +#ifdef ENABLE_FAIL_TESTS +ZUC_TEST(infrastructure, fatal_stops_test) +{ + ZUC_FATAL("Time to kill testing"); + + ZUC_FATAL("Should never reach here"); + ZUC_ASSERT_NE(1, 1); /* in case ZUC_FATAL doesn't work. */ +} +#endif + +#ifdef ENABLE_SKIP_TESTS +ZUC_TEST(infrastructure, skip_stops_test) +{ + ZUC_SKIP("Time to skip testing"); + + ZUC_FATAL("Should never reach here"); + ZUC_ASSERT_NE(1, 1); /* in case ZUC_FATAL doesn't work. */ +} +#endif + +struct fixture_data { + int case_counter; + int test_counter; +}; + +static struct fixture_data fixture_info = {0, 0}; + +static void * +complex_test_set_up_case(const void *data) +{ + fixture_info.case_counter++; + return &fixture_info; +} + +static void +complex_test_tear_down_case(void *data) +{ + ZUC_ASSERT_TRUE(&fixture_info == data); + fixture_info.case_counter--; +} + +static void * +complex_test_set_up(void *data) +{ + fixture_info.test_counter = fixture_info.case_counter; + return &fixture_info; +} + +static void +complex_test_tear_down(void *data) +{ + ZUC_ASSERT_EQ(1, fixture_info.case_counter); +} + +struct zuc_fixture complex_test = { + .set_up = complex_test_set_up, + .tear_down = complex_test_tear_down, + .set_up_test_case = complex_test_set_up_case, + .tear_down_test_case = complex_test_tear_down_case +}; + +/* + * Note that these next cases all try to modify the test_counter member, + * but the fixture should reset that. +*/ + +ZUC_TEST_F(complex_test, bases_cenario, data) +{ + struct fixture_data *fdata = data; + ZUC_ASSERT_NOT_NULL(fdata); + + ZUC_ASSERT_EQ(4, 3 + 1); + ZUC_ASSERT_EQ(1, fdata->case_counter); + ZUC_ASSERT_EQ(1, fdata->test_counter); + fdata->test_counter++; + ZUC_ASSERT_EQ(2, fdata->test_counter); +} + +ZUC_TEST_F(complex_test, something, data) +{ + struct fixture_data *fdata = data; + ZUC_ASSERT_NOT_NULL(fdata); + + ZUC_ASSERT_EQ(4, 3 + 1); + ZUC_ASSERT_EQ(1, fdata->case_counter); + ZUC_ASSERT_EQ(1, fdata->test_counter); + fdata->test_counter++; + ZUC_ASSERT_EQ(2, fdata->test_counter); +} + +ZUC_TEST_F(complex_test, else_here, data) +{ + struct fixture_data *fdata = data; + ZUC_ASSERT_NOT_NULL(fdata); + + ZUC_ASSERT_EQ(4, 3 + 1); + ZUC_ASSERT_EQ(1, fdata->case_counter); + ZUC_ASSERT_EQ(1, fdata->test_counter); + fdata->test_counter++; + ZUC_ASSERT_EQ(2, fdata->test_counter); +} + +ZUC_TEST(more, DISABLED_not_run) +{ + ZUC_ASSERT_EQ(1, 2); +} + +ZUC_TEST(more, failure_states) +{ +#ifdef ENABLE_FAIL_TESTS + bool expected_to_fail_here = true; +#endif + + ZUC_ASSERT_FALSE(zuc_has_failure()); + +#ifdef ENABLE_FAIL_TESTS + ZUC_ASSERT_FALSE(expected_to_fail_here); /* should fail */ + + ZUC_ASSERT_TRUE(zuc_has_failure()); +#endif +} + +ZUC_TEST(more, failure_sub_fatal) +{ + ZUC_ASSERT_FALSE(zuc_has_failure()); + + force_fatal_failure(); + +#ifdef ENABLE_FAIL_TESTS + ZUC_ASSERT_TRUE(zuc_has_failure()); +#endif +} + +ZUC_TEST(pointers, null) +{ + const char *a = NULL; + + ZUC_ASSERT_NULL(NULL); + ZUC_ASSERT_NULL(0); + ZUC_ASSERT_NULL(a); +} + +#ifdef ENABLE_FAIL_TESTS +ZUC_TEST(pointers, null_fail) +{ + const char *a = "a"; + + ZUC_ASSERT_NULL(!NULL); + ZUC_ASSERT_NULL(!0); + ZUC_ASSERT_NULL(a); +} +#endif + +ZUC_TEST(pointers, not_null) +{ + const char *a = "a"; + + ZUC_ASSERT_NOT_NULL(!NULL); + ZUC_ASSERT_NOT_NULL(!0); + ZUC_ASSERT_NOT_NULL(a); +} + +#ifdef ENABLE_FAIL_TESTS +ZUC_TEST(pointers, not_null_fail) +{ + const char *a = NULL; + + ZUC_ASSERT_NOT_NULL(NULL); + ZUC_ASSERT_NOT_NULL(0); + ZUC_ASSERT_NOT_NULL(a); +} +#endif + +ZUC_TEST(strings, eq) +{ + /* Note that we use strdup() to ensure different addresses. */ + char *str_a = strdup("a"); + const char *str_nil = NULL; + + ZUC_ASSERT_STREQ(str_a, str_a); + ZUC_ASSERT_STREQ("a", str_a); + ZUC_ASSERT_STREQ(str_a, "a"); + + ZUC_ASSERT_STREQ(str_nil, str_nil); + ZUC_ASSERT_STREQ(NULL, str_nil); + ZUC_ASSERT_STREQ(str_nil, NULL); + + free(str_a); +} + +#ifdef ENABLE_FAIL_TESTS +ZUC_TEST(strings, eq_fail) +{ + /* Note that we use strdup() to ensure different addresses. */ + char *str_a = strdup("a"); + char *str_b = strdup("b"); + const char *str_nil = NULL; + + ZUC_ASSERT_STREQ(str_a, str_b); + ZUC_ASSERT_STREQ("b", str_a); + ZUC_ASSERT_STREQ(str_a, "b"); + + ZUC_ASSERT_STREQ(str_nil, str_a); + ZUC_ASSERT_STREQ(str_nil, str_b); + ZUC_ASSERT_STREQ(str_a, str_nil); + ZUC_ASSERT_STREQ(str_b, str_nil); + + ZUC_ASSERT_STREQ(NULL, str_a); + ZUC_ASSERT_STREQ(NULL, str_b); + ZUC_ASSERT_STREQ(str_a, NULL); + ZUC_ASSERT_STREQ(str_b, NULL); + + free(str_a); + free(str_b); +} +#endif + +ZUC_TEST(strings, ne) +{ + /* Note that we use strdup() to ensure different addresses. */ + char *str_a = strdup("a"); + char *str_b = strdup("b"); + const char *str_nil = NULL; + + ZUC_ASSERT_STRNE(str_a, str_b); + ZUC_ASSERT_STRNE("b", str_a); + ZUC_ASSERT_STRNE(str_a, "b"); + + ZUC_ASSERT_STRNE(str_nil, str_a); + ZUC_ASSERT_STRNE(str_nil, str_b); + ZUC_ASSERT_STRNE(str_a, str_nil); + ZUC_ASSERT_STRNE(str_b, str_nil); + + ZUC_ASSERT_STRNE(NULL, str_a); + ZUC_ASSERT_STRNE(NULL, str_b); + ZUC_ASSERT_STRNE(str_a, NULL); + ZUC_ASSERT_STRNE(str_b, NULL); + + free(str_a); + free(str_b); +} + +#ifdef ENABLE_FAIL_TESTS +ZUC_TEST(strings, ne_fail01) +{ + /* Note that we use strdup() to ensure different addresses. */ + char *str_a = strdup("a"); + + ZUC_ASSERTG_STRNE(str_a, str_a, err); + +err: + free(str_a); +} + +ZUC_TEST(strings, ne_fail02) +{ + /* Note that we use strdup() to ensure different addresses. */ + char *str_a = strdup("a"); + + ZUC_ASSERTG_STRNE("a", str_a, err); + +err: + free(str_a); +} + +ZUC_TEST(strings, ne_fail03) +{ + /* Note that we use strdup() to ensure different addresses. */ + char *str_a = strdup("a"); + + ZUC_ASSERTG_STRNE(str_a, "a", err); + +err: + free(str_a); +} + +ZUC_TEST(strings, ne_fail04) +{ + const char *str_nil = NULL; + + ZUC_ASSERT_STRNE(str_nil, str_nil); +} + +ZUC_TEST(strings, ne_fail05) +{ + const char *str_nil = NULL; + + ZUC_ASSERT_STRNE(NULL, str_nil); +} + +ZUC_TEST(strings, ne_fail06) +{ + const char *str_nil = NULL; + + ZUC_ASSERT_STRNE(str_nil, NULL); +} +#endif + +ZUC_TEST(base_test, later) +{ + /* an additional test for the same case but later in source */ + ZUC_ASSERT_EQ(3, 5 - 2); +} + +ZUC_TEST(base_test, zed) +{ + /* an additional test for the same case but later in source */ + ZUC_ASSERT_EQ(3, 5 - 2); +} diff --git a/wcap/README b/wcap/README new file mode 100644 index 0000000..0c0d0f0 --- /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 +individual 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 0000000..7eba6d6 --- /dev/null +++ b/wcap/main.c @@ -0,0 +1,331 @@ +2021-222222 + MIT License + +Copyright (c) 2021 Jianhui Zhao + +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 shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#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; + + 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 (decoder == NULL) { + fprintf(stderr, "Creating wcap decoder failed\n"); + exit(EXIT_FAILURE); + } + + 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; + while (has_frame) { + 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/meson.build b/wcap/meson.build new file mode 100644 index 0000000..04f2d1e --- /dev/null +++ b/wcap/meson.build @@ -0,0 +1,21 @@ +if not get_option('wcap-decode') + subdir_done() +endif + +srcs_wcap = [ + 'main.c', + 'wcap-decode.c', +] + +wcap_dep_cairo = dependency('cairo', required: false) +if not wcap_dep_cairo.found() + error('wcap requires cairo which was not found. Or, you can use \'-Dwcap-decode=false\'.') +endif + +executable( + 'wcap-decode', + srcs_wcap, + include_directories: common_inc, + dependencies: [ dep_libm, wcap_dep_cairo ], + install: true +) diff --git a/wcap/wcap-decode.c b/wcap/wcap-decode.c new file mode 100644 index 0000000..111f1b1 --- /dev/null +++ b/wcap/wcap-decode.c @@ -0,0 +1,847 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + + + + + + + +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#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); + if (decoder->map == MAP_FAILED) { + fprintf(stderr, "mmap failed\n"); + close(decoder->fd); + free(decoder); + return NULL; + } + + 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); + if (decoder->frame == NULL) { + close(decoder->fd); + free(decoder); + return NULL; + } + 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 0000000..00b757f --- /dev/null +++ b/wcap/wcap-decode.h @@ -0,0 +1,68 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _WCAP_DECODE_ +#define _WCAP_DECODE_ + +#include + +#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 0000000..fc2bfc8 --- /dev/null +++ b/weston.ini @@ -0,0 +1,16 @@ +[core] +shell=libivi-shell.z.so +modules=libivi-controller.z.so + +[ivi-shell] +ivi-input-module=libivi-input-controller.z.so +ivi-id-agent-module=libivi-id-agent.z.so +screen-info-module=libscreen-info-module.z.so +transition-duration=30 +cursor-theme=default + +[keyboard] +keymap_layout=de + +[input-method] +path= diff --git a/weston.ini.in b/weston.ini.in new file mode 100644 index 0000000..74bcb51 --- /dev/null +++ b/weston.ini.in @@ -0,0 +1,87 @@ +[core] +#modules=cms-colord.so +#xwayland=true +#shell=desktop-shell.so +#gbm-format=xrgb2101010 +#require-input=true + +[shell] +background-image=/usr/share/backgrounds/gnome/Aqua.jpg +background-color=0xff002244 +background-type=tile +clock-format=minutes +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=@bindir@/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=@bindir@/weston-flower + +[input-method] +path=@libexecdir@/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@60 +#transform=flipped-90 + +#[libinput] +#enable-tap=true +#tap-and-drag=true +#tap-and-drag-lock=true +#disable-while-typing=false +#middle-button-emulation=true +#left-handed=true +#rotation=90 +#accel-profile=flat +#accel-speed=.9 +#natural-scroll=true +#scroll-method=edge +# For button-triggered scrolling: +#scroll-method=button +#scroll-button=BTN_RIGHT + +#[touchpad] +#constant_accel_factor = 50 +#min_accel_factor = 0.16 +#max_accel_factor = 1.0 + +[screen-share] +command=@bindir@/weston --backend=rdp-backend.so --shell=fullscreen-shell.so --no-clients-resize + +#[xwayland] +#path=@bindir@/Xwayland diff --git a/weston.rc b/weston.rc new file mode 100644 index 0000000..1dcdf37 --- /dev/null +++ b/weston.rc @@ -0,0 +1,15 @@ +service weston /system/bin/logwrapper /system/bin/weston -c /system/etc/weston.ini -B drm-backend.so --tty=1 --use-pixman + class weston + disabled + seclabel u:r:ueventd:s0 + +on boot + export WESTON_MODULE_MAP "drm-backend.so=libdrm-backend.z.so" + export XDG_RUNTIME_DIR /data/weston + mkdir /data/weston + start weston + exec /system/bin/sleep 2 + exec /system/bin/killall weston + exec /system/bin/sleep 2 + + trigger weston_start diff --git a/xwayland/dnd.c b/xwayland/dnd.c new file mode 100644 index 0000000..e981e81 --- /dev/null +++ b/xwayland/dnd.c @@ -0,0 +1,255 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "xwayland.h" + +#include "hash.h" + +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); + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + 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 = zalloc(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_pointer_start_drag(pointer, &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); + 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 values[1], version = 4, 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); + + 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); +} diff --git a/xwayland/hash.c b/xwayland/hash.c new file mode 100644 index 0000000..820e51f --- /dev/null +++ b/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 "config.h" + +#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/xwayland/hash.h b/xwayland/hash.h new file mode 100644 index 0000000..334d8f4 --- /dev/null +++ b/xwayland/hash.h @@ -0,0 +1,51 @@ +/* + * 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 + +#include + +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/xwayland/launcher.c b/xwayland/launcher.c new file mode 100644 index 0000000..60854c8 --- /dev/null +++ b/xwayland/launcher.c @@ -0,0 +1,409 @@ +/* + * Copyright © 2011 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xwayland.h" +#include +#include "shared/helpers.h" +#include "shared/string-helpers.h" + +static int +weston_xserver_handle_event(int listen_fd, uint32_t mask, void *data) +{ + struct weston_xserver *wxs = data; + char display[8]; + + snprintf(display, sizeof display, ":%d", wxs->display); + + wxs->pid = wxs->spawn_func(wxs->user_data, display, wxs->abstract_fd, wxs->unix_fd); + if (wxs->pid == -1) { + weston_log("Failed to spawn the Xwayland server\n"); + return 1; + } + + weston_log("Spawned Xwayland server, pid %d\n", wxs->pid); + wl_event_source_remove(wxs->abstract_source); + wl_event_source_remove(wxs->unix_source); + + 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->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->wm = NULL; + } + wxs->loop = NULL; +} + +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) +{ + /* 10 decimal characters, trailing LF and NUL byte; see comment + * at end of function. */ + char pid[11]; + 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; + } + + /* Trim the trailing LF, or at least ensure it's NULL. */ + pid[10] = '\0'; + + if (!safe_strtoint(pid, &other)) { + 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. + * Also subtle is that we don't emit a trailing NUL to the file, so + * our size here is 11 rather than 12. */ + size = dprintf(fd, "%10d\n", getpid()); + if (size != 11) { + 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); + + weston_log_scope_destroy(wxs->wm_debug); + + free(wxs); +} + +static struct weston_xwayland * +weston_xwayland_get(struct weston_compositor *compositor) +{ + struct wl_listener *listener; + struct weston_xserver *wxs; + + listener = wl_signal_get(&compositor->destroy_signal, + weston_xserver_destroy); + if (!listener) + return NULL; + + wxs = wl_container_of(listener, wxs, destroy_listener); + return (struct weston_xwayland *)wxs; +} + +static int +weston_xwayland_listen(struct weston_xwayland *xwayland, void *user_data, + weston_xwayland_spawn_xserver_func_t spawn_func) +{ + struct weston_xserver *wxs = (struct weston_xserver *)xwayland; + char lockfile[256], display_name[8]; + + wxs->user_data = user_data; + wxs->spawn_func = spawn_func; + +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(wxs->wl_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); + + return 0; +} + +static void +weston_xwayland_xserver_loaded(struct weston_xwayland *xwayland, + struct wl_client *client, int wm_fd) +{ + struct weston_xserver *wxs = (struct weston_xserver *)xwayland; + wxs->wm = weston_wm_create(wxs, wm_fd); + wxs->client = client; +} + +static void +weston_xwayland_xserver_exited(struct weston_xwayland *xwayland, + int exit_status) +{ + struct weston_xserver *wxs = (struct weston_xserver *)xwayland; + + wxs->pid = 0; + wxs->client = 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", exit_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", exit_status); + weston_xserver_shutdown(wxs); + } +} + +const struct weston_xwayland_api api = { + weston_xwayland_get, + weston_xwayland_listen, + weston_xwayland_xserver_loaded, + weston_xwayland_xserver_exited, +}; +extern const struct weston_xwayland_surface_api surface_api; + +WL_EXPORT int +weston_module_init(struct weston_compositor *compositor) + +{ + struct wl_display *display = compositor->wl_display; + struct weston_xserver *wxs; + int ret; + + wxs = zalloc(sizeof *wxs); + if (wxs == NULL) + return -1; + wxs->wl_display = display; + wxs->compositor = compositor; + + if (!weston_compositor_add_destroy_listener_once(compositor, + &wxs->destroy_listener, + weston_xserver_destroy)) { + free(wxs); + return 0; + } + + if (weston_xwayland_get_api(compositor) != NULL || + weston_xwayland_surface_get_api(compositor) != NULL) { + weston_log("The xwayland module APIs are already loaded.\n"); + goto out_free; + } + + ret = weston_plugin_api_register(compositor, WESTON_XWAYLAND_API_NAME, + &api, sizeof(api)); + if (ret < 0) { + weston_log("Failed to register the xwayland module API.\n"); + goto out_free; + } + + ret = weston_plugin_api_register(compositor, + WESTON_XWAYLAND_SURFACE_API_NAME, + &surface_api, sizeof(surface_api)); + if (ret < 0) { + weston_log("Failed to register the xwayland surface API.\n"); + goto out_free; + } + + wxs->wm_debug = + weston_compositor_add_log_scope(wxs->compositor, "xwm-wm-x11", + "XWM's window management X11 events\n", + NULL, NULL, NULL); + + return 0; + +out_free: + wl_list_remove(&wxs->destroy_listener.link); + free(wxs); + return -1; +} diff --git a/xwayland/meson.build b/xwayland/meson.build new file mode 100644 index 0000000..896d631 --- /dev/null +++ b/xwayland/meson.build @@ -0,0 +1,44 @@ +if not get_option('xwayland') + subdir_done() +endif + +srcs_xwayland = [ + 'launcher.c', + 'window-manager.c', + 'selection.c', + 'dnd.c', + 'hash.c', +] + +dep_names_xwayland = [ + 'xcb', + 'xcb-composite', + 'xcb-shape', + 'xcb-xfixes', + 'xcursor', + 'cairo-xcb', +] + +deps_xwayland = [ dep_libweston_public ] + +foreach name : dep_names_xwayland + d = dependency(name, required: false) + if not d.found() + error('xwayland requires @0@ which was not found. Or, you can use \'-Dxwayland=false\'.'.format(name)) + endif + deps_xwayland += d +endforeach + +plugin_xwayland = shared_library( + 'xwayland', + srcs_xwayland, + link_with: lib_cairo_shared, + include_directories: common_inc, + dependencies: deps_xwayland, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'xwayland.so=@0@;'.format(plugin_xwayland.full_path()) + +install_headers(xwayland_api_h, subdir: dir_include_libweston_install) diff --git a/xwayland/selection.c b/xwayland/selection.c new file mode 100644 index 0000000..c4845f2 --- /dev/null +++ b/xwayland/selection.c @@ -0,0 +1,762 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include "xwayland.h" +#include "shared/helpers.h" + +#ifdef WM_DEBUG +#define wm_log(...) weston_log(__VA_ARGS__) +#else +#define wm_log(...) do {} while (0) +#endif + +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); + wm->property_source = NULL; + close(fd); + weston_log("write error to target fd: %s\n", strerror(errno)); + 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); + wm->property_source = NULL; + + 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; + FILE *fp; + char *logstr; + size_t logsize; + + 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); + if (reply == NULL) + return; + + fp = open_memstream(&logstr, &logsize); + if (fp) { + dump_property(fp, wm, wm->atom.wl_selection, reply); + if (fclose(fp) == 0) + wm_log("%s", logstr); + free(logstr); + } + + if (xcb_get_property_value_length(reply) > 0) { + /* reply's ownership is transferred to wm, which is responsible + * for freeing it */ + 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; + FILE *fp; + char *logstr; + size_t logsize; + + 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); + if (reply == NULL) + return; + + fp = open_memstream(&logstr, &logsize); + if (fp) { + dump_property(fp, wm, wm->atom.wl_selection, reply); + if (fclose(fp) == 0) + wm_log("%s", logstr); + free(logstr); + } + + if (reply->type != XCB_ATOM_ATOM) { + free(reply); + return; + } + + source = zalloc(sizeof *source); + if (source == NULL) { + free(reply); + 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; + FILE *fp; + char *logstr; + size_t logsize; + + 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); + + fp = open_memstream(&logstr, &logsize); + if (fp) { + dump_property(fp, wm, wm->atom.wl_selection, reply); + if (fclose(fp) == 0) + wm_log("%s", logstr); + free(logstr); + } + + if (reply == NULL) { + return; + } else if (reply->type == wm->atom.incr) { + wm->incr = 1; + free(reply); + } else { + wm->incr = 0; + /* reply's ownership is transferred to wm, which is responsible + * for freeing it */ + 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: %s\n", + strerror(errno)); + weston_wm_send_selection_notify(wm, XCB_ATOM_NONE); + if (wm->property_source) + wl_event_source_remove(wm->property_source); + wm->property_source = NULL; + close(fd); + wl_array_release(&wm->source_data); + return 1; + } + + 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; + if (wm->property_source) + wl_event_source_remove(wm->property_source); + wm->property_source = NULL; + 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; + if (wm->property_source) + wl_event_source_remove(wm->property_source); + wm->property_source = NULL; + } else { + weston_log("got %zu bytes, " + "property deleted, setting 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); + if (wm->property_source) + wl_event_source_remove(wm->property_source); + wm->property_source = NULL; + 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, setting new property\n", + wm->source_data.size); + weston_wm_flush_source_data(wm); + } + xcb_flush(wm->conn); + if (wm->property_source) + wl_event_source_remove(wm->property_source); + wm->property_source = NULL; + 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: %s\n", strerror(errno)); + 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 property 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; + + 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; + + xcb_set_selection_owner(wm->conn, + wm->selection_window, + 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; + + wl_list_init(&wm->selection_listener.link); + + 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); + if (seat == NULL) + return; + 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/xwayland/window-manager.c b/xwayland/window-manager.c new file mode 100644 index 0000000..de86821 --- /dev/null +++ b/xwayland/window-manager.c @@ -0,0 +1,2986 @@ +/* + * Copyright © 2011 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "xwayland.h" +#include "xwayland-internal-interface.h" + +#include "shared/cairo-util.h" +#include "hash.h" +#include "shared/helpers.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_DECOR_EVERYTHING \ + (MWM_DECOR_BORDER | MWM_DECOR_RESIZEH | MWM_DECOR_TITLE | \ + MWM_DECOR_MENU | MWM_DECOR_MINIMIZE | MWM_DECOR_MAXIMIZE) + +#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_output_weak_ref { + struct weston_output *output; + struct wl_listener destroy_listener; +}; + +struct weston_wm_window { + struct weston_wm *wm; + xcb_window_t id; + xcb_window_t frame_id; + struct frame *frame; + cairo_surface_t *cairo_surface; + uint32_t surface_id; + struct weston_surface *surface; + struct weston_desktop_xwayland_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; + int y; + bool pos_dirty; + int map_request_x; + int map_request_y; + struct weston_output_weak_ref legacy_fullscreen_output; + int saved_width, saved_height; + int decorate; + uint32_t last_button_time; + int did_double; + int override_redirect; + int fullscreen; + int has_alpha; + int delete_window; + int maximized_vert; + int maximized_horz; + struct wm_size_hints size_hints; + struct motif_wm_hints motif_hints; + struct wl_list link; +}; + +static void +weston_wm_window_set_allow_commits(struct weston_wm_window *window, bool allow); + +static struct weston_wm_window * +get_wm_window(struct weston_surface *surface); + +static void +weston_wm_set_net_active_window(struct weston_wm *wm, xcb_window_t window); + +static void +weston_wm_window_schedule_repaint(struct weston_wm_window *window); + +static int +legacy_fullscreen(struct weston_wm *wm, + struct weston_wm_window *window, + struct weston_output **output_ret); + +static void +xserver_map_shell_surface(struct weston_wm_window *window, + struct weston_surface *surface); + +static bool +wm_debug_is_enabled(struct weston_wm *wm) +{ + return weston_log_scope_is_enabled(wm->server->wm_debug); +} + +static void __attribute__ ((format (printf, 2, 3))) +wm_printf(struct weston_wm *wm, const char *fmt, ...) +{ + va_list ap; + char timestr[128]; + + if (wm_debug_is_enabled(wm)) + weston_log_scope_printf(wm->server->wm_debug, "%s ", + weston_log_scope_timestamp(wm->server->wm_debug, + timestr, sizeof timestr)); + + va_start(ap, fmt); + weston_log_scope_vprintf(wm->server->wm_debug, fmt, ap); + va_end(ap); +} +static void +weston_output_weak_ref_init(struct weston_output_weak_ref *ref) +{ + ref->output = NULL; +} + +static void +weston_output_weak_ref_clear(struct weston_output_weak_ref *ref) +{ + if (!ref->output) + return; + + wl_list_remove(&ref->destroy_listener.link); + ref->output = NULL; +} + +static void +weston_output_weak_ref_handle_destroy(struct wl_listener *listener, void *data) +{ + struct weston_output_weak_ref *ref; + + ref = wl_container_of(listener, ref, destroy_listener); + assert(ref->output == data); + + weston_output_weak_ref_clear(ref); +} + +static void +weston_output_weak_ref_set(struct weston_output_weak_ref *ref, + struct weston_output *output) +{ + weston_output_weak_ref_clear(ref); + + if (!output) + return; + + ref->destroy_listener.notify = weston_output_weak_ref_handle_destroy; + wl_signal_add(&output->destroy_signal, &ref->destroy_listener); + ref->output = output; +} + +static bool __attribute__ ((warn_unused_result)) +wm_lookup_window(struct weston_wm *wm, xcb_window_t hash, + struct weston_wm_window **window) +{ + *window = hash_table_lookup(wm->window_hash, hash); + if (*window) + return true; + return false; +} + +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; +} + +static unsigned +dump_cardinal_array_elem(FILE *fp, unsigned format, + void *arr, unsigned len, unsigned ind) +{ + const char *comma; + + /* If more than 16 elements, print 0-14, ..., last */ + if (ind > 14 && ind < len - 1) { + fprintf(fp, ", ..."); + return len - 1; + } + + comma = ind ? ", " : ""; + + switch (format) { + case 32: + fprintf(fp, "%s%" PRIu32, comma, ((uint32_t *)arr)[ind]); + break; + case 16: + fprintf(fp, "%s%" PRIu16, comma, ((uint16_t *)arr)[ind]); + break; + case 8: + fprintf(fp, "%s%" PRIu8, comma, ((uint8_t *)arr)[ind]); + break; + default: + fprintf(fp, "%s???", comma); + } + + return ind + 1; +} + +static void +dump_cardinal_array(FILE *fp, xcb_get_property_reply_t *reply) +{ + unsigned i = 0; + void *arr; + + assert(reply->type == XCB_ATOM_CARDINAL); + + arr = xcb_get_property_value(reply); + + fprintf(fp, "["); + while (i < reply->value_len) + i = dump_cardinal_array_elem(fp, reply->format, + arr, reply->value_len, i); + fprintf(fp, "]"); +} + +void +dump_property(FILE *fp, 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; + xcb_window_t *window_value; + int width, len; + uint32_t i; + + width = fprintf(fp, "%s: ", get_atom_name(wm->conn, property)); + if (reply == NULL) { + fprintf(fp, "(no reply)\n"); + return; + } + + width += fprintf(fp, "%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); + fprintf(fp, "%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; + fprintf(fp, "\"%.*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) { + fprintf(fp, "\n "); + width = 4; + } else if (i > 0) { + width += fprintf(fp, ", "); + } + + width += fprintf(fp, "%s", name); + } + fprintf(fp, "\n"); + } else if (reply->type == XCB_ATOM_CARDINAL) { + dump_cardinal_array(fp, reply); + fprintf(fp, "\n"); + } else if (reply->type == XCB_ATOM_WINDOW && reply->format == 32) { + window_value = xcb_get_property_value(reply); + fprintf(fp, "win %u\n", *window_value); + } else { + fprintf(fp, "huh?\n"); + } +} + +static void +read_and_dump_property(FILE *fp, 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(fp, wm, property, reply); + + free(reply); +} + +/* We reuse some predefined, but otherwise useles atoms + * as local type placeholders that never touch the X11 server, + * to make weston_wm_window_read_properties() less exceptional. + */ +#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; + +#define F(field) (&window->field) + const struct { + xcb_atom_t atom; + xcb_atom_t type; + void *ptr; + } 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, NULL }, + { wm->atom.wm_normal_hints, TYPE_WM_NORMAL_HINTS, NULL }, + { wm->atom.net_wm_state, TYPE_NET_WM_STATE, NULL }, + { 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, NULL }, + { 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; + char name[1024]; + + 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 ? 0 : MWM_DECOR_EVERYTHING; + window->size_hints.flags = 0; + window->motif_hints.flags = 0; + window->delete_window = 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 = props[i].ptr; + + 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); + if (!wm_lookup_window(wm, *xid, p)) + weston_log("XCB_ATOM_WINDOW contains window" + " id not found in hash table.\n"); + 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: + atom = xcb_get_property_value(reply); + for (i = 0; i < reply->value_len; i++) + if (atom[i] == wm->atom.wm_delete_window) { + window->delete_window = 1; + break; + } + 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; + if (atom[i] == wm->atom.net_wm_state_maximized_vert) + window->maximized_vert = 1; + if (atom[i] == wm->atom.net_wm_state_maximized_horz) + window->maximized_horz = 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) { + if (window->motif_hints.decorations & MWM_DECOR_ALL) + /* MWM_DECOR_ALL means all except the other values listed. */ + window->decorate = + MWM_DECOR_EVERYTHING & (~window->motif_hints.decorations); + else + window->decorate = + window->motif_hints.decorations; + } + break; + default: + break; + } + free(reply); + } + + if (window->pid > 0) { + gethostname(name, sizeof(name)); + for (i = 0; i < sizeof(name); i++) { + if (name[i] == '\0') + break; + } + if (i == sizeof(name)) + name[0] = '\0'; /* ignore stupid hostnames */ + + /* 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 (!window->machine || strcmp(window->machine, name)) + window->pid = 0; + } +} + +#undef TYPE_WM_PROTOCOLS +#undef TYPE_MOTIF_WM_HINTS +#undef TYPE_NET_WM_STATE +#undef TYPE_WM_NORMAL_HINTS + +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 && window->frame) { + *width = frame_width(window->frame); + *height = frame_height(window->frame); + } 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 && window->frame) { + frame_interior(window->frame, x, y, NULL, NULL); + } 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_configure_window(struct weston_wm *wm, xcb_window_t window_id, + uint16_t mask, const uint32_t *values) +{ + static const struct { + xcb_config_window_t bitmask; + const char *name; + } names[] = { + { XCB_CONFIG_WINDOW_X, "x" }, + { XCB_CONFIG_WINDOW_Y, "y" }, + { XCB_CONFIG_WINDOW_WIDTH, "width" }, + { XCB_CONFIG_WINDOW_HEIGHT, "height" }, + { XCB_CONFIG_WINDOW_BORDER_WIDTH, "border_width" }, + { XCB_CONFIG_WINDOW_SIBLING, "sibling" }, + { XCB_CONFIG_WINDOW_STACK_MODE, "stack_mode" }, + }; + char *buf = NULL; + size_t sz = 0; + FILE *fp; + unsigned i, v; + + xcb_configure_window(wm->conn, window_id, mask, values); + + if (!wm_debug_is_enabled(wm)) + return; + + fp = open_memstream(&buf, &sz); + if (!fp) + return; + + fprintf(fp, "XWM: configure window %d:", window_id); + for (i = 0, v = 0; i < ARRAY_LENGTH(names); i++) { + if (mask & names[i].bitmask) + fprintf(fp, " %s=%d", names[i].name, values[v++]); + } + fclose(fp); + + wm_printf(wm, "%s\n", buf); + free(buf); +} + +static void +weston_wm_window_configure_frame(struct weston_wm_window *window) +{ + uint16_t mask; + uint32_t values[2]; + int width, height; + + if (!window->frame_id) + return; + + weston_wm_window_get_frame_size(window, &width, &height); + values[0] = width; + values[1] = height; + mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; + weston_wm_configure_window(window->wm, window->frame_id, mask, values); +} + +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 values[16]; + uint16_t mask; + int x, y; + int i = 0; + + wm_printf(wm, "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); + + if (!wm_lookup_window(wm, configure_request->window, &window)) + return; + + 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; + + if (window->frame) { + weston_wm_window_set_allow_commits(window, false); + frame_resize_inside(window->frame, window->width, window->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; + } + + weston_wm_configure_window(wm, window->id, mask, values); + weston_wm_window_configure_frame(window); + 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; + const struct weston_desktop_xwayland_interface *xwayland_api = + wm->server->compositor->xwayland_interface; + struct weston_wm_window *window; + + wm_printf(wm, "XCB_CONFIGURE_NOTIFY (window %d) %d,%d @ %dx%d%s\n", + configure_notify->window, + configure_notify->x, configure_notify->y, + configure_notify->width, configure_notify->height, + configure_notify->override_redirect ? ", override" : ""); + + if (!wm_lookup_window(wm, configure_notify->window, &window)) + return; + + window->x = configure_notify->x; + window->y = configure_notify->y; + window->pos_dirty = false; + + if (window->override_redirect) { + window->width = configure_notify->width; + window->height = configure_notify->height; + if (window->frame) + frame_resize_inside(window->frame, + window->width, window->height); + + /* We should check if shsurf has been created because sometimes + * there are races + * (configure_notify is sent before xserver_map_surface) */ + if (window->shsurf) + xwayland_api->set_xwayland(window->shsurf, + window->x, window->y); + } +} + +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); + if (!window) + return; + + if (window->pid > 0) + kill(window->pid, SIGKILL); +} + +static void +weston_wm_create_surface(struct wl_listener *listener, void *data) +{ + struct weston_surface *surface = data; + struct weston_wm *wm = + container_of(listener, + struct weston_wm, create_surface_listener); + struct weston_wm_window *window; + + if (wl_resource_get_client(surface->resource) != wm->server->client) + return; + + wm_printf(wm, "XWM: create weston_surface %p\n", surface); + + wl_list_for_each(window, &wm->unpaired_window_list, link) + if (window->surface_id == + wl_resource_get_id(surface->resource)) { + xserver_map_shell_surface(window, surface); + window->surface_id = 0; + wl_list_remove(&window->link); + break; + } +} + +static void +weston_wm_send_focus_window(struct weston_wm *wm, + struct weston_wm_window *window) +{ + xcb_client_message_event_t client_message; + + if (window) { + uint32_t values[1]; + + if (window->override_redirect) + return; + + 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); + + values[0] = XCB_STACK_MODE_ABOVE; + weston_wm_configure_window(wm, window->frame_id, + XCB_CONFIG_WINDOW_STACK_MODE, + values); + } else { + xcb_set_input_focus (wm->conn, + XCB_INPUT_FOCUS_POINTER_ROOT, + XCB_NONE, + XCB_TIME_CURRENT_TIME); + } +} + +static void +weston_wm_window_activate(struct wl_listener *listener, void *data) +{ + struct weston_surface_activation_data *activation_data = data; + struct weston_surface *surface = activation_data->surface; + struct weston_wm_window *window = NULL; + struct weston_wm *wm = + container_of(listener, struct weston_wm, activate_listener); + + if (surface) { + window = get_wm_window(surface); + } + + if (window) { + weston_wm_set_net_active_window(wm, window->id); + } else { + weston_wm_set_net_active_window(wm, XCB_WINDOW_NONE); + } + + weston_wm_send_focus_window(wm, window); + + if (wm->focus_window) { + if (wm->focus_window->frame) + frame_unset_flag(wm->focus_window->frame, FRAME_FLAG_ACTIVE); + weston_wm_window_schedule_repaint(wm->focus_window); + } + wm->focus_window = window; + if (wm->focus_window) { + if (wm->focus_window->frame) + frame_set_flag(wm->focus_window->frame, FRAME_FLAG_ACTIVE); + weston_wm_window_schedule_repaint(wm->focus_window); + } + + xcb_flush(wm->conn); + +} + +/** Control Xwayland wl_surface.commit behaviour + * + * This function sets the "_XWAYLAND_ALLOW_COMMITS" property of the frame window + * (not the content window!) to \p allow. + * + * If the property is set to \c true, Xwayland will commit whenever it likes. + * If the property is set to \c false, Xwayland will not commit. + * If the property is not set at all, Xwayland assumes it is \c true. + * + * \param window The XWM window to control. + * \param allow Whether Xwayland is allowed to wl_surface.commit for the window. + */ +static void +weston_wm_window_set_allow_commits(struct weston_wm_window *window, bool allow) +{ + struct weston_wm *wm = window->wm; + uint32_t property[1]; + + assert(window->frame_id != XCB_WINDOW_NONE); + + wm_printf(wm, "XWM: window %d set _XWAYLAND_ALLOW_COMMITS = %s\n", + window->id, allow ? "true" : "false"); + + property[0] = allow ? 1 : 0; + + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + window->frame_id, + wm->atom.allow_commits, + XCB_ATOM_CARDINAL, + 32, /* format */ + 1, property); + 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[3]; + int i; + + i = 0; + if (window->fullscreen) + property[i++] = wm->atom.net_wm_state_fullscreen; + if (window->maximized_vert) + property[i++] = wm->atom.net_wm_state_maximized_vert; + if (window->maximized_horz) + property[i++] = wm->atom.net_wm_state_maximized_horz; + + 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; + int buttons = FRAME_BUTTON_CLOSE; + + if (window->decorate & MWM_DECOR_MAXIMIZE) + buttons |= FRAME_BUTTON_MAXIMIZE; + + window->frame = frame_create(window->wm->theme, + window->width, window->height, + buttons, window->name, NULL); + + if (!window->frame) + return; + + frame_resize_inside(window->frame, window->width, window->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; + weston_wm_configure_window(wm, 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); +} + +/* + * Sets the _NET_WM_DESKTOP property for the window to 'desktop'. + * Passing a <0 desktop value deletes the property. + */ +static void +weston_wm_window_set_virtual_desktop(struct weston_wm_window *window, + int desktop) +{ + if (desktop >= 0) { + xcb_change_property(window->wm->conn, + XCB_PROP_MODE_REPLACE, + window->id, + window->wm->atom.net_wm_desktop, + XCB_ATOM_CARDINAL, + 32, /* format */ + 1, &desktop); + } else { + xcb_delete_property(window->wm->conn, + window->id, + window->wm->atom.net_wm_desktop); + } +} + +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; + struct weston_output *output; + + if (our_resource(wm, map_request->window)) { + wm_printf(wm, "XCB_MAP_REQUEST (window %d, ours)\n", + map_request->window); + return; + } + + if (!wm_lookup_window(wm, map_request->window, &window)) + return; + + weston_wm_window_read_properties(window); + + /* For a new Window, MapRequest happens before the Window is realized + * in Xwayland. We do the real xcb_map_window() here as a response to + * MapRequest. The Window will get realized (wl_surface created in + * Wayland and WL_SURFACE_ID sent in X11) when it has been mapped for + * real. + * + * MapRequest only happens for (X11) unmapped Windows. On UnmapNotify, + * we reset shsurf to NULL, so even if X11 connection races far ahead + * of the Wayland connection and the X11 client is repeatedly mapping + * and unmapping, we will never have shsurf set on MapRequest. + */ + assert(!window->shsurf); + + window->map_request_x = window->x; + window->map_request_y = window->y; + + if (window->frame_id == XCB_WINDOW_NONE) + weston_wm_window_create_frame(window); /* sets frame_id */ + assert(window->frame_id != XCB_WINDOW_NONE); + + wm_printf(wm, "XCB_MAP_REQUEST (window %d, %p, frame %d, %dx%d @ %d,%d)\n", + window->id, window, window->frame_id, + window->width, window->height, + window->map_request_x, window->map_request_y); + + weston_wm_window_set_allow_commits(window, false); + weston_wm_window_set_wm_state(window, ICCCM_NORMAL_STATE); + weston_wm_window_set_net_wm_state(window); + weston_wm_window_set_virtual_desktop(window, 0); + + if (legacy_fullscreen(wm, window, &output)) { + window->fullscreen = 1; + weston_output_weak_ref_set(&window->legacy_fullscreen_output, + output); + } + + xcb_map_window(wm->conn, map_request->window); + xcb_map_window(wm->conn, window->frame_id); + + /* Mapped in the X server, we can draw immediately. + * Cannot set pending state though, no weston_surface until + * xserver_map_shell_surface() time. */ + weston_wm_window_schedule_repaint(window); +} + +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_printf(wm, "XCB_MAP_NOTIFY (window %d, ours)\n", + map_notify->window); + return; + } + + wm_printf(wm, "XCB_MAP_NOTIFY (window %d%s)\n", map_notify->window, + map_notify->override_redirect ? ", override" : ""); +} + +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_printf(wm, "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; + + if (!wm_lookup_window(wm, unmap_notify->window, &window)) + return; + + if (window->surface_id) { + /* Make sure we're not on the unpaired surface list or we + * could be assigned a surface during surface creation that + * was mapped before this unmap request. + */ + wl_list_remove(&window->link); + window->surface_id = 0; + } + 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; + + weston_wm_window_set_wm_state(window, ICCCM_WITHDRAWN_STATE); + weston_wm_window_set_virtual_desktop(window, -1); + + xcb_unmap_window(wm->conn, window->frame_id); +} + +static void +weston_wm_window_draw_decoration(struct weston_wm_window *window) +{ + cairo_t *cr; + int width, height; + const char *how; + + weston_wm_window_get_frame_size(window, &width, &height); + + cairo_xcb_surface_set_size(window->cairo_surface, width, height); + cr = cairo_create(window->cairo_surface); + + if (window->fullscreen) { + how = "fullscreen"; + /* nothing */ + } else if (window->decorate) { + how = "decorate"; + frame_set_title(window->frame, window->name); + frame_repaint(window->frame, cr); + } else { + how = "shadow"; + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 0, 0, 0, 0); + cairo_paint(cr); + + render_shadow(cr, window->wm->theme->shadow, + 2, 2, width + 8, height + 8, 64, 64); + } + + wm_printf(window->wm, "XWM: draw decoration, win %d, %s\n", + window->id, how); + + cairo_destroy(cr); + cairo_surface_flush(window->cairo_surface); + xcb_flush(window->wm->conn); +} + +static void +weston_wm_window_set_pending_state(struct weston_wm_window *window) +{ + int x, y, width, height; + int32_t input_x, input_y, input_w, input_h; + const struct weston_desktop_xwayland_interface *xwayland_interface = + window->wm->server->compositor->xwayland_interface; + + if (!window->surface) + return; + + weston_wm_window_get_frame_size(window, &width, &height); + weston_wm_window_get_child_position(window, &x, &y); + + 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); + } + + if (window->decorate && !window->fullscreen) { + frame_input_rect(window->frame, &input_x, &input_y, + &input_w, &input_h); + } else { + input_x = x; + input_y = y; + input_w = width; + input_h = height; + } + + wm_printf(window->wm, "XWM: win %d geometry: %d,%d %dx%d\n", + window->id, input_x, input_y, input_w, input_h); + + pixman_region32_fini(&window->surface->pending.input); + pixman_region32_init_rect(&window->surface->pending.input, + input_x, input_y, input_w, input_h); + + xwayland_interface->set_window_geometry(window->shsurf, + input_x, input_y, + input_w, input_h); + if (window->name) + xwayland_interface->set_title(window->shsurf, window->name); + if (window->pid > 0) + xwayland_interface->set_pid(window->shsurf, window->pid); +} + +static void +weston_wm_window_do_repaint(void *data) +{ + struct weston_wm_window *window = data; + + window->repaint_source = NULL; + + weston_wm_window_set_allow_commits(window, false); + weston_wm_window_read_properties(window); + + weston_wm_window_draw_decoration(window); + weston_wm_window_set_pending_state(window); + weston_wm_window_set_allow_commits(window, true); +} + +static void +weston_wm_window_set_pending_state_OR(struct weston_wm_window *window) +{ + int width, height; + + /* for override-redirect windows */ + assert(window->frame_id == XCB_WINDOW_NONE); + + if (!window->surface) + return; + + 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); + } +} + +static void +weston_wm_window_schedule_repaint(struct weston_wm_window *window) +{ + struct weston_wm *wm = window->wm; + + if (window->frame_id == XCB_WINDOW_NONE) { + /* Override-redirect windows go through here, but we + * cannot assert(window->override_redirect); because + * we do not deal with changing OR flag yet. + * XXX: handle OR flag changes in message handlers + */ + weston_wm_window_set_pending_state_OR(window); + return; + } + + if (window->repaint_source) + return; + + wm_printf(wm, "XWM: schedule repaint, win %d\n", window->id); + + window->repaint_source = + wl_event_loop_add_idle(wm->server->loop, + weston_wm_window_do_repaint, 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; + FILE *fp = NULL; + char *logstr; + size_t logsize; + char timestr[128]; + + if (!wm_lookup_window(wm, property_notify->window, &window)) + return; + + window->properties_dirty = 1; + + if (wm_debug_is_enabled(wm)) + fp = open_memstream(&logstr, &logsize); + + if (fp) { + fprintf(fp, "%s XCB_PROPERTY_NOTIFY: window %d, ", + weston_log_scope_timestamp(wm->server->wm_debug, + timestr, sizeof timestr), + property_notify->window); + if (property_notify->state == XCB_PROPERTY_DELETE) + fprintf(fp, "deleted %s\n", + get_atom_name(wm->conn, property_notify->atom)); + else + read_and_dump_property(fp, wm, property_notify->window, + property_notify->atom); + + if (fclose(fp) == 0) + weston_log_scope_write(wm->server->wm_debug, + logstr, logsize); + free(logstr); + } else { + /* read_and_dump_property() is a X11 roundtrip. + * Mimic it to maintain ordering semantics between debug + * and non-debug paths. + */ + get_atom_name(wm->conn, 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_printf(wm, "failed to allocate window\n"); + return; + } + + geometry_cookie = xcb_get_geometry(wm->conn, id); + + values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE | + XCB_EVENT_MASK_FOCUS_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; + window->pos_dirty = false; + window->map_request_x = INT_MIN; /* out of range for valid positions */ + window->map_request_y = INT_MIN; /* out of range for valid positions */ + weston_output_weak_ref_init(&window->legacy_fullscreen_output); + + 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; + + weston_output_weak_ref_clear(&window->legacy_fullscreen_output); + + if (window->configure_source) + wl_event_source_remove(window->configure_source); + 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); + weston_wm_window_set_virtual_desktop(window, -1); + hash_table_remove(wm->window_hash, window->frame_id); + window->frame_id = XCB_WINDOW_NONE; + } + + if (window->frame) + frame_destroy(window->frame); + + if (window->surface_id) + wl_list_remove(&window->link); + + if (window->surface) + wl_list_remove(&window->surface_destroy_listener.link); + + 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_printf(wm, "XCB_CREATE_NOTIFY (window %d, at (%d, %d), width %d, height %d%s%s)\n", + create_notify->window, + create_notify->x, create_notify->y, + 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_printf(wm, "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; + + if (!wm_lookup_window(wm, destroy_notify->window, &window)) + return; + + 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_printf(wm, "XCB_REPARENT_NOTIFY (window %d, parent %d, event %d%s)\n", + reparent_notify->window, + reparent_notify->parent, + reparent_notify->event, + reparent_notify->override_redirect ? ", override" : ""); + + 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)) { + if (!wm_lookup_window(wm, reparent_notify->window, &window)) + return; + weston_wm_window_destroy(window); + } +} + +struct weston_seat * +weston_wm_pick_seat(struct weston_wm *wm) +{ + struct wl_list *seats = wm->server->compositor->seat_list.next; + if (wl_list_empty(seats)) + return NULL; + return container_of(seats, struct weston_seat, link); +} + +static struct weston_seat * +weston_wm_pick_seat_for_window(struct weston_wm_window *window) +{ + struct weston_wm *wm = window->wm; + struct weston_seat *seat, *s; + + seat = NULL; + wl_list_for_each(s, &wm->server->compositor->seat_list, link) { + struct weston_pointer *pointer = weston_seat_get_pointer(s); + struct weston_pointer *old_pointer = + weston_seat_get_pointer(seat); + + if (pointer && pointer->focus && + pointer->focus->surface == window->surface && + pointer->button_count > 0 && + (!seat || + pointer->grab_serial - + old_pointer->grab_serial < (1 << 30))) + seat = s; + } + + return seat; +} + +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_for_window(window); + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + int detail; + const struct weston_desktop_xwayland_interface *xwayland_interface = + wm->server->compositor->xwayland_interface; + + if (!pointer || pointer->button_count != 1 + || !pointer->focus + || pointer->focus->surface != window->surface) + return; + + detail = client_message->data.data32[2]; + switch (detail) { + case _NET_WM_MOVERESIZE_MOVE: + xwayland_interface->move(window->shsurf, pointer); + 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: + xwayland_interface->resize(window->shsurf, pointer, 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_set_toplevel(struct weston_wm_window *window) +{ + const struct weston_desktop_xwayland_interface *xwayland_interface = + window->wm->server->compositor->xwayland_interface; + + xwayland_interface->set_toplevel(window->shsurf); + window->width = window->saved_width; + window->height = window->saved_height; + if (window->frame) + frame_resize_inside(window->frame, + window->width, + window->height); + weston_wm_window_configure(window); +} + +static inline bool +weston_wm_window_is_maximized(struct weston_wm_window *window) +{ + return window->maximized_horz && window->maximized_vert; +} + +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; + const struct weston_desktop_xwayland_interface *xwayland_interface = + wm->server->compositor->xwayland_interface; + uint32_t action, property1, property2; + int maximized = weston_wm_window_is_maximized(window); + + action = client_message->data.data32[0]; + property1 = client_message->data.data32[1]; + property2 = client_message->data.data32[2]; + + if ((property1 == wm->atom.net_wm_state_fullscreen || + property2 == 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) + xwayland_interface->set_fullscreen(window->shsurf, + NULL); + } else { + if (window->shsurf) + weston_wm_window_set_toplevel(window); + } + } else { + if ((property1 == wm->atom.net_wm_state_maximized_vert || + property2 == wm->atom.net_wm_state_maximized_vert) && + update_state(action, &window->maximized_vert)) + weston_wm_window_set_net_wm_state(window); + if ((property1 == wm->atom.net_wm_state_maximized_horz || + property2 == wm->atom.net_wm_state_maximized_horz) && + update_state(action, &window->maximized_horz)) + weston_wm_window_set_net_wm_state(window); + + if (maximized != weston_wm_window_is_maximized(window)) { + if (weston_wm_window_is_maximized(window)) { + window->saved_width = window->width; + window->saved_height = window->height; + + if (window->shsurf) + xwayland_interface->set_maximized(window->shsurf); + } else if (window->shsurf) { + weston_wm_window_set_toplevel(window); + } + } + } +} + +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_printf(window->wm, "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 void +weston_wm_window_handle_surface_id(struct weston_wm_window *window, + xcb_client_message_event_t *client_message) +{ + struct weston_wm *wm = window->wm; + struct wl_resource *resource; + + if (window->surface_id != 0) { + wm_printf(wm, "already have surface id for window %d\n", + window->id); + return; + } + + /* Xwayland will send the wayland requests to create the + * wl_surface before sending this client message. Even so, we + * can end up handling the X event before the wayland requests + * and thus when we try to look up the surface ID, the surface + * hasn't been created yet. In that case put the window on + * the unpaired window list and continue when the surface gets + * created. */ + uint32_t id = client_message->data.data32[0]; + resource = wl_client_get_object(wm->server->client, id); + if (resource) { + window->surface_id = 0; + xserver_map_shell_surface(window, + wl_resource_get_user_data(resource)); + } + else { + window->surface_id = id; + wl_list_insert(&wm->unpaired_window_list, &window->link); + } +} + +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; + + wm_printf(wm, "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); + + /* The window may get created and destroyed before we actually + * handle the message. If it doesn't exist, bail. + */ + if (!wm_lookup_window(wm, client_message->window, &window)) + return; + + 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); + else if (client_message->type == wm->atom.wl_surface_id) + weston_wm_window_handle_surface_id(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(enum theme_location location) +{ + 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_window_close(struct weston_wm_window *window, xcb_timestamp_t time) +{ + xcb_client_message_event_t client_message; + + if (window->delete_window) { + client_message.response_type = XCB_CLIENT_MESSAGE; + client_message.format = 32; + client_message.window = window->id; + client_message.type = window->wm->atom.wm_protocols; + client_message.data.data32[0] = + window->wm->atom.wm_delete_window; + client_message.data.data32[1] = time; + + xcb_send_event(window->wm->conn, 0, window->id, + XCB_EVENT_MASK_NO_EVENT, + (char *) &client_message); + } else { + xcb_kill_client(window->wm->conn, window->id); + } +} + +#define DOUBLE_CLICK_PERIOD 250 +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; + const struct weston_desktop_xwayland_interface *xwayland_interface = + wm->server->compositor->xwayland_interface; + struct weston_seat *seat; + struct weston_pointer *pointer; + struct weston_wm_window *window; + enum theme_location location; + enum wl_pointer_button_state button_state; + uint32_t button_id; + uint32_t double_click = 0; + + wm_printf(wm, "XCB_BUTTON_%s (detail %d)\n", + button->response_type == XCB_BUTTON_PRESS ? + "PRESS" : "RELEASE", button->detail); + + if (!wm_lookup_window(wm, button->event, &window) || + !window->decorate) + return; + + if (button->detail != 1 && button->detail != 2) + return; + + seat = weston_wm_pick_seat_for_window(window); + pointer = weston_seat_get_pointer(seat); + + button_state = button->response_type == XCB_BUTTON_PRESS ? + WL_POINTER_BUTTON_STATE_PRESSED : + WL_POINTER_BUTTON_STATE_RELEASED; + button_id = button->detail == 1 ? BTN_LEFT : BTN_RIGHT; + + if (button_state == WL_POINTER_BUTTON_STATE_PRESSED) { + if (button->time - window->last_button_time <= DOUBLE_CLICK_PERIOD) { + double_click = 1; + window->did_double = 1; + } else + window->did_double = 0; + + window->last_button_time = button->time; + } else if (window->did_double == 1) { + double_click = 1; + window->did_double = 0; + } + + /* Make sure we're looking at the right location. The frame + * could have received a motion event from a pointer from a + * different wl_seat, but under X it looks like our core + * pointer moved. Move the frame pointer to the button press + * location before deciding what to do. */ + location = frame_pointer_motion(window->frame, NULL, + button->event_x, button->event_y); + if (double_click) + location = frame_double_click(window->frame, NULL, + button_id, button_state); + else + location = frame_pointer_button(window->frame, NULL, + button_id, button_state); + + if (frame_status(window->frame) & FRAME_STATUS_REPAINT) + weston_wm_window_schedule_repaint(window); + + if (frame_status(window->frame) & FRAME_STATUS_MOVE) { + if (pointer) + xwayland_interface->move(window->shsurf, pointer); + frame_status_clear(window->frame, FRAME_STATUS_MOVE); + } + + if (frame_status(window->frame) & FRAME_STATUS_RESIZE) { + if (pointer) + xwayland_interface->resize(window->shsurf, pointer, location); + frame_status_clear(window->frame, FRAME_STATUS_RESIZE); + } + + if (frame_status(window->frame) & FRAME_STATUS_CLOSE) { + weston_wm_window_close(window, button->time); + frame_status_clear(window->frame, FRAME_STATUS_CLOSE); + } + + if (frame_status(window->frame) & FRAME_STATUS_MAXIMIZE) { + window->maximized_horz = !window->maximized_horz; + window->maximized_vert = !window->maximized_vert; + if (weston_wm_window_is_maximized(window)) { + window->saved_width = window->width; + window->saved_height = window->height; + xwayland_interface->set_maximized(window->shsurf); + } else { + weston_wm_window_set_toplevel(window); + } + frame_status_clear(window->frame, FRAME_STATUS_MAXIMIZE); + } +} + +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; + enum theme_location location; + int cursor; + + if (!wm_lookup_window(wm, motion->event, &window) || + !window->decorate) + return; + + location = frame_pointer_motion(window->frame, NULL, + motion->event_x, motion->event_y); + if (frame_status(window->frame) & FRAME_STATUS_REPAINT) + weston_wm_window_schedule_repaint(window); + + cursor = get_cursor_for_location(location); + 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; + enum theme_location location; + int cursor; + + if (!wm_lookup_window(wm, enter->event, &window) || + !window->decorate) + return; + + location = frame_pointer_enter(window->frame, NULL, + enter->event_x, enter->event_y); + if (frame_status(window->frame) & FRAME_STATUS_REPAINT) + weston_wm_window_schedule_repaint(window); + + cursor = get_cursor_for_location(location); + 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; + + if (!wm_lookup_window(wm, leave->event, &window) || + !window->decorate) + return; + + frame_pointer_leave(window->frame, NULL); + if (frame_status(window->frame) & FRAME_STATUS_REPAINT) + weston_wm_window_schedule_repaint(window); + + weston_wm_window_set_cursor(wm, window->frame_id, XWM_CURSOR_LEFT_PTR); +} + +static void +weston_wm_handle_focus_in(struct weston_wm *wm, xcb_generic_event_t *event) +{ + xcb_focus_in_event_t *focus = (xcb_focus_in_event_t *) event; + + /* Do not interfere with grabs */ + if (focus->mode == XCB_NOTIFY_MODE_GRAB || + focus->mode == XCB_NOTIFY_MODE_UNGRAB) + return; + + /* Do not let X clients change the focus behind the compositor's + * back. Reset the focus to the old one if it changed. */ + if (!wm->focus_window || focus->event != wm->focus_window->id) + weston_wm_send_focus_window(wm, wm->focus_window); +} + +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_printf(wm, "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; + case XCB_FOCUS_IN: + weston_wm_handle_focus_in(wm, event); + break; + } + + free(event); + count++; + } + + if (count != 0) + xcb_flush(wm->conn); + + return count; +} + +static void +weston_wm_set_net_active_window(struct weston_wm *wm, xcb_window_t window) { + xcb_change_property(wm->conn, XCB_PROP_MODE_REPLACE, + wm->screen->root, wm->atom.net_active_window, + wm->atom.window, 32, 1, &window); +} + +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_MAXIMIZED_VERT", F(atom.net_wm_state_maximized_vert) }, + { "_NET_WM_STATE_MAXIMIZED_HORZ", F(atom.net_wm_state_maximized_horz) }, + { "_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_DESKTOP", F(atom.net_wm_desktop) }, + { "_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) }, + { "_NET_ACTIVE_WINDOW", F(atom.net_active_window) }, + { "_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) }, + { "WINDOW", F(atom.window) }, + { "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) }, + { "_XWAYLAND_ALLOW_COMMITS", F(atom.allow_commits) }, + { "WL_SURFACE_ID", F(atom.wl_surface_id) } + }; +#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 support + * 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, int fd) +{ + struct weston_wm *wm; + struct wl_event_loop *loop; + xcb_screen_iterator_t s; + uint32_t values[1]; + xcb_atom_t supported[6]; + + 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; + } + + /* xcb_connect_to_fd takes ownership of the fd. */ + wm->conn = xcb_connect_to_fd(fd, NULL); + if (xcb_connection_has_error(wm->conn)) { + weston_log("xcb_connect_to_fd failed\n"); + close(fd); + 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, fd, + 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(); + + supported[0] = wm->atom.net_wm_moveresize; + supported[1] = wm->atom.net_wm_state; + supported[2] = wm->atom.net_wm_state_fullscreen; + supported[3] = wm->atom.net_wm_state_maximized_vert; + supported[4] = wm->atom.net_wm_state_maximized_horz; + supported[5] = wm->atom.net_active_window; + 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_set_net_active_window(wm, XCB_WINDOW_NONE); + + weston_wm_selection_init(wm); + + weston_wm_dnd_init(wm); + + xcb_flush(wm->conn); + + wm->create_surface_listener.notify = weston_wm_create_surface; + wl_signal_add(&wxs->compositor->create_surface_signal, + &wm->create_surface_listener); + wm->activate_listener.notify = weston_wm_window_activate; + wl_signal_add(&wxs->compositor->activate_signal, + &wm->activate_listener); + wm->kill_listener.notify = weston_wm_kill_client; + wl_signal_add(&wxs->compositor->kill_signal, + &wm->kill_listener); + wl_list_init(&wm->unpaired_window_list); + + weston_wm_create_cursors(wm); + weston_wm_window_set_cursor(wm, wm->screen->root, XWM_CURSOR_LEFT_PTR); + + /* Create wm window and take WM_S0 selection last, which + * signals to Xwayland that we're done with setup. */ + weston_wm_create_wm_window(wm); + + weston_log("created wm, root %d\n", wm->screen->root); + + 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->create_surface_listener.link); + + free(wm); +} + +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 bool +is_wm_window(struct weston_surface *surface) +{ + return get_wm_window(surface) != 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; + + if (window->configure_source) { + wl_event_source_remove(window->configure_source); + window->configure_source = NULL; + } + + weston_wm_window_set_allow_commits(window, false); + + weston_wm_window_get_child_position(window, &x, &y); + values[0] = x; + values[1] = y; + values[2] = window->width; + values[3] = window->height; + weston_wm_configure_window(wm, window->id, + XCB_CONFIG_WINDOW_X | + XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT, + values); + + weston_wm_window_configure_frame(window); + weston_wm_window_schedule_repaint(window); +} + +static void +send_configure(struct weston_surface *surface, int32_t width, int32_t height) +{ + struct weston_wm_window *window = get_wm_window(surface); + struct weston_wm *wm; + struct theme *t; + int new_width, new_height; + int vborder, hborder; + + if (!window || !window->wm) + return; + wm = window->wm; + t = wm->theme; + + if (window->decorate && !window->fullscreen) { + hborder = 2 * t->width; + vborder = t->titlebar_height + t->width; + } else { + hborder = 0; + vborder = 0; + } + + if (width > hborder) + new_width = width - hborder; + else + new_width = 1; + + if (height > vborder) + new_height = height - vborder; + else + new_height = 1; + + if (window->width != new_width || window->height != new_height) { + window->width = new_width; + window->height = new_height; + + if (window->frame) { + frame_resize_inside(window->frame, + window->width, window->height); + } + } + + if (window->configure_source) + return; + + window->configure_source = + wl_event_loop_add_idle(wm->server->loop, + weston_wm_window_configure, window); +} + +static void +send_position(struct weston_surface *surface, int32_t x, int32_t y) +{ + struct weston_wm_window *window = get_wm_window(surface); + struct weston_wm *wm; + uint32_t values[2]; + uint16_t mask; + + if (!window || !window->wm) + return; + + wm = window->wm; + /* We use pos_dirty to tell whether a configure message is in flight. + * This is needed in case we send two configure events in a very + * short time, since window->x/y is set in after a roundtrip, hence + * we cannot just check if the current x and y are different. */ + if (window->x != x || window->y != y || window->pos_dirty) { + window->pos_dirty = true; + values[0] = x; + values[1] = y; + mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; + + weston_wm_configure_window(wm, window->frame_id, mask, values); + xcb_flush(wm->conn); + } +} + +static const struct weston_xwayland_client_interface 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 bool +weston_wm_window_is_positioned(struct weston_wm_window *window) +{ + if (window->map_request_x == INT_MIN || + window->map_request_y == INT_MIN) + weston_log("XWM warning: win %d did not see map request\n", + window->id); + + return window->map_request_x != 0 || window->map_request_y != 0; +} + +static bool +weston_wm_window_type_inactive(struct weston_wm_window *window) +{ + struct weston_wm *wm = window->wm; + + return window->type == wm->atom.net_wm_window_type_tooltip || + window->type == wm->atom.net_wm_window_type_dropdown || + window->type == wm->atom.net_wm_window_type_dnd || + window->type == wm->atom.net_wm_window_type_combo || + window->type == wm->atom.net_wm_window_type_popup || + window->type == wm->atom.net_wm_window_type_utility; +} + +static void +xserver_map_shell_surface(struct weston_wm_window *window, + struct weston_surface *surface) +{ + struct weston_wm *wm = window->wm; + struct weston_desktop_xwayland *xwayland = + wm->server->compositor->xwayland; + const struct weston_desktop_xwayland_interface *xwayland_interface = + wm->server->compositor->xwayland_interface; + struct weston_wm_window *parent; + + /* This should be necessary only for override-redirected windows, + * because otherwise MapRequest handler would have already updated + * the properties. However, if X11 clients set properties after + * sending MapWindow, here we can still process them. The decorations + * have already been drawn once with the old property values, so if the + * app changes something affecting decor after MapWindow, we glitch. + * We only hit xserver_map_shell_surface() once per MapWindow and + * wl_surface, so better ensure we get the window type right. + */ + 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 = surface; + window->surface_destroy_listener.notify = surface_destroy; + wl_signal_add(&window->surface->destroy_signal, + &window->surface_destroy_listener); + + if (!xwayland_interface) + return; + + if (window->surface->committed) { + weston_log("warning, unexpected in %s: " + "surface's configure hook is already set.\n", + __func__); + return; + } + + window->shsurf = + xwayland_interface->create_surface(xwayland, + window->surface, + &shell_client); + + wm_printf(wm, "XWM: map shell surface, win %d, weston_surface %p, xwayland surface %p\n", + window->id, window->surface, window->shsurf); + + if (window->name) + xwayland_interface->set_title(window->shsurf, window->name); + if (window->pid > 0) + xwayland_interface->set_pid(window->shsurf, window->pid); + + if (window->fullscreen) { + window->saved_width = window->width; + window->saved_height = window->height; + xwayland_interface->set_fullscreen(window->shsurf, + window->legacy_fullscreen_output.output); + } else if (window->override_redirect) { + xwayland_interface->set_xwayland(window->shsurf, + window->x, window->y); + } else if (window->transient_for && window->transient_for->surface) { + parent = window->transient_for; + if (weston_wm_window_type_inactive(window)) { + xwayland_interface->set_transient(window->shsurf, + parent->surface, + window->x - parent->x, + window->y - parent->y); + } else { + xwayland_interface->set_toplevel(window->shsurf); + xwayland_interface->set_parent(window->shsurf, + parent->surface); + } + } else if (weston_wm_window_is_maximized(window)) { + xwayland_interface->set_maximized(window->shsurf); + } else { + if (weston_wm_window_type_inactive(window)) { + xwayland_interface->set_xwayland(window->shsurf, + window->x, + window->y); + } else if (weston_wm_window_is_positioned(window)) { + xwayland_interface->set_toplevel_with_position(window->shsurf, + window->map_request_x, + window->map_request_y); + } else { + xwayland_interface->set_toplevel(window->shsurf); + } + } + + if (window->frame_id == XCB_WINDOW_NONE) { + weston_wm_window_set_pending_state_OR(window); + } else { + weston_wm_window_set_pending_state(window); + weston_wm_window_set_allow_commits(window, true); + xcb_flush(wm->conn); + } +} + +const struct weston_xwayland_surface_api surface_api = { + is_wm_window, + send_position, +}; diff --git a/xwayland/xwayland-internal-interface.h b/xwayland/xwayland-internal-interface.h new file mode 100644 index 0000000..1096444 --- /dev/null +++ b/xwayland/xwayland-internal-interface.h @@ -0,0 +1,64 @@ +/* + * Copyright © 2016 Quentin "Sardem FF7" Glidic + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef XWAYLAND_INTERNAL_INTERFACE_H +#define XWAYLAND_INTERNAL_INTERFACE_H + +struct weston_desktop_xwayland; +struct weston_desktop_xwayland_surface; + +struct weston_xwayland_client_interface { + void (*send_configure)(struct weston_surface *surface, int32_t width, int32_t height); +}; + +struct weston_desktop_xwayland_interface { + struct weston_desktop_xwayland_surface *(*create_surface)(struct weston_desktop_xwayland *xwayland, + struct weston_surface *surface, + const struct weston_xwayland_client_interface *client); + void (*set_toplevel)(struct weston_desktop_xwayland_surface *shsurf); + void (*set_toplevel_with_position)(struct weston_desktop_xwayland_surface *shsurf, + int32_t x, int32_t y); + void (*set_parent)(struct weston_desktop_xwayland_surface *shsurf, + struct weston_surface *parent); + void (*set_transient)(struct weston_desktop_xwayland_surface *shsurf, + struct weston_surface *parent, int x, int y); + void (*set_fullscreen)(struct weston_desktop_xwayland_surface *shsurf, + struct weston_output *output); + void (*set_xwayland)(struct weston_desktop_xwayland_surface *shsurf, + int x, int y); + int (*move)(struct weston_desktop_xwayland_surface *shsurf, + struct weston_pointer *pointer); + int (*resize)(struct weston_desktop_xwayland_surface *shsurf, + struct weston_pointer *pointer, uint32_t edges); + void (*set_title)(struct weston_desktop_xwayland_surface *shsurf, + const char *title); + void (*set_window_geometry)(struct weston_desktop_xwayland_surface *shsurf, + int32_t x, int32_t y, + int32_t width, int32_t height); + void (*set_maximized)(struct weston_desktop_xwayland_surface *shsurf); + void (*set_pid)(struct weston_desktop_xwayland_surface *shsurf, pid_t pid); +}; + +#endif diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h new file mode 100644 index 0000000..3ef0404 --- /dev/null +++ b/xwayland/xwayland.h @@ -0,0 +1,189 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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; + int abstract_fd; + struct wl_event_source *abstract_source; + int unix_fd; + struct wl_event_source *unix_source; + int display; + pid_t pid; + struct wl_client *client; + struct weston_compositor *compositor; + struct weston_wm *wm; + struct wl_listener destroy_listener; + weston_xwayland_spawn_xserver_func_t spawn_func; + void *user_data; + + struct weston_log_scope *wm_debug; +}; + +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 create_surface_listener; + struct wl_listener activate_listener; + struct wl_listener kill_listener; + struct wl_list unpaired_window_list; + + 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_maximized_vert; + xcb_atom_t net_wm_state_maximized_horz; + 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_desktop; + 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 net_active_window; + 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 window; + 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; + xcb_atom_t wl_surface_id; + xcb_atom_t allow_commits; + } atom; +}; + +void +dump_property(FILE *fp, 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); + +struct weston_wm * +weston_wm_create(struct weston_xserver *wxs, int fd); +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); -- Gitee From d9f5499ab3fd28340dfd9deb6bc30aacb7cc18c6 Mon Sep 17 00:00:00 2001 From: 18408672952 <2518665972@qq.com> Date: Fri, 27 Aug 2021 21:48:34 +0800 Subject: [PATCH 2/2] test for third Signed-off-by: 18408672952 <2518665972@qq.com> --- clients/calibrator.c | 311 - clients/clickdot.c | 345 - clients/cliptest.c | 637 -- clients/confine.c | 512 -- clients/content_protection.c | 384 - clients/desktop-shell.c | 1559 ---- clients/dnd.c | 867 -- clients/editor.c | 1681 ---- clients/eventdemo.c | 541 -- clients/flower.c | 206 - clients/fullscreen.c | 581 -- clients/gears.c | 503 -- clients/image.c | 437 - clients/ivi-shell-user-interface.c | 1338 --- clients/keyboard.c | 1041 --- clients/meson.build | 373 - clients/multi-resource.c | 591 -- clients/nested-client.c | 374 - clients/nested.c | 1137 --- clients/presentation-shm.c | 956 -- clients/resizor.c | 456 - clients/scaler.c | 326 - clients/screenshot.c | 329 - clients/simple-damage.c | 957 -- clients/simple-dmabuf-egl.c | 1562 ---- clients/simple-dmabuf-v4l.c | 1080 --- clients/simple-egl.c | 896 -- clients/simple-im.c | 526 -- clients/simple-shm.c | 528 -- clients/simple-touch.c | 360 - clients/smoke.c | 318 - clients/stacking.c | 309 - clients/subsurfaces.c | 811 -- clients/terminal.c | 3185 ------- clients/touch-calibrator.c | 970 -- clients/transformed.c | 303 - clients/weston-debug.c | 501 -- clients/weston-info.c | 1890 ---- clients/window.c | 6673 -------------- clients/window.h | 744 -- compositor/cms-colord.c | 588 -- compositor/cms-helper.c | 136 - compositor/cms-helper.h | 75 - compositor/cms-static.c | 124 - compositor/executable.c | 34 - compositor/main.c | 3463 ------- compositor/meson.build | 187 - compositor/screen-share.c | 1184 --- compositor/systemd-notify.c | 168 - compositor/testsuite-util.c | 62 - compositor/text-backend.c | 1099 --- compositor/weston-screenshooter.c | 202 - compositor/weston.desktop | 5 - compositor/weston.h | 117 - compositor/xwayland.c | 212 - data/COPYING | 55 - data/background.png | Bin 135501 -> 0 bytes data/border.png | Bin 1585 -> 0 bytes data/fullscreen.png | Bin 3152 -> 0 bytes data/home.png | Bin 4234 -> 0 bytes data/icon_editor.png | Bin 737 -> 0 bytes data/icon_flower.png | Bin 1160 -> 0 bytes data/icon_ivi_clickdot.png | Bin 30955 -> 0 bytes data/icon_ivi_flower.png | Bin 20214 -> 0 bytes data/icon_ivi_simple-egl.png | Bin 8688 -> 0 bytes data/icon_ivi_simple-shm.png | Bin 39650 -> 0 bytes data/icon_ivi_smoke.png | Bin 24569 -> 0 bytes data/icon_terminal.png | Bin 525 -> 0 bytes data/icon_window.png | Bin 86 -> 0 bytes data/icons.svg | 1012 --- data/meson.build | 29 - data/panel.png | Bin 27761 -> 0 bytes data/pattern.png | Bin 825 -> 0 bytes data/random.png | Bin 4553 -> 0 bytes data/sidebyside.png | Bin 3630 -> 0 bytes data/sign_close.png | Bin 104 -> 0 bytes data/sign_maximize.png | Bin 85 -> 0 bytes data/sign_minimize.png | Bin 79 -> 0 bytes data/terminal.png | Bin 765 -> 0 bytes data/tiling.png | Bin 5148 -> 0 bytes data/wayland.png | Bin 4596 -> 0 bytes data/wayland.svg | 101 - desktop-shell/exposay.c | 737 -- desktop-shell/input-panel.c | 412 - desktop-shell/meson.build | 31 - desktop-shell/shell.c | 5253 ----------- desktop-shell/shell.h | 263 - doc/doxygen/devtools.dox | 51 - doc/doxygen/tooldev.doxygen.in | 12 - doc/doxygen/tools.dox | 31 - doc/doxygen/tools.doxygen.in | 11 - doc/doxygen/tools_arch_new.gv | 85 - doc/doxygen/tools_arch_old.gv | 53 - doc/scripts/calibration-helper.bash | 66 - doc/scripts/gdb/flight_rec.py | 103 - doc/scripts/remoting-client-receive.bash | 38 - doc/sphinx/conf.py.in | 204 - doc/sphinx/doxygen.ini.in | 2480 ----- doc/sphinx/index.rst | 34 - doc/sphinx/meson.build | 88 - doc/sphinx/run_doxygen_sphinx.sh.in | 2 - doc/sphinx/toc/kiosk-shell.rst | 17 - doc/sphinx/toc/libweston.rst | 46 - doc/sphinx/toc/libweston/compositor.rst | 14 - doc/sphinx/toc/libweston/head.rst | 44 - .../toc/libweston/images/create_output.msc | 31 - .../toc/libweston/images/create_output.png | Bin 39923 -> 0 bytes .../toc/libweston/images/destroy-output.msc | 29 - .../toc/libweston/images/destroy-output.png | Bin 30249 -> 0 bytes .../toc/libweston/images/head-destroyed.msc | 26 - .../toc/libweston/images/head-destroyed.png | Bin 28457 -> 0 bytes .../toc/libweston/images/initial-heads.msc | 35 - .../toc/libweston/images/initial-heads.png | Bin 37770 -> 0 bytes doc/sphinx/toc/libweston/images/meson.build | 13 - .../images/react-to-heads-changed.msc | 24 - .../images/react-to-heads-changed.png | Bin 27241 -> 0 bytes doc/sphinx/toc/libweston/log.rst | 215 - doc/sphinx/toc/libweston/meson.build | 14 - .../toc/libweston/output-management.rst | 104 - doc/sphinx/toc/libweston/output.rst | 30 - doc/sphinx/toc/meson.build | 13 - doc/sphinx/toc/test-suite-api.rst | 15 - doc/sphinx/toc/test-suite.rst | 269 - doc/wayland-screenshot.jpg | Bin 143832 -> 0 bytes fullscreen-shell/fullscreen-shell.c | 941 -- fullscreen-shell/meson.build | 16 - include/config.h | 260 - include/libweston-desktop/libweston-desktop.h | 202 - include/libweston/backend-drm.h | 235 - include/libweston/backend-fbdev.h | 70 - include/libweston/backend-headless.h | 53 - include/libweston/backend-rdp.h | 75 - include/libweston/backend-wayland.h | 54 - include/libweston/backend-x11.h | 53 - include/libweston/config-parser.h | 130 - include/libweston/libweston.h | 2083 ----- include/libweston/matrix.h | 85 - include/libweston/meson.build | 31 - include/libweston/plugin-registry.h | 55 - include/libweston/version.h.in | 50 - include/libweston/weston-log.h | 142 - include/libweston/windowed-output-api.h | 95 - include/libweston/xwayland-api.h | 176 - include/libweston/zalloc.h | 45 - include/meson.build | 6 - ivi-shell/README | 78 - ivi-shell/hmi-controller.c | 2006 ----- ivi-shell/ivi-layout-export.h | 610 -- ivi-shell/ivi-layout-private.h | 205 - ivi-shell/ivi-layout-shell.h | 67 - ivi-shell/ivi-layout-transition.c | 908 -- ivi-shell/ivi-layout.c | 2127 ----- ivi-shell/ivi-shell.c | 660 -- ivi-shell/ivi-shell.h | 55 - ivi-shell/meson.build | 61 - ivi-shell/weston.ini.in | 98 - kiosk-shell/kiosk-shell-grab.c | 314 - kiosk-shell/kiosk-shell-grab.h | 43 - kiosk-shell/kiosk-shell.c | 1071 --- kiosk-shell/kiosk-shell.h | 91 - kiosk-shell/meson.build | 29 - kiosk-shell/util.c | 168 - kiosk-shell/util.h | 48 - libweston-desktop/client.c | 212 - libweston-desktop/internal.h | 242 - libweston-desktop/libweston-desktop.c | 255 - libweston-desktop/meson.build | 36 - libweston-desktop/seat.c | 381 - libweston-desktop/surface.c | 830 -- libweston-desktop/wl-shell.c | 497 - libweston-desktop/xdg-shell-v6.c | 1444 --- libweston-desktop/xdg-shell.c | 1498 --- libweston-desktop/xwayland.c | 425 - libweston/animation.c | 533 -- libweston/backend-drm/auth/wayland_drm_auth.h | 76 - .../auth/wayland_drm_auth_server.c | 80 - .../auth/wayland_drm_auth_server.h | 47 - libweston/backend-drm/drm-gbm.c | 365 - libweston/backend-drm/drm-internal.h | 831 -- libweston/backend-drm/drm-virtual.c | 362 - libweston/backend-drm/drm.c | 3086 ------- libweston/backend-drm/fb.c | 582 -- libweston/backend-drm/kms.c | 1507 ---- libweston/backend-drm/libbacklight.c | 318 - libweston/backend-drm/libbacklight.h | 79 - libweston/backend-drm/meson.build | 93 - libweston/backend-drm/modes.c | 812 -- libweston/backend-drm/state-helpers.c | 473 - libweston/backend-drm/state-propose.c | 1149 --- libweston/backend-drm/vaapi-recorder.c | 1161 --- libweston/backend-drm/vaapi-recorder.h | 38 - libweston/backend-fbdev/fbdev.c | 999 -- libweston/backend-fbdev/meson.build | 30 - libweston/backend-headless/headless.c | 521 -- libweston/backend-headless/meson.build | 21 - libweston/backend-rdp/meson.build | 60 - libweston/backend-rdp/rdp.c | 1515 ---- libweston/backend-wayland/meson.build | 44 - libweston/backend-wayland/wayland.c | 2915 ------ libweston/backend-x11/meson.build | 58 - libweston/backend-x11/x11.c | 1957 ---- libweston/backend.h | 248 - libweston/bindings.c | 590 -- libweston/clipboard.c | 308 - libweston/compositor.c | 8005 ----------------- libweston/content-protection.c | 352 - libweston/data-device.c | 1370 --- libweston/dbus.c | 408 - libweston/dbus.h | 110 - libweston/git-version.h | 1 - libweston/git-version.h.meson | 1 - libweston/input.c | 5063 ----------- libweston/launcher-direct.c | 351 - libweston/launcher-impl.h | 49 - libweston/launcher-logind.c | 856 -- libweston/launcher-util.c | 120 - libweston/launcher-util.h | 55 - libweston/launcher-weston-launch.c | 345 - libweston/libinput-device.c | 761 -- libweston/libinput-device.h | 82 - libweston/libinput-seat.c | 490 - libweston/libinput-seat.h | 73 - libweston/libweston-internal.h | 334 - libweston/linux-dmabuf.c | 591 -- libweston/linux-dmabuf.h | 103 - libweston/linux-explicit-synchronization.c | 287 - libweston/linux-explicit-synchronization.h | 39 - libweston/linux-sync-file-uapi.h | 30 - libweston/linux-sync-file.c | 83 - libweston/linux-sync-file.h | 38 - libweston/log.c | 157 - libweston/meson.build | 242 - libweston/noop-renderer.c | 123 - libweston/pixel-formats.c | 516 -- libweston/pixel-formats.h | 262 - libweston/pixman-renderer-protected.h | 67 - libweston/pixman-renderer.c | 998 -- libweston/pixman-renderer.h | 58 - libweston/plugin-registry.c | 156 - libweston/renderer-gl/egl-glue.c | 693 -- libweston/renderer-gl/gl-renderer-internal.h | 154 - libweston/renderer-gl/gl-renderer.c | 3820 -------- libweston/renderer-gl/gl-renderer.h | 228 - libweston/renderer-gl/meson.build | 39 - libweston/screenshooter.c | 494 - libweston/spring-tool.c | 79 - libweston/tde-render-part.c | 389 - libweston/tde-render-part.h | 49 - libweston/timeline.c | 461 - libweston/timeline.h | 103 - libweston/touch-calibration.c | 716 -- libweston/version.h | 50 - libweston/vertex-clipping.c | 330 - libweston/vertex-clipping.h | 66 - libweston/weston-direct-display.c | 92 - libweston/weston-launch.c | 916 -- libweston/weston-launch.h | 50 - libweston/weston-log-file.c | 98 - libweston/weston-log-flight-rec.c | 296 - libweston/weston-log-internal.h | 117 - libweston/weston-log-wayland.c | 290 - libweston/weston-log.c | 1011 --- libweston/zoom.c | 184 - man/meson.build | 55 - man/weston-bindings.man | 136 - man/weston-debug.man | 51 - man/weston-drm.man | 240 - man/weston-rdp.man | 92 - man/weston.ini.man | 663 -- man/weston.man | 377 - meson.build | 176 - meson_options.txt | 224 - pipewire/meson.build | 31 - pipewire/pipewire-plugin.c | 855 -- pipewire/pipewire-plugin.h | 62 - protocol/drm-auth.xml | 34 - protocol/ivi-application.xml | 100 - protocol/ivi-hmi-controller.xml | 98 - protocol/meson.build | 77 - protocol/text-cursor-position.xml | 11 - protocol/weston-content-protection.xml | 251 - protocol/weston-debug.xml | 139 - protocol/weston-desktop-shell.xml | 135 - protocol/weston-direct-display.xml | 82 - protocol/weston-screenshooter.xml | 12 - protocol/weston-test.xml | 144 - protocol/weston-touch-calibration.xml | 342 - remoting/README | 28 - remoting/meson.build | 33 - remoting/remoting-plugin.c | 967 -- remoting/remoting-plugin.h | 78 - shared/cairo-util.c | 635 -- shared/cairo-util.h | 233 - shared/config-parser.c | 525 -- shared/fd-util.h | 56 - shared/file-util.c | 146 - shared/file-util.h | 46 - shared/frame.c | 1021 --- shared/helpers.h | 141 - shared/image-loader.c | 440 - shared/image-loader.h | 34 - shared/matrix.c | 276 - shared/meson.build | 84 - shared/option-parser.c | 204 - shared/os-compatibility.c | 407 - shared/os-compatibility.h | 74 - shared/platform.h | 167 - shared/string-helpers.h | 71 - shared/timespec-util.h | 259 - shared/weston-egl-ext.h | 221 - shared/xalloc.c | 55 - shared/xalloc.h | 51 - test-qc.c | 2 - tests/bad-buffer-test.c | 189 - tests/buffer-transforms-test.c | 137 - tests/config-parser-test.c | 632 -- tests/devices-test.c | 356 - tests/drm-smoke-test.c | 72 - tests/event-test.c | 179 - tests/input-timestamps-helper.c | 177 - tests/input-timestamps-helper.h | 46 - tests/internal-screenshot-test.c | 178 - tests/internal-screenshot.ini | 3 - tests/ivi-layout-internal-test.c | 1022 --- tests/ivi-layout-test-client.c | 509 -- tests/ivi-layout-test-plugin.c | 1018 --- tests/ivi-shell-app-test.c | 90 - tests/ivi-test.h | 42 - tests/keyboard-test.c | 169 - tests/linux-explicit-synchronization-test.c | 498 - tests/matrix-test.c | 424 - tests/meson.build | 346 - tests/output-transforms-test.c | 137 - tests/plugin-registry-test.c | 102 - tests/pointer-test.c | 467 - tests/presentation-test.c | 250 - tests/reference/basic-test-card.png | Bin 8347 -> 0 bytes .../reference/internal-screenshot-bad-00.png | Bin 4204 -> 0 bytes .../reference/internal-screenshot-good-00.png | Bin 733 -> 0 bytes .../output_1-180_buffer_1-NORMAL-00.png | Bin 4642 -> 0 bytes .../reference/output_1-180_buffer_2-90-00.png | Bin 4869 -> 0 bytes .../output_1-270_buffer_1-NORMAL-00.png | Bin 4783 -> 0 bytes .../reference/output_1-270_buffer_2-90-00.png | Bin 4638 -> 0 bytes .../output_1-90_buffer_1-NORMAL-00.png | Bin 4880 -> 0 bytes .../reference/output_1-90_buffer_2-90-00.png | Bin 4655 -> 0 bytes ...utput_1-FLIPPED_180_buffer_1-NORMAL-00.png | Bin 4669 -> 0 bytes .../output_1-FLIPPED_180_buffer_2-90-00.png | Bin 4884 -> 0 bytes ...utput_1-FLIPPED_270_buffer_1-NORMAL-00.png | Bin 4789 -> 0 bytes .../output_1-FLIPPED_270_buffer_2-90-00.png | Bin 4570 -> 0 bytes ...output_1-FLIPPED_90_buffer_1-NORMAL-00.png | Bin 4914 -> 0 bytes .../output_1-FLIPPED_90_buffer_2-90-00.png | Bin 4667 -> 0 bytes .../output_1-FLIPPED_buffer_1-NORMAL-00.png | Bin 4570 -> 0 bytes .../output_1-FLIPPED_buffer_2-90-00.png | Bin 4892 -> 0 bytes .../output_1-NORMAL_buffer_1-180-00.png | Bin 4674 -> 0 bytes .../output_1-NORMAL_buffer_1-270-00.png | Bin 4877 -> 0 bytes .../output_1-NORMAL_buffer_1-90-00.png | Bin 4854 -> 0 bytes .../output_1-NORMAL_buffer_1-FLIPPED-00.png | Bin 4598 -> 0 bytes ...utput_1-NORMAL_buffer_1-FLIPPED_180-00.png | Bin 4667 -> 0 bytes ...utput_1-NORMAL_buffer_1-FLIPPED_270-00.png | Bin 4883 -> 0 bytes ...output_1-NORMAL_buffer_1-FLIPPED_90-00.png | Bin 4914 -> 0 bytes .../output_1-NORMAL_buffer_1-NORMAL-00.png | Bin 4655 -> 0 bytes .../output_1-NORMAL_buffer_2-180-00.png | Bin 4674 -> 0 bytes .../output_1-NORMAL_buffer_2-90-00.png | Bin 4854 -> 0 bytes .../output_1-NORMAL_buffer_2-FLIPPED-00.png | Bin 4598 -> 0 bytes .../output_1-NORMAL_buffer_2-NORMAL-00.png | Bin 4655 -> 0 bytes ...output_1-NORMAL_buffer_3-FLIPPED_90-00.png | Bin 4914 -> 0 bytes .../output_1-NORMAL_buffer_3-NORMAL-00.png | Bin 4655 -> 0 bytes .../output_2-180_buffer_1-NORMAL-00.png | Bin 14395 -> 0 bytes .../reference/output_2-180_buffer_2-90-00.png | Bin 5270 -> 0 bytes .../reference/output_2-90_buffer_1-180-00.png | Bin 14282 -> 0 bytes .../reference/output_2-90_buffer_1-270-00.png | Bin 14385 -> 0 bytes .../reference/output_2-90_buffer_1-90-00.png | Bin 14482 -> 0 bytes .../output_2-90_buffer_1-FLIPPED-00.png | Bin 14336 -> 0 bytes .../output_2-90_buffer_1-FLIPPED_180-00.png | Bin 14762 -> 0 bytes .../output_2-90_buffer_1-FLIPPED_270-00.png | Bin 14394 -> 0 bytes .../output_2-90_buffer_1-FLIPPED_90-00.png | Bin 14446 -> 0 bytes .../output_2-90_buffer_1-NORMAL-00.png | Bin 14832 -> 0 bytes .../reference/output_2-90_buffer_2-180-00.png | Bin 5217 -> 0 bytes .../reference/output_2-90_buffer_2-90-00.png | Bin 5179 -> 0 bytes .../output_2-90_buffer_2-FLIPPED-00.png | Bin 5258 -> 0 bytes .../output_2-90_buffer_2-NORMAL-00.png | Bin 5288 -> 0 bytes .../output_2-90_buffer_3-FLIPPED_90-00.png | Bin 5167 -> 0 bytes .../output_2-90_buffer_3-NORMAL-00.png | Bin 5288 -> 0 bytes .../output_2-FLIPPED_buffer_1-NORMAL-00.png | Bin 14407 -> 0 bytes .../output_2-FLIPPED_buffer_2-90-00.png | Bin 5360 -> 0 bytes .../output_2-NORMAL_buffer_1-NORMAL-00.png | Bin 14482 -> 0 bytes .../output_2-NORMAL_buffer_2-90-00.png | Bin 5313 -> 0 bytes ...utput_3-FLIPPED_270_buffer_1-NORMAL-00.png | Bin 12107 -> 0 bytes .../output_3-FLIPPED_270_buffer_2-90-00.png | Bin 12541 -> 0 bytes .../output_3-NORMAL_buffer_1-NORMAL-00.png | Bin 15856 -> 0 bytes .../output_3-NORMAL_buffer_2-90-00.png | Bin 11702 -> 0 bytes tests/reference/subsurface_z_order-00.png | Bin 122 -> 0 bytes tests/reference/subsurface_z_order-01.png | Bin 154 -> 0 bytes tests/reference/subsurface_z_order-02.png | Bin 166 -> 0 bytes tests/reference/subsurface_z_order-03.png | Bin 233 -> 0 bytes tests/reference/subsurface_z_order-04.png | Bin 237 -> 0 bytes tests/reference/viewport_upscale_solid-00.png | Bin 829 -> 0 bytes tests/roles-test.c | 155 - tests/setbacklight.c | 191 - tests/string-test.c | 88 - tests/subsurface-shot-test.c | 200 - tests/subsurface-test.c | 768 -- tests/surface-global-test.c | 91 - tests/surface-screenshot-test.c | 224 - tests/surface-test.c | 71 - tests/text-test.c | 234 - tests/timespec-test.c | 308 - tests/touch-test.c | 135 - tests/vertex-clip-test.c | 223 - tests/viewporter-shot-test.c | 89 - tests/viewporter-test.c | 519 -- tests/weston-test-client-helper.c | 1899 ---- tests/weston-test-client-helper.h | 287 - tests/weston-test-desktop-shell.c | 235 - tests/weston-test-fixture-compositor.c | 427 - tests/weston-test-fixture-compositor.h | 134 - tests/weston-test-runner.c | 620 -- tests/weston-test-runner.h | 259 - tests/weston-test.c | 849 -- tests/weston-testsuite-data.h | 88 - tests/xwayland-test.c | 119 - tools/zunitc/doc/zunitc.dox | 220 - tools/zunitc/inc/zunitc/zunitc.h | 743 -- tools/zunitc/inc/zunitc/zunitc_impl.h | 105 - tools/zunitc/src/main.c | 58 - tools/zunitc/src/zuc_base_logger.c | 401 - tools/zunitc/src/zuc_base_logger.h | 38 - tools/zunitc/src/zuc_collector.c | 427 - tools/zunitc/src/zuc_collector.h | 58 - tools/zunitc/src/zuc_context.h | 58 - tools/zunitc/src/zuc_event.h | 86 - tools/zunitc/src/zuc_event_listener.h | 174 - tools/zunitc/src/zuc_junit_reporter.c | 479 - tools/zunitc/src/zuc_junit_reporter.h | 38 - tools/zunitc/src/zuc_types.h | 80 - tools/zunitc/src/zunitc_impl.c | 1578 ---- tools/zunitc/test/fixtures_test.c | 106 - tools/zunitc/test/zunitc_test.c | 464 - xwayland/dnd.c | 255 - xwayland/hash.c | 309 - xwayland/hash.h | 51 - xwayland/launcher.c | 409 - xwayland/meson.build | 44 - xwayland/selection.c | 762 -- xwayland/window-manager.c | 2986 ------ xwayland/xwayland-internal-interface.h | 64 - xwayland/xwayland.h | 189 - 447 files changed, 166398 deletions(-) delete mode 100644 clients/calibrator.c delete mode 100644 clients/clickdot.c delete mode 100644 clients/cliptest.c delete mode 100644 clients/confine.c delete mode 100644 clients/content_protection.c delete mode 100644 clients/desktop-shell.c delete mode 100644 clients/dnd.c delete mode 100644 clients/editor.c delete mode 100644 clients/eventdemo.c delete mode 100644 clients/flower.c delete mode 100644 clients/fullscreen.c delete mode 100644 clients/gears.c delete mode 100644 clients/image.c delete mode 100644 clients/ivi-shell-user-interface.c delete mode 100644 clients/keyboard.c delete mode 100644 clients/meson.build delete mode 100644 clients/multi-resource.c delete mode 100644 clients/nested-client.c delete mode 100644 clients/nested.c delete mode 100644 clients/presentation-shm.c delete mode 100644 clients/resizor.c delete mode 100644 clients/scaler.c delete mode 100644 clients/screenshot.c delete mode 100644 clients/simple-damage.c delete mode 100644 clients/simple-dmabuf-egl.c delete mode 100644 clients/simple-dmabuf-v4l.c delete mode 100644 clients/simple-egl.c delete mode 100644 clients/simple-im.c delete mode 100644 clients/simple-shm.c delete mode 100644 clients/simple-touch.c delete mode 100644 clients/smoke.c delete mode 100644 clients/stacking.c delete mode 100644 clients/subsurfaces.c delete mode 100644 clients/terminal.c delete mode 100644 clients/touch-calibrator.c delete mode 100644 clients/transformed.c delete mode 100644 clients/weston-debug.c delete mode 100644 clients/weston-info.c delete mode 100644 clients/window.c delete mode 100644 clients/window.h delete mode 100644 compositor/cms-colord.c delete mode 100644 compositor/cms-helper.c delete mode 100644 compositor/cms-helper.h delete mode 100644 compositor/cms-static.c delete mode 100644 compositor/executable.c delete mode 100755 compositor/main.c delete mode 100644 compositor/meson.build delete mode 100644 compositor/screen-share.c delete mode 100644 compositor/systemd-notify.c delete mode 100644 compositor/testsuite-util.c delete mode 100644 compositor/text-backend.c delete mode 100755 compositor/weston-screenshooter.c delete mode 100644 compositor/weston.desktop delete mode 100644 compositor/weston.h delete mode 100644 compositor/xwayland.c delete mode 100644 data/COPYING delete mode 100644 data/background.png delete mode 100644 data/border.png delete mode 100644 data/fullscreen.png delete mode 100644 data/home.png delete mode 100644 data/icon_editor.png delete mode 100644 data/icon_flower.png delete mode 100644 data/icon_ivi_clickdot.png delete mode 100644 data/icon_ivi_flower.png delete mode 100644 data/icon_ivi_simple-egl.png delete mode 100644 data/icon_ivi_simple-shm.png delete mode 100644 data/icon_ivi_smoke.png delete mode 100644 data/icon_terminal.png delete mode 100644 data/icon_window.png delete mode 100644 data/icons.svg delete mode 100644 data/meson.build delete mode 100644 data/panel.png delete mode 100644 data/pattern.png delete mode 100644 data/random.png delete mode 100644 data/sidebyside.png delete mode 100644 data/sign_close.png delete mode 100644 data/sign_maximize.png delete mode 100644 data/sign_minimize.png delete mode 100644 data/terminal.png delete mode 100644 data/tiling.png delete mode 100644 data/wayland.png delete mode 100644 data/wayland.svg delete mode 100644 desktop-shell/exposay.c delete mode 100644 desktop-shell/input-panel.c delete mode 100644 desktop-shell/meson.build delete mode 100755 desktop-shell/shell.c delete mode 100644 desktop-shell/shell.h delete mode 100644 doc/doxygen/devtools.dox delete mode 100644 doc/doxygen/tooldev.doxygen.in delete mode 100644 doc/doxygen/tools.dox delete mode 100644 doc/doxygen/tools.doxygen.in delete mode 100644 doc/doxygen/tools_arch_new.gv delete mode 100644 doc/doxygen/tools_arch_old.gv delete mode 100755 doc/scripts/calibration-helper.bash delete mode 100755 doc/scripts/gdb/flight_rec.py delete mode 100755 doc/scripts/remoting-client-receive.bash delete mode 100644 doc/sphinx/conf.py.in delete mode 100644 doc/sphinx/doxygen.ini.in delete mode 100644 doc/sphinx/index.rst delete mode 100644 doc/sphinx/meson.build delete mode 100755 doc/sphinx/run_doxygen_sphinx.sh.in delete mode 100644 doc/sphinx/toc/kiosk-shell.rst delete mode 100644 doc/sphinx/toc/libweston.rst delete mode 100644 doc/sphinx/toc/libweston/compositor.rst delete mode 100644 doc/sphinx/toc/libweston/head.rst delete mode 100644 doc/sphinx/toc/libweston/images/create_output.msc delete mode 100644 doc/sphinx/toc/libweston/images/create_output.png delete mode 100644 doc/sphinx/toc/libweston/images/destroy-output.msc delete mode 100644 doc/sphinx/toc/libweston/images/destroy-output.png delete mode 100644 doc/sphinx/toc/libweston/images/head-destroyed.msc delete mode 100644 doc/sphinx/toc/libweston/images/head-destroyed.png delete mode 100644 doc/sphinx/toc/libweston/images/initial-heads.msc delete mode 100644 doc/sphinx/toc/libweston/images/initial-heads.png delete mode 100644 doc/sphinx/toc/libweston/images/meson.build delete mode 100644 doc/sphinx/toc/libweston/images/react-to-heads-changed.msc delete mode 100644 doc/sphinx/toc/libweston/images/react-to-heads-changed.png delete mode 100644 doc/sphinx/toc/libweston/log.rst delete mode 100644 doc/sphinx/toc/libweston/meson.build delete mode 100644 doc/sphinx/toc/libweston/output-management.rst delete mode 100644 doc/sphinx/toc/libweston/output.rst delete mode 100644 doc/sphinx/toc/meson.build delete mode 100644 doc/sphinx/toc/test-suite-api.rst delete mode 100644 doc/sphinx/toc/test-suite.rst delete mode 100644 doc/wayland-screenshot.jpg delete mode 100644 fullscreen-shell/fullscreen-shell.c delete mode 100644 fullscreen-shell/meson.build delete mode 100644 include/config.h delete mode 100644 include/libweston-desktop/libweston-desktop.h delete mode 100644 include/libweston/backend-drm.h delete mode 100644 include/libweston/backend-fbdev.h delete mode 100644 include/libweston/backend-headless.h delete mode 100644 include/libweston/backend-rdp.h delete mode 100644 include/libweston/backend-wayland.h delete mode 100644 include/libweston/backend-x11.h delete mode 100644 include/libweston/config-parser.h delete mode 100755 include/libweston/libweston.h delete mode 100644 include/libweston/matrix.h delete mode 100644 include/libweston/meson.build delete mode 100644 include/libweston/plugin-registry.h delete mode 100644 include/libweston/version.h.in delete mode 100755 include/libweston/weston-log.h delete mode 100644 include/libweston/windowed-output-api.h delete mode 100644 include/libweston/xwayland-api.h delete mode 100644 include/libweston/zalloc.h delete mode 100644 include/meson.build delete mode 100644 ivi-shell/README delete mode 100644 ivi-shell/hmi-controller.c delete mode 100644 ivi-shell/ivi-layout-export.h delete mode 100644 ivi-shell/ivi-layout-private.h delete mode 100644 ivi-shell/ivi-layout-shell.h delete mode 100644 ivi-shell/ivi-layout-transition.c delete mode 100644 ivi-shell/ivi-layout.c delete mode 100644 ivi-shell/ivi-shell.c delete mode 100644 ivi-shell/ivi-shell.h delete mode 100644 ivi-shell/meson.build delete mode 100644 ivi-shell/weston.ini.in delete mode 100644 kiosk-shell/kiosk-shell-grab.c delete mode 100644 kiosk-shell/kiosk-shell-grab.h delete mode 100644 kiosk-shell/kiosk-shell.c delete mode 100644 kiosk-shell/kiosk-shell.h delete mode 100644 kiosk-shell/meson.build delete mode 100644 kiosk-shell/util.c delete mode 100644 kiosk-shell/util.h delete mode 100644 libweston-desktop/client.c delete mode 100644 libweston-desktop/internal.h delete mode 100755 libweston-desktop/libweston-desktop.c delete mode 100644 libweston-desktop/meson.build delete mode 100644 libweston-desktop/seat.c delete mode 100644 libweston-desktop/surface.c delete mode 100644 libweston-desktop/wl-shell.c delete mode 100644 libweston-desktop/xdg-shell-v6.c delete mode 100644 libweston-desktop/xdg-shell.c delete mode 100644 libweston-desktop/xwayland.c delete mode 100644 libweston/animation.c delete mode 100755 libweston/backend-drm/auth/wayland_drm_auth.h delete mode 100755 libweston/backend-drm/auth/wayland_drm_auth_server.c delete mode 100755 libweston/backend-drm/auth/wayland_drm_auth_server.h delete mode 100644 libweston/backend-drm/drm-gbm.c delete mode 100755 libweston/backend-drm/drm-internal.h delete mode 100644 libweston/backend-drm/drm-virtual.c delete mode 100755 libweston/backend-drm/drm.c delete mode 100644 libweston/backend-drm/fb.c delete mode 100755 libweston/backend-drm/kms.c delete mode 100644 libweston/backend-drm/libbacklight.c delete mode 100644 libweston/backend-drm/libbacklight.h delete mode 100644 libweston/backend-drm/meson.build delete mode 100755 libweston/backend-drm/modes.c delete mode 100644 libweston/backend-drm/state-helpers.c delete mode 100644 libweston/backend-drm/state-propose.c delete mode 100644 libweston/backend-drm/vaapi-recorder.c delete mode 100644 libweston/backend-drm/vaapi-recorder.h delete mode 100755 libweston/backend-fbdev/fbdev.c delete mode 100644 libweston/backend-fbdev/meson.build delete mode 100644 libweston/backend-headless/headless.c delete mode 100644 libweston/backend-headless/meson.build delete mode 100644 libweston/backend-rdp/meson.build delete mode 100644 libweston/backend-rdp/rdp.c delete mode 100644 libweston/backend-wayland/meson.build delete mode 100644 libweston/backend-wayland/wayland.c delete mode 100644 libweston/backend-x11/meson.build delete mode 100644 libweston/backend-x11/x11.c delete mode 100644 libweston/backend.h delete mode 100755 libweston/bindings.c delete mode 100644 libweston/clipboard.c delete mode 100755 libweston/compositor.c delete mode 100755 libweston/content-protection.c delete mode 100644 libweston/data-device.c delete mode 100644 libweston/dbus.c delete mode 100644 libweston/dbus.h delete mode 100644 libweston/git-version.h delete mode 100644 libweston/git-version.h.meson delete mode 100644 libweston/input.c delete mode 100755 libweston/launcher-direct.c delete mode 100644 libweston/launcher-impl.h delete mode 100644 libweston/launcher-logind.c delete mode 100644 libweston/launcher-util.c delete mode 100644 libweston/launcher-util.h delete mode 100644 libweston/launcher-weston-launch.c delete mode 100644 libweston/libinput-device.c delete mode 100644 libweston/libinput-device.h delete mode 100644 libweston/libinput-seat.c delete mode 100644 libweston/libinput-seat.h delete mode 100755 libweston/libweston-internal.h delete mode 100644 libweston/linux-dmabuf.c delete mode 100644 libweston/linux-dmabuf.h delete mode 100644 libweston/linux-explicit-synchronization.c delete mode 100644 libweston/linux-explicit-synchronization.h delete mode 100644 libweston/linux-sync-file-uapi.h delete mode 100644 libweston/linux-sync-file.c delete mode 100644 libweston/linux-sync-file.h delete mode 100644 libweston/log.c delete mode 100644 libweston/meson.build delete mode 100644 libweston/noop-renderer.c delete mode 100755 libweston/pixel-formats.c delete mode 100644 libweston/pixel-formats.h delete mode 100644 libweston/pixman-renderer-protected.h delete mode 100755 libweston/pixman-renderer.c delete mode 100644 libweston/pixman-renderer.h delete mode 100644 libweston/plugin-registry.c delete mode 100644 libweston/renderer-gl/egl-glue.c delete mode 100644 libweston/renderer-gl/gl-renderer-internal.h delete mode 100755 libweston/renderer-gl/gl-renderer.c delete mode 100755 libweston/renderer-gl/gl-renderer.h delete mode 100644 libweston/renderer-gl/meson.build delete mode 100644 libweston/screenshooter.c delete mode 100644 libweston/spring-tool.c delete mode 100644 libweston/tde-render-part.c delete mode 100644 libweston/tde-render-part.h delete mode 100644 libweston/timeline.c delete mode 100644 libweston/timeline.h delete mode 100644 libweston/touch-calibration.c delete mode 100644 libweston/version.h delete mode 100644 libweston/vertex-clipping.c delete mode 100644 libweston/vertex-clipping.h delete mode 100644 libweston/weston-direct-display.c delete mode 100644 libweston/weston-launch.c delete mode 100644 libweston/weston-launch.h delete mode 100644 libweston/weston-log-file.c delete mode 100644 libweston/weston-log-flight-rec.c delete mode 100644 libweston/weston-log-internal.h delete mode 100644 libweston/weston-log-wayland.c delete mode 100644 libweston/weston-log.c delete mode 100644 libweston/zoom.c delete mode 100644 man/meson.build delete mode 100644 man/weston-bindings.man delete mode 100644 man/weston-debug.man delete mode 100644 man/weston-drm.man delete mode 100644 man/weston-rdp.man delete mode 100644 man/weston.ini.man delete mode 100644 man/weston.man delete mode 100644 meson.build delete mode 100644 meson_options.txt delete mode 100644 pipewire/meson.build delete mode 100644 pipewire/pipewire-plugin.c delete mode 100644 pipewire/pipewire-plugin.h delete mode 100755 protocol/drm-auth.xml delete mode 100644 protocol/ivi-application.xml delete mode 100644 protocol/ivi-hmi-controller.xml delete mode 100644 protocol/meson.build delete mode 100644 protocol/text-cursor-position.xml delete mode 100644 protocol/weston-content-protection.xml delete mode 100644 protocol/weston-debug.xml delete mode 100644 protocol/weston-desktop-shell.xml delete mode 100644 protocol/weston-direct-display.xml delete mode 100644 protocol/weston-screenshooter.xml delete mode 100644 protocol/weston-test.xml delete mode 100644 protocol/weston-touch-calibration.xml delete mode 100644 remoting/README delete mode 100644 remoting/meson.build delete mode 100644 remoting/remoting-plugin.c delete mode 100644 remoting/remoting-plugin.h delete mode 100644 shared/cairo-util.c delete mode 100644 shared/cairo-util.h delete mode 100644 shared/config-parser.c delete mode 100644 shared/fd-util.h delete mode 100644 shared/file-util.c delete mode 100644 shared/file-util.h delete mode 100644 shared/frame.c delete mode 100644 shared/helpers.h delete mode 100644 shared/image-loader.c delete mode 100644 shared/image-loader.h delete mode 100644 shared/matrix.c delete mode 100644 shared/meson.build delete mode 100644 shared/option-parser.c delete mode 100644 shared/os-compatibility.c delete mode 100644 shared/os-compatibility.h delete mode 100644 shared/platform.h delete mode 100644 shared/string-helpers.h delete mode 100644 shared/timespec-util.h delete mode 100644 shared/weston-egl-ext.h delete mode 100644 shared/xalloc.c delete mode 100644 shared/xalloc.h delete mode 100644 test-qc.c delete mode 100644 tests/bad-buffer-test.c delete mode 100644 tests/buffer-transforms-test.c delete mode 100644 tests/config-parser-test.c delete mode 100644 tests/devices-test.c delete mode 100644 tests/drm-smoke-test.c delete mode 100644 tests/event-test.c delete mode 100644 tests/input-timestamps-helper.c delete mode 100644 tests/input-timestamps-helper.h delete mode 100644 tests/internal-screenshot-test.c delete mode 100644 tests/internal-screenshot.ini delete mode 100644 tests/ivi-layout-internal-test.c delete mode 100644 tests/ivi-layout-test-client.c delete mode 100644 tests/ivi-layout-test-plugin.c delete mode 100644 tests/ivi-shell-app-test.c delete mode 100644 tests/ivi-test.h delete mode 100644 tests/keyboard-test.c delete mode 100644 tests/linux-explicit-synchronization-test.c delete mode 100644 tests/matrix-test.c delete mode 100644 tests/meson.build delete mode 100644 tests/output-transforms-test.c delete mode 100644 tests/plugin-registry-test.c delete mode 100644 tests/pointer-test.c delete mode 100644 tests/presentation-test.c delete mode 100644 tests/reference/basic-test-card.png delete mode 100644 tests/reference/internal-screenshot-bad-00.png delete mode 100644 tests/reference/internal-screenshot-good-00.png delete mode 100644 tests/reference/output_1-180_buffer_1-NORMAL-00.png delete mode 100644 tests/reference/output_1-180_buffer_2-90-00.png delete mode 100644 tests/reference/output_1-270_buffer_1-NORMAL-00.png delete mode 100644 tests/reference/output_1-270_buffer_2-90-00.png delete mode 100644 tests/reference/output_1-90_buffer_1-NORMAL-00.png delete mode 100644 tests/reference/output_1-90_buffer_2-90-00.png delete mode 100644 tests/reference/output_1-FLIPPED_180_buffer_1-NORMAL-00.png delete mode 100644 tests/reference/output_1-FLIPPED_180_buffer_2-90-00.png delete mode 100644 tests/reference/output_1-FLIPPED_270_buffer_1-NORMAL-00.png delete mode 100644 tests/reference/output_1-FLIPPED_270_buffer_2-90-00.png delete mode 100644 tests/reference/output_1-FLIPPED_90_buffer_1-NORMAL-00.png delete mode 100644 tests/reference/output_1-FLIPPED_90_buffer_2-90-00.png delete mode 100644 tests/reference/output_1-FLIPPED_buffer_1-NORMAL-00.png delete mode 100644 tests/reference/output_1-FLIPPED_buffer_2-90-00.png delete mode 100644 tests/reference/output_1-NORMAL_buffer_1-180-00.png delete mode 100644 tests/reference/output_1-NORMAL_buffer_1-270-00.png delete mode 100644 tests/reference/output_1-NORMAL_buffer_1-90-00.png delete mode 100644 tests/reference/output_1-NORMAL_buffer_1-FLIPPED-00.png delete mode 100644 tests/reference/output_1-NORMAL_buffer_1-FLIPPED_180-00.png delete mode 100644 tests/reference/output_1-NORMAL_buffer_1-FLIPPED_270-00.png delete mode 100644 tests/reference/output_1-NORMAL_buffer_1-FLIPPED_90-00.png delete mode 100644 tests/reference/output_1-NORMAL_buffer_1-NORMAL-00.png delete mode 100644 tests/reference/output_1-NORMAL_buffer_2-180-00.png delete mode 100644 tests/reference/output_1-NORMAL_buffer_2-90-00.png delete mode 100644 tests/reference/output_1-NORMAL_buffer_2-FLIPPED-00.png delete mode 100644 tests/reference/output_1-NORMAL_buffer_2-NORMAL-00.png delete mode 100644 tests/reference/output_1-NORMAL_buffer_3-FLIPPED_90-00.png delete mode 100644 tests/reference/output_1-NORMAL_buffer_3-NORMAL-00.png delete mode 100644 tests/reference/output_2-180_buffer_1-NORMAL-00.png delete mode 100644 tests/reference/output_2-180_buffer_2-90-00.png delete mode 100644 tests/reference/output_2-90_buffer_1-180-00.png delete mode 100644 tests/reference/output_2-90_buffer_1-270-00.png delete mode 100644 tests/reference/output_2-90_buffer_1-90-00.png delete mode 100644 tests/reference/output_2-90_buffer_1-FLIPPED-00.png delete mode 100644 tests/reference/output_2-90_buffer_1-FLIPPED_180-00.png delete mode 100644 tests/reference/output_2-90_buffer_1-FLIPPED_270-00.png delete mode 100644 tests/reference/output_2-90_buffer_1-FLIPPED_90-00.png delete mode 100644 tests/reference/output_2-90_buffer_1-NORMAL-00.png delete mode 100644 tests/reference/output_2-90_buffer_2-180-00.png delete mode 100644 tests/reference/output_2-90_buffer_2-90-00.png delete mode 100644 tests/reference/output_2-90_buffer_2-FLIPPED-00.png delete mode 100644 tests/reference/output_2-90_buffer_2-NORMAL-00.png delete mode 100644 tests/reference/output_2-90_buffer_3-FLIPPED_90-00.png delete mode 100644 tests/reference/output_2-90_buffer_3-NORMAL-00.png delete mode 100644 tests/reference/output_2-FLIPPED_buffer_1-NORMAL-00.png delete mode 100644 tests/reference/output_2-FLIPPED_buffer_2-90-00.png delete mode 100644 tests/reference/output_2-NORMAL_buffer_1-NORMAL-00.png delete mode 100644 tests/reference/output_2-NORMAL_buffer_2-90-00.png delete mode 100644 tests/reference/output_3-FLIPPED_270_buffer_1-NORMAL-00.png delete mode 100644 tests/reference/output_3-FLIPPED_270_buffer_2-90-00.png delete mode 100644 tests/reference/output_3-NORMAL_buffer_1-NORMAL-00.png delete mode 100644 tests/reference/output_3-NORMAL_buffer_2-90-00.png delete mode 100644 tests/reference/subsurface_z_order-00.png delete mode 100644 tests/reference/subsurface_z_order-01.png delete mode 100644 tests/reference/subsurface_z_order-02.png delete mode 100644 tests/reference/subsurface_z_order-03.png delete mode 100644 tests/reference/subsurface_z_order-04.png delete mode 100644 tests/reference/viewport_upscale_solid-00.png delete mode 100644 tests/roles-test.c delete mode 100644 tests/setbacklight.c delete mode 100644 tests/string-test.c delete mode 100644 tests/subsurface-shot-test.c delete mode 100644 tests/subsurface-test.c delete mode 100644 tests/surface-global-test.c delete mode 100644 tests/surface-screenshot-test.c delete mode 100644 tests/surface-test.c delete mode 100644 tests/text-test.c delete mode 100644 tests/timespec-test.c delete mode 100644 tests/touch-test.c delete mode 100644 tests/vertex-clip-test.c delete mode 100644 tests/viewporter-shot-test.c delete mode 100644 tests/viewporter-test.c delete mode 100644 tests/weston-test-client-helper.c delete mode 100644 tests/weston-test-client-helper.h delete mode 100644 tests/weston-test-desktop-shell.c delete mode 100644 tests/weston-test-fixture-compositor.c delete mode 100644 tests/weston-test-fixture-compositor.h delete mode 100644 tests/weston-test-runner.c delete mode 100644 tests/weston-test-runner.h delete mode 100644 tests/weston-test.c delete mode 100644 tests/weston-testsuite-data.h delete mode 100644 tests/xwayland-test.c delete mode 100644 tools/zunitc/doc/zunitc.dox delete mode 100644 tools/zunitc/inc/zunitc/zunitc.h delete mode 100644 tools/zunitc/inc/zunitc/zunitc_impl.h delete mode 100644 tools/zunitc/src/main.c delete mode 100644 tools/zunitc/src/zuc_base_logger.c delete mode 100644 tools/zunitc/src/zuc_base_logger.h delete mode 100644 tools/zunitc/src/zuc_collector.c delete mode 100644 tools/zunitc/src/zuc_collector.h delete mode 100644 tools/zunitc/src/zuc_context.h delete mode 100644 tools/zunitc/src/zuc_event.h delete mode 100644 tools/zunitc/src/zuc_event_listener.h delete mode 100644 tools/zunitc/src/zuc_junit_reporter.c delete mode 100644 tools/zunitc/src/zuc_junit_reporter.h delete mode 100644 tools/zunitc/src/zuc_types.h delete mode 100644 tools/zunitc/src/zunitc_impl.c delete mode 100644 tools/zunitc/test/fixtures_test.c delete mode 100644 tools/zunitc/test/zunitc_test.c delete mode 100644 xwayland/dnd.c delete mode 100644 xwayland/hash.c delete mode 100644 xwayland/hash.h delete mode 100644 xwayland/launcher.c delete mode 100644 xwayland/meson.build delete mode 100644 xwayland/selection.c delete mode 100644 xwayland/window-manager.c delete mode 100644 xwayland/xwayland-internal-interface.h delete mode 100644 xwayland/xwayland.h diff --git a/clients/calibrator.c b/clients/calibrator.c deleted file mode 100644 index 21ca876..0000000 --- a/clients/calibrator.c +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "window.h" -#include "shared/helpers.h" -#include - -/* 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, bool enable_button) -{ - 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; - - if (enable_button) - 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); -} - -static void -help(const char *name) -{ - fprintf(stderr, "Usage: %s [args...]\n", name); - fprintf(stderr, " -m, --enable-mouse Enable mouse for testing the touchscreen\n"); - fprintf(stderr, " -h, --help Display this help message\n"); -} - -int -main(int argc, char *argv[]) -{ - struct display *display; - struct calibrator *calibrator; - int c; - bool enable_mouse = 0; - struct option opts[] = { - { "enable-mouse", no_argument, NULL, 'm' }, - { "help", no_argument, NULL, 'h' }, - { 0, 0, NULL, 0 } - }; - - while ((c = getopt_long(argc, argv, "mh", opts, NULL)) != -1) { - switch (c) { - case 'm': - enable_mouse = 1; - break; - case 'h': - help(argv[0]); - exit(EXIT_FAILURE); - default: - break; - } - } - - display = display_create(&argc, argv); - - if (display == NULL) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - return -1; - } - - calibrator = calibrator_create(display, enable_mouse); - - 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 deleted file mode 100644 index 4e8a945..0000000 --- a/clients/clickdot.c +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright © 2010 Intel Corporation - * Copyright © 2012 Collabora, Ltd. - * Copyright © 2012 Jonas Ådahl - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "window.h" -#include "shared/helpers.h" -#include "shared/xalloc.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; - - struct input *cursor_timeout_input; - struct toytimer cursor_timeout; -}; - -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 void -cursor_timeout_reset(struct clickdot *clickdot) -{ - toytimer_arm_once_usec(&clickdot->cursor_timeout, 500 * 1000); -} - -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); - - cursor_timeout_reset(clickdot); - clickdot->cursor_timeout_input = input; - - return CURSOR_BLANK; -} - -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 void -cursor_timeout_func(struct toytimer *tt) -{ - struct clickdot *clickdot = - container_of(tt, struct clickdot, cursor_timeout); - - input_set_pointer_image(clickdot->cursor_timeout_input, - CURSOR_LEFT_PTR); -} - -static struct clickdot * -clickdot_create(struct display *display) -{ - struct clickdot *clickdot; - - clickdot = xzalloc(sizeof *clickdot); - clickdot->window = window_create(display); - clickdot->widget = window_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; - - toytimer_init(&clickdot->cursor_timeout, CLOCK_MONOTONIC, - display, cursor_timeout_func); - - return clickdot; -} - -static void -clickdot_destroy(struct clickdot *clickdot) -{ - toytimer_fini(&clickdot->cursor_timeout); - 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: %s\n", - strerror(errno)); - 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 deleted file mode 100644 index 8998385..0000000 --- a/clients/cliptest.c +++ /dev/null @@ -1,637 +0,0 @@ -/* - * Copyright © 2012 Collabora, Ltd. - * Copyright © 2012 Rob Clark - * - * 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. - */ - -/* cliptest: for debugging calculate_edges() function. - * 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 "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "libweston/vertex-clipping.h" -#include "shared/xalloc.h" -#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_view { - struct { - int enabled; - } transform; - - struct geometry *geometry; -}; - -static void -weston_view_to_global_float(struct weston_view *view, - float sx, float sy, float *x, float *y) -{ - struct geometry *g = view->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 -----------------------*/ -/* Keep this in sync with what is in gl-renderer.c! */ - -#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_view *ev, 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_view_to_global_float(ev, 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 (!ev->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; -} - - -/* ---------------------- 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_view view; -}; - -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_view *view) -{ - GLfloat x[4], y[4]; - - if (view) { - weston_view_to_global_float(view, box->x1, box->y1, &x[0], &y[0]); - weston_view_to_global_float(view, box->x2, box->y1, &x[1], &y[1]); - weston_view_to_global_float(view, box->x2, box->y2, &x[2], &y[2]); - weston_view_to_global_float(view, 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_view *view, - GLfloat *ex, GLfloat *ey, int n) -{ - struct geometry *g = view->geometry; - float cx, cy; - - draw_box(cr, &g->surf, view); - cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.4); - cairo_fill(cr); - weston_view_to_global_float(view, g->surf.x1 - 4, g->surf.y1 - 4, &cx, &cy); - cairo_arc(cr, cx, cy, 1.5, 0.0, 2.0 * M_PI); - if (view->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); - - if (n) { - 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->view.geometry; - struct rectangle allocation; - cairo_t *cr; - cairo_surface_t *surface; - GLfloat ex[8]; - GLfloat ey[8]; - int n; - - n = calculate_edges(&cliptest->view, &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->view, 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->view.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->view.transform.enabled = 1; - break; - case XKB_KEY_m: - geometry_set_phi(g, g->phi - (M_PI / 24.0)); - cliptest->view.transform.enabled = 1; - break; - case XKB_KEY_r: - geometry_set_phi(g, 0.0); - cliptest->view.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->view.geometry = &cliptest->geometry; - cliptest->view.transform.enabled = 0; - geometry_init(&cliptest->geometry); - geometry_init(&cliptest->ui.geometry); - - cliptest->window = window_create(display); - cliptest->widget = window_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_view view; - 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); - - view.transform.enabled = 1; - view.geometry = &geom; - - reset_timer(); - for (i = 0; i < N; i++) { - geometry_set_phi(&geom, (float)i / 360.0f); - calculate_edges(&view, &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; -} - -static void -cliptest_destroy(struct cliptest *cliptest) -{ - widget_destroy(cliptest->widget); - window_destroy(cliptest->window); - free(cliptest); -} - -int -main(int argc, char *argv[]) -{ - struct display *d; - struct cliptest *cliptest; - - if (argc > 1) { - if (argc == 2 && !strcmp(argv[1], "-b")) - return benchmark(); - printf("Usage: %s [OPTIONS]\n -b run benchmark\n", argv[0]); - return 1; - } - - d = display_create(&argc, argv); - if (d == NULL) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - return -1; - } - - cliptest = cliptest_create(d); - display_run(d); - - cliptest_destroy(cliptest); - display_destroy(d); - - return 0; -} diff --git a/clients/confine.c b/clients/confine.c deleted file mode 100644 index 6f38457..0000000 --- a/clients/confine.c +++ /dev/null @@ -1,512 +0,0 @@ -/* - * Copyright © 2010 Intel Corporation - * Copyright © 2012 Collabora, Ltd. - * Copyright © 2012 Jonas Ådahl - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "window.h" -#include "shared/helpers.h" -#include "shared/xalloc.h" - -#define NUM_COMPLEX_REGION_RECTS 9 - -static bool option_complex_confine_region; -static bool option_help; - -struct confine { - struct display *display; - struct window *window; - struct widget *widget; - - cairo_surface_t *buffer; - - struct { - int32_t x, y; - int32_t old_x, old_y; - } line; - - int reset; - - struct input *cursor_timeout_input; - struct toytimer cursor_timeout; - - bool pointer_confined; - - bool complex_confine_region_enabled; - bool complex_confine_region_dirty; - struct rectangle complex_confine_region[NUM_COMPLEX_REGION_RECTS]; -}; - -static void -draw_line(struct confine *confine, cairo_t *cr, - struct rectangle *allocation) -{ - cairo_t *bcr; - cairo_surface_t *tmp_buffer = NULL; - - if (confine->reset) { - tmp_buffer = confine->buffer; - confine->buffer = NULL; - confine->line.x = -1; - confine->line.y = -1; - confine->line.old_x = -1; - confine->line.old_y = -1; - confine->reset = 0; - } - - if (confine->buffer == NULL) { - confine->buffer = - cairo_image_surface_create(CAIRO_FORMAT_ARGB32, - allocation->width, - allocation->height); - bcr = cairo_create(confine->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(confine->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 (confine->line.x != -1 && confine->line.y != -1) { - if (confine->line.old_x != -1 && - confine->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, - confine->line.old_x, - confine->line.old_y); - cairo_line_to(bcr, - confine->line.x, - confine->line.y); - - cairo_stroke(bcr); - } - - confine->line.old_x = confine->line.x; - confine->line.old_y = confine->line.y; - } - cairo_destroy(bcr); - - cairo_set_source_surface(cr, confine->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 -calculate_complex_confine_region(struct confine *confine) -{ - struct rectangle allocation; - int32_t x, y, w, h; - struct rectangle *rs = confine->complex_confine_region; - - if (!confine->complex_confine_region_dirty) - return; - - widget_get_allocation(confine->widget, &allocation); - x = allocation.x; - y = allocation.y; - w = allocation.width; - h = allocation.height; - - /* - * The code below constructs a region made up of rectangles that - * is then used to set up both an illustrative shaded region in the - * widget and a confine region used when confining the pointer. - */ - - rs[0].x = x + (int)round(w * 0.05); - rs[0].y = y + (int)round(h * 0.15); - rs[0].width = (int)round(w * 0.35); - rs[0].height = (int)round(h * 0.7); - - rs[1].x = rs[0].x + rs[0].width; - rs[1].y = y + (int)round(h * 0.45); - rs[1].width = (int)round(w * 0.09); - rs[1].height = (int)round(h * 0.1); - - rs[2].x = rs[1].x + rs[1].width; - rs[2].y = y + (int)round(h * 0.48); - rs[2].width = (int)round(w * 0.02); - rs[2].height = (int)round(h * 0.04); - - rs[3].x = rs[2].x + rs[2].width; - rs[3].y = y + (int)round(h * 0.45); - rs[3].width = (int)round(w * 0.09); - rs[3].height = (int)round(h * 0.1); - - rs[4].x = rs[3].x + rs[3].width; - rs[4].y = y + (int)round(h * 0.15); - rs[4].width = (int)round(w * 0.35); - rs[4].height = (int)round(h * 0.7); - - rs[5].x = x + (int)round(w * 0.05); - rs[5].y = y + (int)round(h * 0.05); - rs[5].width = rs[0].width + rs[1].width + rs[2].width + - rs[3].width + rs[4].width; - rs[5].height = (int)round(h * 0.10); - - rs[6].x = x + (int)round(w * 0.1); - rs[6].y = rs[4].y + rs[4].height + (int)round(h * 0.02); - rs[6].width = (int)round(w * 0.8); - rs[6].height = (int)round(h * 0.03); - - rs[7].x = x + (int)round(w * 0.05); - rs[7].y = rs[6].y + rs[6].height; - rs[7].width = (int)round(w * 0.9); - rs[7].height = (int)round(h * 0.03); - - rs[8].x = x + (int)round(w * 0.1); - rs[8].y = rs[7].y + rs[7].height; - rs[8].width = (int)round(w * 0.8); - rs[8].height = (int)round(h * 0.03); - - confine->complex_confine_region_dirty = false; -} - -static void -draw_complex_confine_region_mask(struct confine *confine, cairo_t *cr) -{ - int i; - - calculate_complex_confine_region(confine); - - cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); - - for (i = 0; i < NUM_COMPLEX_REGION_RECTS; i++) { - cairo_rectangle(cr, - confine->complex_confine_region[i].x, - confine->complex_confine_region[i].y, - confine->complex_confine_region[i].width, - confine->complex_confine_region[i].height); - cairo_set_source_rgba(cr, 0.14, 0.14, 0.14, 0.9); - cairo_fill(cr); - } -} - -static void -redraw_handler(struct widget *widget, void *data) -{ - struct confine *confine = data; - cairo_surface_t *surface; - cairo_t *cr; - struct rectangle allocation; - - widget_get_allocation(confine->widget, &allocation); - - surface = window_get_surface(confine->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); - - if (confine->complex_confine_region_enabled) { - draw_complex_confine_region_mask(confine, cr); - } - - draw_line(confine, cr, &allocation); - - cairo_destroy(cr); - - cairo_surface_destroy(surface); -} - -static void -keyboard_focus_handler(struct window *window, - struct input *device, void *data) -{ - struct confine *confine = data; - - window_schedule_redraw(confine->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 confine *confine = data; - - if (state == WL_KEYBOARD_KEY_STATE_RELEASED) - return; - - switch (sym) { - case XKB_KEY_Escape: - display_exit(confine->display); - break; - case XKB_KEY_BackSpace: - cairo_surface_destroy(confine->buffer); - confine->buffer = NULL; - window_schedule_redraw(confine->window); - break; - case XKB_KEY_m: - window_set_maximized(confine->window, - !window_is_maximized(window)); - break; - } -} - -static void -toggle_pointer_confine(struct confine *confine, struct input *input) -{ - if (confine->pointer_confined) { - window_unconfine_pointer(confine->window); - } else if (confine->complex_confine_region_enabled) { - calculate_complex_confine_region(confine); - window_confine_pointer_to_rectangles( - confine->window, - input, - confine->complex_confine_region, - NUM_COMPLEX_REGION_RECTS); - - } else { - window_confine_pointer_to_widget(confine->window, - confine->widget, - input); - } - - confine->pointer_confined = !confine->pointer_confined; -} - -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 confine *confine = data; - bool is_pressed = state == WL_POINTER_BUTTON_STATE_PRESSED; - - if (is_pressed && button == BTN_LEFT) - toggle_pointer_confine(confine, input); - widget_schedule_redraw(widget); -} - -static void -cursor_timeout_reset(struct confine *confine) -{ - toytimer_arm_once_usec(&confine->cursor_timeout, 500 * 1000); -} - -static int -motion_handler(struct widget *widget, - struct input *input, uint32_t time, - float x, float y, void *data) -{ - struct confine *confine = data; - confine->line.x = x; - confine->line.y = y; - - window_schedule_redraw(confine->window); - - cursor_timeout_reset(confine); - confine->cursor_timeout_input = input; - - return CURSOR_BLANK; -} - -static void -resize_handler(struct widget *widget, - int32_t width, int32_t height, - void *data) -{ - struct confine *confine = data; - - confine->reset = 1; - - if (confine->complex_confine_region_enabled) { - confine->complex_confine_region_dirty = true; - - if (confine->pointer_confined) { - calculate_complex_confine_region(confine); - window_update_confine_rectangles( - confine->window, - confine->complex_confine_region, - NUM_COMPLEX_REGION_RECTS); - } - } -} - -static void -leave_handler(struct widget *widget, - struct input *input, void *data) -{ - struct confine *confine = data; - - confine->reset = 1; -} - -static void -cursor_timeout_func(struct toytimer *tt) -{ - struct confine *confine = - container_of(tt, struct confine, cursor_timeout); - - input_set_pointer_image(confine->cursor_timeout_input, - CURSOR_LEFT_PTR); -} - -static void -pointer_unconfined(struct window *window, struct input *input, void *data) -{ - struct confine *confine = data; - - confine->pointer_confined = false; -} - -static struct confine * -confine_create(struct display *display) -{ - struct confine *confine; - - confine = xzalloc(sizeof *confine); - confine->window = window_create(display); - confine->widget = window_frame_create(confine->window, confine); - window_set_title(confine->window, "Wayland Confine"); - confine->display = display; - confine->buffer = NULL; - - window_set_key_handler(confine->window, key_handler); - window_set_user_data(confine->window, confine); - window_set_keyboard_focus_handler(confine->window, - keyboard_focus_handler); - window_set_pointer_confined_handler(confine->window, - NULL, - pointer_unconfined); - - widget_set_redraw_handler(confine->widget, redraw_handler); - widget_set_button_handler(confine->widget, button_handler); - widget_set_motion_handler(confine->widget, motion_handler); - widget_set_resize_handler(confine->widget, resize_handler); - widget_set_leave_handler(confine->widget, leave_handler); - - widget_schedule_resize(confine->widget, 500, 400); - confine->line.x = -1; - confine->line.y = -1; - confine->line.old_x = -1; - confine->line.old_y = -1; - confine->reset = 0; - - toytimer_init(&confine->cursor_timeout, CLOCK_MONOTONIC, - display, cursor_timeout_func); - - return confine; -} - -static void -confine_destroy(struct confine *confine) -{ - toytimer_fini(&confine->cursor_timeout); - if (confine->buffer) - cairo_surface_destroy(confine->buffer); - widget_destroy(confine->widget); - window_destroy(confine->window); - free(confine); -} - -static const struct weston_option confine_options[] = { - { WESTON_OPTION_BOOLEAN, "complex-confine-region", 0, &option_complex_confine_region }, - { WESTON_OPTION_BOOLEAN, "help", 0, &option_help }, -}; - -static void -print_help(const char *argv0) -{ - printf("Usage: %s [--complex-confine-region]\n", argv0); -} - -int -main(int argc, char *argv[]) -{ - struct display *display; - struct confine *confine; - - if (parse_options(confine_options, - ARRAY_LENGTH(confine_options), - &argc, argv) > 1 || - option_help) { - print_help(argv[0]); - return 0; - } - - display = display_create(&argc, argv); - if (display == NULL) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - return -1; - } - - confine = confine_create(display); - - if (option_complex_confine_region) { - confine->complex_confine_region_dirty = true; - confine->complex_confine_region_enabled = true; - } - - display_run(display); - - confine_destroy(confine); - display_destroy(display); - - return 0; -} diff --git a/clients/content_protection.c b/clients/content_protection.c deleted file mode 100644 index 27e2b8a..0000000 --- a/clients/content_protection.c +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Copyright © 2018 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include "weston-content-protection-client-protocol.h" -#include "window.h" -#include - -#define WIDTH 500 -#define HEIGHT 400 -#define FRAME_H 18 -#define FRAME_W 5 -#define BUTTON_WIDTH 65 -#define BUTTON_HEIGHT 20 - -enum protection_mode { - RELAXED, - ENFORCED -}; - -struct protected_content_player { - struct weston_content_protection *protection; - struct weston_protected_surface *psurface; - struct display *display; - struct window *window; - struct widget *widget; - struct button_t *b0, *b1, *off, *enforced, *relaxed; - int width, height, x, y; - enum weston_protected_surface_type protection_type; - enum protection_mode mode; -}; - -struct button_t { - struct window *window; - struct widget *widget; - struct protected_content_player *pc_player; - const char *name; -}; -/** - * An event to tell the client that there is a change in protection status - * - * This event is sent whenever there is a change in content - * protection. The content protection status can be ON or OFF. ON - * in case of the desired protection type is accepted on all - * connectors, and Off in case of any of the connector - * content-protection property is changed from "enabled" - */ -static void -handle_status_changed(void *data, struct weston_protected_surface *psurface, - uint32_t status) -{ - struct protected_content_player *pc_player = data; - enum weston_protected_surface_type event_status = status; - - switch (event_status) { - case WESTON_PROTECTED_SURFACE_TYPE_HDCP_0: - pc_player->protection_type = WESTON_PROTECTED_SURFACE_TYPE_HDCP_0; - break; - case WESTON_PROTECTED_SURFACE_TYPE_HDCP_1: - pc_player->protection_type = WESTON_PROTECTED_SURFACE_TYPE_HDCP_1; - break; - case WESTON_PROTECTED_SURFACE_TYPE_UNPROTECTED: - default: - pc_player->protection_type = WESTON_PROTECTED_SURFACE_TYPE_UNPROTECTED; - } - window_schedule_redraw(pc_player->window); -} - -static const struct weston_protected_surface_listener pc_player_listener = { - handle_status_changed, -}; - -static void -draw_content(cairo_surface_t *surface, int x, int y, int width, int height, - enum weston_protected_surface_type type, enum protection_mode mode) -{ - cairo_t *cr; - cairo_text_extents_t extents; - const char *content_text; - const char *mode_text; - - cr = cairo_create(surface); - cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); - cairo_rectangle(cr, x, y, width, height); - if (type == WESTON_PROTECTED_SURFACE_TYPE_HDCP_0) - cairo_set_source_rgba(cr, 0, 1.0, 0, 1.0); - else if (type == WESTON_PROTECTED_SURFACE_TYPE_HDCP_1) - cairo_set_source_rgba(cr, 0, 0, 1.0, 1.0); - else - cairo_set_source_rgba(cr, 1.0, 0, 0, 1.0); - cairo_fill(cr); - - cairo_set_source_rgba(cr, 0, 0, 0, 1.0); - cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, - CAIRO_FONT_WEIGHT_NORMAL); - cairo_set_font_size(cr, 15); - if (type == WESTON_PROTECTED_SURFACE_TYPE_HDCP_0) - content_text = "Content-Type : Type-0"; - else if (type == WESTON_PROTECTED_SURFACE_TYPE_HDCP_1) - content_text = "Content-Type : Type-1"; - else - content_text = "Content-Type : Unprotected"; - cairo_text_extents(cr, content_text, &extents); - cairo_move_to(cr, width/2 - (extents.width/2), - height/2 - (extents.height/2)); - cairo_show_text(cr, content_text); - - if (mode == ENFORCED) - mode_text = "Mode : Enforced"; - else - mode_text = "Mode : Relaxed"; - cairo_text_extents(cr, mode_text, &extents); - cairo_move_to(cr, width / 2 - (extents.width / 2), - 2 * height / 3 - (2 * extents.height / 3)); - cairo_show_text(cr, mode_text); - - cairo_fill(cr); - cairo_destroy(cr); -} - -static void -redraw_handler(struct widget *widget, void *data) -{ - struct protected_content_player *pc_player = data; - cairo_surface_t *surface; - struct rectangle rect; - - widget_get_allocation(pc_player->widget, &rect); - surface = window_get_surface(pc_player->window); - if (surface == NULL || - cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { - fprintf(stderr, "failed to create cairo egl surface\n"); - return; - } - draw_content(surface, rect.x, rect.y, rect.width, rect.height, - pc_player->protection_type, pc_player->mode); - cairo_surface_destroy(surface); -} - -static void -resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) -{ - struct rectangle allocation; - struct protected_content_player *pc_player = data; - - widget_get_allocation(pc_player->widget, &allocation); - widget_set_allocation(pc_player->b0->widget, - allocation.x + 20, allocation.y + 30, - BUTTON_WIDTH, BUTTON_HEIGHT); - widget_set_allocation(pc_player->b1->widget, - allocation.x + 20 + BUTTON_WIDTH + 5, - allocation.y + 30, - BUTTON_WIDTH, BUTTON_HEIGHT); - widget_set_allocation(pc_player->off->widget, - allocation.x + 20 + 2 * (BUTTON_WIDTH + 5), - allocation.y + 30, - BUTTON_WIDTH, BUTTON_HEIGHT); - widget_set_allocation(pc_player->enforced->widget, - allocation.x + 20 + 3 * (BUTTON_WIDTH + 5), - allocation.y + 30, - BUTTON_WIDTH, BUTTON_HEIGHT); - widget_set_allocation(pc_player->relaxed->widget, - allocation.x + 20 + 4 * (BUTTON_WIDTH + 5), - allocation.y + 30, - BUTTON_WIDTH, BUTTON_HEIGHT); -} - -static void -buttons_handler(struct widget *widget, struct input *input, uint32_t time, - uint32_t button, enum wl_pointer_button_state state, void *data) -{ - struct button_t *b = data; - struct protected_content_player *pc_player = b->pc_player; - struct wl_surface *surface; - - if (strcmp(b->name, "ENFORCED") == 0) { - weston_protected_surface_enforce(pc_player->psurface); - pc_player->mode = ENFORCED; - window_schedule_redraw(pc_player->window); - } - else if (strcmp(b->name, "RELAXED") == 0) { - weston_protected_surface_relax(pc_player->psurface); - pc_player->mode = RELAXED; - window_schedule_redraw(pc_player->window); - } - else if (strcmp(b->name, "TYPE-0") == 0) - weston_protected_surface_set_type(pc_player->psurface, - WESTON_PROTECTED_SURFACE_TYPE_HDCP_0); - else if (strcmp(b->name, "TYPE-1") == 0) - weston_protected_surface_set_type(pc_player->psurface, - WESTON_PROTECTED_SURFACE_TYPE_HDCP_1); - else - weston_protected_surface_set_type(pc_player->psurface, - WESTON_PROTECTED_SURFACE_TYPE_UNPROTECTED); - - surface = window_get_wl_surface(pc_player->window); - wl_surface_commit(surface); -} - -static void -handle_global(struct display *display, uint32_t name, const char *interface, - uint32_t version, void *data) -{ - struct protected_content_player *pc_player = data; - - if (strcmp(interface, "weston_content_protection") == 0) { - pc_player->protection = display_bind(display, name, - &weston_content_protection_interface, - 1); - } -} - -static void -buttons_redraw_handler(struct widget *widget, void *data) -{ - struct button_t *b = data; - cairo_surface_t *surface; - struct rectangle allocation; - cairo_t *cr; - - surface = window_get_surface(b->window); - widget_get_allocation(b->widget, &allocation); - - cr = cairo_create(surface); - cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, - allocation.height); - - cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); - - cairo_set_source_rgba(cr, 1, 1, 1, 1); - cairo_fill(cr); - - cairo_set_source_rgba(cr, 0, 0, 0, 1.0); - cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, - CAIRO_FONT_WEIGHT_NORMAL); - cairo_set_font_size(cr, 10); - cairo_move_to(cr, allocation.x + 5, allocation.y + 15); - cairo_show_text(cr, b->name); - cairo_fill(cr); - - cairo_destroy(cr); - cairo_surface_destroy(surface); -} - -static struct button_t* -create_button(struct protected_content_player *pc_player, const char *name) -{ - struct button_t *b; - - b = zalloc(sizeof(struct button_t)); - if (b == NULL) { - fprintf(stderr, "Failed to allocate memory for button.\n"); - exit(0); - } - b->widget = widget_add_widget(pc_player->widget, b); - b->window = pc_player->window; - b->pc_player = pc_player; - b->name = name; - widget_set_redraw_handler(b->widget, buttons_redraw_handler); - widget_set_button_handler(b->widget, buttons_handler); - return b; -} - -static void -destroy_button(struct button_t *b) -{ - if (!b) - return; - widget_destroy(b->widget); - free(b); -} - -static void free_pc_player(struct protected_content_player *pc_player) -{ - if (!pc_player) - return; - - destroy_button(pc_player->b0); - destroy_button(pc_player->b1); - destroy_button(pc_player->off); - destroy_button(pc_player->enforced); - destroy_button(pc_player->relaxed); - widget_destroy(pc_player->widget); - window_destroy(pc_player->window); - free(pc_player); -} - -int main(int argc, char *argv[]) -{ - struct protected_content_player *pc_player; - struct display *d; - static const char str_type_0[] = "TYPE-0"; - static const char str_type_1[] = "TYPE-1"; - static const char str_type_off[] = "OFF"; - static const char str_type_enforced[] = "ENFORCED"; - static const char str_type_relaxed[] = "RELAXED"; - struct wl_surface *surface; - - pc_player = zalloc(sizeof(struct protected_content_player)); - if (pc_player == NULL) { - fprintf(stderr, "failed to allocate memory: %m\n"); - return -1; - } - d = display_create(&argc, argv); - if (d == NULL) { - fprintf(stderr, "failed to create display: %m\n"); - return -1; - } - pc_player->protection_type = WESTON_PROTECTED_SURFACE_TYPE_UNPROTECTED; - pc_player->mode = RELAXED; - pc_player->width = WIDTH * 2.0/4.0; - pc_player->height = HEIGHT * 2.0/4.0; - pc_player->x = WIDTH * 1.0/4.0; - pc_player->y = HEIGHT * 1.0/4.0; - pc_player->window = window_create(d); - pc_player->widget = window_frame_create(pc_player->window, pc_player); - pc_player->display = d; - display_set_user_data(d, pc_player); - - display_set_global_handler(d, handle_global); - surface = window_get_wl_surface(pc_player->window); - if (pc_player->protection == NULL) { - printf("The content-protection object is NULL\n"); - return -1; - } - pc_player->psurface = weston_content_protection_get_protection(pc_player->protection, - surface); - weston_protected_surface_add_listener(pc_player->psurface, - &pc_player_listener, - pc_player); - - pc_player->b0 = create_button(pc_player, str_type_0); - pc_player->b1 = create_button(pc_player, str_type_1); - pc_player->off = create_button(pc_player, str_type_off); - pc_player->enforced = create_button(pc_player, str_type_enforced); - pc_player->relaxed = create_button(pc_player, str_type_relaxed); - - window_set_title(pc_player->window, "Player"); - widget_set_redraw_handler(pc_player->widget, redraw_handler); - widget_set_resize_handler(pc_player->widget, resize_handler); - window_schedule_resize(pc_player->window, WIDTH, HEIGHT); - widget_schedule_redraw(pc_player->b0->widget); - widget_schedule_redraw(pc_player->b1->widget); - widget_schedule_redraw(pc_player->off->widget); - - display_run(d); - weston_protected_surface_destroy(pc_player->psurface); - weston_content_protection_destroy(pc_player->protection); - free_pc_player(pc_player); - display_destroy(d); - return 0; -} diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c deleted file mode 100644 index bde5dc8..0000000 --- a/clients/desktop-shell.c +++ /dev/null @@ -1,1559 +0,0 @@ -/* - * Copyright © 2011 Kristian Høgsberg - * Copyright © 2011 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#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 -#include "shared/helpers.h" -#include "shared/xalloc.h" -#include -#include "shared/file-util.h" - -#include "weston-desktop-shell-client-protocol.h" - -#define DEFAULT_CLOCK_FORMAT CLOCK_FORMAT_MINUTES -#define DEFAULT_SPACING 10 - -extern char **environ; /* defined by libc */ - -enum clock_format { - CLOCK_FORMAT_MINUTES, - CLOCK_FORMAT_SECONDS, - CLOCK_FORMAT_NONE -}; - -struct desktop { - struct display *display; - struct weston_desktop_shell *shell; - struct unlock_dialog *unlock_dialog; - struct task unlock_task; - struct wl_list outputs; - - int want_panel; - enum weston_desktop_shell_panel_position panel_position; - enum clock_format clock_format; - - struct window *grab_window; - struct widget *grab_widget; - - struct weston_config *config; - bool locking; - - enum cursor_type grab_cursor; - - int painted; -}; - -struct surface { - void (*configure)(void *data, - struct weston_desktop_shell *desktop_shell, - uint32_t edges, struct window *window, - int32_t width, int32_t height); -}; - -struct output; - -struct panel { - struct surface base; - - struct output *owner; - - struct window *window; - struct widget *widget; - struct wl_list launcher_list; - struct panel_clock *clock; - int painted; - enum weston_desktop_shell_panel_position panel_position; - enum clock_format clock_format; - uint32_t color; -}; - -struct background { - struct surface base; - - struct output *owner; - - struct window *window; - struct widget *widget; - int painted; - - char *image; - int type; - uint32_t color; -}; - -struct output { - struct wl_output *output; - uint32_t server_output_id; - struct wl_list link; - - int x; - int y; - 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 toytimer timer; - char *format_string; - time_t refresh_timer; -}; - -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 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; - - weston_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: %s\n", strerror(errno)); - return; - } - - if (pid) - return; - - argv = widget->argv.data; - - if (setsid() == -1) - exit(EXIT_FAILURE); - - if (execve(argv[0], argv, widget->envp.data) < 0) { - fprintf(stderr, "execl '%s' failed: %s\n", argv[0], - strerror(errno)); - 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); - allocation.x += allocation.width / 2 - - cairo_image_surface_get_width(launcher->icon) / 2; - if (allocation.width > allocation.height) - allocation.x += allocation.width / 2 - allocation.height / 2; - allocation.y += allocation.height / 2 - - cairo_image_surface_get_height(launcher->icon) / 2; - if (allocation.height > allocation.width) - allocation.y += allocation.height / 2 - allocation.width / 2; - 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 toytimer *tt) -{ - struct panel_clock *clock = container_of(tt, struct panel_clock, timer); - - 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; - time_t rawtime; - struct tm * timeinfo; - char string[128]; - - time(&rawtime); - timeinfo = localtime(&rawtime); - strftime(string, sizeof string, clock->format_string, timeinfo); - - widget_get_allocation(widget, &allocation); - if (allocation.width == 0) - return; - - cr = widget_cairo_create(clock->panel->widget); - cairo_set_font_size(cr, 14); - cairo_text_extents(cr, string, &extents); - if (allocation.x > 0) - allocation.x += - allocation.width - DEFAULT_SPACING * 1.5 - extents.width; - else - allocation.x += - allocation.width / 2 - extents.width / 2; - allocation.y += allocation.height / 2 - 1 + extents.height / 2; - cairo_move_to(cr, allocation.x + 1, allocation.y + 1); - cairo_set_source_rgba(cr, 0, 0, 0, 0.85); - cairo_show_text(cr, string); - cairo_move_to(cr, allocation.x, allocation.y); - cairo_set_source_rgba(cr, 1, 1, 1, 0.85); - 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 = clock->refresh_timer; - its.it_interval.tv_nsec = 0; - its.it_value.tv_sec = clock->refresh_timer; - its.it_value.tv_nsec = 0; - toytimer_arm(&clock->timer, &its); - - return 0; -} - -static void -panel_destroy_clock(struct panel_clock *clock) -{ - widget_destroy(clock->widget); - toytimer_fini(&clock->timer); - free(clock); -} - -static void -panel_add_clock(struct panel *panel) -{ - struct panel_clock *clock; - - clock = xzalloc(sizeof *clock); - clock->panel = panel; - panel->clock = clock; - - switch (panel->clock_format) { - case CLOCK_FORMAT_MINUTES: - clock->format_string = "%a %b %d, %I:%M %p"; - clock->refresh_timer = 60; - break; - case CLOCK_FORMAT_SECONDS: - clock->format_string = "%a %b %d, %I:%M:%S %p"; - clock->refresh_timer = 1; - break; - case CLOCK_FORMAT_NONE: - assert(!"not reached"); - } - - toytimer_init(&clock->timer, CLOCK_MONOTONIC, - window_get_display(panel->window), clock_func); - 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_resize_handler(struct widget *widget, - int32_t width, int32_t height, void *data) -{ - struct panel_launcher *launcher; - struct panel *panel = data; - int x = 0; - int y = 0; - int w = height > width ? width : height; - int h = w; - int horizontal = panel->panel_position == WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP || panel->panel_position == WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM; - int first_pad_h = horizontal ? 0 : DEFAULT_SPACING / 2; - int first_pad_w = horizontal ? DEFAULT_SPACING / 2 : 0; - - wl_list_for_each(launcher, &panel->launcher_list, link) { - widget_set_allocation(launcher->widget, x, y, - w + first_pad_w + 1, h + first_pad_h + 1); - if (horizontal) - x += w + first_pad_w; - else - y += h + first_pad_h; - first_pad_h = first_pad_w = 0; - } - - if (panel->clock_format == CLOCK_FORMAT_SECONDS) - w = 170; - else /* CLOCK_FORMAT_MINUTES */ - w = 150; - - if (horizontal) - x = width - w; - else - y = height - (h = DEFAULT_SPACING * 3); - - if (panel->clock) - widget_set_allocation(panel->clock->widget, - x, y, w + 1, h + 1); -} - -static void -panel_destroy(struct panel *panel); - -static void -panel_configure(void *data, - struct weston_desktop_shell *desktop_shell, - uint32_t edges, struct window *window, - int32_t width, int32_t height) -{ - struct desktop *desktop = data; - struct surface *surface = window_get_user_data(window); - struct panel *panel = container_of(surface, struct panel, base); - struct output *owner; - - if (width < 1 || height < 1) { - /* Shell plugin configures 0x0 for redundant panel. */ - owner = panel->owner; - panel_destroy(panel); - owner->panel = NULL; - return; - } - - switch (desktop->panel_position) { - case WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP: - case WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM: - height = 32; - break; - case WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT: - case WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT: - switch (desktop->clock_format) { - case CLOCK_FORMAT_NONE: - width = 32; - break; - case CLOCK_FORMAT_MINUTES: - width = 150; - break; - case CLOCK_FORMAT_SECONDS: - width = 170; - break; - } - break; - } - window_schedule_resize(panel->window, width, height); -} - -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; - - if (panel->clock) - 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 output *output) -{ - struct panel *panel; - struct weston_config_section *s; - - panel = xzalloc(sizeof *panel); - - panel->owner = output; - 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); - - panel->panel_position = desktop->panel_position; - panel->clock_format = desktop->clock_format; - if (panel->clock_format != CLOCK_FORMAT_NONE) - panel_add_clock(panel); - - s = weston_config_get_section(desktop->config, "shell", NULL, NULL); - weston_config_section_get_color(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 = xstrdup(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, - BACKGROUND_CENTERED -}; - -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; - - surface = window_get_surface(background->window); - - cr = widget_cairo_create(background->widget); - cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); - if (background->color == 0) - cairo_set_source_rgba(cr, 0.0, 0.0, 0.2, 1.0); - else - set_hex_color(cr, background->color); - cairo_paint(cr); - - widget_get_allocation(widget, &allocation); - image = NULL; - if (background->image) - image = load_cairo_surface(background->image); - else if (background->color == 0) { - char *name = file_name_with_datadir("pattern.png"); - - image = load_cairo_surface(name); - free(name); - } - - 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); - cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD); - 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); - cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD); - break; - case BACKGROUND_TILE: - cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); - break; - case BACKGROUND_CENTERED: - s = (sx < sy) ? sx : sy; - if (s < 1.0) - s = 1.0; - - /* 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; - } - - cairo_set_source(cr, pattern); - cairo_pattern_destroy (pattern); - cairo_surface_destroy(image); - cairo_mask(cr, pattern); - } - - cairo_destroy(cr); - cairo_surface_destroy(surface); - - background->painted = 1; - check_desktop_ready(background->window); -} - -static void -background_destroy(struct background *background); - -static void -background_configure(void *data, - struct weston_desktop_shell *desktop_shell, - uint32_t edges, struct window *window, - int32_t width, int32_t height) -{ - struct output *owner; - struct background *background = - (struct background *) window_get_user_data(window); - - if (width < 1 || height < 1) { - /* Shell plugin configures 0x0 for redundant background. */ - owner = background->owner; - background_destroy(background); - owner->background = NULL; - return; - } - - if (!background->image) { - widget_set_viewport_destination(background->widget, width, height); - width = 1; - height = 1; - } - - 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; - struct wl_surface *surface; - - dialog = xzalloc(sizeof *dialog); - - dialog->window = window_create_custom(display); - dialog->widget = window_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); - - surface = window_get_wl_surface(dialog->window); - weston_desktop_shell_set_lock_surface(desktop->shell, surface); - - 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); - - weston_desktop_shell_unlock(desktop->shell); - unlock_dialog_destroy(desktop->unlock_dialog); - desktop->unlock_dialog = NULL; -} - -static void -desktop_shell_configure(void *data, - struct weston_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 weston_desktop_shell *desktop_shell) -{ - struct desktop *desktop = data; - - if (!desktop->locking) { - weston_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 weston_desktop_shell *desktop_shell, - uint32_t cursor) -{ - struct desktop *desktop = data; - - switch (cursor) { - case WESTON_DESKTOP_SHELL_CURSOR_NONE: - desktop->grab_cursor = CURSOR_BLANK; - break; - case WESTON_DESKTOP_SHELL_CURSOR_BUSY: - desktop->grab_cursor = CURSOR_WATCH; - break; - case WESTON_DESKTOP_SHELL_CURSOR_MOVE: - desktop->grab_cursor = CURSOR_DRAGGING; - break; - case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_TOP: - desktop->grab_cursor = CURSOR_TOP; - break; - case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_BOTTOM: - desktop->grab_cursor = CURSOR_BOTTOM; - break; - case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_LEFT: - desktop->grab_cursor = CURSOR_LEFT; - break; - case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_RIGHT: - desktop->grab_cursor = CURSOR_RIGHT; - break; - case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_TOP_LEFT: - desktop->grab_cursor = CURSOR_TOP_LEFT; - break; - case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_TOP_RIGHT: - desktop->grab_cursor = CURSOR_TOP_RIGHT; - break; - case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_BOTTOM_LEFT: - desktop->grab_cursor = CURSOR_BOTTOM_LEFT; - break; - case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_BOTTOM_RIGHT: - desktop->grab_cursor = CURSOR_BOTTOM_RIGHT; - break; - case WESTON_DESKTOP_SHELL_CURSOR_ARROW: - default: - desktop->grab_cursor = CURSOR_LEFT_PTR; - } -} - -static const struct weston_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 output *output) -{ - struct background *background; - struct weston_config_section *s; - char *type; - - background = xzalloc(sizeof *background); - background->owner = output; - 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); - widget_set_transparent(background->widget, 0); - - s = weston_config_get_section(desktop->config, "shell", NULL, NULL); - weston_config_section_get_string(s, "background-image", - &background->image, NULL); - weston_config_section_get_color(s, "background-color", - &background->color, 0x00000000); - - weston_config_section_get_string(s, "background-type", - &type, "tile"); - if (type == NULL) { - fprintf(stderr, "%s: out of memory\n", program_invocation_short_name); - exit(EXIT_FAILURE); - } - - 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 if (strcmp(type, "centered") == 0) { - background->type = BACKGROUND_CENTERED; - } 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); - weston_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) -{ - if (output->background) - background_destroy(output->background); - if (output->panel) - 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; - - output->x = x; - output->y = y; - - if (output->panel) - window_set_buffer_transform(output->panel->window, transform); - if (output->background) - 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; - - if (output->panel) - window_set_buffer_scale(output->panel->window, scale); - if (output->background) - 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; - - if (desktop->want_panel) { - output->panel = panel_create(desktop, output); - surface = window_get_wl_surface(output->panel->window); - weston_desktop_shell_set_panel(desktop->shell, - output->output, surface); - } - - output->background = background_create(desktop, output); - surface = window_get_wl_surface(output->background->window); - weston_desktop_shell_set_background(desktop->shell, - output->output, surface); -} - -static void -create_output(struct desktop *desktop, uint32_t id) -{ - struct output *output; - - output = zalloc(sizeof *output); - if (!output) - return; - - output->output = - display_bind(desktop->display, id, &wl_output_interface, 2); - output->server_output_id = id; - - 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 -output_remove(struct desktop *desktop, struct output *output) -{ - struct output *cur; - struct output *rep = NULL; - - if (!output->background) { - output_destroy(output); - return; - } - - /* Find a wl_output that is a clone of the removed wl_output. - * We don't want to leave the clone without a background or panel. */ - wl_list_for_each(cur, &desktop->outputs, link) { - if (cur == output) - continue; - - /* XXX: Assumes size matches. */ - if (cur->x == output->x && cur->y == output->y) { - rep = cur; - break; - } - } - - if (rep) { - /* If found and it does not already have a background or panel, - * hand over the background and panel so they don't get - * destroyed. - * - * We never create multiple backgrounds or panels for clones, - * but if the compositor moves outputs, a pair of wl_outputs - * might become "clones". This may happen temporarily when - * an output is about to be removed and the rest are reflowed. - * In this case it is correct to let the background/panel be - * destroyed. - */ - - if (!rep->background) { - rep->background = output->background; - output->background = NULL; - rep->background->owner = rep; - } - - if (!rep->panel) { - rep->panel = output->panel; - output->panel = NULL; - if (rep->panel) - rep->panel->owner = rep; - } - } - - output_destroy(output); -} - -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, "weston_desktop_shell")) { - desktop->shell = display_bind(desktop->display, - id, - &weston_desktop_shell_interface, - 1); - weston_desktop_shell_add_listener(desktop->shell, - &listener, - desktop); - } else if (!strcmp(interface, "wl_output")) { - create_output(desktop, id); - } -} - -static void -global_handler_remove(struct display *display, uint32_t id, - const char *interface, uint32_t version, void *data) -{ - struct desktop *desktop = data; - struct output *output; - - if (!strcmp(interface, "wl_output")) { - wl_list_for_each(output, &desktop->outputs, link) { - if (output->server_output_id == id) { - output_remove(desktop, output); - break; - } - } - } -} - -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) { - char *name = file_name_with_datadir("terminal.png"); - - /* add default launcher */ - panel_add_launcher(panel, - name, - BINDIR "/weston-terminal"); - free(name); - } -} - -static void -parse_panel_position(struct desktop *desktop, struct weston_config_section *s) -{ - char *position; - - desktop->want_panel = 1; - - weston_config_section_get_string(s, "panel-position", &position, "top"); - if (strcmp(position, "top") == 0) { - desktop->panel_position = WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP; - } else if (strcmp(position, "bottom") == 0) { - desktop->panel_position = WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM; - } else if (strcmp(position, "left") == 0) { - desktop->panel_position = WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT; - } else if (strcmp(position, "right") == 0) { - desktop->panel_position = WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT; - } else { - /* 'none' is valid here */ - if (strcmp(position, "none") != 0) - fprintf(stderr, "Wrong panel position: %s\n", position); - desktop->want_panel = 0; - } - free(position); -} - -static void -parse_clock_format(struct desktop *desktop, struct weston_config_section *s) -{ - char *clock_format; - - weston_config_section_get_string(s, "clock-format", &clock_format, ""); - if (strcmp(clock_format, "minutes") == 0) - desktop->clock_format = CLOCK_FORMAT_MINUTES; - else if (strcmp(clock_format, "seconds") == 0) - desktop->clock_format = CLOCK_FORMAT_SECONDS; - else if (strcmp(clock_format, "none") == 0) - desktop->clock_format = CLOCK_FORMAT_NONE; - else - desktop->clock_format = DEFAULT_CLOCK_FORMAT; - free(clock_format); -} - -int main(int argc, char *argv[]) -{ - struct desktop desktop = { 0 }; - struct output *output; - struct weston_config_section *s; - const char *config_file; - - desktop.unlock_task.run = unlock_dialog_finish; - wl_list_init(&desktop.outputs); - - config_file = weston_config_get_name_from_env(); - desktop.config = weston_config_parse(config_file); - s = weston_config_get_section(desktop.config, "shell", NULL, NULL); - weston_config_section_get_bool(s, "locking", &desktop.locking, true); - parse_panel_position(&desktop, s); - parse_clock_format(&desktop, s); - - desktop.display = display_create(&argc, argv); - if (desktop.display == NULL) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - return -1; - } - - display_set_user_data(desktop.display, &desktop); - display_set_global_handler(desktop.display, global_handler); - display_set_global_handler_remove(desktop.display, global_handler_remove); - - /* Create panel and background for outputs processed before the shell - * global interface was processed */ - if (desktop.want_panel) - weston_desktop_shell_set_panel_position(desktop.shell, desktop.panel_position); - 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); - weston_desktop_shell_destroy(desktop.shell); - display_destroy(desktop.display); - - return 0; -} diff --git a/clients/dnd.c b/clients/dnd.c deleted file mode 100644 index 8323f4f..0000000 --- a/clients/dnd.c +++ /dev/null @@ -1,867 +0,0 @@ -/* - * Copyright © 2010 Kristian Høgsberg - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#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/helpers.h" -#include "shared/xalloc.h" - -struct dnd_drag; - -struct pointer { - struct input *input; - bool dragging; - struct wl_list link; -}; - -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 wl_list pointers; -}; - -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; - uint32_t dnd_action; - 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, *item_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; - - if (dnd->current_drag && dnd->items[i] == dnd->current_drag->item) - item_surface = dnd->current_drag->translucent; - else - item_surface = dnd->items[i]->surface; - - cairo_set_source_surface(cr, item_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 int -lookup_dnd_cursor(uint32_t dnd_action) -{ - if (dnd_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) - return CURSOR_DND_MOVE; - else if (dnd_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) - return CURSOR_DND_COPY; - - return CURSOR_DND_FORBIDDEN; -} - -static void -dnd_drag_update_cursor(struct dnd_drag *dnd_drag) -{ - int cursor; - - if (dnd_drag->mime_type == NULL) - cursor = CURSOR_DND_FORBIDDEN; - else - cursor = lookup_dnd_cursor(dnd_drag->dnd_action); - - input_set_pointer_image(dnd_drag->input, cursor); -} - -static void -dnd_drag_update_surface(struct dnd_drag *dnd_drag) -{ - struct dnd *dnd = dnd_drag->dnd; - cairo_surface_t *surface; - struct wl_buffer *buffer; - - if (dnd_drag->mime_type && dnd_drag->dnd_action) - 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_target(void *data, - struct wl_data_source *source, const char *mime_type) -{ - struct dnd_drag *dnd_drag = data; - - dnd_drag->mime_type = mime_type; - dnd_drag_update_surface(dnd_drag); - dnd_drag_update_cursor(dnd_drag); -} - -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 -dnd_drag_destroy(struct dnd_drag *dnd_drag, bool delete_item) -{ - struct dnd *dnd = dnd_drag->dnd; - unsigned int i; - - wl_data_source_destroy(dnd_drag->data_source); - - if (delete_item) { - for (i = 0; i < ARRAY_LENGTH(dnd->items); i++) { - if (dnd_drag->item == dnd->items[i]) { - dnd->items[i] = NULL; - break; - } - } - - /* Destroy the item that has been dragged out */ - cairo_surface_destroy(dnd_drag->item->surface); - free(dnd_drag->item); - } - - dnd->current_drag = NULL; - - wl_surface_destroy(dnd_drag->drag_surface); - - cairo_surface_destroy(dnd_drag->translucent); - cairo_surface_destroy(dnd_drag->opaque); - free(dnd_drag); -} - -static void -data_source_cancelled(void *data, struct wl_data_source *source) -{ - struct dnd_drag *dnd_drag = data; - struct dnd *dnd = dnd_drag->dnd; - - /* 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. */ - dnd_drag_destroy(dnd_drag, false); - window_schedule_redraw(dnd->window); -} - -static void -data_source_dnd_drop_performed(void *data, struct wl_data_source *source) -{ -} - -static void -data_source_dnd_finished(void *data, struct wl_data_source *source) -{ - struct dnd_drag *dnd_drag = data; - struct dnd *dnd = dnd_drag->dnd; - bool delete_item; - - delete_item = - dnd_drag->dnd_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; - - /* The operation is already finished, we can destroy all - * related data. - */ - dnd_drag_destroy(dnd_drag, delete_item); - window_schedule_redraw(dnd->window); -} - -static void -data_source_action(void *data, struct wl_data_source *source, uint32_t dnd_action) -{ - struct dnd_drag *dnd_drag = data; - - dnd_drag->dnd_action = dnd_action; - dnd_drag_update_surface(dnd_drag); - dnd_drag_update_cursor(dnd_drag); -} - -static const struct wl_data_source_listener data_source_listener = { - data_source_target, - data_source_send, - data_source_cancelled, - data_source_dnd_drop_performed, - data_source_dnd_finished, - data_source_action, -}; - -static cairo_surface_t * -create_drag_icon(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 rectangle rectangle; - cairo_pattern_t *pattern; - cairo_t *cr; - - rectangle.width = item_width; - rectangle.height = item_height; - surface = display_create_surface(dnd->display, NULL, &rectangle, - SURFACE_SHM); - - cr = cairo_create(surface); - cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); - 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); - - cairo_destroy(cr); - - dnd_drag->hotspot_x = x - item->x; - dnd_drag->hotspot_y = y - item->y; - dnd_drag->width = rectangle.width; - dnd_drag->height = rectangle.height; - - return surface; -} - -static int -create_drag_source(struct dnd *dnd, - struct input *input, uint32_t time, - int32_t x, int32_t 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; - uint32_t actions; - - widget_get_allocation(dnd->widget, &allocation); - item = dnd_get_item(dnd, x, y); - x -= allocation.x; - y -= allocation.y; - - if (item) { - 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; - dnd_drag->dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; - dnd_drag->mime_type = NULL; - - actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | - WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; - - 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); - - if (display_get_data_device_manager_version(display) < - WL_DATA_SOURCE_SET_ACTIONS_SINCE_VERSION) { - /* Data sources version < 3 will not get action - * nor dnd_finished events, as we can't honor - * the "move" action at the time of finishing - * drag-and-drop, do it preemptively here. - */ - for (i = 0; i < ARRAY_LENGTH(dnd->items); i++) { - if (item == dnd->items[i]){ - dnd->items[i] = NULL; - break; - } - } - } - - if (dnd->self_only) { - dnd_drag->data_source = NULL; - } else { - dnd_drag->data_source = - display_create_data_source(dnd->display); - if (!dnd_drag->data_source) { - fprintf(stderr, "No data device manager\n"); - abort(); - } - 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); - } - - if (display_get_data_device_manager_version(display) >= - WL_DATA_SOURCE_SET_ACTIONS_SINCE_VERSION) { - wl_data_source_set_actions(dnd_drag->data_source, actions); - } - - 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); - - dnd_drag->opaque = - create_drag_icon(dnd_drag, item, x, y, 1); - dnd_drag->translucent = - create_drag_icon(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); - - return 0; - } else - return -1; -} - -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; -} - -/* Update all the mouse pointers in the window appropriately. - * Optionally, skip one (which will be the current pointer just - * about to start a drag). This is done here to save a scan - * through the pointer list. - */ -static void -update_pointer_images_except(struct dnd *dnd, struct input *except) -{ - struct pointer *pointer; - int32_t x, y; - - wl_list_for_each(pointer, &dnd->pointers, link) { - if (pointer->input == except) { - pointer->dragging = true; - continue; - } - input_get_position(pointer->input, &x, &y); - input_set_pointer_image(pointer->input, - lookup_cursor(dnd, x, y)); - } -} - -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; - - input_get_position(input, &x, &y); - if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - input_ungrab(input); - if (create_drag_source(dnd, input, time, x, y) == 0) { - input_set_pointer_image(input, CURSOR_DRAGGING); - update_pointer_images_except(dnd, input); - } - } -} - -static void -dnd_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 dnd *dnd = data; - int32_t int_x, int_y; - - if (id > 0) - return; - - int_x = (int32_t)x; - int_y = (int32_t)y; - if (create_drag_source(dnd, input, time, int_x, int_y) == 0) - touch_grab(input, 0); -} - -static int -dnd_enter_handler(struct widget *widget, - struct input *input, float x, float y, void *data) -{ - struct dnd *dnd = data; - struct pointer *new_pointer = malloc(sizeof *new_pointer); - - if (new_pointer) { - new_pointer->input = input; - new_pointer->dragging = false; - wl_list_insert(dnd->pointers.prev, &new_pointer->link); - } - - return lookup_cursor(dnd, x, y); -} - -static void -dnd_leave_handler(struct widget *widget, - struct input *input, void *data) -{ - struct dnd *dnd = data; - struct pointer *pointer, *tmp; - - wl_list_for_each_safe(pointer, tmp, &dnd->pointers, link) - if (pointer->input == input) { - wl_list_remove(&pointer->link); - free(pointer); - } -} - -static int -dnd_motion_handler(struct widget *widget, - struct input *input, uint32_t time, - float x, float y, void *data) -{ - struct dnd *dnd = data; - struct pointer *pointer; - - wl_list_for_each(pointer, &dnd->pointers, link) - if (pointer->input == input) { - if (pointer->dragging) - return CURSOR_DRAGGING; - break; - } - - 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); - update_pointer_images_except(dnd, NULL); - 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); - } 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 = window_frame_create(dnd->window, dnd); - window_set_title(dnd->window, "Wayland Drag and Drop Demo"); - - dnd->display = display; - dnd->key = 100; - - wl_list_init(&dnd->pointers); - - 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_leave_handler(dnd->widget, dnd_leave_handler); - widget_set_motion_handler(dnd->widget, dnd_motion_handler); - widget_set_button_handler(dnd->widget, dnd_button_handler); - widget_set_touch_down_handler(dnd->widget, dnd_touch_down_handler); - - width = 4 * (item_width + item_padding) + item_padding; - height = 4 * (item_height + item_padding) + item_padding; - - window_frame_set_child_size(dnd->widget, width, height); - - return dnd; -} - -static void -dnd_destroy(struct dnd *dnd) -{ - widget_destroy(dnd->widget); - window_destroy(dnd->window); - free(dnd); -} - -int -main(int argc, char *argv[]) -{ - struct display *d; - struct dnd *dnd; - int self_only = 0; - - if (argc == 2 && !strcmp(argv[1], "--self-only")) - self_only = 1; - else if (argc > 1) { - printf("Usage: %s [OPTIONS]\n --self-only\n", argv[0]); - return 1; - } - - d = display_create(&argc, argv); - if (d == NULL) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - return -1; - } - - dnd = dnd_create(d); - if (self_only) - dnd->self_only = 1; - - display_run(d); - - dnd_destroy(dnd); - display_destroy(d); - - return 0; -} diff --git a/clients/editor.c b/clients/editor.c deleted file mode 100644 index a59f967..0000000 --- a/clients/editor.c +++ /dev/null @@ -1,1681 +0,0 @@ -/* - * Copyright © 2012 Openismus GmbH - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include "shared/helpers.h" -#include "shared/xalloc.h" -#include "window.h" -#include "text-input-unstable-v1-client-protocol.h" - -struct text_entry { - struct widget *widget; - struct window *window; - char *text; - int active; - bool panel_visible; - 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 zwp_text_input_v1 *text_input; - PangoLayout *layout; - struct { - xkb_mod_mask_t shift_mask; - } keysym; - uint32_t serial; - uint32_t reset_serial; - uint32_t content_purpose; - bool click_to_show; - char *preferred_language; - bool button_pressed; -}; - -struct editor { - struct zwp_text_input_manager_v1 *text_input_manager; - struct wl_data_source *selection; - char *selected_text; - 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 -move_up(const char *p, uint32_t *cursor) -{ - const char *posr, *posr_i; - char text[16]; - - xkb_keysym_to_utf8(XKB_KEY_Return, text, sizeof(text)); - - posr = strstr(p, text); - while (posr) { - if (*cursor > (unsigned)(posr-p)) { - posr_i = strstr(posr+1, text); - if (!posr_i || !(*cursor > (unsigned)(posr_i-p))) { - *cursor = posr-p; - break; - } - posr = posr_i; - } else { - break; - } - } -} - -static void -move_down(const char *p, uint32_t *cursor) -{ - const char *posr; - char text[16]; - - xkb_keysym_to_utf8(XKB_KEY_Return, text, sizeof(text)); - - posr = strstr(p, text); - while (posr) { - if (*cursor <= (unsigned)(posr-p)) { - *cursor = posr-p + 1; - break; - } - posr = strstr(posr+1, text); - } -} - -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 void text_entry_touch_handler(struct widget *widget, struct input *input, - uint32_t serial, uint32_t time, int32_t id, - float tx, float ty, 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 zwp_text_input_v1 *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 zwp_text_input_v1 *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 zwp_text_input_v1 *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 zwp_text_input_v1 *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 zwp_text_input_v1 *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 ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_DEFAULT: - case ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_UNDERLINE: - attr1 = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); - break; - case ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_INCORRECT: - attr1 = pango_attr_underline_new(PANGO_UNDERLINE_ERROR); - attr2 = pango_attr_underline_color_new(65535, 0, 0); - break; - case ZWP_TEXT_INPUT_V1_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 ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_HIGHLIGHT: - case ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_ACTIVE: - attr1 = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); - attr2 = pango_attr_weight_new(PANGO_WEIGHT_BOLD); - break; - case ZWP_TEXT_INPUT_V1_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 zwp_text_input_v1 *text_input, - int32_t index) -{ - struct text_entry *entry = data; - - entry->preedit_info.cursor = index; -} - -static void -text_input_modifiers_map(void *data, - struct zwp_text_input_v1 *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 zwp_text_input_v1 *text_input, - uint32_t serial, - uint32_t time, - uint32_t key, - uint32_t state, - uint32_t modifiers) -{ - struct text_entry *entry = data; - const char *new_char; - - 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_Up || - key == XKB_KEY_Down) { - if (state != WL_KEYBOARD_KEY_STATE_RELEASED) - return; - - if (key == XKB_KEY_Up) - move_up(entry->text, &entry->cursor); - else - move_down(entry->text, &entry->cursor); - - 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; - } - - if (key == XKB_KEY_Tab || - key == XKB_KEY_KP_Enter || - key == XKB_KEY_Return) { - char text[16]; - - if (state != WL_KEYBOARD_KEY_STATE_RELEASED) - return; - - xkb_keysym_to_utf8(key, text, sizeof(text)); - - text_entry_insert_at_cursor(entry, text, 0, 0); - - return; - } -} - -static void -text_input_enter(void *data, - struct zwp_text_input_v1 *text_input, - struct wl_surface *surface) -{ - struct text_entry *entry = data; - - if (surface != window_get_wl_surface(entry->window)) - return; - - entry->active++; - - text_entry_update(entry); - entry->reset_serial = entry->serial; - - widget_schedule_redraw(entry->widget); -} - -static void -text_input_leave(void *data, - struct zwp_text_input_v1 *text_input) -{ - struct text_entry *entry = data; - - text_entry_commit_and_reset(entry); - entry->active--; - - if (!entry->active) { - zwp_text_input_v1_hide_input_panel(text_input); - entry->panel_visible = false; - } - - widget_schedule_redraw(entry->widget); -} - -static void -text_input_input_panel_state(void *data, - struct zwp_text_input_v1 *text_input, - uint32_t state) -{ -} - -static void -text_input_language(void *data, - struct zwp_text_input_v1 *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 zwp_text_input_v1 *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 ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_LTR: - pango_direction = PANGO_DIRECTION_LTR; - break; - case ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_RTL: - pango_direction = PANGO_DIRECTION_RTL; - break; - case ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_AUTO: - default: - pango_direction = PANGO_DIRECTION_NEUTRAL; - } - - pango_context_set_base_dir(context, pango_direction); -} - -static const struct zwp_text_input_v1_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 void -data_source_target(void *data, - struct wl_data_source *source, const char *mime_type) -{ -} - -static void -data_source_send(void *data, - struct wl_data_source *source, - const char *mime_type, int32_t fd) -{ - struct editor *editor = data; - - if (write(fd, editor->selected_text, strlen(editor->selected_text) + 1) < 0) - fprintf(stderr, "write failed: %s\n", strerror(errno)); - - close(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 void -paste_func(void *buffer, size_t len, - int32_t x, int32_t y, void *data) -{ - struct editor *editor = data; - struct text_entry *entry = editor->active_entry; - char *pasted_text; - - if (!entry) - return; - - pasted_text = malloc(len + 1); - strncpy(pasted_text, buffer, len); - pasted_text[len] = '\0'; - - text_entry_insert_at_cursor(entry, pasted_text, 0, 0); - - free(pasted_text); -} - -static void -editor_copy_cut(struct editor *editor, struct input *input, bool cut) -{ - struct text_entry *entry = editor->active_entry; - - if (!entry) - return; - - if (entry->cursor != entry->anchor) { - int start_index = MIN(entry->cursor, entry->anchor); - int end_index = MAX(entry->cursor, entry->anchor); - int len = end_index - start_index; - - editor->selected_text = realloc(editor->selected_text, len + 1); - strncpy(editor->selected_text, &entry->text[start_index], len); - editor->selected_text[len] = '\0'; - - if (cut) - text_entry_delete_text(entry, start_index, len); - - editor->selection = - display_create_data_source(editor->display); - if (!editor->selection) - return; - - wl_data_source_offer(editor->selection, - "text/plain;charset=utf-8"); - wl_data_source_add_listener(editor->selection, - &data_source_listener, editor); - input_set_selection(input, editor->selection, - display_get_serial(editor->display)); - } -} - -static void -editor_paste(struct editor *editor, struct input *input) -{ - input_receive_selection_data(input, - "text/plain;charset=utf-8", - paste_func, editor); -} - -static void -menu_func(void *data, struct input *input, int index) -{ - struct window *window = data; - struct editor *editor = window_get_user_data(window); - - fprintf(stderr, "picked entry %d\n", index); - - switch (index) { - case 0: - editor_copy_cut(editor, input, true); - break; - case 1: - editor_copy_cut(editor, input, false); - break; - case 2: - editor_paste(editor, input); - break; - } -} - -static void -show_menu(struct editor *editor, struct input *input, uint32_t time) -{ - int32_t x, y; - static const char *entries[] = { - "Cut", "Copy", "Paste" - }; - - input_get_position(input, &x, &y); - window_show_menu(editor->display, input, time, editor->window, - x + 10, y + 20, menu_func, - entries, ARRAY_LENGTH(entries)); -} - -static struct text_entry* -text_entry_create(struct editor *editor, const char *text) -{ - struct text_entry *entry; - - entry = xzalloc(sizeof *entry); - - entry->widget = widget_add_widget(editor->widget, entry); - entry->window = editor->window; - entry->text = strdup(text); - entry->active = 0; - entry->panel_visible = false; - entry->cursor = strlen(text); - entry->anchor = entry->cursor; - entry->text_input = - zwp_text_input_manager_v1_create_text_input(editor->text_input_manager); - zwp_text_input_v1_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); - widget_set_touch_down_handler(entry->widget, text_entry_touch_handler); - - return entry; -} - -static void -text_entry_destroy(struct text_entry *entry) -{ - widget_destroy(entry->widget); - zwp_text_input_v1_destroy(entry->text_input); - g_clear_object(&entry->layout); - free(entry->text); - free(entry->preferred_language); - 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) { - entry->panel_visible = !entry->panel_visible; - - if (entry->panel_visible) - zwp_text_input_v1_show_input_panel(entry->text_input); - else - zwp_text_input_v1_hide_input_panel(entry->text_input); - - return; - } - - if (!entry->click_to_show) - zwp_text_input_v1_show_input_panel(entry->text_input); - - zwp_text_input_v1_activate(entry->text_input, - seat, - surface); -} - -static void -text_entry_deactivate(struct text_entry *entry, - struct wl_seat *seat) -{ - zwp_text_input_v1_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 = xmalloc(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; - - zwp_text_input_v1_set_content_type(entry->text_input, - ZWP_TEXT_INPUT_V1_CONTENT_HINT_NONE, - entry->content_purpose); - - zwp_text_input_v1_set_surrounding_text(entry->text_input, - entry->text, - entry->cursor, - entry->anchor); - - if (entry->preferred_language) - zwp_text_input_v1_set_preferred_language(entry->text_input, - entry->preferred_language); - - text_entry_get_cursor_rectangle(entry, &cursor_rectangle); - zwp_text_input_v1_set_cursor_rectangle(entry->text_input, - cursor_rectangle.x, - cursor_rectangle.y, - cursor_rectangle.width, - cursor_rectangle.height); - - zwp_text_input_v1_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 = xmalloc(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); - } - - zwp_text_input_v1_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) - zwp_text_input_v1_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 int -text_offset_left(struct rectangle *allocation) -{ - return 10; -} - -static int -text_offset_top(struct rectangle *allocation) -{ - return allocation->height / 2; -} - -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), - text_offset_top(&allocation)); - - 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; - int tx, ty; - - if (!entry->button_pressed) { - return CURSOR_IBEAM; - } - - widget_get_allocation(entry->widget, &allocation); - - tx = x - allocation.x - text_offset_left(&allocation); - ty = y - allocation.y - text_offset_top(&allocation); - - text_entry_set_cursor_position(entry, tx, ty, 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(&allocation); - y -= allocation.y + text_offset_top(&allocation); - - editor = window_get_user_data(entry->window); - - switch (button) { - case 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); - break; - case BTN_RIGHT: - if (state == WL_POINTER_BUTTON_STATE_PRESSED) - show_menu(editor, input, time); - break; - } - - 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 && - button == BTN_LEFT) { - 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 -text_entry_touch_handler(struct widget *widget, struct input *input, - uint32_t serial, uint32_t time, int32_t id, - float tx, float ty, void *data) -{ - struct text_entry *entry = data; - struct wl_seat *seat = input_get_seat(input); - struct rectangle allocation; - struct editor *editor; - int32_t x, y; - - widget_get_allocation(entry->widget, &allocation); - - x = tx - (allocation.x + text_offset_left(&allocation)); - y = ty - (allocation.y + text_offset_top(&allocation)); - - editor = window_get_user_data(entry->window); - 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 -editor_touch_handler(struct widget *widget, struct input *input, - uint32_t serial, uint32_t time, int32_t id, - float tx, float ty, void *data) -{ - struct editor *editor = data; - - 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 -keyboard_focus_handler(struct window *window, - struct input *device, void *data) -{ - struct editor *editor = data; - - window_schedule_redraw(editor->window); -} - -static int -handle_bound_key(struct editor *editor, - struct input *input, uint32_t sym, uint32_t time) -{ - switch (sym) { - case XKB_KEY_X: - editor_copy_cut(editor, input, true); - return 1; - case XKB_KEY_C: - editor_copy_cut(editor, input, false); - return 1; - case XKB_KEY_V: - editor_paste(editor, input); - 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 editor *editor = data; - struct text_entry *entry; - const char *new_char; - char text[16]; - uint32_t modifiers; - - if (!editor->active_entry) - return; - - entry = editor->active_entry; - - if (state != WL_KEYBOARD_KEY_STATE_PRESSED) - return; - - modifiers = input_get_modifiers(input); - if ((modifiers & MOD_CONTROL_MASK) && - (modifiers & MOD_SHIFT_MASK) && - handle_bound_key(editor, input, sym, time)) - 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_Up: - text_entry_commit_and_reset(entry); - - move_up(entry->text, &entry->cursor); - if (!(input_get_modifiers(input) & MOD_SHIFT_MASK)) - entry->anchor = entry->cursor; - widget_schedule_redraw(entry->widget); - break; - case XKB_KEY_Down: - text_entry_commit_and_reset(entry); - - move_down(entry->text, &entry->cursor); - 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, "zwp_text_input_manager_v1")) { - editor->text_input_manager = - display_bind(display, name, - &zwp_text_input_manager_v1_interface, 1); - } -} - -/** Display help for command line options, and exit */ -static bool opt_help = false; - -/** Require a distinct click to show the input panel (virtual keyboard) */ -static bool opt_click_to_show = false; - -/** Set a specific (RFC-3066) language. Used for the virtual keyboard, etc. */ -static const char *opt_preferred_language = NULL; - -/** - * \brief command line options for editor - */ -static const struct weston_option editor_options[] = { - { WESTON_OPTION_BOOLEAN, "help", 'h', &opt_help }, - { WESTON_OPTION_BOOLEAN, "click-to-show", 'C', &opt_click_to_show }, - { WESTON_OPTION_STRING, "preferred-language", 'L', &opt_preferred_language }, -}; - -static void -usage(const char *program_name, int exit_code) -{ - unsigned k; - - fprintf(stderr, "Usage: %s [OPTIONS] [FILENAME]\n\n", program_name); - for (k = 0; k < ARRAY_LENGTH(editor_options); k++) { - const struct weston_option *p = &editor_options[k]; - if (p->name) { - fprintf(stderr, " --%s", p->name); - if (p->type != WESTON_OPTION_BOOLEAN) - fprintf(stderr, "=VALUE"); - fprintf(stderr, "\n"); - } - if (p->short_name) { - fprintf(stderr, " -%c", p->short_name); - if (p->type != WESTON_OPTION_BOOLEAN) - fprintf(stderr, "VALUE"); - fprintf(stderr, "\n"); - } - } - exit(exit_code); -} - -/* Load the contents of a file into a UTF-8 text buffer and return it. - * - * Caller is responsible for freeing the buffer when done. - * On error, returns NULL. - */ -static char * -read_file(char *filename) -{ - char *buffer = NULL; - int buf_size, read_size; - FILE *fin; - int errsv; - - fin = fopen(filename, "r"); - if (fin == NULL) - goto error; - - /* Determine required buffer size */ - if (fseek(fin, 0, SEEK_END) != 0) - goto error; - buf_size = ftell(fin); - if (buf_size < 0) - goto error; - rewind(fin); - - /* Create buffer and read in the text */ - buffer = (char*) malloc(sizeof(char) * (buf_size + 1)); - if (buffer == NULL) - goto error; - read_size = fread(buffer, sizeof(char), buf_size, fin); - fclose(fin); - if (buf_size != read_size) - goto error; - buffer[buf_size] = '\0'; - - return buffer; - -error: - errsv = errno; - if (fin) - fclose(fin); - free(buffer); - errno = errsv ? errsv : EINVAL; - - return NULL; -} - -int -main(int argc, char *argv[]) -{ - struct editor editor; - char *text_buffer = NULL; - - parse_options(editor_options, ARRAY_LENGTH(editor_options), - &argc, argv); - if (opt_help) - usage(argv[0], EXIT_SUCCESS); - - if (argc > 1) { - if (argv[1][0] == '-') - usage(argv[0], EXIT_FAILURE); - - text_buffer = read_file(argv[1]); - if (text_buffer == NULL) { - fprintf(stderr, "could not read file '%s': %s\n", - argv[1], strerror(errno)); - return -1; - } - } - - memset(&editor, 0, sizeof editor); - - editor.display = display_create(&argc, argv); - if (editor.display == NULL) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - free(text_buffer); - return -1; - } - - display_set_user_data(editor.display, &editor); - display_set_global_handler(editor.display, global_handler); - - if (editor.text_input_manager == NULL) { - fprintf(stderr, "No text input manager global\n"); - display_destroy(editor.display); - free(text_buffer); - return -1; - } - - editor.window = window_create(editor.display); - editor.widget = window_frame_create(editor.window, &editor); - - if (text_buffer) - editor.entry = text_entry_create(&editor, text_buffer); - else - editor.entry = text_entry_create(&editor, "Entry"); - editor.entry->click_to_show = opt_click_to_show; - if (opt_preferred_language) - editor.entry->preferred_language = strdup(opt_preferred_language); - editor.editor = text_entry_create(&editor, "Numeric"); - editor.editor->content_purpose = ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_NUMBER; - editor.editor->click_to_show = opt_click_to_show; - editor.selection = NULL; - editor.selected_text = NULL; - - window_set_title(editor.window, "Text Editor"); - window_set_key_handler(editor.window, key_handler); - window_set_keyboard_focus_handler(editor.window, - keyboard_focus_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); - widget_set_touch_down_handler(editor.widget, editor_touch_handler); - - window_schedule_resize(editor.window, 500, 400); - - display_run(editor.display); - - if (editor.selected_text) - free(editor.selected_text); - if (editor.selection) - wl_data_source_destroy(editor.selection); - text_entry_destroy(editor.entry); - text_entry_destroy(editor.editor); - widget_destroy(editor.widget); - window_destroy(editor.window); - display_destroy(editor.display); - free(text_buffer); - - return 0; -} diff --git a/clients/eventdemo.c b/clients/eventdemo.c deleted file mode 100644 index 0a1a7ca..0000000 --- a/clients/eventdemo.c +++ /dev/null @@ -1,541 +0,0 @@ -/* - * Copyright © 2011 Tim Wiederhake - * - * 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. - */ - -/** - * \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 "config.h" - -#include -#include -#include -#include -#include -#include - -#include - -#include "shared/helpers.h" -#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 bool noborder = false; - -/** 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 bool log_redraw = false; - -/** set to log resizing */ -static bool log_resize = false; - -/** set to log keyboard focus */ -static bool log_focus = false; - -/** set to log key events */ -static bool log_key = false; - -/** set to log button events */ -static bool log_button = false; - -/** set to log axis events */ -static bool log_axis = false; - -/** set to log motion events */ -static bool log_motion = false; - -/** - * \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; - - bool print_pointer_frame; -}; - -/** - * \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 input input - * \param time time - * \param key keycode - * \param unicode associated character - * \param state pressed or released - * \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: %u, unicode: %u, 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) -{ - struct eventdemo *e = data; - int32_t x, y; - - if (!log_button) - return; - - e->print_pointer_frame = true; - - input_get_position(input, &x, &y); - printf("button time: %u, button: %u, 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) -{ - struct eventdemo *e = data; - - if (!log_axis) - return; - - e->print_pointer_frame = true; - - printf("axis time: %u, axis: %s, value: %f\n", - time, - axis == WL_POINTER_AXIS_VERTICAL_SCROLL ? "vertical" : - "horizontal", - wl_fixed_to_double(value)); -} - -static void -pointer_frame_handler(struct widget *widget, struct input *input, void *data) -{ - struct eventdemo *e = data; - - if (!e->print_pointer_frame) - return; - - printf("pointer frame\n"); - e->print_pointer_frame = false; -} - -static void -axis_source_handler(struct widget *widget, struct input *input, - uint32_t source, void *data) -{ - const char *axis_source; - struct eventdemo *e = data; - - if (!log_axis) - return; - - e->print_pointer_frame = true; - - switch (source) { - case WL_POINTER_AXIS_SOURCE_WHEEL: - axis_source = "wheel"; - break; - case WL_POINTER_AXIS_SOURCE_FINGER: - axis_source = "finger"; - break; - case WL_POINTER_AXIS_SOURCE_CONTINUOUS: - axis_source = "continuous"; - break; - default: - axis_source = ""; - break; - } - - printf("axis source: %s\n", axis_source); -} - -static void -axis_stop_handler(struct widget *widget, struct input *input, - uint32_t time, uint32_t axis, - void *data) -{ - struct eventdemo *e = data; - - if (!log_axis) - return; - - e->print_pointer_frame = true; - printf("axis stop time: %u, axis: %s\n", - time, - axis == WL_POINTER_AXIS_VERTICAL_SCROLL ? "vertical" : - "horizontal"); -} - -static void -axis_discrete_handler(struct widget *widget, struct input *input, - uint32_t axis, int32_t discrete, void *data) -{ - struct eventdemo *e = data; - - if (!log_axis) - return; - - e->print_pointer_frame = true; - printf("axis discrete axis: %u value: %d\n", axis, discrete); -} - -/** - * \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 x x position relative to the window - * \param y 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: %u, x: %f, y: %f\n", time, x, y); - e->print_pointer_frame = true; - } - - 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 = zalloc(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 = window_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 pointer frame handler for the window */ - widget_set_pointer_frame_handler(e->widget, pointer_frame_handler); - - /* Set the callback axis handler for the window */ - widget_set_axis_handlers(e->widget, - axis_handler, - axis_source_handler, - axis_stop_handler, - axis_discrete_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; - - if (parse_options(eventdemo_options, - ARRAY_LENGTH(eventdemo_options), &argc, argv) > 1) { - unsigned k; - printf("Usage: %s [OPTIONS]\n\n", argv[0]); - for (k = 0; k < ARRAY_LENGTH(eventdemo_options); k++) { - const struct weston_option* p = &eventdemo_options[k]; - if (p->name) { - printf(" --%s", p->name); - if (p->type != WESTON_OPTION_BOOLEAN) - printf("=VALUE"); - putchar('\n'); - } - if (p->short_name) { - printf(" -%c", p->short_name); - if (p->type != WESTON_OPTION_BOOLEAN) - printf("VALUE"); - putchar('\n'); - } - } - return 1; - } - - if (!log_redraw && !log_resize && !log_focus && !log_key && - !log_button && !log_axis && !log_motion) - log_redraw = log_resize = log_focus = log_key = - log_button = log_axis = log_motion = true; - - /* Connect to the display and have the arguments parsed */ - d = display_create(&argc, argv); - if (d == NULL) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - return -1; - } - - /* Create new eventdemo window */ - e = eventdemo_create(d); - if (e == NULL) { - fprintf(stderr, "failed to create eventdemo: %s\n", - strerror(errno)); - 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 deleted file mode 100644 index e3471ce..0000000 --- a/clients/flower.c +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#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; - - /* Don't 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_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: %s\n", - strerror(errno)); - 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); - - widget_destroy(flower.widget); - window_destroy(flower.window); - display_destroy(d); - - return 0; -} diff --git a/clients/fullscreen.c b/clients/fullscreen.c deleted file mode 100644 index 1b44ad5..0000000 --- a/clients/fullscreen.c +++ /dev/null @@ -1,581 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include "window.h" -#include "fullscreen-shell-unstable-v1-client-protocol.h" -#include - -struct fs_output { - struct wl_list link; - struct output *output; -}; - -struct fullscreen { - struct display *display; - struct window *window; - struct widget *widget; - struct zwp_fullscreen_shell_v1 *fshell; - enum zwp_fullscreen_shell_v1_present_method present_method; - int width, height; - int fullscreen; - float pointer_x, pointer_y; - int draw_cursor; - - struct wl_list output_list; - struct fs_output *current_output; -}; - -static void -fullscreen_handler(struct window *window, void *data) -{ - struct fullscreen *fullscreen = data; - - fullscreen->fullscreen ^= 1; - window_set_fullscreen(window, fullscreen->fullscreen); -} - -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", "center", "zoom", "zoom_crop", "stretch"}; - - 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); - - if (fullscreen->fshell) { - draw_string(cr, - "Surface size: %d, %d\n" - "Scale: %d, transform: %d\n" - "Pointer: %f,%f\n" - "Output: %s, present method: %s\n" - "Keys: (s)cale, (t)ransform, si(z)e, (m)ethod,\n" - " (o)utput, modes(w)itch, (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, - method_name[fullscreen->present_method], - fullscreen->current_output ? output_get_model(fullscreen->current_output->output): "null"); - } else { - draw_string(cr, - "Surface size: %d, %d\n" - "Scale: %d, transform: %d\n" - "Pointer: %f,%f\n" - "Fullscreen: %d\n" - "Keys: (s)cale, (t)ransform, si(z)e, (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); - } - - 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 (window_has_focus(fullscreen->window) && - 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; - } - - if (window_has_focus(fullscreen->window) && fullscreen->draw_cursor) { - cairo_set_source_rgb(cr, 1, 1, 1); - cairo_set_line_width (cr, 8); - cairo_move_to(cr, - fullscreen->pointer_x - 12, - fullscreen->pointer_y - 12); - cairo_line_to(cr, - fullscreen->pointer_x + 12, - fullscreen->pointer_y + 12); - cairo_stroke(cr); - - cairo_move_to(cr, - fullscreen->pointer_x + 12, - fullscreen->pointer_y - 12); - cairo_line_to(cr, - fullscreen->pointer_x - 12, - fullscreen->pointer_y + 12); - cairo_stroke(cr); - - cairo_set_source_rgb(cr, 0, 0, 0); - cairo_set_line_width (cr, 4); - cairo_move_to(cr, - fullscreen->pointer_x - 10, - fullscreen->pointer_y - 10); - cairo_line_to(cr, - fullscreen->pointer_x + 10, - fullscreen->pointer_y + 10); - cairo_stroke(cr); - - cairo_move_to(cr, - fullscreen->pointer_x + 10, - fullscreen->pointer_y - 10); - cairo_line_to(cr, - fullscreen->pointer_x - 10, - fullscreen->pointer_y + 10); - cairo_stroke(cr); - } - - 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; - struct fs_output *fsout; - struct wl_output *wl_output; - 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: - if (fullscreen->fullscreen) - break; - - 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: - if (!fullscreen->fshell) - break; - - wl_output = NULL; - if (fullscreen->current_output) - wl_output = output_get_wl_output(fullscreen->current_output->output); - fullscreen->present_method = (fullscreen->present_method + 1) % 5; - zwp_fullscreen_shell_v1_present_surface(fullscreen->fshell, - window_get_wl_surface(fullscreen->window), - fullscreen->present_method, - wl_output); - window_schedule_redraw(window); - break; - - case XKB_KEY_o: - if (!fullscreen->fshell) - break; - - fsout = fullscreen->current_output; - wl_output = fsout ? output_get_wl_output(fsout->output) : NULL; - - /* Clear the current presentation */ - zwp_fullscreen_shell_v1_present_surface(fullscreen->fshell, NULL, - 0, wl_output); - - if (fullscreen->current_output) { - if (fullscreen->current_output->link.next == &fullscreen->output_list) - fsout = NULL; - else - fsout = wl_container_of(fullscreen->current_output->link.next, - fsout, link); - } else { - fsout = wl_container_of(fullscreen->output_list.next, - fsout, link); - } - - fullscreen->current_output = fsout; - wl_output = fsout ? output_get_wl_output(fsout->output) : NULL; - zwp_fullscreen_shell_v1_present_surface(fullscreen->fshell, - window_get_wl_surface(fullscreen->window), - fullscreen->present_method, - wl_output); - window_schedule_redraw(window); - break; - - case XKB_KEY_w: - if (!fullscreen->fshell || !fullscreen->current_output) - break; - - wl_output = NULL; - if (fullscreen->current_output) - wl_output = output_get_wl_output(fullscreen->current_output->output); - zwp_fullscreen_shell_mode_feedback_v1_destroy( - zwp_fullscreen_shell_v1_present_surface_for_mode(fullscreen->fshell, - window_get_wl_surface(fullscreen->window), - wl_output, 0)); - window_schedule_redraw(window); - break; - - case XKB_KEY_f: - if (fullscreen->fshell) - break; - 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 fullscreen->draw_cursor ? CURSOR_BLANK : CURSOR_LEFT_PTR; -} - -static int -enter_handler(struct widget *widget, - struct input *input, - float x, float y, void *data) -{ - struct fullscreen *fullscreen = data; - - fullscreen->pointer_x = x; - fullscreen->pointer_y = y; - - widget_schedule_redraw(widget); - - return fullscreen->draw_cursor ? CURSOR_BLANK : 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 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_move(fullscreen->window, input, display_get_serial(fullscreen->display)); -} - -static void -fshell_capability_handler(void *data, struct zwp_fullscreen_shell_v1 *fshell, - uint32_t capability) -{ - struct fullscreen *fullscreen = data; - - switch (capability) { - case ZWP_FULLSCREEN_SHELL_V1_CAPABILITY_CURSOR_PLANE: - fullscreen->draw_cursor = 0; - break; - default: - break; - } -} - -struct zwp_fullscreen_shell_v1_listener fullscreen_shell_listener = { - fshell_capability_handler -}; - -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); -} - -static void -output_handler(struct output *output, void *data) -{ - struct fullscreen *fullscreen = data; - struct fs_output *fsout; - - /* If we've already seen this one, don't add it to the list */ - wl_list_for_each(fsout, &fullscreen->output_list, link) - if (fsout->output == output) - return; - - fsout = zalloc(sizeof *fsout); - if (fsout == NULL) { - fprintf(stderr, "out of memory in output_handler\n"); - return; - } - fsout->output = output; - wl_list_insert(&fullscreen->output_list, &fsout->link); -} - -static void -global_handler(struct display *display, uint32_t id, const char *interface, - uint32_t version, void *data) -{ - struct fullscreen *fullscreen = data; - - if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { - fullscreen->fshell = display_bind(display, id, - &zwp_fullscreen_shell_v1_interface, - 1); - zwp_fullscreen_shell_v1_add_listener(fullscreen->fshell, - &fullscreen_shell_listener, - fullscreen); - } -} - -int main(int argc, char *argv[]) -{ - struct fullscreen fullscreen; - struct display *d; - int i; - - fullscreen.width = 640; - fullscreen.height = 480; - fullscreen.fullscreen = 0; - fullscreen.present_method = ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT; - wl_list_init(&fullscreen.output_list); - fullscreen.current_output = NULL; - - 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: %s\n", - strerror(errno)); - return -1; - } - - fullscreen.display = d; - fullscreen.fshell = NULL; - display_set_user_data(fullscreen.display, &fullscreen); - display_set_global_handler(fullscreen.display, global_handler); - display_set_output_configure_handler(fullscreen.display, output_handler); - - if (fullscreen.fshell) { - fullscreen.window = window_create_custom(d); - zwp_fullscreen_shell_v1_present_surface(fullscreen.fshell, - window_get_wl_surface(fullscreen.window), - fullscreen.present_method, - NULL); - /* If we get the CURSOR_PLANE capability, we'll change this */ - fullscreen.draw_cursor = 1; - } else { - fullscreen.window = window_create(d); - fullscreen.draw_cursor = 0; - } - - fullscreen.widget = - window_add_widget(fullscreen.window, &fullscreen); - - window_set_title(fullscreen.window, "Fullscreen"); - - widget_set_transparent(fullscreen.widget, 0); - - widget_set_default_cursor(fullscreen.widget, CURSOR_LEFT_PTR); - 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_enter_handler(fullscreen.widget, enter_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); - - widget_destroy(fullscreen.widget); - window_destroy(fullscreen.window); - display_destroy(d); - - return 0; -} diff --git a/clients/gears.c b/clients/gears.c deleted file mode 100644 index 6090a85..0000000 --- a/clients/gears.c +++ /dev/null @@ -1,503 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#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; - static bool first_call = true; - - if (first_call) { - gears->last_fps = time; - first_call = false; - } else - 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; - int i; - - gears = zalloc(sizeof *gears); - gears->d = display; - gears->window = window_create(display); - gears->widget = window_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; - - 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; -} - -static void -gears_destroy(struct gears *gears) -{ - widget_destroy(gears->widget); - window_destroy(gears->window); - free(gears); -} - -int main(int argc, char *argv[]) -{ - struct display *d; - struct gears *gears; - - d = display_create(&argc, argv); - if (d == NULL) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - return -1; - } - gears = gears_create(d); - display_run(d); - - gears_destroy(gears); - display_destroy(d); - - return 0; -} diff --git a/clients/image.c b/clients/image.c deleted file mode 100644 index 0a8fb5b..0000000 --- a/clients/image.c +++ /dev/null @@ -1,437 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * Copyright © 2009 Chris Wilson - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#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.height) { - 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(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) { - free(image->filename); - free(image); - return NULL; - } - - image->window = window_create(display); - image->widget = window_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; - - if (argc <= 1 || argv[1][0]=='-') { - printf("Usage: %s image...\n", argv[0]); - return 1; - } - - d = display_create(&argc, argv); - if (d == NULL) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - return -1; - } - - for (i = 1; i < argc; i++) - image_create(d, argv[i], &image_counter); - - if (image_counter > 0) - display_run(d); - - display_destroy(d); - - return 0; -} diff --git a/clients/ivi-shell-user-interface.c b/clients/ivi-shell-user-interface.c deleted file mode 100644 index 7d2d1a2..0000000 --- a/clients/ivi-shell-user-interface.c +++ /dev/null @@ -1,1338 +0,0 @@ -/* - * Copyright (C) 2013 DENSO CORPORATION - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "shared/cairo-util.h" -#include -#include "shared/helpers.h" -#include "shared/os-compatibility.h" -#include "shared/xalloc.h" -#include -#include "shared/file-util.h" -#include "ivi-application-client-protocol.h" -#include "ivi-hmi-controller-client-protocol.h" - -/** - * A reference implementation how to use ivi-hmi-controller interface to - * interact with hmi-controller. This is launched from hmi-controller by using - * hmi_client_start and create a pthread. - * - * The basic flow is as followed, - * 1/ read configuration from weston.ini. - * 2/ draw png file to surface according to configuration of weston.ini - * 3/ set up UI by using ivi-hmi-controller protocol - * 4/ Enter event loop - * 5/ If a surface receives touch/pointer event, followings are invoked - * according to type of event and surface - * 5-1/ If a surface to launch ivi_application receive touch up, it execs - * ivi-application configured in weston.ini. - * 5-2/ If a surface to switch layout mode receive touch up, it sends a request, - * ivi_hmi_controller_switch_mode, to hmi-controller. - * 5-3/ If a surface to show workspace having launchers, it sends a request, - * ivi_hmi_controller_home, to hmi-controller. - * 5-4/ If touch down events happens in workspace, - * ivi_hmi_controller_workspace_control is sent to slide workspace. - * When control finished, event: ivi_hmi_controller_workspace_end_control - * is received. - */ - -/***************************************************************************** - * structure, globals - ****************************************************************************/ -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 -}; -struct wlContextCommon { - struct wl_display *wlDisplay; - struct wl_registry *wlRegistry; - struct wl_compositor *wlCompositor; - struct wl_shm *wlShm; - uint32_t formats; - struct wl_seat *wlSeat; - struct wl_pointer *wlPointer; - struct wl_touch *wlTouch; - struct ivi_application *iviApplication; - struct ivi_hmi_controller *hmiCtrl; - struct hmi_homescreen_setting *hmi_setting; - struct wl_list list_wlContextStruct; - struct wl_surface *enterSurface; - int32_t is_home_on; - struct wl_cursor_theme *cursor_theme; - struct wl_cursor **cursors; - struct wl_surface *pointer_surface; - enum cursor_type current_cursor; - uint32_t enter_serial; -}; - -struct wlContextStruct { - struct wlContextCommon *cmm; - struct wl_surface *wlSurface; - struct wl_buffer *wlBuffer; - cairo_surface_t *ctx_image; - void *data; - uint32_t id_surface; - struct wl_list link; -}; - -struct -hmi_homescreen_srf { - uint32_t id; - char *filePath; - uint32_t color; -}; - -struct -hmi_homescreen_workspace { - struct wl_array launcher_id_array; - struct wl_list link; -}; - -struct -hmi_homescreen_launcher { - uint32_t icon_surface_id; - uint32_t workspace_id; - char *icon; - char *path; - struct wl_list link; -}; - -struct -hmi_homescreen_setting { - struct hmi_homescreen_srf background; - struct hmi_homescreen_srf panel; - struct hmi_homescreen_srf tiling; - struct hmi_homescreen_srf sidebyside; - struct hmi_homescreen_srf fullscreen; - struct hmi_homescreen_srf random; - struct hmi_homescreen_srf home; - struct hmi_homescreen_srf workspace_background; - - struct wl_list workspace_list; - struct wl_list launcher_list; - - char *cursor_theme; - int32_t cursor_size; - uint32_t transition_duration; - uint32_t surface_id_offset; - int32_t screen_num; -}; - -/***************************************************************************** - * Event Handler - ****************************************************************************/ - -static void -shm_format(void *data, struct wl_shm *pWlShm, uint32_t format) -{ - struct wlContextCommon *pCtx = data; - - pCtx->formats |= (1 << format); -} - -static struct wl_shm_listener shm_listenter = { - shm_format -}; - -static int32_t -getIdOfWlSurface(struct wlContextCommon *pCtx, struct wl_surface *wlSurface) -{ - struct wlContextStruct *pWlCtxSt = NULL; - - if (NULL == pCtx || NULL == wlSurface ) - return 0; - - wl_list_for_each(pWlCtxSt, &pCtx->list_wlContextStruct, link) { - if (pWlCtxSt->wlSurface == wlSurface) - return pWlCtxSt->id_surface; - } - - return -1; -} - -static void -set_pointer_image(struct wlContextCommon *pCtx, uint32_t index) -{ - struct wl_cursor *cursor = NULL; - struct wl_cursor_image *image = NULL; - struct wl_buffer *buffer = NULL; - - if (!pCtx->wlPointer || !pCtx->cursors) - return; - - if (CURSOR_BLANK == pCtx->current_cursor) { - wl_pointer_set_cursor(pCtx->wlPointer, pCtx->enter_serial, - NULL, 0, 0); - return; - } - - cursor = pCtx->cursors[pCtx->current_cursor]; - if (!cursor) - return; - - if (cursor->image_count <= index) { - 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(pCtx->wlPointer, pCtx->enter_serial, - pCtx->pointer_surface, - image->hotspot_x, image->hotspot_y); - - wl_surface_attach(pCtx->pointer_surface, buffer, 0, 0); - - wl_surface_damage(pCtx->pointer_surface, 0, 0, - image->width, image->height); - - wl_surface_commit(pCtx->pointer_surface); -} - -static void -PointerHandleEnter(void *data, struct wl_pointer *wlPointer, uint32_t serial, - struct wl_surface *wlSurface, wl_fixed_t sx, wl_fixed_t sy) -{ - struct wlContextCommon *pCtx = data; - - pCtx->enter_serial = serial; - pCtx->enterSurface = wlSurface; - set_pointer_image(pCtx, 0); -#ifdef _DEBUG - printf("ENTER PointerHandleEnter: x(%d), y(%d)\n", sx, sy); -#endif -} - -static void -PointerHandleLeave(void *data, struct wl_pointer *wlPointer, uint32_t serial, - struct wl_surface *wlSurface) -{ - struct wlContextCommon *pCtx = data; - - pCtx->enterSurface = NULL; - -#ifdef _DEBUG - printf("ENTER PointerHandleLeave: serial(%d)\n", serial); -#endif -} - -static void -PointerHandleMotion(void *data, struct wl_pointer *wlPointer, uint32_t time, - wl_fixed_t sx, wl_fixed_t sy) -{ -#ifdef _DEBUG - printf("ENTER PointerHandleMotion: x(%d), y(%d)\n", sx, sy); -#endif -} - -/** - * if a surface assigned as launcher receives touch-off event, invoking - * ivi-application which configured in weston.ini with path to binary. - */ -extern char **environ; /*defied by libc */ - -static pid_t -execute_process(char *path, char *argv[]) -{ - pid_t pid = fork(); - if (pid < 0) - fprintf(stderr, "Failed to fork\n"); - - if (pid) - return pid; - - if (-1 == execve(path, argv, environ)) { - fprintf(stderr, "Failed to execve %s\n", path); - exit(1); - } - - return pid; -} - -static int32_t -launcher_button(uint32_t surfaceId, struct wl_list *launcher_list) -{ - struct hmi_homescreen_launcher *launcher = NULL; - - wl_list_for_each(launcher, launcher_list, link) { - char *argv[] = { NULL }; - - if (surfaceId != launcher->icon_surface_id) - continue; - - execute_process(launcher->path, argv); - - return 1; - } - - return 0; -} - -/** - * is-method to identify a surface set as launcher in workspace or workspace - * itself. This is-method is used to decide whether request; - * ivi_hmi_controller_workspace_control is sent or not. - */ -static int32_t -isWorkspaceSurface(uint32_t id, struct hmi_homescreen_setting *hmi_setting) -{ - struct hmi_homescreen_launcher *launcher = NULL; - - if (id == hmi_setting->workspace_background.id) - return 1; - - wl_list_for_each(launcher, &hmi_setting->launcher_list, link) { - if (id == launcher->icon_surface_id) - return 1; - } - - return 0; -} - -/** - * Decide which request is sent to hmi-controller - */ -static void -touch_up(struct ivi_hmi_controller *hmi_ctrl, uint32_t id_surface, - int32_t *is_home_on, struct hmi_homescreen_setting *hmi_setting) -{ - if (launcher_button(id_surface, &hmi_setting->launcher_list)) { - *is_home_on = 0; - ivi_hmi_controller_home(hmi_ctrl, IVI_HMI_CONTROLLER_HOME_OFF); - } else if (id_surface == hmi_setting->tiling.id) { - ivi_hmi_controller_switch_mode(hmi_ctrl, - IVI_HMI_CONTROLLER_LAYOUT_MODE_TILING); - } else if (id_surface == hmi_setting->sidebyside.id) { - ivi_hmi_controller_switch_mode(hmi_ctrl, - IVI_HMI_CONTROLLER_LAYOUT_MODE_SIDE_BY_SIDE); - } else if (id_surface == hmi_setting->fullscreen.id) { - ivi_hmi_controller_switch_mode(hmi_ctrl, - IVI_HMI_CONTROLLER_LAYOUT_MODE_FULL_SCREEN); - } else if (id_surface == hmi_setting->random.id) { - ivi_hmi_controller_switch_mode(hmi_ctrl, - IVI_HMI_CONTROLLER_LAYOUT_MODE_RANDOM); - } else if (id_surface == hmi_setting->home.id) { - *is_home_on = !(*is_home_on); - if (*is_home_on) { - ivi_hmi_controller_home(hmi_ctrl, - IVI_HMI_CONTROLLER_HOME_ON); - } else { - ivi_hmi_controller_home(hmi_ctrl, - IVI_HMI_CONTROLLER_HOME_OFF); - } - } -} - -/** - * Even handler of Pointer event. IVI system is usually manipulated by touch - * screen. However, some systems also have pointer device. - * Release is the same behavior as touch off - * Pressed is the same behavior as touch on - */ -static void -PointerHandleButton(void *data, struct wl_pointer *wlPointer, uint32_t serial, - uint32_t time, uint32_t button, uint32_t state) -{ - struct wlContextCommon *pCtx = data; - struct ivi_hmi_controller *hmi_ctrl = pCtx->hmiCtrl; - const uint32_t id_surface = getIdOfWlSurface(pCtx, pCtx->enterSurface); - - if (BTN_RIGHT == button) - return; - - switch (state) { - case WL_POINTER_BUTTON_STATE_RELEASED: - touch_up(hmi_ctrl, id_surface, &pCtx->is_home_on, - pCtx->hmi_setting); - break; - - case WL_POINTER_BUTTON_STATE_PRESSED: - - if (isWorkspaceSurface(id_surface, pCtx->hmi_setting)) { - ivi_hmi_controller_workspace_control(hmi_ctrl, - pCtx->wlSeat, - serial); - } - - break; - } -#ifdef _DEBUG - printf("ENTER PointerHandleButton: button(%d), state(%d)\n", - button, state); -#endif -} - -static void -PointerHandleAxis(void *data, struct wl_pointer *wlPointer, uint32_t time, - uint32_t axis, wl_fixed_t value) -{ -#ifdef _DEBUG - printf("ENTER PointerHandleAxis: axis(%d), value(%d)\n", axis, value); -#endif -} - -static struct wl_pointer_listener pointer_listener = { - PointerHandleEnter, - PointerHandleLeave, - PointerHandleMotion, - PointerHandleButton, - PointerHandleAxis -}; - -/** - * Even handler of touch event - */ -static void -TouchHandleDown(void *data, struct wl_touch *wlTouch, uint32_t serial, - uint32_t time, struct wl_surface *surface, int32_t id, - wl_fixed_t x_w, wl_fixed_t y_w) -{ - struct wlContextCommon *pCtx = data; - struct ivi_hmi_controller *hmi_ctrl = pCtx->hmiCtrl; - uint32_t id_surface = 0; - - if (0 == id) - pCtx->enterSurface = surface; - - id_surface = getIdOfWlSurface(pCtx, pCtx->enterSurface); - - /** - * When touch down happens on surfaces of workspace, ask - * hmi-controller to start control workspace to select page of - * workspace. After sending seat to hmi-controller by - * ivi_hmi_controller_workspace_control, - * hmi-controller-homescreen doesn't receive any event till - * hmi-controller sends back it. - */ - if (isWorkspaceSurface(id_surface, pCtx->hmi_setting)) { - ivi_hmi_controller_workspace_control(hmi_ctrl, pCtx->wlSeat, - serial); - } -} - -static void -TouchHandleUp(void *data, struct wl_touch *wlTouch, uint32_t serial, - uint32_t time, int32_t id) -{ - struct wlContextCommon *pCtx = data; - struct ivi_hmi_controller *hmi_ctrl = pCtx->hmiCtrl; - - const uint32_t id_surface = getIdOfWlSurface(pCtx, pCtx->enterSurface); - - /** - * triggering event according to touch-up happening on which surface. - */ - if (id == 0){ - touch_up(hmi_ctrl, id_surface, &pCtx->is_home_on, - pCtx->hmi_setting); - } -} - -static void -TouchHandleMotion(void *data, struct wl_touch *wlTouch, uint32_t time, - int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) -{ -} - -static void -TouchHandleFrame(void *data, struct wl_touch *wlTouch) -{ -} - -static void -TouchHandleCancel(void *data, struct wl_touch *wlTouch) -{ -} - -static struct wl_touch_listener touch_listener = { - TouchHandleDown, - TouchHandleUp, - TouchHandleMotion, - TouchHandleFrame, - TouchHandleCancel, -}; - -/** - * Handler of capabilities - */ -static void -seat_handle_capabilities(void *data, struct wl_seat *seat, uint32_t caps) -{ - struct wlContextCommon *p_wlCtx = (struct wlContextCommon*)data; - struct wl_seat *wlSeat = p_wlCtx->wlSeat; - struct wl_pointer *wlPointer = p_wlCtx->wlPointer; - struct wl_touch *wlTouch = p_wlCtx->wlTouch; - - if (p_wlCtx->hmi_setting->cursor_theme) { - if ((caps & WL_SEAT_CAPABILITY_POINTER) && !wlPointer){ - wlPointer = wl_seat_get_pointer(wlSeat); - wl_pointer_add_listener(wlPointer, - &pointer_listener, data); - } else - if (!(caps & WL_SEAT_CAPABILITY_POINTER) && wlPointer){ - wl_pointer_destroy(wlPointer); - wlPointer = NULL; - } - p_wlCtx->wlPointer = wlPointer; - } - - if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !wlTouch){ - wlTouch = wl_seat_get_touch(wlSeat); - wl_touch_add_listener(wlTouch, &touch_listener, data); - } else - if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && wlTouch){ - wl_touch_destroy(wlTouch); - wlTouch = NULL; - } - p_wlCtx->wlTouch = wlTouch; -} - -static struct wl_seat_listener seat_Listener = { - seat_handle_capabilities, -}; - -/** - * Registration of event - * This event is received when hmi-controller server finished controlling - * workspace. - */ -static void -ivi_hmi_controller_workspace_end_control(void *data, - struct ivi_hmi_controller *hmi_ctrl, - int32_t is_controlled) -{ - struct wlContextCommon *pCtx = data; - const uint32_t id_surface = getIdOfWlSurface(pCtx, pCtx->enterSurface); - - if (is_controlled) - return; - - /** - * During being controlled by hmi-controller, any input event is not - * notified. So when control ends with touch up, it invokes launcher - * if up event happens on a launcher surface. - * - */ - if (launcher_button(id_surface, &pCtx->hmi_setting->launcher_list)) { - pCtx->is_home_on = 0; - ivi_hmi_controller_home(hmi_ctrl, IVI_HMI_CONTROLLER_HOME_OFF); - } -} - -static const struct ivi_hmi_controller_listener hmi_controller_listener = { - ivi_hmi_controller_workspace_end_control -}; - -/** - * Registration of interfaces - */ -static void -registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, - const char *interface, uint32_t version) -{ - struct wlContextCommon *p_wlCtx = (struct wlContextCommon*)data; - - if (!strcmp(interface, "wl_compositor")) { - p_wlCtx->wlCompositor = - wl_registry_bind(registry, name, - &wl_compositor_interface, 1); - } else if (!strcmp(interface, "wl_shm")) { - p_wlCtx->wlShm = - wl_registry_bind(registry, name, &wl_shm_interface, 1); - wl_shm_add_listener(p_wlCtx->wlShm, &shm_listenter, p_wlCtx); - } else if (!strcmp(interface, "wl_seat")) { - /* XXX: should be handling multiple wl_seats */ - if (p_wlCtx->wlSeat) - return; - - p_wlCtx->wlSeat = - wl_registry_bind(registry, name, &wl_seat_interface, 1); - wl_seat_add_listener(p_wlCtx->wlSeat, &seat_Listener, data); - } else if (!strcmp(interface, "ivi_application")) { - p_wlCtx->iviApplication = - wl_registry_bind(registry, name, - &ivi_application_interface, 1); - } else if (!strcmp(interface, "ivi_hmi_controller")) { - p_wlCtx->hmiCtrl = - wl_registry_bind(registry, name, - &ivi_hmi_controller_interface, 1); - - ivi_hmi_controller_add_listener(p_wlCtx->hmiCtrl, - &hmi_controller_listener, p_wlCtx); - } else if (!strcmp(interface, "wl_output")) { - p_wlCtx->hmi_setting->screen_num++; - } -} - -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 -frame_listener_func(void *data, struct wl_callback *callback, uint32_t time) -{ - if (callback) - wl_callback_destroy(callback); -} - -static const struct wl_callback_listener frame_listener = { - frame_listener_func -}; - -/* - * 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 wlContextCommon *cmm) -{ - uint32_t i = 0; - uint32_t j = 0; - struct wl_cursor *cursor = NULL; - char *cursor_theme = cmm->hmi_setting->cursor_theme; - int32_t cursor_size = cmm->hmi_setting->cursor_size; - - cmm->cursor_theme = wl_cursor_theme_load(cursor_theme, cursor_size, - cmm->wlShm); - - cmm->cursors = - xzalloc(ARRAY_LENGTH(cursors) * sizeof(cmm->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( - cmm->cursor_theme, cursors[i].names[j]); - } - - if (!cursor) { - fprintf(stderr, "could not load cursor '%s'\n", - cursors[i].names[0]); - } - - cmm->cursors[i] = cursor; - } -} - -static void -destroy_cursors(struct wlContextCommon *cmm) -{ - if (cmm->cursor_theme) - wl_cursor_theme_destroy(cmm->cursor_theme); - - free(cmm->cursors); -} - -/** - * Internal method to prepare parts of UI - */ -static void -createShmBuffer(struct wlContextStruct *p_wlCtx) -{ - struct wl_shm_pool *pool; - - int fd = -1; - int size = 0; - int width = 0; - int height = 0; - int stride = 0; - - width = cairo_image_surface_get_width(p_wlCtx->ctx_image); - height = cairo_image_surface_get_height(p_wlCtx->ctx_image); - stride = cairo_image_surface_get_stride(p_wlCtx->ctx_image); - - size = stride * height; - - fd = os_create_anonymous_file(size); - if (fd < 0) { - fprintf(stderr, "creating a buffer file for %d B failed: %s\n", - size, strerror(errno)); - return ; - } - - p_wlCtx->data = - mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - - if (MAP_FAILED == p_wlCtx->data) { - fprintf(stderr, "mmap failed: %s\n", strerror(errno)); - close(fd); - return; - } - - pool = wl_shm_create_pool(p_wlCtx->cmm->wlShm, fd, size); - p_wlCtx->wlBuffer = wl_shm_pool_create_buffer(pool, 0, - width, - height, - stride, - WL_SHM_FORMAT_ARGB8888); - - if (NULL == p_wlCtx->wlBuffer) { - fprintf(stderr, "wl_shm_create_buffer failed: %s\n", - strerror(errno)); - close(fd); - return; - } - - wl_shm_pool_destroy(pool); - close(fd); -} - -static void -destroyWLContextCommon(struct wlContextCommon *p_wlCtx) -{ - destroy_cursors(p_wlCtx); - - if (p_wlCtx->pointer_surface) - wl_surface_destroy(p_wlCtx->pointer_surface); - - if (p_wlCtx->wlCompositor) - wl_compositor_destroy(p_wlCtx->wlCompositor); -} - -static void -destroyWLContextStruct(struct wlContextStruct *p_wlCtx) -{ - if (p_wlCtx->wlSurface) - wl_surface_destroy(p_wlCtx->wlSurface); - - if (p_wlCtx->ctx_image) { - cairo_surface_destroy(p_wlCtx->ctx_image); - p_wlCtx->ctx_image = NULL; - } -} - -static int -createSurface(struct wlContextStruct *p_wlCtx) -{ - p_wlCtx->wlSurface = - wl_compositor_create_surface(p_wlCtx->cmm->wlCompositor); - if (NULL == p_wlCtx->wlSurface) { - printf("Error: wl_compositor_create_surface failed.\n"); - destroyWLContextCommon(p_wlCtx->cmm); - abort(); - } - - return 0; -} - -static void -drawImage(struct wlContextStruct *p_wlCtx) -{ - struct wl_callback *callback; - - int width = 0; - int height = 0; - int stride = 0; - void *data = NULL; - - width = cairo_image_surface_get_width(p_wlCtx->ctx_image); - height = cairo_image_surface_get_height(p_wlCtx->ctx_image); - stride = cairo_image_surface_get_stride(p_wlCtx->ctx_image); - data = cairo_image_surface_get_data(p_wlCtx->ctx_image); - - memcpy(p_wlCtx->data, data, stride * height); - - wl_surface_attach(p_wlCtx->wlSurface, p_wlCtx->wlBuffer, 0, 0); - wl_surface_damage(p_wlCtx->wlSurface, 0, 0, width, height); - - callback = wl_surface_frame(p_wlCtx->wlSurface); - wl_callback_add_listener(callback, &frame_listener, NULL); - - wl_surface_commit(p_wlCtx->wlSurface); -} - -static void -create_ivisurface(struct wlContextStruct *p_wlCtx, - uint32_t id_surface, - cairo_surface_t *surface) -{ - struct ivi_surface *ivisurf = NULL; - - p_wlCtx->ctx_image = surface; - - p_wlCtx->id_surface = id_surface; - wl_list_init(&p_wlCtx->link); - wl_list_insert(&p_wlCtx->cmm->list_wlContextStruct, &p_wlCtx->link); - - createSurface(p_wlCtx); - createShmBuffer(p_wlCtx); - - ivisurf = ivi_application_surface_create(p_wlCtx->cmm->iviApplication, - id_surface, - p_wlCtx->wlSurface); - if (ivisurf == NULL) { - fprintf(stderr, "Failed to create ivi_client_surface\n"); - return; - } - - drawImage(p_wlCtx); -} - -static void -create_ivisurfaceFromFile(struct wlContextStruct *p_wlCtx, - uint32_t id_surface, - const char *imageFile) -{ - cairo_surface_t *surface = load_cairo_surface(imageFile); - - if (NULL == surface) { - fprintf(stderr, "Failed to load_cairo_surface %s\n", imageFile); - return; - } - - create_ivisurface(p_wlCtx, id_surface, surface); -} - -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 -create_ivisurfaceFromColor(struct wlContextStruct *p_wlCtx, - uint32_t id_surface, - uint32_t width, uint32_t height, - uint32_t color) -{ - cairo_surface_t *surface = NULL; - cairo_t *cr = NULL; - - surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, - width, height); - - cr = cairo_create(surface); - cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); - cairo_rectangle(cr, 0, 0, width, height); - set_hex_color(cr, color); - cairo_fill(cr); - cairo_destroy(cr); - - create_ivisurface(p_wlCtx, id_surface, surface); -} - -static void -UI_ready(struct ivi_hmi_controller *controller) -{ - ivi_hmi_controller_UI_ready(controller); -} - -/** - * Internal method to set up UI by using ivi-hmi-controller - */ -static void -create_background(struct wlContextStruct *p_wlCtx, const uint32_t id_surface, - const char *imageFile) -{ - create_ivisurfaceFromFile(p_wlCtx, id_surface, imageFile); -} - -static void -create_panel(struct wlContextStruct *p_wlCtx, const uint32_t id_surface, - const char *imageFile) -{ - create_ivisurfaceFromFile(p_wlCtx, id_surface, imageFile); -} - -static void -create_button(struct wlContextStruct *p_wlCtx, const uint32_t id_surface, - const char *imageFile, uint32_t number) -{ - create_ivisurfaceFromFile(p_wlCtx, id_surface, imageFile); -} - -static void -create_home_button(struct wlContextStruct *p_wlCtx, const uint32_t id_surface, - const char *imageFile) -{ - create_ivisurfaceFromFile(p_wlCtx, id_surface, imageFile); -} - -static void -create_workspace_background(struct wlContextStruct *p_wlCtx, - struct hmi_homescreen_srf *srf) -{ - create_ivisurfaceFromColor(p_wlCtx, srf->id, 1, 1, srf->color); -} - -static void -create_launchers(struct wlContextCommon *cmm, struct wl_list *launcher_list) -{ - struct hmi_homescreen_launcher **launchers; - struct hmi_homescreen_launcher *launcher = NULL; - - int launcher_count = wl_list_length(launcher_list); - int ii = 0; - int start = 0; - - if (0 == launcher_count) - return; - - launchers = xzalloc(launcher_count * sizeof(*launchers)); - - wl_list_for_each(launcher, launcher_list, link) { - launchers[ii] = launcher; - ii++; - } - - for (ii = 0; ii < launcher_count; ii++) { - int jj = 0; - - if (ii != launcher_count - 1 && - launchers[ii]->workspace_id == - launchers[ii + 1]->workspace_id) - continue; - - for (jj = start; jj <= ii; jj++) { - struct wlContextStruct *p_wlCtx; - - p_wlCtx = xzalloc(sizeof(*p_wlCtx)); - p_wlCtx->cmm = cmm; - create_ivisurfaceFromFile(p_wlCtx, - launchers[jj]->icon_surface_id, - launchers[jj]->icon); - } - - start = ii + 1; - } - - free(launchers); -} - -/** - * Internal method to read out weston.ini to get configuration - */ -static struct hmi_homescreen_setting * -hmi_homescreen_setting_create(void) -{ - const char *config_file; - struct weston_config *config = NULL; - struct weston_config_section *shellSection = NULL; - struct hmi_homescreen_setting *setting = xzalloc(sizeof(*setting)); - struct weston_config_section *section = NULL; - const char *name = NULL; - uint32_t workspace_layer_id; - uint32_t icon_surface_id = 0; - char *filename; - - wl_list_init(&setting->workspace_list); - wl_list_init(&setting->launcher_list); - - config_file = weston_config_get_name_from_env(); - config = weston_config_parse(config_file); - - shellSection = - weston_config_get_section(config, "ivi-shell", NULL, NULL); - - weston_config_section_get_string( - shellSection, "cursor-theme", &setting->cursor_theme, NULL); - - weston_config_section_get_int( - shellSection, "cursor-size", &setting->cursor_size, 32); - - weston_config_section_get_uint( - shellSection, "workspace-layer-id", &workspace_layer_id, 3000); - - filename = file_name_with_datadir("background.png"); - weston_config_section_get_string( - shellSection, "background-image", &setting->background.filePath, - filename); - free(filename); - - weston_config_section_get_uint( - shellSection, "background-id", &setting->background.id, 1001); - - filename = file_name_with_datadir("panel.png"); - weston_config_section_get_string( - shellSection, "panel-image", &setting->panel.filePath, - filename); - free(filename); - - weston_config_section_get_uint( - shellSection, "panel-id", &setting->panel.id, 1002); - - filename = file_name_with_datadir("tiling.png"); - weston_config_section_get_string( - shellSection, "tiling-image", &setting->tiling.filePath, - filename); - free(filename); - - weston_config_section_get_uint( - shellSection, "tiling-id", &setting->tiling.id, 1003); - - filename = file_name_with_datadir("sidebyside.png"); - weston_config_section_get_string( - shellSection, "sidebyside-image", &setting->sidebyside.filePath, - filename); - free(filename); - - weston_config_section_get_uint( - shellSection, "sidebyside-id", &setting->sidebyside.id, 1004); - - filename = file_name_with_datadir("fullscreen.png"); - weston_config_section_get_string( - shellSection, "fullscreen-image", &setting->fullscreen.filePath, - filename); - free(filename); - - weston_config_section_get_uint( - shellSection, "fullscreen-id", &setting->fullscreen.id, 1005); - - filename = file_name_with_datadir("random.png"); - weston_config_section_get_string( - shellSection, "random-image", &setting->random.filePath, - filename); - free(filename); - - weston_config_section_get_uint( - shellSection, "random-id", &setting->random.id, 1006); - - filename = file_name_with_datadir("home.png"); - weston_config_section_get_string( - shellSection, "home-image", &setting->home.filePath, - filename); - free(filename); - - weston_config_section_get_uint( - shellSection, "home-id", &setting->home.id, 1007); - - weston_config_section_get_color( - shellSection, "workspace-background-color", - &setting->workspace_background.color, 0x99000000); - - weston_config_section_get_uint( - shellSection, "workspace-background-id", - &setting->workspace_background.id, 2001); - - weston_config_section_get_uint( - shellSection, "surface-id-offset", &setting->surface_id_offset, 10); - - icon_surface_id = workspace_layer_id + 1; - - while (weston_config_next_section(config, §ion, &name)) { - struct hmi_homescreen_launcher *launcher; - - if (strcmp(name, "ivi-launcher") != 0) - continue; - - launcher = xzalloc(sizeof(*launcher)); - wl_list_init(&launcher->link); - - weston_config_section_get_string(section, "icon", - &launcher->icon, NULL); - weston_config_section_get_string(section, "path", - &launcher->path, NULL); - weston_config_section_get_uint(section, "workspace-id", - &launcher->workspace_id, 0); - weston_config_section_get_uint(section, "icon-id", - &launcher->icon_surface_id, - icon_surface_id); - icon_surface_id++; - - wl_list_insert(setting->launcher_list.prev, &launcher->link); - } - - weston_config_destroy(config); - return setting; -} - -/** - * Main thread - * - * The basic flow are as followed, - * 1/ read configuration from weston.ini by hmi_homescreen_setting_create - * 2/ draw png file to surface according to configuration of weston.ini and - * set up UI by using ivi-hmi-controller protocol by each create_* method - */ -int main(int argc, char **argv) -{ - struct wlContextCommon wlCtxCommon; - struct wlContextStruct *wlCtx_BackGround; - struct wlContextStruct *wlCtx_Panel; - struct wlContextStruct wlCtx_Button_1; - struct wlContextStruct wlCtx_Button_2; - struct wlContextStruct wlCtx_Button_3; - struct wlContextStruct wlCtx_Button_4; - struct wlContextStruct wlCtx_HomeButton; - struct wlContextStruct wlCtx_WorkSpaceBackGround; - struct wl_list launcher_wlCtxList; - int ret = 0; - struct hmi_homescreen_setting *hmi_setting; - struct wlContextStruct *pWlCtxSt = NULL; - int i = 0; - - hmi_setting = hmi_homescreen_setting_create(); - - memset(&wlCtxCommon, 0x00, sizeof(wlCtxCommon)); - memset(&wlCtx_Button_1, 0x00, sizeof(wlCtx_Button_1)); - memset(&wlCtx_Button_2, 0x00, sizeof(wlCtx_Button_2)); - memset(&wlCtx_Button_3, 0x00, sizeof(wlCtx_Button_3)); - memset(&wlCtx_Button_4, 0x00, sizeof(wlCtx_Button_4)); - memset(&wlCtx_HomeButton, 0x00, sizeof(wlCtx_HomeButton)); - memset(&wlCtx_WorkSpaceBackGround, 0x00, - sizeof(wlCtx_WorkSpaceBackGround)); - wl_list_init(&launcher_wlCtxList); - wl_list_init(&wlCtxCommon.list_wlContextStruct); - - wlCtxCommon.hmi_setting = hmi_setting; - - wlCtxCommon.wlDisplay = wl_display_connect(NULL); - if (NULL == wlCtxCommon.wlDisplay) { - printf("Error: wl_display_connect failed.\n"); - return -1; - } - - /* get wl_registry */ - wlCtxCommon.formats = 0; - wlCtxCommon.wlRegistry = wl_display_get_registry(wlCtxCommon.wlDisplay); - wl_registry_add_listener(wlCtxCommon.wlRegistry, - ®istry_listener, &wlCtxCommon); - wl_display_roundtrip(wlCtxCommon.wlDisplay); - - if (wlCtxCommon.wlShm == NULL) { - fprintf(stderr, "No wl_shm global\n"); - exit(1); - } - - wl_display_roundtrip(wlCtxCommon.wlDisplay); - - if (!(wlCtxCommon.formats & (1 << WL_SHM_FORMAT_XRGB8888))) { - fprintf(stderr, "WL_SHM_FORMAT_XRGB32 not available\n"); - exit(1); - } - - wlCtx_BackGround = xzalloc(hmi_setting->screen_num * sizeof(struct wlContextStruct)); - wlCtx_Panel= xzalloc(hmi_setting->screen_num * sizeof(struct wlContextStruct)); - - if (wlCtxCommon.hmi_setting->cursor_theme) { - create_cursors(&wlCtxCommon); - - wlCtxCommon.pointer_surface = - wl_compositor_create_surface(wlCtxCommon.wlCompositor); - - wlCtxCommon.current_cursor = CURSOR_LEFT_PTR; - } - - wlCtx_Button_1.cmm = &wlCtxCommon; - wlCtx_Button_2.cmm = &wlCtxCommon; - wlCtx_Button_3.cmm = &wlCtxCommon; - wlCtx_Button_4.cmm = &wlCtxCommon; - wlCtx_HomeButton.cmm = &wlCtxCommon; - wlCtx_WorkSpaceBackGround.cmm = &wlCtxCommon; - - /* create desktop widgets */ - for (i = 0; i < hmi_setting->screen_num; i++) { - wlCtx_BackGround[i].cmm = &wlCtxCommon; - create_background(&wlCtx_BackGround[i], - hmi_setting->background.id + - (i * hmi_setting->surface_id_offset), - hmi_setting->background.filePath); - - wlCtx_Panel[i].cmm = &wlCtxCommon; - create_panel(&wlCtx_Panel[i], - hmi_setting->panel.id + (i * hmi_setting->surface_id_offset), - hmi_setting->panel.filePath); - } - - create_button(&wlCtx_Button_1, hmi_setting->tiling.id, - hmi_setting->tiling.filePath, 0); - - create_button(&wlCtx_Button_2, hmi_setting->sidebyside.id, - hmi_setting->sidebyside.filePath, 1); - - create_button(&wlCtx_Button_3, hmi_setting->fullscreen.id, - hmi_setting->fullscreen.filePath, 2); - - create_button(&wlCtx_Button_4, hmi_setting->random.id, - hmi_setting->random.filePath, 3); - - create_workspace_background(&wlCtx_WorkSpaceBackGround, - &hmi_setting->workspace_background); - - create_launchers(&wlCtxCommon, &hmi_setting->launcher_list); - - create_home_button(&wlCtx_HomeButton, hmi_setting->home.id, - hmi_setting->home.filePath); - - UI_ready(wlCtxCommon.hmiCtrl); - - while (ret != -1) - ret = wl_display_dispatch(wlCtxCommon.wlDisplay); - - wl_list_for_each(pWlCtxSt, &wlCtxCommon.list_wlContextStruct, link) { - destroyWLContextStruct(pWlCtxSt); - } - - free(wlCtx_BackGround); - free(wlCtx_Panel); - - destroyWLContextCommon(&wlCtxCommon); - - return 0; -} diff --git a/clients/keyboard.c b/clients/keyboard.c deleted file mode 100644 index e39d76d..0000000 --- a/clients/keyboard.c +++ /dev/null @@ -1,1041 +0,0 @@ -/* - * Copyright © 2012 Openismus GmbH - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "window.h" -#include "input-method-unstable-v1-client-protocol.h" -#include "text-input-unstable-v1-client-protocol.h" -#include "shared/xalloc.h" - -struct keyboard; - -struct virtual_keyboard { - struct zwp_input_panel_v1 *input_panel; - struct zwp_input_method_v1 *input_method; - struct zwp_input_method_context_v1 *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; - bool toplevel; -}; - -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 *uppercase; - char *symbol; - - 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", 1}, - { keytype_default, "w", "W", "2", 1}, - { keytype_default, "e", "E", "3", 1}, - { keytype_default, "r", "R", "4", 1}, - { keytype_default, "t", "T", "5", 1}, - { keytype_default, "y", "Y", "6", 1}, - { keytype_default, "u", "U", "7", 1}, - { keytype_default, "i", "I", "8", 1}, - { keytype_default, "o", "O", "9", 1}, - { keytype_default, "p", "P", "0", 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", "Enter", 2}, - - { keytype_switch, "ABC", "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", "ABC", 1}, - - { keytype_symbols, "?123", "?123", "abc", 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", 1}, - { keytype_default, "2", "2", "2", 1}, - { keytype_default, "3", "3", "3", 1}, - { keytype_default, "4", "4", "4", 1}, - { keytype_default, "5", "5", "5", 1}, - { keytype_default, "6", "6", "6", 1}, - { keytype_default, "7", "7", "7", 1}, - { keytype_default, "8", "8", "8", 1}, - { keytype_default, "9", "9", "9", 1}, - { keytype_default, "0", "0", "0", 1}, - { keytype_backspace, "<--", "<--", "<--", 2}, - - { keytype_space, "", "", "", 4}, - { keytype_enter, "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_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, "ن", "x", "&", 1}, - { keytype_default, "م", "/", "*", 1}, - { keytype_default, "ك", ":", "_", 1}, - { keytype_default, "د", "\"", "+", 1}, - { keytype_enter, "Enter", "Enter", "Enter", 2}, - - { keytype_switch, "Shift", "Base", "Shift", 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, "Shift", "Base", "Shift", 2}, - - { keytype_symbols, "؟٣٢١", "؟٣٢١", "Base", 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", - ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_LTR -}; - -static const struct layout numeric_layout = { - numeric_keys, - sizeof(numeric_keys) / sizeof(*numeric_keys), - 12, - 2, - "en", - ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_LTR -}; - -static const struct layout arabic_layout = { - arabic_keys, - sizeof(arabic_keys) / sizeof(*arabic_keys), - 13, - 4, - "ar", - ZWP_TEXT_INPUT_V1_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 { - KEYBOARD_STATE_DEFAULT, - KEYBOARD_STATE_UPPERCASE, - KEYBOARD_STATE_SYMBOLS -}; - -struct keyboard { - struct virtual_keyboard *keyboard; - struct window *window; - struct widget *widget; - - enum keyboard_state state; -}; - -static void __attribute__ ((format (printf, 1, 2))) -dbg(const char *fmt, ...) -{ -#ifdef DEBUG - va_list argp; - - va_start(argp, fmt); - vfprintf(stderr, fmt, argp); - va_end(argp); -#endif -} - -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]; - - switch(keyboard->state) { - case KEYBOARD_STATE_DEFAULT: - return key->label; - case KEYBOARD_STATE_UPPERCASE: - return key->uppercase; - case KEYBOARD_STATE_SYMBOLS: - return key->symbol; - } - - return ""; -} - -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 ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_DIGITS: - case ZWP_TEXT_INPUT_V1_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) -{ - int tlen = strlen(text), ilen = strlen(insert); - char *new_text = xmalloc(tlen + ilen + 1); - - memcpy(new_text, text, offset); - memcpy(new_text + offset, insert, ilen); - memcpy(new_text + offset + ilen, text + offset, tlen - offset); - new_text[tlen + ilen] = '\0'; - - 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; - - zwp_input_method_context_v1_cursor_position(keyboard->context, - 0, 0); - zwp_input_method_context_v1_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) - zwp_input_method_context_v1_preedit_styling(keyboard->context, - 0, - strlen(keyboard->preedit_string), - keyboard->preedit_style); - if (cursor > 0) - index = cursor; - zwp_input_method_context_v1_preedit_cursor(keyboard->context, - index); - zwp_input_method_context_v1_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) { - dbg("delete_before_cursor: No surrounding text available\n"); - return; - } - - start = prev_utf8_char(keyboard->surrounding_text, - keyboard->surrounding_text + keyboard->surrounding_cursor); - if (!start) { - dbg("delete_before_cursor: No previous character to delete\n"); - return; - } - - end = keyboard->surrounding_text + keyboard->surrounding_cursor; - - zwp_input_method_context_v1_delete_surrounding_text(keyboard->context, - (start - keyboard->surrounding_text) - keyboard->surrounding_cursor, - end - start); - zwp_input_method_context_v1_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 char * -append(char *s1, const char *s2) -{ - int len1, len2; - char *s; - - len1 = strlen(s1); - len2 = strlen(s2); - s = xrealloc(s1, len1 + len2 + 1); - memcpy(s + len1, s2, len2); - s[len1 + len2] = '\0'; - - return s; -} - -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 = NULL; - - switch(keyboard->state) { - case KEYBOARD_STATE_DEFAULT : - label = key->label; - break; - case KEYBOARD_STATE_UPPERCASE : - label = key->uppercase; - break; - case KEYBOARD_STATE_SYMBOLS : - label = key->symbol; - break; - } - - xkb_mod_mask_t mod_mask = keyboard->state == KEYBOARD_STATE_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 = - append(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); - zwp_input_method_context_v1_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 = - append(keyboard->keyboard->preedit_string, " "); - virtual_keyboard_commit_preedit(keyboard->keyboard); - break; - case keytype_switch: - if (state != WL_POINTER_BUTTON_STATE_PRESSED) - break; - switch(keyboard->state) { - case KEYBOARD_STATE_DEFAULT: - keyboard->state = KEYBOARD_STATE_UPPERCASE; - break; - case KEYBOARD_STATE_UPPERCASE: - keyboard->state = KEYBOARD_STATE_DEFAULT; - break; - case KEYBOARD_STATE_SYMBOLS: - keyboard->state = KEYBOARD_STATE_UPPERCASE; - break; - } - break; - case keytype_symbols: - if (state != WL_POINTER_BUTTON_STATE_PRESSED) - break; - switch(keyboard->state) { - case KEYBOARD_STATE_DEFAULT: - keyboard->state = KEYBOARD_STATE_SYMBOLS; - break; - case KEYBOARD_STATE_UPPERCASE: - keyboard->state = KEYBOARD_STATE_SYMBOLS; - break; - case KEYBOARD_STATE_SYMBOLS: - keyboard->state = KEYBOARD_STATE_DEFAULT; - break; - } - break; - case keytype_tab: - virtual_keyboard_commit_preedit(keyboard->keyboard); - zwp_input_method_context_v1_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); - zwp_input_method_context_v1_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); - zwp_input_method_context_v1_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); - zwp_input_method_context_v1_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); - zwp_input_method_context_v1_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_handler(struct input *input, uint32_t time, - float x, float y, uint32_t state, 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, state); - break; - } - } - - widget_schedule_redraw(keyboard->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) -{ - touch_handler(input, time, x, y, - WL_POINTER_BUTTON_STATE_PRESSED, data); -} - -static void -touch_up_handler(struct widget *widget, struct input *input, - uint32_t serial, uint32_t time, int32_t id, - void *data) -{ - float x, y; - - input_get_touch(input, id, &x, &y); - - touch_handler(input, time, x, y, - WL_POINTER_BUTTON_STATE_RELEASED, data); -} - -static void -handle_surrounding_text(void *data, - struct zwp_input_method_context_v1 *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 zwp_input_method_context_v1 *context) -{ - struct virtual_keyboard *keyboard = data; - - dbg("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 zwp_input_method_context_v1 *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 zwp_input_method_context_v1 *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 zwp_input_method_context_v1 *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) - dbg("Surrounding text updated: %s\n", keyboard->surrounding_text); - - window_schedule_resize(keyboard->keyboard->window, - layout->columns * key_width, - layout->rows * key_height); - - zwp_input_method_context_v1_language(context, - keyboard->serial, - layout->language); - zwp_input_method_context_v1_text_direction(context, - keyboard->serial, - layout->text_direction); - - widget_schedule_redraw(keyboard->keyboard->widget); -} - -static void -handle_preferred_language(void *data, - struct zwp_input_method_context_v1 *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 zwp_input_method_context_v1_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 zwp_input_method_v1 *input_method, - struct zwp_input_method_context_v1 *context) -{ - struct virtual_keyboard *keyboard = data; - struct wl_array modifiers_map; - const struct layout *layout; - - keyboard->keyboard->state = KEYBOARD_STATE_DEFAULT; - - if (keyboard->context) - zwp_input_method_context_v1_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; - zwp_input_method_context_v1_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"); - zwp_input_method_context_v1_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); - - zwp_input_method_context_v1_language(context, - keyboard->serial, - layout->language); - zwp_input_method_context_v1_text_direction(context, - keyboard->serial, - layout->text_direction); - - widget_schedule_redraw(keyboard->keyboard->widget); -} - -static void -input_method_deactivate(void *data, - struct zwp_input_method_v1 *input_method, - struct zwp_input_method_context_v1 *context) -{ - struct virtual_keyboard *keyboard = data; - - if (!keyboard->context) - return; - - zwp_input_method_context_v1_destroy(keyboard->context); - keyboard->context = NULL; -} - -static const struct zwp_input_method_v1_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, "zwp_input_panel_v1")) { - keyboard->input_panel = - display_bind(display, name, &zwp_input_panel_v1_interface, 1); - } else if (!strcmp(interface, "zwp_input_method_v1")) { - keyboard->input_method = - display_bind(display, name, - &zwp_input_method_v1_interface, 1); - zwp_input_method_v1_add_listener(keyboard->input_method, - &input_method_listener, - keyboard); - } -} - -static void -set_toplevel(struct output *output, struct virtual_keyboard *virtual_keyboard) -{ - struct zwp_input_panel_surface_v1 *ips; - struct keyboard *keyboard = virtual_keyboard->keyboard; - - ips = zwp_input_panel_v1_get_input_panel_surface(virtual_keyboard->input_panel, - window_get_wl_surface(keyboard->window)); - - zwp_input_panel_surface_v1_set_toplevel(ips, - output_get_wl_output(output), - ZWP_INPUT_PANEL_SURFACE_V1_POSITION_CENTER_BOTTOM); - - virtual_keyboard->toplevel = true; -} - -static void -display_output_handler(struct output *output, void *data) { - struct virtual_keyboard *keyboard = data; - - if (!keyboard->toplevel) - set_toplevel(output, keyboard); -} - -static void -keyboard_create(struct virtual_keyboard *virtual_keyboard) -{ - struct keyboard *keyboard; - const struct layout *layout; - - 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); - - display_set_output_configure_handler(virtual_keyboard->display, - display_output_handler); -} - -int -main(int argc, char *argv[]) -{ - struct virtual_keyboard virtual_keyboard; - - 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: %s\n", - strerror(errno)); - return -1; - } - - display_set_user_data(virtual_keyboard.display, &virtual_keyboard); - display_set_global_handler(virtual_keyboard.display, global_handler); - - if (virtual_keyboard.input_panel == NULL) { - fprintf(stderr, "No input panel global\n"); - return -1; - } - - keyboard_create(&virtual_keyboard); - - display_run(virtual_keyboard.display); - - return 0; -} diff --git a/clients/meson.build b/clients/meson.build deleted file mode 100644 index 2c016b8..0000000 --- a/clients/meson.build +++ /dev/null @@ -1,373 +0,0 @@ -if get_option('resize-pool') - config_h.set('USE_RESIZE_POOL', '1') -endif - -srcs_toytoolkit = [ - 'window.c', - xdg_shell_client_protocol_h, - xdg_shell_protocol_c, - text_cursor_position_client_protocol_h, - text_cursor_position_protocol_c, - relative_pointer_unstable_v1_client_protocol_h, - relative_pointer_unstable_v1_protocol_c, - pointer_constraints_unstable_v1_client_protocol_h, - pointer_constraints_unstable_v1_protocol_c, - ivi_application_client_protocol_h, - ivi_application_protocol_c, - viewporter_client_protocol_h, - viewporter_protocol_c, -] -deps_toytoolkit = [ - dep_wayland_client, - dep_lib_cairo_shared, - dep_xkbcommon, - dependency('wayland-cursor'), - cc.find_library('util'), -] -lib_toytoolkit = static_library( - 'toytoolkit', - srcs_toytoolkit, - include_directories: common_inc, - dependencies: deps_toytoolkit, - install: false, -) -dep_toytoolkit = declare_dependency( - link_with: lib_toytoolkit, - dependencies: deps_toytoolkit, -) - -simple_clients = [ - { - 'name': 'damage', - 'sources': [ - 'simple-damage.c', - viewporter_client_protocol_h, - viewporter_protocol_c, - xdg_shell_client_protocol_h, - xdg_shell_protocol_c, - fullscreen_shell_unstable_v1_client_protocol_h, - fullscreen_shell_unstable_v1_protocol_c, - ], - 'dep_objs': [ dep_wayland_client, dep_libshared ] - }, - { - 'name': 'dmabuf-egl', - 'sources': [ - 'simple-dmabuf-egl.c', - linux_dmabuf_unstable_v1_client_protocol_h, - linux_dmabuf_unstable_v1_protocol_c, - linux_explicit_synchronization_unstable_v1_client_protocol_h, - linux_explicit_synchronization_unstable_v1_protocol_c, - xdg_shell_client_protocol_h, - xdg_shell_protocol_c, - weston_direct_display_client_protocol_h, - weston_direct_display_protocol_c, - fullscreen_shell_unstable_v1_client_protocol_h, - fullscreen_shell_unstable_v1_protocol_c, - ], - 'dep_objs': [ - dep_wayland_client, - dep_libdrm, - dep_libm - ], - 'deps': [ 'egl', 'glesv2', 'gbm' ], - 'options': [ 'renderer-gl' ] - }, - { - 'name': 'dmabuf-v4l', - 'sources': [ - 'simple-dmabuf-v4l.c', - linux_dmabuf_unstable_v1_client_protocol_h, - linux_dmabuf_unstable_v1_protocol_c, - xdg_shell_client_protocol_h, - xdg_shell_protocol_c, - weston_direct_display_client_protocol_h, - weston_direct_display_protocol_c, - fullscreen_shell_unstable_v1_client_protocol_h, - fullscreen_shell_unstable_v1_protocol_c, - ], - 'dep_objs': [ dep_wayland_client, dep_libdrm_headers ] - }, - { - 'name': 'egl', - 'sources': [ - 'simple-egl.c', - xdg_shell_client_protocol_h, - xdg_shell_protocol_c, - ivi_application_client_protocol_h, - ivi_application_protocol_c, - ], - 'dep_objs': [ dep_wayland_client, dep_libshared, dep_libm ], - 'deps': [ 'egl', 'wayland-egl', 'glesv2', 'wayland-cursor' ], - 'options': [ 'renderer-gl' ] - }, - # weston-simple-im is handled specially separately due to install_dir and odd window.h usage - { - 'name': 'shm', - 'sources': [ - 'simple-shm.c', - xdg_shell_client_protocol_h, - xdg_shell_protocol_c, - fullscreen_shell_unstable_v1_client_protocol_h, - fullscreen_shell_unstable_v1_protocol_c, - ivi_application_client_protocol_h, - ivi_application_protocol_c, - ], - 'dep_objs': [ dep_wayland_client, dep_libshared ] - }, - { - 'name': 'touch', - 'sources': [ - 'simple-touch.c', - ], - 'dep_objs': [ dep_wayland_client, dep_libshared ] - }, -] - -simple_clients_enabled = get_option('simple-clients') -simple_build_all = simple_clients_enabled.contains('all') -foreach t : simple_clients - if simple_build_all or simple_clients_enabled.contains(t.get('name')) - t_name = 'weston-simple-' + t.get('name') - t_deps = t.get('dep_objs', []) - foreach depname : t.get('deps', []) - dep = dependency(depname, required: false) - if not dep.found() - error('@0@ requires @1@ which was not found. If you rather not build this, drop "@2@" from simple-clients option.'.format(t_name, depname, t.get('name'))) - endif - t_deps += dep - endforeach - - foreach optname : t.get('options', []) - if not get_option(optname) - error('@0@ requires option @1@ which is not enabled. If you rather not build this, drop "@2@" from simple-clients option.'.format(t_name, optname, t.get('name'))) - endif - endforeach - - executable( - t_name, t.get('sources'), - include_directories: common_inc, - dependencies: t_deps, - install: true - ) - endif -endforeach - -if simple_build_all or simple_clients_enabled.contains('im') - executable( - 'weston-simple-im', [ - 'simple-im.c', - input_method_unstable_v1_client_protocol_h, - input_method_unstable_v1_protocol_c, - ], - include_directories: common_inc, - dependencies: [ - dep_libshared, - dep_wayland_client, - dep_xkbcommon, - dependency('wayland-cursor'), - dependency('cairo') - ], - install: true, - install_dir: dir_libexec - ) -endif - -tools_enabled = get_option('tools') -tools_list = [ - { - 'name': 'calibrator', - 'sources': [ 'calibrator.c' ], - 'deps': [ dep_toytoolkit, dep_matrix_c ], - }, - { - 'name': 'debug', - 'sources': [ - 'weston-debug.c', - weston_debug_client_protocol_h, - weston_debug_protocol_c, - ], - 'deps': [ dep_wayland_client ] - }, - { - 'name': 'info', - 'sources': [ - 'weston-info.c', - presentation_time_client_protocol_h, - presentation_time_protocol_c, - linux_dmabuf_unstable_v1_client_protocol_h, - linux_dmabuf_unstable_v1_protocol_c, - tablet_unstable_v2_client_protocol_h, - tablet_unstable_v2_protocol_c, - xdg_output_unstable_v1_client_protocol_h, - xdg_output_unstable_v1_protocol_c, - ], - 'deps': [ dep_wayland_client, dep_libshared ] - }, - { - 'name': 'terminal', - 'sources': [ 'terminal.c' ], - 'deps': [ dep_toytoolkit ], - }, - { - 'name': 'touch-calibrator', - 'sources': [ - 'touch-calibrator.c', - weston_touch_calibration_client_protocol_h, - weston_touch_calibration_protocol_c, - ], - 'deps': [ dep_toytoolkit, dep_matrix_c ], - }, -] - -foreach t : tools_list - if tools_enabled.contains(t.get('name')) - executable( - 'weston-@0@'.format(t.get('name')), - t.get('sources'), - include_directories: common_inc, - dependencies: t.get('deps', []), - install: true - ) - endif -endforeach - -demo_clients = [ - { 'basename': 'clickdot' }, - { - 'basename': 'cliptest', - 'dep_objs': dep_vertex_clipping - }, - { 'basename': 'confine' }, - { - 'basename': 'content_protection', - 'add_sources': [ - weston_content_protection_client_protocol_h, - weston_content_protection_protocol_c, - ] - }, - - { 'basename': 'dnd' }, - { - 'basename': 'editor', - 'add_sources': [ - text_input_unstable_v1_client_protocol_h, - text_input_unstable_v1_protocol_c, - ], - 'deps': [ 'pangocairo', 'gobject-2.0' ] - }, - { 'basename': 'eventdemo' }, - { 'basename': 'flower' }, - { - 'basename': 'fullscreen', - 'add_sources': [ - fullscreen_shell_unstable_v1_client_protocol_h, - fullscreen_shell_unstable_v1_protocol_c, - ] - }, - { 'basename': 'image' }, - { 'basename': 'multi-resource' }, - { - 'basename': 'presentation-shm', - 'add_sources': [ - presentation_time_client_protocol_h, - presentation_time_protocol_c, - xdg_shell_client_protocol_h, - xdg_shell_protocol_c, - ] - }, - { 'basename': 'resizor' }, - { - 'basename': 'scaler', - 'add_sources': [ - viewporter_client_protocol_h, - viewporter_protocol_c, - ] - }, - { 'basename': 'smoke' }, - { 'basename': 'stacking' }, - { - 'basename': 'subsurfaces', - 'deps': [ 'egl', 'glesv2', 'wayland-egl' ] - }, - { 'basename': 'transformed' }, -] - -if get_option('demo-clients') - foreach t : demo_clients - t_name = 'weston-' + t.get('basename') - t_srcs = [ t.get('basename') + '.c' ] + t.get('add_sources', []) - t_deps = [ dep_toytoolkit, t.get('dep_objs', []) ] - foreach depname : t.get('deps', []) - dep = dependency(depname, required: false) - if not dep.found() - error('@0@ requires \'@1@\' which was not found. If you rather not build this, set \'-Ddemo-clients=false\'.'.format(t_name, depname)) - endif - t_deps += dep - endforeach - - executable( - t_name, t_srcs, - include_directories: common_inc, - dependencies: t_deps, - install: true - ) - endforeach -endif - -if get_option('shell-desktop') - exe_keyboard = executable( - 'weston-keyboard', - 'keyboard.c', - text_input_unstable_v1_client_protocol_h, - text_input_unstable_v1_protocol_c, - input_method_unstable_v1_client_protocol_h, - input_method_unstable_v1_protocol_c, - include_directories: common_inc, - dependencies: dep_toytoolkit, - install_dir: get_option('libexecdir'), - install: true - ) - env_modmap += 'weston-keyboard=@0@;'.format(exe_keyboard.full_path()) - - exe_shooter = executable( - 'weston-screenshooter', - 'screenshot.c', - weston_screenshooter_client_protocol_h, - weston_screenshooter_protocol_c, - include_directories: common_inc, - dependencies: dep_toytoolkit, - install_dir: get_option('bindir'), - install: true - ) - env_modmap += 'weston-screenshooter=@0@;'.format(exe_shooter.full_path()) - - exe_shell_desktop = executable( - 'weston-desktop-shell', - 'desktop-shell.c', - weston_desktop_shell_client_protocol_h, - weston_desktop_shell_protocol_c, - include_directories: common_inc, - dependencies: dep_toytoolkit, - install_dir: get_option('libexecdir'), - install: true - ) - env_modmap += 'weston-desktop-shell=@0@;'.format(exe_shell_desktop.full_path()) -endif - - -if get_option('shell-ivi') - exe_shell_ivi_ui = executable( - 'weston-ivi-shell-user-interface', - 'ivi-shell-user-interface.c', - ivi_hmi_controller_client_protocol_h, - ivi_hmi_controller_protocol_c, - ivi_application_client_protocol_h, - ivi_application_protocol_c, - include_directories: common_inc, - dependencies: dep_toytoolkit, - install: true, - install_dir: get_option('libexecdir') - ) - env_modmap += 'weston-ivi-shell-user-interface=@0@;'.format(exe_shell_ivi_ui.full_path()) -endif diff --git a/clients/multi-resource.c b/clients/multi-resource.c deleted file mode 100644 index b86503d..0000000 --- a/clients/multi-resource.c +++ /dev/null @@ -1,591 +0,0 @@ -/* - * Copyright © 2011 Benjamin Franzke - * Copyright © 2010, 2013 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "shared/os-compatibility.h" -#include "shared/xalloc.h" -#include - -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 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: %s\n", - size, strerror(errno)); - 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_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) -{ - /* Just so we don’t leak the keymap fd */ - close(fd); -} - -static void -keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, - uint32_t serial, struct wl_surface *surface, - struct wl_array *keys) -{ -} - -static void -keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, - uint32_t serial, struct wl_surface *surface) -{ -} - -static void -keyboard_handle_key(void *data, struct wl_keyboard *keyboard, - uint32_t serial, uint32_t time, uint32_t key, - uint32_t state_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 || tail == time_desc) - goto error; - - if (*tail == ':') { - time_desc = tail + 1; - end_time = strtoul(time_desc, &tail, 10); - if (errno || tail == time_desc || *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 deleted file mode 100644 index a9e034e..0000000 --- a/clients/nested-client.c +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright © 2013 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include "shared/platform.h" - -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 = - weston_platform_get_egl_display(EGL_PLATFORM_WAYLAND_KHR, - client->display, NULL); - 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 = weston_platform_create_egl_surface(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); - - weston_platform_destroy_egl_surface(client->egl_display, - client->egl_surface); - wl_egl_window_destroy(client->native); - - wl_surface_destroy(client->surface); - - if (client->compositor) - wl_compositor_destroy(client->compositor); - - wl_registry_destroy(client->registry); - eglTerminate(client->egl_display); - eglReleaseThread(); - 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 deleted file mode 100644 index 4bbaa27..0000000 --- a/clients/nested.c +++ /dev/null @@ -1,1137 +0,0 @@ -/* - * Copyright © 2013 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include -#define WL_HIDE_DEPRECATED -#include - -#include "shared/helpers.h" -#include "shared/xalloc.h" -#include "window.h" - -#include "shared/weston-egl-ext.h" - - -static bool option_blit; - -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; - - const struct nested_renderer *renderer; -}; - -struct nested_region { - struct wl_resource *resource; - pixman_region32_t region; -}; - -struct nested_buffer_reference { - struct nested_buffer *buffer; - struct wl_listener destroy_listener; -}; - -struct nested_buffer { - struct wl_resource *resource; - struct wl_signal destroy_signal; - struct wl_listener destroy_listener; - uint32_t busy_count; - - /* A buffer in the parent compositor representing the same - * data. This is created on-demand when the subsurface - * renderer is used */ - struct wl_buffer *parent_buffer; - /* This reference is used to mark when the parent buffer has - * been attached to the subsurface. It will be unrefenced when - * we receive a buffer release event. That way we won't inform - * the client that the buffer is free until the parent - * compositor is also finished with it */ - struct nested_buffer_reference parent_ref; -}; - -struct nested_surface { - struct wl_resource *resource; - struct nested *nested; - EGLImageKHR *image; - struct wl_list link; - - struct wl_list frame_callback_list; - - struct { - /* wl_surface.attach */ - int newly_attached; - struct nested_buffer *buffer; - struct wl_listener buffer_destroy_listener; - - /* wl_surface.frame */ - struct wl_list frame_callback_list; - - /* wl_surface.damage */ - pixman_region32_t damage; - } pending; - - void *renderer_data; -}; - -/* Data used for the blit renderer */ -struct nested_blit_surface { - struct nested_buffer_reference buffer_ref; - GLuint texture; - cairo_surface_t *cairo_surface; -}; - -/* Data used for the subsurface renderer */ -struct nested_ss_surface { - struct widget *widget; - struct wl_surface *surface; - struct wl_subsurface *subsurface; - struct wl_callback *frame_callback; -}; - -struct nested_frame_callback { - struct wl_resource *resource; - struct wl_list link; -}; - -struct nested_renderer { - void (* surface_init)(struct nested_surface *surface); - void (* surface_fini)(struct nested_surface *surface); - void (* render_clients)(struct nested *nested, cairo_t *cr); - void (* surface_attach)(struct nested_surface *surface, - struct nested_buffer *buffer); -}; - -static const struct weston_option nested_options[] = { - { WESTON_OPTION_BOOLEAN, "blit", 'b', &option_blit }, -}; - -static const struct nested_renderer nested_blit_renderer; -static const struct nested_renderer nested_ss_renderer; - -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 PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWL create_wayland_buffer_from_image; - -static void -nested_buffer_destroy_handler(struct wl_listener *listener, void *data) -{ - struct nested_buffer *buffer = - container_of(listener, struct nested_buffer, destroy_listener); - - wl_signal_emit(&buffer->destroy_signal, buffer); - - if (buffer->parent_buffer) - wl_buffer_destroy(buffer->parent_buffer); - - free(buffer); -} - -static struct nested_buffer * -nested_buffer_from_resource(struct wl_resource *resource) -{ - struct nested_buffer *buffer; - struct wl_listener *listener; - - listener = - wl_resource_get_destroy_listener(resource, - nested_buffer_destroy_handler); - - if (listener) - return container_of(listener, struct nested_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 = nested_buffer_destroy_handler; - wl_resource_add_destroy_listener(resource, &buffer->destroy_listener); - - return buffer; -} - -static void -nested_buffer_reference_handle_destroy(struct wl_listener *listener, - void *data) -{ - struct nested_buffer_reference *ref = - container_of(listener, struct nested_buffer_reference, - destroy_listener); - - assert((struct nested_buffer *)data == ref->buffer); - ref->buffer = NULL; -} - -static void -nested_buffer_reference(struct nested_buffer_reference *ref, - struct nested_buffer *buffer) -{ - if (buffer == ref->buffer) - return; - - if (ref->buffer) { - ref->buffer->busy_count--; - if (ref->buffer->busy_count == 0) { - assert(wl_resource_get_client(ref->buffer->resource)); - wl_buffer_send_release(ref->buffer->resource); - } - wl_list_remove(&ref->destroy_listener.link); - } - - if (buffer) { - buffer->busy_count++; - wl_signal_add(&buffer->destroy_signal, - &ref->destroy_listener); - - ref->destroy_listener.notify = - nested_buffer_reference_handle_destroy; - } - - ref->buffer = buffer; -} - -static void -flush_surface_frame_callback_list(struct nested_surface *surface, - uint32_t time) -{ - struct nested_frame_callback *nc, *next; - - wl_list_for_each_safe(nc, next, &surface->frame_callback_list, link) { - wl_callback_send_done(nc->resource, time); - wl_resource_destroy(nc->resource); - } - wl_list_init(&surface->frame_callback_list); - - /* FIXME: toytoolkit need a pre-block handler where we can - * call this. */ - wl_display_flush_clients(surface->nested->child_display); -} - -static void -redraw_handler(struct widget *widget, void *data) -{ - struct nested *nested = data; - cairo_surface_t *surface; - cairo_t *cr; - struct rectangle allocation; - - 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); - - nested->renderer->render_clients(nested, cr); - - cairo_destroy(cr); - - cairo_surface_destroy(surface); -} - -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': %s\n", - path, strerror(errno)); - 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': %s\n", path, - strerror(errno)); - 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: %s\n", - strerror(errno)); - exit(-1); - } - - snprintf(s, sizeof s, "%d", clientfd); - setenv("WAYLAND_SOCKET", s, 1); - - execl(path, path, NULL); - - fprintf(stderr, "compositor: executing '%s' failed: %s\n", - path, strerror(errno)); - 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); - struct nested *nested = surface->nested; - struct nested_frame_callback *cb, *next; - - wl_list_for_each_safe(cb, next, - &surface->frame_callback_list, link) - wl_resource_destroy(cb->resource); - - wl_list_for_each_safe(cb, next, - &surface->pending.frame_callback_list, link) - wl_resource_destroy(cb->resource); - - pixman_region32_fini(&surface->pending.damage); - - nested->renderer->surface_fini(surface); - - wl_list_remove(&surface->link); - - 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; - struct nested_buffer *buffer = NULL; - - if (buffer_resource) { - int format; - - if (!query_buffer(nested->egl_display, (void *) buffer_resource, - EGL_TEXTURE_FORMAT, &format)) { - wl_resource_post_error(buffer_resource, - WL_DISPLAY_ERROR_INVALID_OBJECT, - "attaching non-egl wl_buffer"); - return; - } - - switch (format) { - case EGL_TEXTURE_RGB: - case EGL_TEXTURE_RGBA: - break; - default: - wl_resource_post_error(buffer_resource, - WL_DISPLAY_ERROR_INVALID_OBJECT, - "invalid format"); - return; - } - - buffer = nested_buffer_from_resource(buffer_resource); - if (buffer == NULL) { - wl_client_post_no_memory(client); - return; - } - } - - if (surface->pending.buffer) - wl_list_remove(&surface->pending.buffer_destroy_listener.link); - - surface->pending.buffer = buffer; - surface->pending.newly_attached = 1; - if (buffer) { - wl_signal_add(&buffer->destroy_signal, - &surface->pending.buffer_destroy_listener); - } -} - -static void -nested_surface_attach(struct nested_surface *surface, - struct nested_buffer *buffer) -{ - struct nested *nested = surface->nested; - - if (surface->image != EGL_NO_IMAGE_KHR) - destroy_image(nested->egl_display, surface->image); - - 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; - } - - nested->renderer->surface_attach(surface, buffer); -} - -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 nested_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 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); - - 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(surface->pending.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) -{ - struct nested_surface *surface = wl_resource_get_user_data(resource); - struct nested *nested = surface->nested; - - /* wl_surface.attach */ - if (surface->pending.newly_attached) - nested_surface_attach(surface, surface->pending.buffer); - - if (surface->pending.buffer) { - wl_list_remove(&surface->pending.buffer_destroy_listener.link); - surface->pending.buffer = NULL; - } - surface->pending.newly_attached = 0; - - /* wl_surface.damage */ - pixman_region32_clear(&surface->pending.damage); - - /* wl_surface.frame */ - wl_list_insert_list(&surface->frame_callback_list, - &surface->pending.frame_callback_list); - wl_list_init(&surface->pending.frame_callback_list); - - /* FIXME: For the subsurface renderer we don't need to - * actually redraw the window. However we do want to cause a - * commit because the subsurface is synchronized. Ideally we - * would just queue the commit */ - window_schedule_redraw(nested->window); -} - -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 -surface_handle_pending_buffer_destroy(struct wl_listener *listener, void *data) -{ - struct nested_surface *surface = - container_of(listener, struct nested_surface, - pending.buffer_destroy_listener); - - surface->pending.buffer = NULL; -} - -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; - - wl_list_init(&surface->frame_callback_list); - - wl_list_init(&surface->pending.frame_callback_list); - surface->pending.buffer_destroy_listener.notify = - surface_handle_pending_buffer_destroy; - pixman_region32_init(&surface->pending.damage); - - display_acquire_window_surface(nested->display, - nested->window, NULL); - - nested->renderer->surface_init(surface); - - 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 use_ss_renderer = 0; - int fd, ret; - - wl_list_init(&nested->surface_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 (!weston_check_egl_extension(extensions, "EGL_WL_bind_wayland_display")) { - 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; - } - - if (display_has_subcompositor(nested->display)) { - const char *func = "eglCreateWaylandBufferFromImageWL"; - const char *ext = "EGL_WL_create_wayland_buffer_from_image"; - - if (weston_check_egl_extension(extensions, ext)) { - create_wayland_buffer_from_image = - (void *) eglGetProcAddress(func); - use_ss_renderer = 1; - } - } - - if (option_blit) - use_ss_renderer = 0; - - if (use_ss_renderer) { - printf("Using subsurfaces to render client surfaces\n"); - nested->renderer = &nested_ss_renderer; - } else { - printf("Using local compositing with blits to " - "render client surfaces\n"); - nested->renderer = &nested_blit_renderer; - } - - 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 = window_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); -} - -/*** blit renderer ***/ - -static void -blit_surface_init(struct nested_surface *surface) -{ - struct nested_blit_surface *blit_surface = - xzalloc(sizeof *blit_surface); - - glGenTextures(1, &blit_surface->texture); - glBindTexture(GL_TEXTURE_2D, blit_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); - - surface->renderer_data = blit_surface; -} - -static void -blit_surface_fini(struct nested_surface *surface) -{ - struct nested_blit_surface *blit_surface = surface->renderer_data; - - nested_buffer_reference(&blit_surface->buffer_ref, NULL); - - glDeleteTextures(1, &blit_surface->texture); - - free(blit_surface); -} - -static void -blit_frame_callback(void *data, struct wl_callback *callback, uint32_t time) -{ - struct nested *nested = data; - struct nested_surface *surface; - - wl_list_for_each(surface, &nested->surface_list, link) - flush_surface_frame_callback_list(surface, time); - - if (callback) - wl_callback_destroy(callback); -} - -static const struct wl_callback_listener blit_frame_listener = { - blit_frame_callback -}; - -static void -blit_render_clients(struct nested *nested, - cairo_t *cr) -{ - struct nested_surface *s; - struct rectangle allocation; - struct wl_callback *callback; - - widget_get_allocation(nested->widget, &allocation); - - wl_list_for_each(s, &nested->surface_list, link) { - struct nested_blit_surface *blit_surface = s->renderer_data; - - display_acquire_window_surface(nested->display, - nested->window, NULL); - - glBindTexture(GL_TEXTURE_2D, blit_surface->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, blit_surface->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); - } - - callback = wl_surface_frame(window_get_wl_surface(nested->window)); - wl_callback_add_listener(callback, &blit_frame_listener, nested); -} - -static void -blit_surface_attach(struct nested_surface *surface, - struct nested_buffer *buffer) -{ - struct nested *nested = surface->nested; - struct nested_blit_surface *blit_surface = surface->renderer_data; - EGLint width, height; - cairo_device_t *device; - - nested_buffer_reference(&blit_surface->buffer_ref, buffer); - - if (blit_surface->cairo_surface) - cairo_surface_destroy(blit_surface->cairo_surface); - - 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); - blit_surface->cairo_surface = - cairo_gl_surface_create_for_texture(device, - CAIRO_CONTENT_COLOR_ALPHA, - blit_surface->texture, - width, height); -} - -static const struct nested_renderer -nested_blit_renderer = { - .surface_init = blit_surface_init, - .surface_fini = blit_surface_fini, - .render_clients = blit_render_clients, - .surface_attach = blit_surface_attach -}; - -/*** subsurface renderer ***/ - -static void -ss_surface_init(struct nested_surface *surface) -{ - struct nested *nested = surface->nested; - struct wl_compositor *compositor = - display_get_compositor(nested->display); - struct nested_ss_surface *ss_surface = - xzalloc(sizeof *ss_surface); - struct rectangle allocation; - struct wl_region *region; - - ss_surface->widget = - window_add_subsurface(nested->window, - nested, - SUBSURFACE_SYNCHRONIZED); - - widget_set_use_cairo(ss_surface->widget, 0); - - ss_surface->surface = widget_get_wl_surface(ss_surface->widget); - ss_surface->subsurface = widget_get_wl_subsurface(ss_surface->widget); - - /* The toy toolkit gets confused about the pointer position - * when it gets motion events for a subsurface so we'll just - * disable input on it */ - region = wl_compositor_create_region(compositor); - wl_surface_set_input_region(ss_surface->surface, region); - wl_region_destroy(region); - - widget_get_allocation(nested->widget, &allocation); - wl_subsurface_set_position(ss_surface->subsurface, - allocation.x + 10, - allocation.y + 10); - - surface->renderer_data = ss_surface; -} - -static void -ss_surface_fini(struct nested_surface *surface) -{ - struct nested_ss_surface *ss_surface = surface->renderer_data; - - widget_destroy(ss_surface->widget); - - if (ss_surface->frame_callback) - wl_callback_destroy(ss_surface->frame_callback); - - free(ss_surface); -} - -static void -ss_render_clients(struct nested *nested, - cairo_t *cr) -{ - /* The clients are composited by the parent compositor so we - * don't need to do anything here */ -} - -static void -ss_buffer_release(void *data, struct wl_buffer *wl_buffer) -{ - struct nested_buffer *buffer = data; - - nested_buffer_reference(&buffer->parent_ref, NULL); -} - -static struct wl_buffer_listener ss_buffer_listener = { - ss_buffer_release -}; - -static void -ss_frame_callback(void *data, struct wl_callback *callback, uint32_t time) -{ - struct nested_surface *surface = data; - struct nested_ss_surface *ss_surface = surface->renderer_data; - - flush_surface_frame_callback_list(surface, time); - - if (callback) - wl_callback_destroy(callback); - - ss_surface->frame_callback = NULL; -} - -static const struct wl_callback_listener ss_frame_listener = { - ss_frame_callback -}; - -static void -ss_surface_attach(struct nested_surface *surface, - struct nested_buffer *buffer) -{ - struct nested *nested = surface->nested; - struct nested_ss_surface *ss_surface = surface->renderer_data; - struct wl_buffer *parent_buffer; - const pixman_box32_t *rects; - int n_rects, i; - - if (buffer) { - /* Create a representation of the buffer in the parent - * compositor if we haven't already */ - if (buffer->parent_buffer == NULL) { - EGLDisplay *edpy = nested->egl_display; - EGLImageKHR image = surface->image; - - buffer->parent_buffer = - create_wayland_buffer_from_image(edpy, image); - - wl_buffer_add_listener(buffer->parent_buffer, - &ss_buffer_listener, - buffer); - } - - parent_buffer = buffer->parent_buffer; - - /* We'll take a reference to the buffer while the parent - * compositor is using it so that we won't report the release - * event until the parent has also finished with it */ - nested_buffer_reference(&buffer->parent_ref, buffer); - } else { - parent_buffer = NULL; - } - - wl_surface_attach(ss_surface->surface, parent_buffer, 0, 0); - - rects = pixman_region32_rectangles(&surface->pending.damage, &n_rects); - - for (i = 0; i < n_rects; i++) { - const pixman_box32_t *rect = rects + i; - wl_surface_damage(ss_surface->surface, - rect->x1, - rect->y1, - rect->x2 - rect->x1, - rect->y2 - rect->y1); - } - - if (ss_surface->frame_callback) - wl_callback_destroy(ss_surface->frame_callback); - - ss_surface->frame_callback = wl_surface_frame(ss_surface->surface); - wl_callback_add_listener(ss_surface->frame_callback, - &ss_frame_listener, - surface); - - wl_surface_commit(ss_surface->surface); -} - -static const struct nested_renderer -nested_ss_renderer = { - .surface_init = ss_surface_init, - .surface_fini = ss_surface_fini, - .render_clients = ss_render_clients, - .surface_attach = ss_surface_attach -}; - -int -main(int argc, char *argv[]) -{ - struct display *display; - struct nested *nested; - - if (parse_options(nested_options, - ARRAY_LENGTH(nested_options), &argc, argv) > 1) { - printf("Usage: %s [OPTIONS]\n --blit or -b\n", argv[0]); - exit(1); - } - - display = display_create(&argc, argv); - if (display == NULL) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - 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/presentation-shm.c b/clients/presentation-shm.c deleted file mode 100644 index d5d73a2..0000000 --- a/clients/presentation-shm.c +++ /dev/null @@ -1,956 +0,0 @@ -/* - * Copyright © 2011 Benjamin Franzke - * Copyright © 2010 Intel Corporation - * Copyright © 2014 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "shared/helpers.h" -#include -#include "shared/timespec-util.h" -#include "shared/os-compatibility.h" -#include "presentation-time-client-protocol.h" -#include "xdg-shell-client-protocol.h" - -enum run_mode { - RUN_MODE_FEEDBACK, - RUN_MODE_FEEDBACK_IDLE, - RUN_MODE_PRESENT, -}; - -static const char * const run_mode_name[] = { - [RUN_MODE_FEEDBACK] = "feedback", - [RUN_MODE_FEEDBACK_IDLE] = "feedback-idle", - [RUN_MODE_PRESENT] = "low-lat present", -}; - -struct output { - struct wl_output *output; - uint32_t name; - struct wl_list link; -}; - -struct display { - struct wl_display *display; - struct wl_registry *registry; - struct wl_compositor *compositor; - struct xdg_wm_base *wm_base; - - struct wl_shm *shm; - uint32_t formats; - - struct wp_presentation *presentation; - clockid_t clk_id; - - struct wl_list output_list; /* struct output::link */ -}; - -struct feedback { - struct window *window; - unsigned frame_no; - struct wp_presentation_feedback *feedback; - struct timespec commit; - struct timespec target; - uint32_t frame_stamp; - struct wl_list link; - struct timespec present; -}; - -struct buffer { - struct wl_buffer *buffer; - void *shm_data; - int busy; -}; - -struct window { - struct display *display; - int width, height; - enum run_mode mode; - struct wl_surface *surface; - struct xdg_surface *xdg_surface; - struct xdg_toplevel *xdg_toplevel; - uint32_t configure_serial; - - struct buffer *buffers; - int num_buffers; - int next; - int refresh_nsec; - int commit_delay_msecs; - - struct wl_callback *callback; - struct wl_list feedback_list; - - struct feedback *received_feedback; -}; - -#define NSEC_PER_SEC 1000000000 - -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_buffers(struct display *display, struct buffer **buffers, - int num_buffers, int width, int height, uint32_t format) -{ - struct buffer *bufs; - struct wl_shm_pool *pool; - int fd, size, stride, offset; - void *data; - int i; - - stride = width * 4; - size = stride * height * num_buffers; - - fd = os_create_anonymous_file(size); - if (fd < 0) { - fprintf(stderr, "creating a buffer file for %d B failed: %s\n", - size, strerror(errno)); - return -1; - } - - data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (data == MAP_FAILED) { - fprintf(stderr, "mmap failed: %s\n", strerror(errno)); - close(fd); - return -1; - } - - pool = wl_shm_create_pool(display->shm, fd, size); - offset = 0; - - bufs = calloc(num_buffers, sizeof(*bufs)); - assert(bufs); - - for (i = 0; i < num_buffers; i++) { - bufs[i].buffer = wl_shm_pool_create_buffer(pool, offset, - width, height, - stride, format); - assert(bufs[i].buffer); - wl_buffer_add_listener(bufs[i].buffer, - &buffer_listener, &bufs[i]); - - bufs[i].shm_data = (char *)data + offset; - offset += stride * height; - } - - wl_shm_pool_destroy(pool); - close(fd); - - *buffers = bufs; - - return 0; -} - -static void -xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *xdg_wm_base, - uint32_t serial) -{ - xdg_wm_base_pong(xdg_wm_base, serial); -} - -static const struct xdg_wm_base_listener xdg_wm_base_listener = { - .ping = xdg_wm_base_handle_ping, -}; - -static void -xdg_surface_handle_configure(void *data, struct xdg_surface *xdg_surface, - uint32_t serial) -{ - struct window *window = data; - - window->configure_serial = serial; -} - -static const struct xdg_surface_listener xdg_surface_listener = { - .configure = xdg_surface_handle_configure, -}; - - -static void -xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *xdg_toplevel, - int32_t width, int32_t height, - struct wl_array *states) -{ - /* noop */ -} - -static void -xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) -{ - fprintf(stderr, "presentation-shm exiting\n"); - exit(0); -} - -static const struct xdg_toplevel_listener xdg_toplevel_listener = { - .configure = xdg_toplevel_handle_configure, - .close = xdg_toplevel_handle_close, -}; - -static struct window * -create_window(struct display *display, int width, int height, - enum run_mode mode, int commit_delay_msecs) -{ - struct window *window; - char title[128]; - int ret; - - snprintf(title, sizeof(title), - "presentation-shm: %s [Delay %i msecs]", run_mode_name[mode], - commit_delay_msecs); - - window = zalloc(sizeof *window); - if (!window) - return NULL; - - window->commit_delay_msecs = commit_delay_msecs; - window->mode = mode; - window->callback = NULL; - wl_list_init(&window->feedback_list); - window->display = display; - window->width = width; - window->height = height; - window->surface = wl_compositor_create_surface(display->compositor); - window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, - window->surface); - - if (!window->xdg_surface) - return NULL; - - window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); - - if (!window->xdg_toplevel) - return NULL; - - xdg_wm_base_add_listener(display->wm_base, &xdg_wm_base_listener, - NULL); - xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, - window); - xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, - window); - - xdg_toplevel_set_title(window->xdg_toplevel, title); - xdg_toplevel_set_min_size(window->xdg_toplevel, width, height); - xdg_toplevel_set_max_size(window->xdg_toplevel, width, height); - - wl_surface_commit(window->surface); - wl_display_roundtrip(window->display->display); - - window->num_buffers = 60; - window->refresh_nsec = NSEC_PER_SEC / 60; /* 60 Hz guess */ - window->next = 0; - ret = create_shm_buffers(window->display, - &window->buffers, window->num_buffers, - window->width, window->height, - WL_SHM_FORMAT_XRGB8888); - assert(ret == 0); - - return window; -} - -static void -destroy_feedback(struct feedback *feedback) -{ - if (feedback->feedback) - wp_presentation_feedback_destroy(feedback->feedback); - - wl_list_remove(&feedback->link); - free(feedback); -} - -static void -destroy_window(struct window *window) -{ - int i; - - while (!wl_list_empty(&window->feedback_list)) { - struct feedback *f; - - f = wl_container_of(window->feedback_list.next, f, link); - printf("clean up feedback %u\n", f->frame_no); - destroy_feedback(f); - } - - if (window->callback) - wl_callback_destroy(window->callback); - - xdg_surface_destroy(window->xdg_surface); - wl_surface_destroy(window->surface); - - for (i = 0; i < window->num_buffers; i++) - wl_buffer_destroy(window->buffers[i].buffer); - /* munmap(window->buffers[0].shm_data, size); */ - free(window->buffers); - - free(window); -} - -static struct buffer * -window_next_buffer(struct window *window) -{ - struct buffer *buf = &window->buffers[window->next]; - - window->next = (window->next + 1) % window->num_buffers; - - return buf; -} - -static void -paint_pixels(void *image, int width, int height, uint32_t phase) -{ - const int halfh = height / 2; - const int halfw = width / 2; - uint32_t *pixel = image; - int y, or; - double ang = M_PI * 2.0 / 1000000.0 * phase; - double s = sin(ang); - double c = cos(ang); - - /* squared radii thresholds */ - or = (halfw < halfh ? halfw : halfh) - 16; - or *= or; - - for (y = 0; y < height; y++) { - int x; - int oy = y - halfh; - int y2 = oy * oy; - - for (x = 0; x < width; x++) { - int ox = x - halfw; - uint32_t v = 0xff000000; - double rx, ry; - - if (ox * ox + y2 > or) { - if (ox * oy > 0) - *pixel++ = 0xff000000; - else - *pixel++ = 0xffffffff; - continue; - } - - rx = c * ox + s * oy; - ry = -s * ox + c * oy; - - if (rx < 0.0) - v |= 0x00ff0000; - if (ry < 0.0) - v |= 0x0000ff00; - if ((rx < 0.0) == (ry < 0.0)) - v |= 0x000000ff; - - *pixel++ = v; - } - } -} - -static void -feedback_sync_output(void *data, - struct wp_presentation_feedback *presentation_feedback, - struct wl_output *output) -{ - /* not interested */ -} - -static char * -pflags_to_str(uint32_t flags, char *str, unsigned len) -{ - static const struct { - uint32_t flag; - char sym; - } desc[] = { - { WP_PRESENTATION_FEEDBACK_KIND_VSYNC, 's' }, - { WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK, 'c' }, - { WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION, 'e' }, - { WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY, 'z' }, - }; - unsigned i; - - *str = '\0'; - if (len < ARRAY_LENGTH(desc) + 1) - return str; - - for (i = 0; i < ARRAY_LENGTH(desc); i++) - str[i] = flags & desc[i].flag ? desc[i].sym : '_'; - str[ARRAY_LENGTH(desc)] = '\0'; - - return str; -} - -static uint32_t -timespec_to_ms(const struct timespec *ts) -{ - return (uint32_t)ts->tv_sec * 1000 + ts->tv_nsec / 1000000; -} - -static int -timespec_diff_to_usec(const struct timespec *a, const struct timespec *b) -{ - time_t secs = a->tv_sec - b->tv_sec; - long nsec = a->tv_nsec - b->tv_nsec; - - return secs * 1000000 + nsec / 1000; -} - -static void -feedback_presented(void *data, - struct wp_presentation_feedback *presentation_feedback, - uint32_t tv_sec_hi, - uint32_t tv_sec_lo, - uint32_t tv_nsec, - uint32_t refresh_nsec, - uint32_t seq_hi, - uint32_t seq_lo, - uint32_t flags) -{ - struct feedback *feedback = data; - struct window *window = feedback->window; - struct feedback *prev_feedback = window->received_feedback; - uint64_t seq = ((uint64_t)seq_hi << 32) + seq_lo; - const struct timespec *prevpresent; - uint32_t commit, present; - uint32_t f2c, c2p, f2p; - int p2p, t2p; - char flagstr[10]; - - timespec_from_proto(&feedback->present, tv_sec_hi, tv_sec_lo, tv_nsec); - commit = timespec_to_ms(&feedback->commit); - present = timespec_to_ms(&feedback->present); - - if (prev_feedback) - prevpresent = &prev_feedback->present; - else - prevpresent = &feedback->present; - - f2c = commit - feedback->frame_stamp; - c2p = present - commit; - f2p = present - feedback->frame_stamp; - p2p = timespec_diff_to_usec(&feedback->present, prevpresent); - t2p = timespec_diff_to_usec(&feedback->present, &feedback->target); - - switch (window->mode) { - case RUN_MODE_PRESENT: - printf("%6u: c2p %4u ms, p2p %5d us, t2p %6d us, [%s] " - "seq %" PRIu64 "\n", feedback->frame_no, c2p, - p2p, t2p, - pflags_to_str(flags, flagstr, sizeof(flagstr)), seq); - break; - case RUN_MODE_FEEDBACK: - case RUN_MODE_FEEDBACK_IDLE: - printf("%6u: f2c %2u ms, c2p %2u ms, f2p %2u ms, p2p %5d us, " - "t2p %6d, [%s], seq %" PRIu64 "\n", feedback->frame_no, - f2c, c2p, f2p, p2p, t2p, - pflags_to_str(flags, flagstr, sizeof(flagstr)), seq); - } - - if (window->received_feedback) - destroy_feedback(window->received_feedback); - window->received_feedback = feedback; -} - -static void -feedback_discarded(void *data, - struct wp_presentation_feedback *presentation_feedback) -{ - struct feedback *feedback = data; - - printf("discarded %u\n", feedback->frame_no); - - destroy_feedback(feedback); -} - -static const struct wp_presentation_feedback_listener feedback_listener = { - feedback_sync_output, - feedback_presented, - feedback_discarded -}; - -static void -window_emulate_rendering(struct window *window) -{ - struct timespec delay; - int ret; - - if (window->commit_delay_msecs <= 0) - return; - - delay.tv_sec = window->commit_delay_msecs / 1000; - delay.tv_nsec = (window->commit_delay_msecs % 1000) * 1000000; - - ret = nanosleep(&delay, NULL); - if (ret) - printf("nanosleep failed: %s\n", strerror(errno)); -} - -static void -window_create_feedback(struct window *window, uint32_t frame_stamp) -{ - static unsigned seq; - struct wp_presentation *pres = window->display->presentation; - struct feedback *feedback; - - seq++; - - if (!pres) - return; - - feedback = zalloc(sizeof *feedback); - if (!feedback) - return; - - feedback->window = window; - feedback->feedback = wp_presentation_feedback(pres, window->surface); - wp_presentation_feedback_add_listener(feedback->feedback, - &feedback_listener, feedback); - - feedback->frame_no = seq; - - clock_gettime(window->display->clk_id, &feedback->commit); - feedback->frame_stamp = frame_stamp; - feedback->target = feedback->commit; - - wl_list_insert(&window->feedback_list, &feedback->link); -} - -static void -window_commit_next(struct window *window) -{ - struct buffer *buffer; - - buffer = window_next_buffer(window); - assert(buffer); - - if (window->configure_serial) { - xdg_surface_ack_configure(window->xdg_surface, - window->configure_serial); - window->configure_serial = 0; - } - - wl_surface_attach(window->surface, buffer->buffer, 0, 0); - wl_surface_damage(window->surface, 0, 0, window->width, window->height); - wl_surface_commit(window->surface); - buffer->busy = 1; -} - -static const struct wl_callback_listener frame_listener_mode_feedback; - -static void -redraw_mode_feedback(void *data, struct wl_callback *callback, uint32_t time) -{ - struct window *window = data; - - if (callback && window->mode == RUN_MODE_FEEDBACK_IDLE) - sleep(1); - - if (callback) - wl_callback_destroy(callback); - - window_emulate_rendering(window); - - window->callback = wl_surface_frame(window->surface); - wl_callback_add_listener(window->callback, - &frame_listener_mode_feedback, window); - - window_create_feedback(window, time); - window_commit_next(window); -} - -static const struct wl_callback_listener frame_listener_mode_feedback = { - redraw_mode_feedback -}; - -static const struct wp_presentation_feedback_listener feedkick_listener; - -static void -window_feedkick(struct window *window) -{ - struct wp_presentation *pres = window->display->presentation; - struct wp_presentation_feedback *fback; - - fback = wp_presentation_feedback(pres, window->surface); - wp_presentation_feedback_add_listener(fback, &feedkick_listener, - window); -} - -static void -feedkick_presented(void *data, - struct wp_presentation_feedback *presentation_feedback, - uint32_t tv_sec_hi, - uint32_t tv_sec_lo, - uint32_t tv_nsec, - uint32_t refresh_nsec, - uint32_t seq_hi, - uint32_t seq_lo, - uint32_t flags) -{ - struct window *window = data; - - wp_presentation_feedback_destroy(presentation_feedback); - window->refresh_nsec = refresh_nsec; - - switch (window->mode) { - case RUN_MODE_PRESENT: - window_emulate_rendering(window); - window_create_feedback(window, 0); - window_feedkick(window); - window_commit_next(window); - break; - case RUN_MODE_FEEDBACK: - case RUN_MODE_FEEDBACK_IDLE: - assert(0 && "bad mode"); - } -} - -static void -feedkick_discarded(void *data, - struct wp_presentation_feedback *presentation_feedback) -{ - struct window *window = data; - - wp_presentation_feedback_destroy(presentation_feedback); - - switch (window->mode) { - case RUN_MODE_PRESENT: - window_emulate_rendering(window); - window_create_feedback(window, 0); - window_feedkick(window); - window_commit_next(window); - break; - case RUN_MODE_FEEDBACK: - case RUN_MODE_FEEDBACK_IDLE: - assert(0 && "bad mode"); - } -} - -static const struct wp_presentation_feedback_listener feedkick_listener = { - feedback_sync_output, - feedkick_presented, - feedkick_discarded -}; - -static void -firstdraw_mode_burst(struct window *window) -{ - window_emulate_rendering(window); - - switch (window->mode) { - case RUN_MODE_PRESENT: - window_create_feedback(window, 0); - break; - case RUN_MODE_FEEDBACK: - case RUN_MODE_FEEDBACK_IDLE: - assert(0 && "bad mode"); - } - - window_feedkick(window); - window_commit_next(window); -} - -static void -window_prerender(struct window *window) -{ - int i; - int timefactor = 1000000 / window->num_buffers; - - for (i = 0; i < window->num_buffers; i++) { - struct buffer *buf = &window->buffers[i]; - - if (buf->busy) - fprintf(stderr, "wl_buffer id %u) busy\n", - wl_proxy_get_id( - (struct wl_proxy *)buf->buffer)); - - paint_pixels(buf->shm_data, window->width, window->height, - i * timefactor); - } -} - -static void -output_destroy(struct output *o) -{ - wl_output_destroy(o->output); - wl_list_remove(&o->link); - free(o); -} - -static void -display_add_output(struct display *d, uint32_t name, uint32_t version) -{ - struct output *o; - - o = zalloc(sizeof(*o)); - assert(o); - - o->output = wl_registry_bind(d->registry, name, - &wl_output_interface, 1); - o->name = name; - wl_list_insert(&d->output_list, &o->link); -} - -static void -presentation_clock_id(void *data, struct wp_presentation *presentation, - uint32_t clk_id) -{ - struct display *d = data; - - d->clk_id = clk_id; -} - -static const struct wp_presentation_listener presentation_listener = { - presentation_clock_id -}; - -static void -shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) -{ - struct display *d = data; - - d->formats |= (1 << format); -} - -static const struct wl_shm_listener shm_listener = { - shm_format -}; - -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, "xdg_wm_base") == 0) { - d->wm_base = - wl_registry_bind(registry, name, - &xdg_wm_base_interface, version); - } else if (strcmp(interface, "wl_shm") == 0) { - d->shm = wl_registry_bind(registry, - name, &wl_shm_interface, 1); - wl_shm_add_listener(d->shm, &shm_listener, d); - } else if (strcmp(interface, "wl_output") == 0) { - display_add_output(d, name, version); - } else if (strcmp(interface, wp_presentation_interface.name) == 0) { - d->presentation = - wl_registry_bind(registry, - name, &wp_presentation_interface, 1); - wp_presentation_add_listener(d->presentation, - &presentation_listener, d); - } -} - -static void -registry_handle_global_remove(void *data, struct wl_registry *registry, - uint32_t name) -{ - struct display *d = data; - struct output *output, *otmp; - - wl_list_for_each_safe(output, otmp, &d->output_list, link) { - if (output->name != name) - continue; - - output_destroy(output); - } -} - -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->clk_id = -1; - wl_list_init(&display->output_list); - 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) -{ - while (!wl_list_empty(&display->output_list)) { - struct output *o; - - o = wl_container_of(display->output_list.next, o, link); - output_destroy(o); - } - - if (display->shm) - wl_shm_destroy(display->shm); - - if (display->wm_base) - xdg_wm_base_destroy(display->wm_base); - - if (display->compositor) - wl_compositor_destroy(display->compositor); - - wl_registry_destroy(display->registry); - wl_display_flush(display->display); - wl_display_disconnect(display->display); - free(display); -} - -static int running = 1; - -static void -signal_int(int signum) -{ - running = 0; -} - -static void -usage(const char *prog, int exit_code) -{ - fprintf(stderr, "Usage: %s [mode] [options]\n" - "where 'mode' is one of\n" - " -f\t\trun in feedback mode (default)\n" - " -i\t\trun in feedback-idle mode; sleep 1s between frames\n" - " -p\t\trun in low-latency presentation mode\n" - "and 'options' may include\n" - " -d msecs\temulate the time used for rendering by a delay \n" - "\t\tof the given milliseconds before commit\n\n", - prog); - - fprintf(stderr, "Printed timing statistics, depending on mode:\n" - " commit sequence number\n" - " f2c: time from frame callback timestamp to commit\n" - " c2p: time from commit to presentation\n" - " f2p: time from frame callback timestamp to presentation\n" - " p2p: time from previous presentation to this one\n" - " t2p: time from target timestamp to presentation\n" - " seq: MSC\n"); - - - exit(exit_code); -} - -int -main(int argc, char **argv) -{ - struct sigaction sigint; - struct display *display; - struct window *window; - int ret = 0; - enum run_mode mode = RUN_MODE_FEEDBACK; - int i; - int commit_delay_msecs = 0; - - for (i = 1; i < argc; i++) { - if (strcmp("-f", argv[i]) == 0) - mode = RUN_MODE_FEEDBACK; - else if (strcmp("-i", argv[i]) == 0) - mode = RUN_MODE_FEEDBACK_IDLE; - else if (strcmp("-p", argv[i]) == 0) - mode = RUN_MODE_PRESENT; - else if ((strcmp("-d", argv[i]) == 0) && (i + 1 < argc)) { - i++; - commit_delay_msecs = atoi(argv[i]); - } - else - usage(argv[0], EXIT_FAILURE); - } - - display = create_display(); - window = create_window(display, 250, 250, mode, commit_delay_msecs); - if (!window) - return 1; - - sigint.sa_handler = signal_int; - sigemptyset(&sigint.sa_mask); - sigint.sa_flags = SA_RESETHAND; - sigaction(SIGINT, &sigint, NULL); - - window_prerender(window); - - switch (mode) { - case RUN_MODE_FEEDBACK: - case RUN_MODE_FEEDBACK_IDLE: - redraw_mode_feedback(window, NULL, 0); - break; - case RUN_MODE_PRESENT: - firstdraw_mode_burst(window); - break; - } - - while (running && ret != -1) - ret = wl_display_dispatch(display->display); - - fprintf(stderr, "presentation-shm exiting\n"); - destroy_window(window); - destroy_display(display); - - return 0; -} diff --git a/clients/resizor.c b/clients/resizor.c deleted file mode 100644 index cfc5d41..0000000 --- a/clients/resizor.c +++ /dev/null @@ -1,456 +0,0 @@ -/* - * Copyright © 2010 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "window.h" -#include "shared/xalloc.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; - bool pointer_locked; - bool locked_frame_callback_registered; - struct input *locked_input; - float pointer_x; - float pointer_y; -}; - -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); - - if (resizor->frame_callback) { - wl_callback_destroy(resizor->frame_callback); - resizor->frame_callback = NULL; - } - - if (window_is_maximized(resizor->window)) - return; - - spring_update(&resizor->width); - spring_update(&resizor->height); - - widget_schedule_resize(resizor->widget, - resizor->width.current + 0.5, - resizor->height.current + 0.5); - - 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(void *data, struct input *input, int index) -{ - 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 -locked_pointer_handle_motion(struct window *window, - struct input *input, - uint32_t time, - float dx, - float dy, - void *data) -{ - struct resizor *resizor = data; - - resizor->width.current += dx; - resizor->width.previous = resizor->width.current; - resizor->width.target = resizor->width.current; - - resizor->height.current += dy; - resizor->height.previous = resizor->height.current; - resizor->height.target = resizor->height.current; - - widget_schedule_resize(resizor->widget, - resizor->width.current, - resizor->height.current); -} - -static void -handle_pointer_locked(struct window *window, struct input *input, void *data) -{ - struct resizor *resizor = data; - - resizor->pointer_locked = true; - input_set_pointer_image(input, CURSOR_BLANK); -} - -static void -handle_pointer_unlocked(struct window *window, struct input *input, void *data) -{ - struct resizor *resizor = data; - - resizor->pointer_locked = false; - input_set_pointer_image(input, CURSOR_LEFT_PTR); -} - -static const struct wl_callback_listener locked_pointer_frame_listener; - -static void -locked_pointer_frame_callback(void *data, - struct wl_callback *callback, - uint32_t time) -{ - struct resizor *resizor = data; - struct wl_surface *surface; - struct rectangle allocation; - float x, y; - - if (resizor->pointer_locked) { - widget_get_allocation(resizor->widget, &allocation); - - x = resizor->pointer_x + (allocation.width - allocation.x); - y = resizor->pointer_y + (allocation.height - allocation.y); - - widget_set_locked_pointer_cursor_hint(resizor->widget, x, y); - } - - wl_callback_destroy(callback); - - surface = window_get_wl_surface(resizor->window); - callback = wl_surface_frame(surface); - wl_callback_add_listener(callback, - &locked_pointer_frame_listener, - resizor); -} - -static const struct wl_callback_listener locked_pointer_frame_listener = { - locked_pointer_frame_callback -}; - -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; - struct rectangle allocation; - struct wl_surface *surface; - struct wl_callback *callback; - - if (button == BTN_RIGHT && state == WL_POINTER_BUTTON_STATE_PRESSED) { - show_menu(resizor, input, time); - } else if (button == BTN_LEFT && - state == WL_POINTER_BUTTON_STATE_PRESSED) { - window_get_allocation(resizor->window, &allocation); - - resizor->width.current = allocation.width; - resizor->width.previous = allocation.width; - resizor->width.target = allocation.width; - - resizor->height.current = allocation.height; - resizor->height.previous = allocation.height; - resizor->height.target = allocation.height; - - window_lock_pointer(resizor->window, input); - window_set_pointer_locked_handler(resizor->window, - handle_pointer_locked, - handle_pointer_unlocked); - resizor->locked_input = input; - - if (resizor->locked_frame_callback_registered) - return; - - surface = window_get_wl_surface(resizor->window); - callback = wl_surface_frame(surface); - wl_callback_add_listener(callback, - &locked_pointer_frame_listener, - resizor); - resizor->locked_frame_callback_registered = true; - } else if (button == BTN_LEFT && - state == WL_POINTER_BUTTON_STATE_RELEASED) { - input_set_pointer_image(input, CURSOR_LEFT_PTR); - window_unlock_pointer(resizor->window); - } -} - -static void -set_cursor_inv_offset(struct resizor *resizor, float x, float y) -{ - struct rectangle allocation; - - widget_get_allocation(resizor->widget, &allocation); - - resizor->pointer_x = x - (allocation.width - allocation.x); - resizor->pointer_y = y - (allocation.height - allocation.y); -} - -static int -enter_handler(struct widget *widget, - struct input *input, - float x, float y, void *data) -{ - struct resizor *resizor = data; - - set_cursor_inv_offset(resizor, x , y); - - return CURSOR_LEFT_PTR; -} - -static int -motion_handler(struct widget *widget, - struct input *input, uint32_t time, - float x, float y, void *data) -{ - struct resizor *resizor = data; - - set_cursor_inv_offset(resizor, x , y); - - return CURSOR_LEFT_PTR; -} - -static struct resizor * -resizor_create(struct display *display) -{ - struct resizor *resizor; - - resizor = xzalloc(sizeof *resizor); - resizor->window = window_create(display); - resizor->widget = window_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_enter_handler(resizor->widget, enter_handler); - widget_set_motion_handler(resizor->widget, motion_handler); - - window_set_locked_pointer_motion_handler( - resizor->window, locked_pointer_handle_motion); - - 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: %s\n", - strerror(errno)); - return -1; - } - - resizor = resizor_create(display); - - display_run(display); - - resizor_destroy(resizor); - display_destroy(display); - - return 0; -} diff --git a/clients/scaler.c b/clients/scaler.c deleted file mode 100644 index 91736fb..0000000 --- a/clients/scaler.c +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * Copyright © 2013 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include - -#include "window.h" -#include "viewporter-client-protocol.h" - -#define BUFFER_SCALE 2 -static const int BUFFER_WIDTH = 421 * BUFFER_SCALE; -static const int BUFFER_HEIGHT = 337 * BUFFER_SCALE; -static const int SURFACE_WIDTH = 55 * 4; -static const int SURFACE_HEIGHT = 77 * 4; -static const double RECT_X = 21 * BUFFER_SCALE; /* buffer coords */ -static const double RECT_Y = 25 * BUFFER_SCALE; -static const double RECT_W = 55 * BUFFER_SCALE; -static const double RECT_H = 77 * BUFFER_SCALE; - -struct box { - struct display *display; - struct window *window; - struct widget *widget; - int width, height; - - struct wp_viewporter *viewporter; - struct wp_viewport *viewport; - - enum { - MODE_NO_VIEWPORT, - MODE_SRC_ONLY, - MODE_DST_ONLY, - MODE_SRC_DST - } mode; -}; - -static void -set_my_viewport(struct box *box) -{ - wl_fixed_t src_x, src_y, src_width, src_height; - int32_t dst_width = SURFACE_WIDTH; - int32_t dst_height = SURFACE_HEIGHT; - - if (box->mode == MODE_NO_VIEWPORT) - return; - - /* Cut the green border in half, take white border fully in, - * and black border fully out. The borders are 1px wide in buffer. - * - * The gl-renderer uses linear texture sampling, this means the - * top and left edges go to 100% green, bottom goes to 50% blue/black, - * right edge has thick white sliding to 50% red. - */ - src_x = wl_fixed_from_double((RECT_X + 0.5) / BUFFER_SCALE); - src_y = wl_fixed_from_double((RECT_Y + 0.5) / BUFFER_SCALE); - src_width = wl_fixed_from_double((RECT_W - 0.5) / BUFFER_SCALE); - src_height = wl_fixed_from_double((RECT_H - 0.5) / BUFFER_SCALE); - - switch (box->mode){ - case MODE_SRC_ONLY: - /* In SRC_ONLY mode we're just cropping - in order - * for the surface size to remain an integer, the - * compositor will generate an error if we use a - * fractional width or height. - * - * We use fractional width/height for the other cases - * to ensure fractional values are still tested. - */ - src_width = wl_fixed_from_int(RECT_W / BUFFER_SCALE); - src_height = wl_fixed_from_int(RECT_H / BUFFER_SCALE); - wp_viewport_set_source(box->viewport, src_x, src_y, - src_width, src_height); - break; - case MODE_DST_ONLY: - wp_viewport_set_destination(box->viewport, - dst_width, dst_height); - break; - case MODE_SRC_DST: - wp_viewport_set_source(box->viewport, src_x, src_y, - src_width, src_height); - wp_viewport_set_destination(box->viewport, - dst_width, dst_height); - break; - default: - assert(!"not reached"); - } -} - -static void -resize_handler(struct widget *widget, - int32_t width, int32_t height, void *data) -{ - struct box *box = data; - - /* Don't resize me */ - widget_set_size(box->widget, box->width, box->height); -} - -static void -redraw_handler(struct widget *widget, void *data) -{ - struct box *box = data; - cairo_surface_t *surface; - cairo_t *cr; - - surface = window_get_surface(box->window); - if (surface == NULL || - cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { - fprintf(stderr, "failed to create cairo egl surface\n"); - return; - } - - cr = cairo_create(surface); - cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); - cairo_set_line_width(cr, 1.0); - cairo_translate(cr, RECT_X, RECT_Y); - - /* red background */ - cairo_set_source_rgba(cr, 255, 0, 0, 255); - cairo_paint(cr); - - /* blue box */ - cairo_set_source_rgba(cr, 0, 0, 255, 255); - cairo_rectangle(cr, 0, 0, RECT_W, RECT_H); - cairo_fill(cr); - - /* black border outside the box */ - cairo_set_source_rgb(cr, 0, 0, 0); - cairo_move_to(cr, 0, RECT_H + 0.5); - cairo_line_to(cr, RECT_W, RECT_H + 0.5); - cairo_stroke(cr); - - /* white border inside the box */ - cairo_set_source_rgb(cr, 1, 1, 1); - cairo_move_to(cr, RECT_W - 0.5, 0); - cairo_line_to(cr, RECT_W - 0.5, RECT_H); - cairo_stroke(cr); - - /* the green border on inside the box, to be split half by crop */ - cairo_set_source_rgb(cr, 0, 1, 0); - cairo_move_to(cr, 0.5, RECT_H); - cairo_line_to(cr, 0.5, 0); - cairo_move_to(cr, 0, 0.5); - cairo_line_to(cr, RECT_W, 0.5); - cairo_stroke(cr); - - cairo_destroy(cr); - - /* TODO: buffer_transform */ - - cairo_surface_destroy(surface); -} - -static void -global_handler(struct display *display, uint32_t name, - const char *interface, uint32_t version, void *data) -{ - struct box *box = data; - - if (strcmp(interface, "wp_viewporter") == 0) { - box->viewporter = display_bind(display, name, - &wp_viewporter_interface, 1); - - box->viewport = wp_viewporter_get_viewport(box->viewporter, - widget_get_wl_surface(box->widget)); - - set_my_viewport(box); - } -} - -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 box *box = data; - - if (button != BTN_LEFT) - return; - - if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - window_move(box->window, input, - display_get_serial(box->display)); - } -} - -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 box *box = data; - window_move(box->window, input, - display_get_serial(box->display)); -} - -static void -usage(const char *progname) -{ - fprintf(stderr, "Usage: %s [mode]\n" - "where 'mode' is one of\n" - " -b\tset both src and dst in viewport (default)\n" - " -d\tset only dst in viewport\n" - " -s\tset only src in viewport\n" - " -n\tdo not set viewport at all\n\n", - progname); - - fprintf(stderr, "Expected output with output_scale=1:\n"); - - fprintf(stderr, "Mode -n:\n" - " window size %dx%d px\n" - " Red box with a blue box in the upper left part.\n" - " The blue box has white right edge, black bottom edge,\n" - " and thin green left and top edges that can really\n" - " be seen only when zoomed in.\n\n", - BUFFER_WIDTH / BUFFER_SCALE, BUFFER_HEIGHT / BUFFER_SCALE); - - fprintf(stderr, "Mode -b:\n" - " window size %dx%d px\n" - " Blue box with green top and left edge,\n" - " thick white right edge with a hint of red,\n" - " and a hint of black in bottom edge.\n\n", - SURFACE_WIDTH, SURFACE_HEIGHT); - - fprintf(stderr, "Mode -s:\n" - " window size %.0fx%.0f px\n" - " The same as mode -b, but scaled a lot smaller.\n\n", - RECT_W / BUFFER_SCALE, RECT_H / BUFFER_SCALE); - - fprintf(stderr, "Mode -d:\n" - " window size %dx%d px\n" - " This is horizontally squashed version of the -n mode.\n\n", - SURFACE_WIDTH, SURFACE_HEIGHT); -} - -int -main(int argc, char *argv[]) -{ - struct box box; - struct display *d; - struct timeval tv; - int i; - - box.mode = MODE_SRC_DST; - - for (i = 1; i < argc; i++) { - if (strcmp("-s", argv[i]) == 0) - box.mode = MODE_SRC_ONLY; - else if (strcmp("-d", argv[i]) == 0) - box.mode = MODE_DST_ONLY; - else if (strcmp("-b", argv[i]) == 0) - box.mode = MODE_SRC_DST; - else if (strcmp("-n", argv[i]) == 0) - box.mode = MODE_NO_VIEWPORT; - else { - usage(argv[0]); - exit(1); - } - } - - d = display_create(&argc, argv); - if (d == NULL) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - return -1; - } - - gettimeofday(&tv, NULL); - srandom(tv.tv_usec); - - box.width = BUFFER_WIDTH / BUFFER_SCALE; - box.height = BUFFER_HEIGHT / BUFFER_SCALE; - box.display = d; - box.window = window_create(d); - box.widget = window_add_widget(box.window, &box); - window_set_title(box.window, "Scaler Test Box"); - window_set_buffer_scale(box.window, BUFFER_SCALE); - - widget_set_resize_handler(box.widget, resize_handler); - widget_set_redraw_handler(box.widget, redraw_handler); - widget_set_button_handler(box.widget, button_handler); - widget_set_default_cursor(box.widget, CURSOR_HAND1); - widget_set_touch_down_handler(box.widget, touch_down_handler); - - window_schedule_resize(box.window, box.width, box.height); - - display_set_user_data(box.display, &box); - display_set_global_handler(box.display, global_handler); - - display_run(d); - - widget_destroy(box.widget); - window_destroy(box.window); - display_destroy(d); - - return 0; -} diff --git a/clients/screenshot.c b/clients/screenshot.c deleted file mode 100644 index bbf2e6b..0000000 --- a/clients/screenshot.c +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "weston-screenshooter-client-protocol.h" -#include "shared/os-compatibility.h" -#include "shared/xalloc.h" -#include "shared/file-util.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 */ - - -struct screenshooter_output { - struct wl_output *output; - struct wl_buffer *buffer; - int width, height, offset_x, offset_y; - void *data; - struct wl_list link; -}; - -struct buffer_size { - int width, height; - - int min_x, min_y; - int max_x, max_y; -}; - -struct screenshooter_data { - struct wl_shm *shm; - struct wl_list output_list; - - struct weston_screenshooter *screenshooter; - int buffer_copy_done; -}; - - -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 -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 weston_screenshooter *screenshooter) -{ - struct screenshooter_data *sh_data = data; - sh_data->buffer_copy_done = 1; -} - -static const struct weston_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; - struct screenshooter_data *sh_data = data; - - if (strcmp(interface, "wl_output") == 0) { - output = xmalloc(sizeof *output); - output->output = wl_registry_bind(registry, name, - &wl_output_interface, 1); - wl_list_insert(&sh_data->output_list, &output->link); - wl_output_add_listener(output->output, &output_listener, output); - } else if (strcmp(interface, "wl_shm") == 0) { - sh_data->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); - } else if (strcmp(interface, "weston_screenshooter") == 0) { - sh_data->screenshooter = wl_registry_bind(registry, name, - &weston_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 * -screenshot_create_shm_buffer(int width, int height, void **data_out, - struct wl_shm *shm) -{ - 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: %s\n", - size, strerror(errno)); - return NULL; - } - - data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (data == MAP_FAILED) { - fprintf(stderr, "mmap failed: %s\n", strerror(errno)); - close(fd); - return 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 -screenshot_write_png(const struct buffer_size *buff_size, - struct wl_list *output_list) -{ - int output_stride, buffer_stride, i; - cairo_surface_t *surface; - void *data, *d, *s; - struct screenshooter_output *output, *next; - FILE *fp; - char filepath[PATH_MAX]; - - buffer_stride = buff_size->width * 4; - - data = xmalloc(buffer_stride * buff_size->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 - buff_size->min_y) * buffer_stride + - (output->offset_x - buff_size->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, - buff_size->width, - buff_size->height, - buffer_stride); - - fp = file_create_dated(getenv("XDG_PICTURES_DIR"), "wayland-screenshot-", - ".png", filepath, sizeof(filepath)); - if (fp) { - fclose (fp); - cairo_surface_write_to_png(surface, filepath); - } - cairo_surface_destroy(surface); - free(data); -} - -static int -screenshot_set_buffer_size(struct buffer_size *buff_size, struct wl_list *output_list) -{ - struct screenshooter_output *output; - buff_size->min_x = buff_size->min_y = INT_MAX; - buff_size->max_x = buff_size->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) { - buff_size->min_x = MIN(buff_size->min_x, output->offset_x); - buff_size->min_y = MIN(buff_size->min_y, output->offset_y); - buff_size->max_x = - MAX(buff_size->max_x, output->offset_x + output->width); - buff_size->max_y = - MAX(buff_size->max_y, output->offset_y + output->height); - } - - if (buff_size->max_x <= buff_size->min_x || - buff_size->max_y <= buff_size->min_y) - return -1; - - buff_size->width = buff_size->max_x - buff_size->min_x; - buff_size->height = buff_size->max_y - buff_size->min_y; - - return 0; -} - -int main(int argc, char *argv[]) -{ - struct wl_display *display; - struct wl_registry *registry; - struct screenshooter_output *output; - struct buffer_size buff_size = {}; - struct screenshooter_data sh_data = {}; - - display = wl_display_connect(NULL); - if (display == NULL) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - return -1; - } - - wl_list_init(&sh_data.output_list); - registry = wl_display_get_registry(display); - wl_registry_add_listener(registry, ®istry_listener, &sh_data); - wl_display_dispatch(display); - wl_display_roundtrip(display); - if (sh_data.screenshooter == NULL) { - fprintf(stderr, "display doesn't support screenshooter\n"); - return -1; - } - - weston_screenshooter_add_listener(sh_data.screenshooter, - &screenshooter_listener, - &sh_data); - - if (screenshot_set_buffer_size(&buff_size, &sh_data.output_list)) - return -1; - - - wl_list_for_each(output, &sh_data.output_list, link) { - output->buffer = - screenshot_create_shm_buffer(output->width, - output->height, - &output->data, - sh_data.shm); - weston_screenshooter_shoot(sh_data.screenshooter, - output->output, - output->buffer); - sh_data.buffer_copy_done = 0; - while (!sh_data.buffer_copy_done) - wl_display_roundtrip(display); - } - - screenshot_write_png(&buff_size, &sh_data.output_list); - - return 0; -} diff --git a/clients/simple-damage.c b/clients/simple-damage.c deleted file mode 100644 index 821b42b..0000000 --- a/clients/simple-damage.c +++ /dev/null @@ -1,957 +0,0 @@ -/* - * Copyright © 2014 Jason Ekstrand - * Copyright © 2011 Benjamin Franzke - * Copyright © 2010 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "shared/os-compatibility.h" -#include -#include "xdg-shell-client-protocol.h" -#include "fullscreen-shell-unstable-v1-client-protocol.h" -#include "viewporter-client-protocol.h" - -int print_debug = 0; - -struct display { - struct wl_display *display; - struct wl_registry *registry; - int compositor_version; - struct wl_compositor *compositor; - struct wp_viewporter *viewporter; - struct xdg_wm_base *wm_base; - struct zwp_fullscreen_shell_v1 *fshell; - struct wl_shm *shm; - uint32_t formats; -}; - -struct buffer { - struct wl_buffer *buffer; - uint32_t *shm_data; - int busy; -}; - -enum window_flags { - WINDOW_FLAG_USE_VIEWPORT = 0x1, - WINDOW_FLAG_ROTATING_TRANSFORM = 0x2, - WINDOW_FLAG_USE_DAMAGE_BUFFER = 0x4, -}; - -struct window { - struct display *display; - int width, height, border; - struct wl_surface *surface; - struct wp_viewport *viewport; - struct xdg_surface *xdg_surface; - struct xdg_toplevel *xdg_toplevel; - struct wl_callback *callback; - struct buffer buffers[2]; - struct buffer *prev_buffer; - bool wait_for_configure; - - enum window_flags flags; - int scale; - enum wl_output_transform transform; - - struct { - float x, y; /* position in pixels */ - float dx, dy; /* velocity in pixels/second */ - int radius; /* radius in pixels */ - uint32_t prev_time; - } ball; -}; - -static int running = 1; - -static void -redraw(void *data, struct wl_callback *callback, uint32_t time); - -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, pitch; - void *data; - - pitch = width * 4; - size = pitch * height; - - fd = os_create_anonymous_file(size); - if (fd < 0) { - fprintf(stderr, "creating a buffer file for %d B failed: %s\n", - size, strerror(errno)); - return -1; - } - - data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (data == MAP_FAILED) { - fprintf(stderr, "mmap failed: %s\n", strerror(errno)); - close(fd); - return -1; - } - - pool = wl_shm_create_pool(display->shm, fd, size); - buffer->buffer = wl_shm_pool_create_buffer(pool, 0, - width, height, - pitch, 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 -xdg_surface_handle_configure(void *data, struct xdg_surface *surface, - uint32_t serial) -{ - struct window *window = data; - - xdg_surface_ack_configure(surface, serial); - - if (window->wait_for_configure) { - redraw(window, NULL, 0); - window->wait_for_configure = false; - } -} - -static const struct xdg_surface_listener xdg_surface_listener = { - xdg_surface_handle_configure, -}; - -static void -xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, - int32_t width, int32_t height, - struct wl_array *states) -{ -} - -static void -xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) -{ - running = 0; -} - -static const struct xdg_toplevel_listener xdg_toplevel_listener = { - xdg_toplevel_handle_configure, - xdg_toplevel_handle_close, -}; - -static float -bounded_randf(float a, float b) -{ - return a + ((float)rand() / (float)RAND_MAX) * (b - a); -} - -static void -window_init_game(struct window *window) -{ - int ax1, ay1, ax2, ay2; /* playable arena size */ - struct timeval tv; - - gettimeofday(&tv, NULL); - srand(tv.tv_usec); - - window->ball.radius = 10; - - ax1 = window->border + window->ball.radius; - ay1 = window->border + window->ball.radius; - ax2 = window->width - window->border - window->ball.radius; - ay2 = window->height - window->border - window->ball.radius; - - window->ball.x = bounded_randf(ax1, ax2); - window->ball.y = bounded_randf(ay1, ay2); - - window->ball.dx = bounded_randf(0, window->width); - window->ball.dy = bounded_randf(0, window->height); - - window->ball.prev_time = 0; -} - -static void -window_advance_game(struct window *window, uint32_t timestamp) -{ - int ax1, ay1, ax2, ay2; /* Arena size */ - float dt; - - if (window->ball.prev_time == 0) { - /* first pass, don't do anything */ - window->ball.prev_time = timestamp; - return; - } - - /* dt in seconds */ - dt = (float)(timestamp - window->ball.prev_time) / 1000.0f; - - ax1 = window->border + window->ball.radius; - ay1 = window->border + window->ball.radius; - ax2 = window->width - window->border - window->ball.radius; - ay2 = window->height - window->border - window->ball.radius; - - window->ball.x += window->ball.dx * dt; - while (window->ball.x < ax1 || ax2 < window->ball.x) { - if (window->ball.x < ax1) - window->ball.x = 2 * ax1 - window->ball.x; - if (ax2 <= window->ball.x) - window->ball.x = 2 * ax2 - window->ball.x; - - window->ball.dx *= -1.0f; - } - - window->ball.y += window->ball.dy * dt; - while (window->ball.y < ay1 || ay2 < window->ball.y) { - if (window->ball.y < ay1) - window->ball.y = 2 * ay1 - window->ball.y; - if (ay2 <= window->ball.y) - window->ball.y = 2 * ay2 - window->ball.y; - - window->ball.dy *= -1.0f; - } - - window->ball.prev_time = timestamp; -} - -static struct window * -create_window(struct display *display, int width, int height, - enum wl_output_transform transform, int scale, - enum window_flags flags) -{ - struct window *window; - - if (display->compositor_version < 2 && - (transform != WL_OUTPUT_TRANSFORM_NORMAL || - flags & WINDOW_FLAG_ROTATING_TRANSFORM)) { - fprintf(stderr, "wl_surface.buffer_transform unsupported in " - "wl_surface version %d\n", - display->compositor_version); - exit(1); - } - - if (display->compositor_version < 3 && - (! (flags & WINDOW_FLAG_USE_VIEWPORT)) && scale != 1) { - fprintf(stderr, "wl_surface.buffer_scale unsupported in " - "wl_surface version %d\n", - display->compositor_version); - exit(1); - } - - if (display->viewporter == NULL && (flags & WINDOW_FLAG_USE_VIEWPORT)) { - fprintf(stderr, "Compositor does not support wp_viewport"); - exit(1); - } - - if (display->compositor_version < - WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION && - (flags & WINDOW_FLAG_USE_DAMAGE_BUFFER)) { - fprintf(stderr, "wl_surface.damage_buffer unsupported in " - "wl_surface version %d\n", - display->compositor_version); - exit(1); - } - - window = zalloc(sizeof *window); - if (!window) - return NULL; - - window->callback = NULL; - window->display = display; - window->width = width; - window->height = height; - window->border = 10; - window->flags = flags; - window->transform = transform; - window->scale = scale; - - window_init_game(window); - - window->surface = wl_compositor_create_surface(display->compositor); - - if (window->flags & WINDOW_FLAG_USE_VIEWPORT) - window->viewport = wp_viewporter_get_viewport(display->viewporter, - window->surface); - - if (display->wm_base) { - window->xdg_surface = - xdg_wm_base_get_xdg_surface(display->wm_base, - window->surface); - - assert(window->xdg_surface); - - xdg_surface_add_listener(window->xdg_surface, - &xdg_surface_listener, window); - - window->xdg_toplevel = - xdg_surface_get_toplevel(window->xdg_surface); - - assert(window->xdg_toplevel); - - xdg_toplevel_add_listener(window->xdg_toplevel, - &xdg_toplevel_listener, window); - - xdg_toplevel_set_title(window->xdg_toplevel, "simple-damage"); - - window->wait_for_configure = true; - wl_surface_commit(window->surface); - } else if (display->fshell) { - zwp_fullscreen_shell_v1_present_surface(display->fshell, - window->surface, - ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT, - NULL); - } else { - assert(0); - } - - /* Initialise damage to full surface, so the padding gets painted */ - if (window->flags & WINDOW_FLAG_USE_DAMAGE_BUFFER) { - wl_surface_damage_buffer(window->surface, 0, 0, - INT32_MAX, INT32_MAX); - } else { - wl_surface_damage(window->surface, 0, 0, INT32_MAX, INT32_MAX); - } - return window; -} - -static void -destroy_window(struct window *window) -{ - if (window->callback) - wl_callback_destroy(window->callback); - - if (window->buffers[0].buffer) - wl_buffer_destroy(window->buffers[0].buffer); - if (window->buffers[1].buffer) - wl_buffer_destroy(window->buffers[1].buffer); - - if (window->xdg_toplevel) - xdg_toplevel_destroy(window->xdg_toplevel); - if (window->xdg_surface) - xdg_surface_destroy(window->xdg_surface); - if (window->viewport) - wp_viewport_destroy(window->viewport); - wl_surface_destroy(window->surface); - free(window); -} - -static struct buffer * -window_next_buffer(struct window *window) -{ - struct buffer *buffer; - int ret = 0, bwidth, bheight; - - if (!window->buffers[0].busy) - buffer = &window->buffers[0]; - else if (!window->buffers[1].busy) - buffer = &window->buffers[1]; - else - return NULL; - - switch (window->transform) { - default: - case WL_OUTPUT_TRANSFORM_NORMAL: - case WL_OUTPUT_TRANSFORM_180: - case WL_OUTPUT_TRANSFORM_FLIPPED: - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - bwidth = window->width * window->scale; - bheight = window->height * window->scale; - break; - case WL_OUTPUT_TRANSFORM_90: - case WL_OUTPUT_TRANSFORM_270: - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - bwidth = window->height * window->scale; - bheight = window->width * window->scale; - break; - } - - if (!buffer->buffer) { - ret = create_shm_buffer(window->display, buffer, - bwidth, bheight, - WL_SHM_FORMAT_ARGB8888); - - if (ret < 0) - return NULL; - } - - return buffer; -} - -static void -paint_box(uint32_t *pixels, int pitch, int x, int y, int width, int height, - uint32_t color) -{ - int i, j; - - for (j = y; j < y + height; ++j) - for (i = x; i < x + width; ++i) - pixels[i + j * pitch] = color; -} - -static void -paint_circle(uint32_t *pixels, int pitch, float x, float y, int radius, - uint32_t color) -{ - int i, j; - - for (j = y - radius; j <= (int)(y + radius); ++j) - for (i = x - radius; i <= (int)(x + radius); ++i) - if ((j+0.5f-y)*(j+0.5f-y) + (i+0.5f-x)*(i+0.5f-x) <= radius * radius) - pixels[i + j * pitch] = color; -} - -static void -window_get_transformed_ball(struct window *window, float *bx, float *by) -{ - float wx, wy; - - wx = window->ball.x; - wy = window->ball.y; - - switch (window->transform) { - default: - case WL_OUTPUT_TRANSFORM_NORMAL: - *bx = wx; - *by = wy; - break; - case WL_OUTPUT_TRANSFORM_90: - *bx = wy; - *by = window->width - wx; - break; - case WL_OUTPUT_TRANSFORM_180: - *bx = window->width - wx; - *by = window->height - wy; - break; - case WL_OUTPUT_TRANSFORM_270: - *bx = window->height - wy; - *by = wx; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED: - *bx = window->width - wx; - *by = wy; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - *bx = wy; - *by = wx; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - *bx = wx; - *by = window->height - wy; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - *bx = window->height - wy; - *by = window->width - wx; - break; - } - - *bx *= window->scale; - *by *= window->scale; - - if (window->viewport) { - /* We're drawing half-size because of the viewport */ - *bx /= 2; - *by /= 2; - } -} - -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; - int off_x = 0, off_y = 0; - int bwidth, bheight, bborder, bpitch, bradius; - float bx, by; - - 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(); - } - - /* Rotate the damage, but keep the even/odd parity so the - * dimensions of the buffers don't change */ - if (window->flags & WINDOW_FLAG_ROTATING_TRANSFORM) - window->transform = (window->transform + 2) % 8; - - switch (window->transform) { - default: - case WL_OUTPUT_TRANSFORM_NORMAL: - case WL_OUTPUT_TRANSFORM_180: - case WL_OUTPUT_TRANSFORM_FLIPPED: - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - bwidth = window->width * window->scale; - bheight = window->height * window->scale; - break; - case WL_OUTPUT_TRANSFORM_90: - case WL_OUTPUT_TRANSFORM_270: - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - bwidth = window->height * window->scale; - bheight = window->width * window->scale; - break; - } - - bpitch = bwidth; - - bborder = window->border * window->scale; - bradius = window->ball.radius * window->scale; - - if (window->viewport) { - int tx, ty; - /* Fill the whole thing with red to detect viewport errors */ - paint_box(buffer->shm_data, bpitch, 0, 0, bwidth, bheight, - 0xffff0000); - - /* The buffer is the same size. However, we crop it - * and scale it up by a factor of 2 */ - bborder /= 2; - bradius /= 2; - bwidth /= 2; - bheight /= 2; - - /* Offset the drawing region */ - tx = (window->width / 3) * window->scale; - ty = (window->height / 5) * window->scale; - switch (window->transform) { - default: - case WL_OUTPUT_TRANSFORM_NORMAL: - off_y = ty; - off_x = tx; - break; - case WL_OUTPUT_TRANSFORM_90: - off_y = bheight - tx; - off_x = ty; - break; - case WL_OUTPUT_TRANSFORM_180: - off_y = bheight - ty; - off_x = bwidth - tx; - break; - case WL_OUTPUT_TRANSFORM_270: - off_y = tx; - off_x = bwidth - ty; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED: - off_y = ty; - off_x = bwidth - tx; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - off_y = tx; - off_x = ty; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - off_y = bheight - ty; - off_x = tx; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - off_y = bheight - tx; - off_x = bwidth - ty; - break; - } - wp_viewport_set_source(window->viewport, - wl_fixed_from_int(window->width / 3), - wl_fixed_from_int(window->height / 5), - wl_fixed_from_int(window->width / 2), - wl_fixed_from_int(window->height / 2)); - } - - /* Paint the border */ - paint_box(buffer->shm_data, bpitch, off_x, off_y, - bwidth, bborder, 0xffffffff); - paint_box(buffer->shm_data, bpitch, off_x, off_y, - bborder, bheight, 0xffffffff); - paint_box(buffer->shm_data, bpitch, off_x + bwidth - bborder, off_y, - bborder, bheight, 0xffffffff); - paint_box(buffer->shm_data, bpitch, off_x, off_y + bheight - bborder, - bwidth, bborder, 0xffffffff); - - /* fill with translucent */ - paint_box(buffer->shm_data, bpitch, off_x + bborder, off_y + bborder, - bwidth - 2 * bborder, bheight - 2 * bborder, 0x80000000); - - /* Damage where the ball was */ - if (window->flags & WINDOW_FLAG_USE_DAMAGE_BUFFER) { - window_get_transformed_ball(window, &bx, &by); - wl_surface_damage_buffer(window->surface, - bx - bradius + off_x, - by - bradius + off_y, - bradius * 2 + 1, - bradius * 2 + 1); - } else { - wl_surface_damage(window->surface, - window->ball.x - window->ball.radius, - window->ball.y - window->ball.radius, - window->ball.radius * 2 + 1, - window->ball.radius * 2 + 1); - } - window_advance_game(window, time); - - window_get_transformed_ball(window, &bx, &by); - - /* Paint the ball */ - paint_circle(buffer->shm_data, bpitch, off_x + bx, off_y + by, - bradius, 0xff00ff00); - - if (print_debug) { - printf("Ball now located at (%f, %f)\n", - window->ball.x, window->ball.y); - - printf("Circle painted at (%f, %f), radius %d\n", bx, by, - bradius); - - printf("Buffer damage rectangle: (%d, %d) @ %dx%d\n", - (int)(bx - bradius) + off_x, - (int)(by - bradius) + off_y, - bradius * 2 + 1, bradius * 2 + 1); - } - - /* Damage where the ball is now */ - if (window->flags & WINDOW_FLAG_USE_DAMAGE_BUFFER) { - wl_surface_damage_buffer(window->surface, - bx - bradius + off_x, - by - bradius + off_y, - bradius * 2 + 1, - bradius * 2 + 1); - } else { - wl_surface_damage(window->surface, - window->ball.x - window->ball.radius, - window->ball.y - window->ball.radius, - window->ball.radius * 2 + 1, - window->ball.radius * 2 + 1); - } - wl_surface_attach(window->surface, buffer->buffer, 0, 0); - - if (window->display->compositor_version >= 2 && - (window->transform != WL_OUTPUT_TRANSFORM_NORMAL || - window->flags & WINDOW_FLAG_ROTATING_TRANSFORM)) - wl_surface_set_buffer_transform(window->surface, - window->transform); - - if (window->viewport) - wp_viewport_set_destination(window->viewport, - window->width, - window->height); - - if (window->scale != 1) - wl_surface_set_buffer_scale(window->surface, - window->scale); - - 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 -xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) -{ - xdg_wm_base_pong(shell, serial); -} - -static const struct xdg_wm_base_listener wm_base_listener = { - xdg_wm_base_ping, -}; - -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) { - if (d->compositor_version > (int)version) { - fprintf(stderr, "Compositor does not support " - "wl_surface version %d\n", d->compositor_version); - exit(1); - } - - if (d->compositor_version < 0) - d->compositor_version = version; - - d->compositor = - wl_registry_bind(registry, - id, &wl_compositor_interface, - d->compositor_version); - } else if (strcmp(interface, "wp_viewporter") == 0) { - d->viewporter = wl_registry_bind(registry, id, - &wp_viewporter_interface, 1); - } else if (strcmp(interface, "xdg_wm_base") == 0) { - d->wm_base = wl_registry_bind(registry, - id, &xdg_wm_base_interface, 1); - xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d); - } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { - d->fshell = wl_registry_bind(registry, - id, &zwp_fullscreen_shell_v1_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(int version) -{ - 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->compositor_version = version; - 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); - } - - return display; -} - -static void -destroy_display(struct display *display) -{ - if (display->shm) - wl_shm_destroy(display->shm); - - if (display->wm_base) - xdg_wm_base_destroy(display->wm_base); - - if (display->fshell) - zwp_fullscreen_shell_v1_release(display->fshell); - - if (display->viewporter) - wp_viewporter_destroy(display->viewporter); - - if (display->compositor) - wl_compositor_destroy(display->compositor); - - wl_registry_destroy(display->registry); - wl_display_flush(display->display); - wl_display_disconnect(display->display); - free(display); -} - -static void -signal_int(int signum) -{ - running = 0; -} - -static void -print_usage(int retval) -{ - printf( - "usage: weston-simple-damage [options]\n\n" - "options:\n" - " -h, --help\t\tPring this help\n" - " --verbose\t\tPrint verbose log information\n" - " --version=VERSION\tVersion of wl_surface to use\n" - " --width=WIDTH\t\tWidth of the window\n" - " --height=HEIGHT\tHeight of the window\n" - " --scale=SCALE\t\tScale factor for the surface\n" - " --transform=TRANSFORM\tTransform for the surface\n" - " --rotating-transform\tUse a different buffer_transform for each frame\n" - " --use-viewport\tUse wp_viewport\n" - " --use-damage-buffer\tUse damage_buffer to post damage\n" - ); - - exit(retval); -} - -static int -parse_transform(const char *str, enum wl_output_transform *transform) -{ - int i; - static const struct { - const char *name; - enum wl_output_transform transform; - } 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 }, - }; - - for (i = 0; i < 8; i++) { - if (strcmp(names[i].name, str) == 0) { - *transform = names[i].transform; - return 1; - } - } - - return 0; -} - -int -main(int argc, char **argv) -{ - struct sigaction sigint; - struct display *display; - struct window *window; - int i, ret = 0; - int version = -1; - int width = 300, height = 200, scale = 1; - enum wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; - enum window_flags flags = 0; - - for (i = 1; i < argc; ++i) { - if (strcmp(argv[i], "--help") == 0 || - strcmp(argv[i], "-h") == 0) { - print_usage(0); - } else if (sscanf(argv[i], "--version=%d", &version) > 0) { - if (version < 1 || version > 4) { - fprintf(stderr, "Unsupported wl_surface version: %d\n", - version); - return 1; - } - continue; - } else if (strcmp(argv[i], "--verbose") == 0) { - print_debug = 1; - continue; - } else if (sscanf(argv[i], "--width=%d", &width) > 0) { - continue; - } else if (sscanf(argv[i], "--height=%d", &height) > 0) { - continue; - } else if (strncmp(argv[i], "--transform=", 12) == 0 && - parse_transform(argv[i] + 12, &transform) > 0) { - continue; - } else if (strcmp(argv[i], "--rotating-transform") == 0) { - flags |= WINDOW_FLAG_ROTATING_TRANSFORM; - continue; - } else if (sscanf(argv[i], "--scale=%d", &scale) > 0) { - continue; - } else if (strcmp(argv[i], "--use-viewport") == 0) { - flags |= WINDOW_FLAG_USE_VIEWPORT; - continue; - } else if (strcmp(argv[i], "--use-damage-buffer") == 0) { - flags |= WINDOW_FLAG_USE_DAMAGE_BUFFER; - continue; - } else { - printf("Invalid option: %s\n", argv[i]); - print_usage(255); - } - } - - display = create_display(version); - - window = create_window(display, width, height, transform, scale, flags); - if (!window) - return 1; - - sigint.sa_handler = signal_int; - sigemptyset(&sigint.sa_mask); - sigint.sa_flags = SA_RESETHAND; - sigaction(SIGINT, &sigint, NULL); - - if (!window->wait_for_configure) - 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-dmabuf-egl.c b/clients/simple-dmabuf-egl.c deleted file mode 100644 index 10e72a9..0000000 --- a/clients/simple-dmabuf-egl.c +++ /dev/null @@ -1,1562 +0,0 @@ -/* - * Copyright © 2011 Benjamin Franzke - * Copyright © 2010 Intel Corporation - * Copyright © 2014,2018 Collabora Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include "shared/helpers.h" -#include "shared/platform.h" -#include -#include "xdg-shell-client-protocol.h" -#include "fullscreen-shell-unstable-v1-client-protocol.h" -#include "linux-dmabuf-unstable-v1-client-protocol.h" -#include "weston-direct-display-client-protocol.h" -#include "linux-explicit-synchronization-unstable-v1-client-protocol.h" - -#include -#include -#include -#include - -#include "shared/weston-egl-ext.h" - -#ifndef DRM_FORMAT_MOD_INVALID -#define DRM_FORMAT_MOD_INVALID ((1ULL << 56) - 1) -#endif - -/* Possible options that affect the displayed image */ -#define OPT_IMMEDIATE (1 << 0) /* create wl_buffer immediately */ -#define OPT_IMPLICIT_SYNC (1 << 1) /* force implicit sync */ -#define OPT_MANDELBROT (1 << 2) /* render mandelbrot */ -#define OPT_DIRECT_DISPLAY (1 << 3) /* direct-display */ - -#define BUFFER_FORMAT DRM_FORMAT_XRGB8888 -#define MAX_BUFFER_PLANES 4 - -struct display { - struct wl_display *display; - struct wl_registry *registry; - struct wl_compositor *compositor; - struct xdg_wm_base *wm_base; - struct zwp_fullscreen_shell_v1 *fshell; - struct zwp_linux_dmabuf_v1 *dmabuf; - struct weston_direct_display_v1 *direct_display; - struct zwp_linux_explicit_synchronization_v1 *explicit_sync; - uint64_t *modifiers; - int modifiers_count; - int req_dmabuf_immediate; - bool use_explicit_sync; - struct { - EGLDisplay display; - EGLContext context; - EGLConfig conf; - bool has_dma_buf_import_modifiers; - bool has_no_config_context; - PFNEGLQUERYDMABUFMODIFIERSEXTPROC query_dma_buf_modifiers; - PFNEGLCREATEIMAGEKHRPROC create_image; - PFNEGLDESTROYIMAGEKHRPROC destroy_image; - PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d; - PFNEGLCREATESYNCKHRPROC create_sync; - PFNEGLDESTROYSYNCKHRPROC destroy_sync; - PFNEGLCLIENTWAITSYNCKHRPROC client_wait_sync; - PFNEGLDUPNATIVEFENCEFDANDROIDPROC dup_native_fence_fd; - PFNEGLWAITSYNCKHRPROC wait_sync; - } egl; - struct { - int drm_fd; - struct gbm_device *device; - } gbm; -}; - -struct buffer { - struct display *display; - struct wl_buffer *buffer; - int busy; - - struct gbm_bo *bo; - - int width; - int height; - int format; - uint64_t modifier; - int plane_count; - int dmabuf_fds[MAX_BUFFER_PLANES]; - uint32_t strides[MAX_BUFFER_PLANES]; - uint32_t offsets[MAX_BUFFER_PLANES]; - - EGLImageKHR egl_image; - GLuint gl_texture; - GLuint gl_fbo; - - struct zwp_linux_buffer_release_v1 *buffer_release; - /* The buffer owns the release_fence_fd, until it passes ownership - * to it to EGL (see wait_for_buffer_release_fence). */ - int release_fence_fd; -}; - -#define NUM_BUFFERS 3 - -struct window { - struct display *display; - int width, height; - struct wl_surface *surface; - struct xdg_surface *xdg_surface; - struct xdg_toplevel *xdg_toplevel; - struct zwp_linux_surface_synchronization_v1 *surface_sync; - struct buffer buffers[NUM_BUFFERS]; - struct wl_callback *callback; - bool initialized; - bool wait_for_configure; - struct { - GLuint program; - GLuint pos; - GLuint color; - GLuint offset_uniform; - } gl; - bool render_mandelbrot; -}; - -static sig_atomic_t running = 1; - -static void -redraw(void *data, struct wl_callback *callback, uint32_t time); - -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 void -buffer_free(struct buffer *buf) -{ - int i; - - if (buf->release_fence_fd >= 0) - close(buf->release_fence_fd); - - if (buf->buffer_release) - zwp_linux_buffer_release_v1_destroy(buf->buffer_release); - - if (buf->gl_fbo) - glDeleteFramebuffers(1, &buf->gl_fbo); - - if (buf->gl_texture) - glDeleteTextures(1, &buf->gl_texture); - - if (buf->egl_image) { - buf->display->egl.destroy_image(buf->display->egl.display, - buf->egl_image); - } - - if (buf->buffer) - wl_buffer_destroy(buf->buffer); - - if (buf->bo) - gbm_bo_destroy(buf->bo); - - for (i = 0; i < buf->plane_count; ++i) { - if (buf->dmabuf_fds[i] >= 0) - close(buf->dmabuf_fds[i]); - } -} - -static void -create_succeeded(void *data, - struct zwp_linux_buffer_params_v1 *params, - struct wl_buffer *new_buffer) -{ - struct buffer *buffer = data; - - buffer->buffer = new_buffer; - /* When not using explicit synchronization listen to wl_buffer.release - * for release notifications, otherwise we are going to use - * zwp_linux_buffer_release_v1. */ - if (!buffer->display->use_explicit_sync) { - wl_buffer_add_listener(buffer->buffer, &buffer_listener, - buffer); - } - - zwp_linux_buffer_params_v1_destroy(params); -} - -static void -create_failed(void *data, struct zwp_linux_buffer_params_v1 *params) -{ - struct buffer *buffer = data; - - buffer->buffer = NULL; - running = 0; - - zwp_linux_buffer_params_v1_destroy(params); - - fprintf(stderr, "Error: zwp_linux_buffer_params.create failed.\n"); -} - -static const struct zwp_linux_buffer_params_v1_listener params_listener = { - create_succeeded, - create_failed -}; - -static bool -create_fbo_for_buffer(struct display *display, struct buffer *buffer) -{ - static const int general_attribs = 3; - static const int plane_attribs = 5; - static const int entries_per_attrib = 2; - EGLint attribs[(general_attribs + plane_attribs * MAX_BUFFER_PLANES) * - entries_per_attrib + 1]; - unsigned int atti = 0; - - attribs[atti++] = EGL_WIDTH; - attribs[atti++] = buffer->width; - attribs[atti++] = EGL_HEIGHT; - attribs[atti++] = buffer->height; - attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; - attribs[atti++] = buffer->format; - -#define ADD_PLANE_ATTRIBS(plane_idx) { \ - attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _FD_EXT; \ - attribs[atti++] = buffer->dmabuf_fds[plane_idx]; \ - attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _OFFSET_EXT; \ - attribs[atti++] = (int) buffer->offsets[plane_idx]; \ - attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _PITCH_EXT; \ - attribs[atti++] = (int) buffer->strides[plane_idx]; \ - if (display->egl.has_dma_buf_import_modifiers) { \ - attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _MODIFIER_LO_EXT; \ - attribs[atti++] = buffer->modifier & 0xFFFFFFFF; \ - attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _MODIFIER_HI_EXT; \ - attribs[atti++] = buffer->modifier >> 32; \ - } \ - } - - if (buffer->plane_count > 0) - ADD_PLANE_ATTRIBS(0); - - if (buffer->plane_count > 1) - ADD_PLANE_ATTRIBS(1); - - if (buffer->plane_count > 2) - ADD_PLANE_ATTRIBS(2); - - if (buffer->plane_count > 3) - ADD_PLANE_ATTRIBS(3); - -#undef ADD_PLANE_ATTRIBS - - attribs[atti] = EGL_NONE; - - assert(atti < ARRAY_LENGTH(attribs)); - - buffer->egl_image = display->egl.create_image(display->egl.display, - EGL_NO_CONTEXT, - EGL_LINUX_DMA_BUF_EXT, - NULL, attribs); - if (buffer->egl_image == EGL_NO_IMAGE_KHR) { - fprintf(stderr, "EGLImageKHR creation failed\n"); - return false; - } - - eglMakeCurrent(display->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, - display->egl.context); - - glGenTextures(1, &buffer->gl_texture); - glBindTexture(GL_TEXTURE_2D, buffer->gl_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_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - - display->egl.image_target_texture_2d(GL_TEXTURE_2D, buffer->egl_image); - - glGenFramebuffers(1, &buffer->gl_fbo); - glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, buffer->gl_texture, 0); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - fprintf(stderr, "FBO creation failed\n"); - return false; - } - - return true; -} - - -static int -create_dmabuf_buffer(struct display *display, struct buffer *buffer, - int width, int height, uint32_t opts) -{ - /* Y-Invert the buffer image, since we are going to renderer to the - * buffer through a FBO. */ - static uint32_t flags = ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; - struct zwp_linux_buffer_params_v1 *params; - int i; - - buffer->display = display; - buffer->width = width; - buffer->height = height; - buffer->format = BUFFER_FORMAT; - buffer->release_fence_fd = -1; - -#ifdef HAVE_GBM_MODIFIERS - if (display->modifiers_count > 0) { - buffer->bo = gbm_bo_create_with_modifiers(display->gbm.device, - buffer->width, - buffer->height, - buffer->format, - display->modifiers, - display->modifiers_count); - if (buffer->bo) - buffer->modifier = gbm_bo_get_modifier(buffer->bo); - } -#endif - - if (!buffer->bo) { - buffer->bo = gbm_bo_create(display->gbm.device, - buffer->width, - buffer->height, - buffer->format, - GBM_BO_USE_RENDERING); - buffer->modifier = DRM_FORMAT_MOD_INVALID; - } - - if (!buffer->bo) { - fprintf(stderr, "create_bo failed\n"); - goto error; - } - -#ifdef HAVE_GBM_MODIFIERS - buffer->plane_count = gbm_bo_get_plane_count(buffer->bo); - for (i = 0; i < buffer->plane_count; ++i) { - int ret; - union gbm_bo_handle handle; - - handle = gbm_bo_get_handle_for_plane(buffer->bo, i); - if (handle.s32 == -1) { - fprintf(stderr, "error: failed to get gbm_bo_handle\n"); - goto error; - } - - ret = drmPrimeHandleToFD(display->gbm.drm_fd, handle.u32, 0, - &buffer->dmabuf_fds[i]); - if (ret < 0 || buffer->dmabuf_fds[i] < 0) { - fprintf(stderr, "error: failed to get dmabuf_fd\n"); - goto error; - } - buffer->strides[i] = gbm_bo_get_stride_for_plane(buffer->bo, i); - buffer->offsets[i] = gbm_bo_get_offset(buffer->bo, i); - } -#else - buffer->plane_count = 1; - buffer->strides[0] = gbm_bo_get_stride(buffer->bo); - buffer->dmabuf_fds[0] = gbm_bo_get_fd(buffer->bo); - if (buffer->dmabuf_fds[0] < 0) { - fprintf(stderr, "error: failed to get dmabuf_fd\n"); - goto error; - } -#endif - - params = zwp_linux_dmabuf_v1_create_params(display->dmabuf); - - if ((opts & OPT_DIRECT_DISPLAY) && display->direct_display) { - weston_direct_display_v1_enable(display->direct_display, params); - /* turn off Y_INVERT otherwise linux-dmabuf will reject it and - * we need all dmabuf flags turned off */ - flags &= ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; - - fprintf(stdout, "image is y-inverted as direct-display flag was set, " - "dmabuf y-inverted attribute flag was removed\n"); - } - - for (i = 0; i < buffer->plane_count; ++i) { - zwp_linux_buffer_params_v1_add(params, - buffer->dmabuf_fds[i], - i, - buffer->offsets[i], - buffer->strides[i], - buffer->modifier >> 32, - buffer->modifier & 0xffffffff); - } - - zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, buffer); - if (display->req_dmabuf_immediate) { - buffer->buffer = - zwp_linux_buffer_params_v1_create_immed(params, - buffer->width, - buffer->height, - buffer->format, - flags); - /* When not using explicit synchronization listen to - * wl_buffer.release for release notifications, otherwise we - * are going to use zwp_linux_buffer_release_v1. */ - if (!buffer->display->use_explicit_sync) { - wl_buffer_add_listener(buffer->buffer, - &buffer_listener, - buffer); - } - } - else { - zwp_linux_buffer_params_v1_create(params, - buffer->width, - buffer->height, - buffer->format, - flags); - } - - if (!create_fbo_for_buffer(display, buffer)) - goto error; - - return 0; - -error: - buffer_free(buffer); - return -1; -} - -static void -xdg_surface_handle_configure(void *data, struct xdg_surface *surface, - uint32_t serial) -{ - struct window *window = data; - - xdg_surface_ack_configure(surface, serial); - - if (window->initialized && window->wait_for_configure) - redraw(window, NULL, 0); - window->wait_for_configure = false; -} - -static const struct xdg_surface_listener xdg_surface_listener = { - xdg_surface_handle_configure, -}; - -static void -xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, - int32_t width, int32_t height, - struct wl_array *states) -{ -} - -static void -xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) -{ - running = 0; -} - -static const struct xdg_toplevel_listener xdg_toplevel_listener = { - xdg_toplevel_handle_configure, - xdg_toplevel_handle_close, -}; - -static const char *vert_shader_text = - "uniform float offset;\n" - "attribute vec4 pos;\n" - "attribute vec4 color;\n" - "varying vec4 v_color;\n" - "void main() {\n" - " gl_Position = pos + vec4(offset, offset, 0.0, 0.0);\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 const char *vert_shader_mandelbrot_text = - "uniform float offset;\n" - "attribute vec4 pos;\n" - "varying vec2 v_pos;\n" - "void main() {\n" - " v_pos = pos.xy;\n" - " gl_Position = pos + vec4(offset, offset, 0.0, 0.0);\n" - "}\n"; - - -/* Mandelbrot set shader using the escape time algorithm. */ -static const char *frag_shader_mandelbrot_text = - "precision mediump float;\n" - "varying vec2 v_pos;\n" - "void main() {\n" - " const int max_iteration = 500;\n" - " // Scale and translate position to get a nice mandelbrot drawing for\n" - " // the used v_pos x and y range (-0.5 to 0.5).\n" - " float x0 = 3.0 * v_pos.x - 0.5;\n" - " float y0 = 3.0 * v_pos.y;\n" - " float x = 0.0;\n" - " float y = 0.0;\n" - " int iteration = 0;\n" - " while (x * x + y * y <= 4.0 && iteration < max_iteration) {\n" - " float xtemp = x * x - y * y + x0;\n" - " y = 2.0 * x * y + y0;\n" - " x = xtemp;\n" - " ++iteration;\n" - " }\n" - " float red = iteration == max_iteration ?\n" - " 0.0 : 1.0 - fract(float(iteration) / 20.0);\n" - " gl_FragColor = vec4(red, 0.0, 0.0, 1.0);\n" - "}\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); - return 0; - } - - return shader; -} - -static GLuint -create_and_link_program(GLuint vert, GLuint frag) -{ - GLint status; - GLuint program = glCreateProgram(); - - glAttachShader(program, vert); - glAttachShader(program, frag); - 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); - return 0; - } - - return program; -} - -static bool -window_set_up_gl(struct window *window) -{ - GLuint vert = create_shader( - window->render_mandelbrot ? vert_shader_mandelbrot_text : - vert_shader_text, - GL_VERTEX_SHADER); - GLuint frag = create_shader( - window->render_mandelbrot ? frag_shader_mandelbrot_text : - frag_shader_text, - GL_FRAGMENT_SHADER); - - window->gl.program = create_and_link_program(vert, frag); - - glDeleteShader(vert); - glDeleteShader(frag); - - window->gl.pos = glGetAttribLocation(window->gl.program, "pos"); - window->gl.color = glGetAttribLocation(window->gl.program, "color"); - - glUseProgram(window->gl.program); - - window->gl.offset_uniform = - glGetUniformLocation(window->gl.program, "offset"); - - return window->gl.program != 0; -} - -static void -destroy_window(struct window *window) -{ - int i; - - if (window->gl.program) - glDeleteProgram(window->gl.program); - - if (window->callback) - wl_callback_destroy(window->callback); - - for (i = 0; i < NUM_BUFFERS; i++) { - if (window->buffers[i].buffer) - buffer_free(&window->buffers[i]); - } - - if (window->xdg_toplevel) - xdg_toplevel_destroy(window->xdg_toplevel); - if (window->xdg_surface) - xdg_surface_destroy(window->xdg_surface); - if (window->surface_sync) - zwp_linux_surface_synchronization_v1_destroy(window->surface_sync); - wl_surface_destroy(window->surface); - free(window); -} - -static struct window * -create_window(struct display *display, int width, int height, int opts) -{ - struct window *window; - int i; - int ret; - - window = zalloc(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); - - if (display->wm_base) { - window->xdg_surface = - xdg_wm_base_get_xdg_surface(display->wm_base, - window->surface); - - assert(window->xdg_surface); - - xdg_surface_add_listener(window->xdg_surface, - &xdg_surface_listener, window); - - window->xdg_toplevel = - xdg_surface_get_toplevel(window->xdg_surface); - - assert(window->xdg_toplevel); - - xdg_toplevel_add_listener(window->xdg_toplevel, - &xdg_toplevel_listener, window); - - xdg_toplevel_set_title(window->xdg_toplevel, "simple-dmabuf-egl"); - - window->wait_for_configure = true; - wl_surface_commit(window->surface); - } else if (display->fshell) { - zwp_fullscreen_shell_v1_present_surface(display->fshell, - window->surface, - ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT, - NULL); - } else { - assert(0); - } - - if (display->explicit_sync) { - window->surface_sync = - zwp_linux_explicit_synchronization_v1_get_synchronization( - display->explicit_sync, window->surface); - assert(window->surface_sync); - } - - for (i = 0; i < NUM_BUFFERS; ++i) { - int j; - for (j = 0; j < MAX_BUFFER_PLANES; ++j) - window->buffers[i].dmabuf_fds[j] = -1; - - } - - for (i = 0; i < NUM_BUFFERS; ++i) { - ret = create_dmabuf_buffer(display, &window->buffers[i], - width, height, opts); - - if (ret < 0) - goto error; - } - - window->render_mandelbrot = opts & OPT_MANDELBROT; - - if (!window_set_up_gl(window)) - goto error; - - return window; - -error: - if (window) - destroy_window(window); - - return NULL; -} - -static int -create_egl_fence_fd(struct window *window) -{ - struct display *d = window->display; - EGLSyncKHR sync = d->egl.create_sync(d->egl.display, - EGL_SYNC_NATIVE_FENCE_ANDROID, - NULL); - int fd; - - assert(sync != EGL_NO_SYNC_KHR); - /* We need to flush before we can get the fence fd. */ - glFlush(); - fd = d->egl.dup_native_fence_fd(d->egl.display, sync); - assert(fd >= 0); - - d->egl.destroy_sync(d->egl.display, sync); - - return fd; -} - -static struct buffer * -window_next_buffer(struct window *window) -{ - int i; - - for (i = 0; i < NUM_BUFFERS; i++) - if (!window->buffers[i].busy) - return &window->buffers[i]; - - return NULL; -} - -static const struct wl_callback_listener frame_listener; - -/* Renders a square moving from the lower left corner to the - * upper right corner of the window. The square's vertices have - * the following colors: - * - * green +-----+ yellow - * | | - * | | - * red +-----+ blue - */ -static void -render(struct window *window, struct buffer *buffer) -{ - /* Complete a movement iteration in 5000 ms. */ - static const uint64_t iteration_ms = 5000; - static const GLfloat verts[4][2] = { - { -0.5, -0.5 }, - { -0.5, 0.5 }, - { 0.5, -0.5 }, - { 0.5, 0.5 } - }; - static const GLfloat colors[4][3] = { - { 1, 0, 0 }, - { 0, 1, 0 }, - { 0, 0, 1 }, - { 1, 1, 0 } - }; - GLfloat offset; - struct timeval tv; - uint64_t time_ms; - - gettimeofday(&tv, NULL); - time_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; - - /* Split time_ms in repeating windows of [0, iteration_ms) and map them - * to offsets in the [-0.5, 0.5) range. */ - offset = (time_ms % iteration_ms) / (float) iteration_ms - 0.5; - - /* Direct all GL draws to the buffer through the FBO */ - glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); - - glViewport(0, 0, window->width, window->height); - - glUniform1f(window->gl.offset_uniform, offset); - - glClearColor(0.0,0.0, 0.0, 1.0); - glClear(GL_COLOR_BUFFER_BIT); - - glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); - glVertexAttribPointer(window->gl.color, 3, GL_FLOAT, GL_FALSE, 0, colors); - glEnableVertexAttribArray(window->gl.pos); - glEnableVertexAttribArray(window->gl.color); - - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - - glDisableVertexAttribArray(window->gl.pos); - glDisableVertexAttribArray(window->gl.color); -} - -static void -render_mandelbrot(struct window *window, struct buffer *buffer) -{ - /* Complete a movement iteration in 5000 ms. */ - static const uint64_t iteration_ms = 5000; - /* Split drawing in a square grid consisting of grid_side * grid_side - * cells. */ - static const int grid_side = 4; - GLfloat norm_cell_side = 1.0 / grid_side; - int num_cells = grid_side * grid_side; - GLfloat offset; - struct timeval tv; - uint64_t time_ms; - int i; - - gettimeofday(&tv, NULL); - time_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; - - /* Split time_ms in repeating windows of [0, iteration_ms) and map them - * to offsets in the [-0.5, 0.5) range. */ - offset = (time_ms % iteration_ms) / (float) iteration_ms - 0.5; - - /* Direct all GL draws to the buffer through the FBO */ - glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); - - glViewport(0, 0, window->width, window->height); - - glUniform1f(window->gl.offset_uniform, offset); - - glClearColor(0.6, 0.6, 0.6, 1.0); - glClear(GL_COLOR_BUFFER_BIT); - - for (i = 0; i < num_cells; ++i) { - /* Calculate the vertex coordinates of the current grid cell. */ - int row = i / grid_side; - int col = i % grid_side; - GLfloat left = -0.5 + norm_cell_side * col; - GLfloat right = left + norm_cell_side; - GLfloat top = 0.5 - norm_cell_side * row; - GLfloat bottom = top - norm_cell_side; - GLfloat verts[4][2] = { - { left, bottom }, - { left, top }, - { right, bottom }, - { right, top } - }; - - /* ... and draw it. */ - glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); - glEnableVertexAttribArray(window->gl.pos); - - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - - glDisableVertexAttribArray(window->gl.pos); - } -} - -static void -buffer_fenced_release(void *data, - struct zwp_linux_buffer_release_v1 *release, - int32_t fence) -{ - struct buffer *buffer = data; - - assert(release == buffer->buffer_release); - assert(buffer->release_fence_fd == -1); - - buffer->busy = 0; - buffer->release_fence_fd = fence; - zwp_linux_buffer_release_v1_destroy(buffer->buffer_release); - buffer->buffer_release = NULL; -} - -static void -buffer_immediate_release(void *data, - struct zwp_linux_buffer_release_v1 *release) -{ - struct buffer *buffer = data; - - assert(release == buffer->buffer_release); - assert(buffer->release_fence_fd == -1); - - buffer->busy = 0; - zwp_linux_buffer_release_v1_destroy(buffer->buffer_release); - buffer->buffer_release = NULL; -} - -static const struct zwp_linux_buffer_release_v1_listener buffer_release_listener = { - buffer_fenced_release, - buffer_immediate_release, -}; - -static void -wait_for_buffer_release_fence(struct buffer *buffer) -{ - struct display *d = buffer->display; - EGLint attrib_list[] = { - EGL_SYNC_NATIVE_FENCE_FD_ANDROID, buffer->release_fence_fd, - EGL_NONE, - }; - EGLSyncKHR sync = d->egl.create_sync(d->egl.display, - EGL_SYNC_NATIVE_FENCE_ANDROID, - attrib_list); - int ret; - - assert(sync); - - /* EGLSyncKHR takes ownership of the fence fd. */ - buffer->release_fence_fd = -1; - - if (d->egl.wait_sync) - ret = d->egl.wait_sync(d->egl.display, sync, 0); - else - ret = d->egl.client_wait_sync(d->egl.display, sync, 0, - EGL_FOREVER_KHR); - assert(ret == EGL_TRUE); - - ret = d->egl.destroy_sync(d->egl.display, sync); - assert(ret == EGL_TRUE); -} - -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" : - "All buffers busy at redraw(). Server bug?\n"); - abort(); - } - - if (buffer->release_fence_fd >= 0) - wait_for_buffer_release_fence(buffer); - - if (window->render_mandelbrot) - render_mandelbrot(window, buffer); - else - render(window, buffer); - - if (window->display->use_explicit_sync) { - int fence_fd = create_egl_fence_fd(window); - zwp_linux_surface_synchronization_v1_set_acquire_fence( - window->surface_sync, fence_fd); - close(fence_fd); - - buffer->buffer_release = - zwp_linux_surface_synchronization_v1_get_release(window->surface_sync); - zwp_linux_buffer_release_v1_add_listener( - buffer->buffer_release, &buffer_release_listener, buffer); - } else { - glFinish(); - } - - wl_surface_attach(window->surface, buffer->buffer, 0, 0); - wl_surface_damage(window->surface, 0, 0, window->width, window->height); - - if (callback) - wl_callback_destroy(callback); - - 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 -dmabuf_modifiers(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, - uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) -{ - struct display *d = data; - - switch (format) { - case BUFFER_FORMAT: - ++d->modifiers_count; - d->modifiers = realloc(d->modifiers, - d->modifiers_count * sizeof(*d->modifiers)); - d->modifiers[d->modifiers_count - 1] = - ((uint64_t)modifier_hi << 32) | modifier_lo; - break; - default: - break; - } -} - -static void -dmabuf_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format) -{ - /* XXX: deprecated */ -} - -static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { - dmabuf_format, - dmabuf_modifiers -}; - -static void -xdg_wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial) -{ - xdg_wm_base_pong(wm_base, serial); -} - -static const struct xdg_wm_base_listener xdg_wm_base_listener = { - xdg_wm_base_ping, -}; - -static void -registry_handle_global(void *data, struct wl_registry *registry, - uint32_t id, const char *interface, uint32_t version) -{ - struct display *d = data; - - if (strcmp(interface, "wl_compositor") == 0) { - d->compositor = - wl_registry_bind(registry, - id, &wl_compositor_interface, 1); - } else if (strcmp(interface, "xdg_wm_base") == 0) { - d->wm_base = wl_registry_bind(registry, - id, &xdg_wm_base_interface, 1); - xdg_wm_base_add_listener(d->wm_base, &xdg_wm_base_listener, d); - } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { - d->fshell = wl_registry_bind(registry, - id, &zwp_fullscreen_shell_v1_interface, 1); - } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { - if (version < 3) - return; - d->dmabuf = wl_registry_bind(registry, - id, &zwp_linux_dmabuf_v1_interface, 3); - zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, d); - } else if (strcmp(interface, "zwp_linux_explicit_synchronization_v1") == 0) { - d->explicit_sync = wl_registry_bind( - registry, id, - &zwp_linux_explicit_synchronization_v1_interface, 1); - } else if (strcmp(interface, "weston_direct_display_v1") == 0) { - d->direct_display = wl_registry_bind(registry, - id, &weston_direct_display_v1_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 void -destroy_display(struct display *display) -{ - if (display->gbm.device) - gbm_device_destroy(display->gbm.device); - - if (display->gbm.drm_fd >= 0) - close(display->gbm.drm_fd); - - if (display->egl.context != EGL_NO_CONTEXT) - eglDestroyContext(display->egl.display, display->egl.context); - - if (display->egl.display != EGL_NO_DISPLAY) - eglTerminate(display->egl.display); - - free(display->modifiers); - - if (display->dmabuf) - zwp_linux_dmabuf_v1_destroy(display->dmabuf); - - if (display->wm_base) - xdg_wm_base_destroy(display->wm_base); - - if (display->fshell) - zwp_fullscreen_shell_v1_release(display->fshell); - - if (display->compositor) - wl_compositor_destroy(display->compositor); - - if (display->registry) - wl_registry_destroy(display->registry); - - if (display->display) { - wl_display_flush(display->display); - wl_display_disconnect(display->display); - } - - free(display); -} - -static bool -display_set_up_egl(struct display *display) -{ - static const EGLint context_attribs[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - EGLint major, minor, ret, count; - const char *egl_extensions = NULL; - const char *gl_extensions = NULL; - - 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 - }; - - display->egl.display = - weston_platform_get_egl_display(EGL_PLATFORM_GBM_KHR, - display->gbm.device, NULL); - if (display->egl.display == EGL_NO_DISPLAY) { - fprintf(stderr, "Failed to create EGLDisplay\n"); - goto error; - } - - if (eglInitialize(display->egl.display, &major, &minor) == EGL_FALSE) { - fprintf(stderr, "Failed to initialize EGLDisplay\n"); - goto error; - } - - if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) { - fprintf(stderr, "Failed to bind OpenGL ES API\n"); - goto error; - } - - egl_extensions = eglQueryString(display->egl.display, EGL_EXTENSIONS); - assert(egl_extensions != NULL); - - if (!weston_check_egl_extension(egl_extensions, - "EGL_EXT_image_dma_buf_import")) { - fprintf(stderr, "EGL_EXT_image_dma_buf_import not supported\n"); - goto error; - } - - if (!weston_check_egl_extension(egl_extensions, - "EGL_KHR_surfaceless_context")) { - fprintf(stderr, "EGL_KHR_surfaceless_context not supported\n"); - goto error; - } - - if (weston_check_egl_extension(egl_extensions, - "EGL_KHR_no_config_context")) { - display->egl.has_no_config_context = true; - } - - if (display->egl.has_no_config_context) { - display->egl.conf = EGL_NO_CONFIG_KHR; - } else { - fprintf(stderr, - "Warning: EGL_KHR_no_config_context not supported\n"); - ret = eglChooseConfig(display->egl.display, config_attribs, - &display->egl.conf, 1, &count); - assert(ret && count >= 1); - } - - display->egl.context = eglCreateContext(display->egl.display, - display->egl.conf, - EGL_NO_CONTEXT, - context_attribs); - if (display->egl.context == EGL_NO_CONTEXT) { - fprintf(stderr, "Failed to create EGLContext\n"); - goto error; - } - - eglMakeCurrent(display->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, - display->egl.context); - - gl_extensions = (const char *) glGetString(GL_EXTENSIONS); - assert(gl_extensions != NULL); - - if (!weston_check_egl_extension(gl_extensions, - "GL_OES_EGL_image")) { - fprintf(stderr, "GL_OES_EGL_image not supported\n"); - goto error; - } - - if (weston_check_egl_extension(egl_extensions, - "EGL_EXT_image_dma_buf_import_modifiers")) { - display->egl.has_dma_buf_import_modifiers = true; - display->egl.query_dma_buf_modifiers = - (void *) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); - assert(display->egl.query_dma_buf_modifiers); - } - - display->egl.create_image = - (void *) eglGetProcAddress("eglCreateImageKHR"); - assert(display->egl.create_image); - - display->egl.destroy_image = - (void *) eglGetProcAddress("eglDestroyImageKHR"); - assert(display->egl.destroy_image); - - display->egl.image_target_texture_2d = - (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES"); - assert(display->egl.image_target_texture_2d); - - if (weston_check_egl_extension(egl_extensions, "EGL_KHR_fence_sync") && - weston_check_egl_extension(egl_extensions, - "EGL_ANDROID_native_fence_sync")) { - display->egl.create_sync = - (void *) eglGetProcAddress("eglCreateSyncKHR"); - assert(display->egl.create_sync); - - display->egl.destroy_sync = - (void *) eglGetProcAddress("eglDestroySyncKHR"); - assert(display->egl.destroy_sync); - - display->egl.client_wait_sync = - (void *) eglGetProcAddress("eglClientWaitSyncKHR"); - assert(display->egl.client_wait_sync); - - display->egl.dup_native_fence_fd = - (void *) eglGetProcAddress("eglDupNativeFenceFDANDROID"); - assert(display->egl.dup_native_fence_fd); - } - - if (weston_check_egl_extension(egl_extensions, - "EGL_KHR_wait_sync")) { - display->egl.wait_sync = - (void *) eglGetProcAddress("eglWaitSyncKHR"); - assert(display->egl.wait_sync); - } - - return true; - -error: - return false; -} - -static bool -display_update_supported_modifiers_for_egl(struct display *d) -{ - uint64_t *egl_modifiers = NULL; - int num_egl_modifiers = 0; - EGLBoolean ret; - int i; - bool try_modifiers = d->egl.has_dma_buf_import_modifiers; - - if (try_modifiers) { - ret = d->egl.query_dma_buf_modifiers(d->egl.display, - BUFFER_FORMAT, - 0, /* max_modifiers */ - NULL, /* modifiers */ - NULL, /* external_only */ - &num_egl_modifiers); - if (ret == EGL_FALSE) { - fprintf(stderr, "Failed to query num EGL modifiers for format\n"); - goto error; - } - } - - if (!num_egl_modifiers) - try_modifiers = false; - - /* If EGL doesn't support modifiers, don't use them at all. */ - if (!try_modifiers) { - d->modifiers_count = 0; - free(d->modifiers); - d->modifiers = NULL; - return true; - } - - egl_modifiers = zalloc(num_egl_modifiers * sizeof(*egl_modifiers)); - - ret = d->egl.query_dma_buf_modifiers(d->egl.display, - BUFFER_FORMAT, - num_egl_modifiers, - egl_modifiers, - NULL, /* external_only */ - &num_egl_modifiers); - if (ret == EGL_FALSE) { - fprintf(stderr, "Failed to query EGL modifiers for format\n"); - goto error; - } - - /* Poor person's set intersection: d->modifiers INTERSECT - * egl_modifiers. If a modifier is not supported, replace it with - * DRM_FORMAT_MOD_INVALID in the d->modifiers array. - */ - for (i = 0; i < d->modifiers_count; ++i) { - uint64_t mod = d->modifiers[i]; - bool egl_supported = false; - int j; - - for (j = 0; j < num_egl_modifiers; ++j) { - if (egl_modifiers[j] == mod) { - egl_supported = true; - break; - } - } - - if (!egl_supported) - d->modifiers[i] = DRM_FORMAT_MOD_INVALID; - } - - free(egl_modifiers); - - return true; - -error: - free(egl_modifiers); - - return false; -} - -static bool -display_set_up_gbm(struct display *display, char const* drm_render_node) -{ - display->gbm.drm_fd = open(drm_render_node, O_RDWR); - if (display->gbm.drm_fd < 0) { - fprintf(stderr, "Failed to open drm render node %s\n", - drm_render_node); - return false; - } - - display->gbm.device = gbm_create_device(display->gbm.drm_fd); - if (display->gbm.device == NULL) { - fprintf(stderr, "Failed to create gbm device\n"); - return false; - } - - return true; -} - -static struct display * -create_display(char const *drm_render_node, int opts) -{ - struct display *display = NULL; - - display = zalloc(sizeof *display); - if (display == NULL) { - fprintf(stderr, "out of memory\n"); - goto error; - } - - display->gbm.drm_fd = -1; - - display->display = wl_display_connect(NULL); - assert(display->display); - - display->req_dmabuf_immediate = opts & OPT_IMMEDIATE; - - display->registry = wl_display_get_registry(display->display); - wl_registry_add_listener(display->registry, - ®istry_listener, display); - wl_display_roundtrip(display->display); - if (display->dmabuf == NULL) { - fprintf(stderr, "No zwp_linux_dmabuf global\n"); - goto error; - } - - wl_display_roundtrip(display->display); - - if (!display->modifiers_count) { - fprintf(stderr, "format XRGB8888 is not available\n"); - goto error; - } - - /* GBM needs to be initialized before EGL, so that we have a valid - * render node gbm_device to create the EGL display from. */ - if (!display_set_up_gbm(display, drm_render_node)) - goto error; - - if (!display_set_up_egl(display)) - goto error; - - if (!display_update_supported_modifiers_for_egl(display)) - goto error; - - /* We use explicit synchronization only if the user hasn't disabled it, - * the compositor supports it, we can handle fence fds. */ - display->use_explicit_sync = - !(opts & OPT_IMPLICIT_SYNC) && - display->explicit_sync && - display->egl.dup_native_fence_fd; - - if (opts & OPT_IMPLICIT_SYNC) { - fprintf(stderr, "Warning: Not using explicit sync, disabled by user\n"); - } else if (!display->explicit_sync) { - fprintf(stderr, - "Warning: zwp_linux_explicit_synchronization_v1 not supported,\n" - " will not use explicit synchronization\n"); - } else if (!display->egl.dup_native_fence_fd) { - fprintf(stderr, - "Warning: EGL_ANDROID_native_fence_sync not supported,\n" - " will not use explicit synchronization\n"); - } else if (!display->egl.wait_sync) { - fprintf(stderr, - "Warning: EGL_KHR_wait_sync not supported,\n" - " will not use server-side wait\n"); - } - - return display; - -error: - if (display != NULL) - destroy_display(display); - return NULL; -} - -static void -signal_int(int signum) -{ - running = 0; -} - -static void -print_usage_and_exit(void) -{ - printf("usage flags:\n" - "\t'-i,--import-immediate=<>'" - "\n\t\t0 to import dmabuf via roundtrip, " - "\n\t\t1 to enable import without roundtrip\n" - "\t'-d,--drm-render-node=<>'" - "\n\t\tthe full path to the drm render node to use\n" - "\t'-s,--size=<>'" - "\n\t\tthe window size in pixels (default: 256)\n" - "\t'-e,--explicit-sync=<>'" - "\n\t\t0 to disable explicit sync, " - "\n\t\t1 to enable explicit sync (default: 1)\n" - "\t'-m,--mandelbrot'" - "\n\t\trender a mandelbrot set with multiple draw calls\n" - "\t'-g,--direct-display'" - "\n\t\tenables weston-direct-display extension to attempt " - "direct scan-out;\n\t\tnote this will cause the image to be " - "displayed inverted as GL uses a\n\t\tdifferent texture " - "coordinate system\n"); - exit(0); -} - -static int -is_true(const char* c) -{ - if (!strcmp(c, "1")) - return 1; - else if (!strcmp(c, "0")) - return 0; - else - print_usage_and_exit(); - - return 0; -} - -int -main(int argc, char **argv) -{ - struct sigaction sigint; - struct display *display; - struct window *window; - int opts = 0; - char const *drm_render_node = "/dev/dri/renderD128"; - int c, option_index, ret = 0; - int window_size = 256; - - static struct option long_options[] = { - {"import-immediate", required_argument, 0, 'i' }, - {"drm-render-node", required_argument, 0, 'd' }, - {"size", required_argument, 0, 's' }, - {"explicit-sync", required_argument, 0, 'e' }, - {"mandelbrot", no_argument, 0, 'm' }, - {"direct-display", no_argument, 0, 'g' }, - {"help", no_argument , 0, 'h' }, - {0, 0, 0, 0} - }; - - while ((c = getopt_long(argc, argv, "hi:d:s:e:mg", - long_options, &option_index)) != -1) { - switch (c) { - case 'i': - if (is_true(optarg)) - opts |= OPT_IMMEDIATE; - break; - case 'd': - drm_render_node = optarg; - break; - case 's': - window_size = strtol(optarg, NULL, 10); - break; - case 'e': - if (!is_true(optarg)) - opts |= OPT_IMPLICIT_SYNC; - break; - case 'm': - opts |= OPT_MANDELBROT; - break; - case 'g': - opts |= OPT_DIRECT_DISPLAY; - break; - default: - print_usage_and_exit(); - } - } - - display = create_display(drm_render_node, opts); - if (!display) - return 1; - window = create_window(display, window_size, window_size, opts); - if (!window) - return 1; - - sigint.sa_handler = signal_int; - sigemptyset(&sigint.sa_mask); - sigint.sa_flags = SA_RESETHAND; - sigaction(SIGINT, &sigint, NULL); - - /* Here we retrieve the linux-dmabuf objects if executed without immed, - * or error */ - wl_display_roundtrip(display->display); - - if (!running) - return 1; - - window->initialized = true; - - if (!window->wait_for_configure) - redraw(window, NULL, 0); - - while (running && ret != -1) - ret = wl_display_dispatch(display->display); - - fprintf(stderr, "simple-dmabuf-egl exiting\n"); - destroy_window(window); - destroy_display(display); - - return 0; -} diff --git a/clients/simple-dmabuf-v4l.c b/clients/simple-dmabuf-v4l.c deleted file mode 100644 index 331f049..0000000 --- a/clients/simple-dmabuf-v4l.c +++ /dev/null @@ -1,1080 +0,0 @@ -/* - * Copyright © 2015 Collabora Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include "xdg-shell-client-protocol.h" -#include "fullscreen-shell-unstable-v1-client-protocol.h" -#include "linux-dmabuf-unstable-v1-client-protocol.h" -#include "weston-direct-display-client-protocol.h" - -#include "shared/helpers.h" - -#define CLEAR(x) memset(&(x), 0, sizeof(x)) -#define OPT_FLAG_INVERT (1 << 0) -#define OPT_FLAG_DIRECT_DISPLAY (1 << 1) - -static void -redraw(void *data, struct wl_callback *callback, uint32_t time); - -static int -xioctl(int fh, int request, void *arg) -{ - int r; - - do { - r = ioctl(fh, request, arg); - } while (r == -1 && errno == EINTR); - - return r; -} - -static uint32_t -parse_format(const char fmt[4]) -{ - return fourcc_code(fmt[0], fmt[1], fmt[2], fmt[3]); -} - -static inline const char * -dump_format(uint32_t format, char out[4]) -{ -#if BYTE_ORDER == BIG_ENDIAN - format = __builtin_bswap32(format); -#endif - memcpy(out, &format, 4); - return out; -} - -struct buffer_format { - int width; - int height; - enum v4l2_buf_type type; - uint32_t format; - - unsigned num_planes; - unsigned strides[VIDEO_MAX_PLANES]; -}; - -struct display { - struct wl_display *display; - struct wl_registry *registry; - struct wl_compositor *compositor; - struct wl_seat *seat; - struct wl_keyboard *keyboard; - struct xdg_wm_base *wm_base; - struct zwp_fullscreen_shell_v1 *fshell; - struct zwp_linux_dmabuf_v1 *dmabuf; - struct weston_direct_display_v1 *direct_display; - bool requested_format_found; - uint32_t opts; - - int v4l_fd; - struct buffer_format format; - uint32_t drm_format; -}; - -struct buffer { - struct wl_buffer *buffer; - struct display *display; - int busy; - int index; - - int dmabuf_fds[VIDEO_MAX_PLANES]; - int data_offsets[VIDEO_MAX_PLANES]; -}; - -#define NUM_BUFFERS 3 - -struct window { - struct display *display; - struct wl_surface *surface; - struct xdg_surface *xdg_surface; - struct xdg_toplevel *xdg_toplevel; - struct buffer buffers[NUM_BUFFERS]; - struct wl_callback *callback; - bool wait_for_configure; - bool initialized; -}; - -static bool running = true; - -static int -queue(struct display *display, struct buffer *buffer) -{ - struct v4l2_buffer buf; - struct v4l2_plane planes[VIDEO_MAX_PLANES]; - unsigned i; - - CLEAR(buf); - buf.type = display->format.type; - buf.memory = V4L2_MEMORY_MMAP; - buf.index = buffer->index; - - if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - CLEAR(planes); - buf.length = VIDEO_MAX_PLANES; - buf.m.planes = planes; - } - - if (xioctl(display->v4l_fd, VIDIOC_QUERYBUF, &buf) == -1) { - perror("VIDIOC_QUERYBUF"); - return 0; - } - - if (xioctl(display->v4l_fd, VIDIOC_QBUF, &buf) == -1) { - perror("VIDIOC_QBUF"); - return 0; - } - - if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - if (display->format.num_planes != buf.length) { - fprintf(stderr, "Wrong number of planes returned by " - "QUERYBUF\n"); - return 0; - } - - for (i = 0; i < buf.length; ++i) - buffer->data_offsets[i] = buf.m.planes[i].data_offset; - } - - return 1; -} - -static void -buffer_release(void *data, struct wl_buffer *buffer) -{ - struct buffer *mybuf = data; - - mybuf->busy = 0; - - if (!queue(mybuf->display, mybuf)) - running = false; -} - -static const struct wl_buffer_listener buffer_listener = { - buffer_release -}; - -static unsigned int -set_format(struct display *display, uint32_t format) -{ - struct v4l2_format fmt; - char buf[4]; - - CLEAR(fmt); - - fmt.type = display->format.type; - - if (xioctl(display->v4l_fd, VIDIOC_G_FMT, &fmt) == -1) { - perror("VIDIOC_G_FMT"); - return 0; - } - - /* No need to set the format if it already is the one we want */ - if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE && - fmt.fmt.pix.pixelformat == format) - return 1; - if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && - fmt.fmt.pix_mp.pixelformat == format) - return fmt.fmt.pix_mp.num_planes; - - if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) - fmt.fmt.pix.pixelformat = format; - else - fmt.fmt.pix_mp.pixelformat = format; - - if (xioctl(display->v4l_fd, VIDIOC_S_FMT, &fmt) == -1) { - perror("VIDIOC_S_FMT"); - return 0; - } - - if ((display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE && - fmt.fmt.pix.pixelformat != format) || - (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && - fmt.fmt.pix_mp.pixelformat != format)) { - fprintf(stderr, "Failed to set format to %.4s\n", - dump_format(format, buf)); - return 0; - } - - if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) - return fmt.fmt.pix_mp.num_planes; - - return 1; -} - -static int -v4l_connect(struct display *display, const char *dev_name) -{ - struct v4l2_capability cap; - struct v4l2_requestbuffers req; - struct v4l2_input input; - int index_input = -1; - unsigned int num_planes; - - display->v4l_fd = open(dev_name, O_RDWR); - if (display->v4l_fd < 0) { - perror(dev_name); - return 0; - } - - if (xioctl(display->v4l_fd, VIDIOC_QUERYCAP, &cap) == -1) { - if (errno == EINVAL) { - fprintf(stderr, "%s is no V4L2 device\n", dev_name); - } else { - perror("VIDIOC_QUERYCAP"); - } - return 0; - } - - if (xioctl(display->v4l_fd, VIDIOC_G_INPUT, &index_input) == 0) { - input.index = index_input; - if (xioctl(display->v4l_fd, VIDIOC_ENUMINPUT, &input) == 0) { - if (input.status & V4L2_IN_ST_VFLIP) { - fprintf(stdout, "Found camera sensor y-flipped\n"); - display->opts |= OPT_FLAG_INVERT; - } - } - } - - if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) - display->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - else if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) - display->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - else { - fprintf(stderr, "%s is no video capture device\n", dev_name); - return 0; - } - - if (!(cap.capabilities & V4L2_CAP_STREAMING)) { - fprintf(stderr, "%s does not support dmabuf i/o\n", dev_name); - return 0; - } - - /* Select video input, video standard and tune here */ - - num_planes = set_format(display, display->format.format); - if (num_planes < 1) - return 0; - - CLEAR(req); - - req.type = display->format.type; - req.memory = V4L2_MEMORY_MMAP; - req.count = NUM_BUFFERS * num_planes; - - if (xioctl(display->v4l_fd, VIDIOC_REQBUFS, &req) == -1) { - if (errno == EINVAL) { - fprintf(stderr, "%s does not support dmabuf\n", - dev_name); - } else { - perror("VIDIOC_REQBUFS"); - } - return 0; - } - - if (req.count < NUM_BUFFERS * num_planes) { - fprintf(stderr, "Insufficient buffer memory on %s\n", dev_name); - return 0; - } - - printf("Created %d buffers\n", req.count); - - return 1; -} - -static void -v4l_shutdown(struct display *display) -{ - close(display->v4l_fd); -} - -static void -create_succeeded(void *data, - struct zwp_linux_buffer_params_v1 *params, - struct wl_buffer *new_buffer) -{ - struct buffer *buffer = data; - unsigned i; - - buffer->buffer = new_buffer; - wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); - - zwp_linux_buffer_params_v1_destroy(params); - - for (i = 0; i < buffer->display->format.num_planes; ++i) - close(buffer->dmabuf_fds[i]); -} - -static void -create_failed(void *data, struct zwp_linux_buffer_params_v1 *params) -{ - struct buffer *buffer = data; - unsigned i; - - buffer->buffer = NULL; - - zwp_linux_buffer_params_v1_destroy(params); - - for (i = 0; i < buffer->display->format.num_planes; ++i) - close(buffer->dmabuf_fds[i]); - - running = false; - - fprintf(stderr, "Error: zwp_linux_buffer_params.create failed.\n"); -} - -static const struct zwp_linux_buffer_params_v1_listener params_listener = { - create_succeeded, - create_failed -}; - -static void -create_dmabuf_buffer(struct display *display, struct buffer *buffer) -{ - struct zwp_linux_buffer_params_v1 *params; - uint64_t modifier; - uint32_t flags; - unsigned i; - - modifier = 0; - flags = 0; - - if (display->opts & OPT_FLAG_INVERT) - flags |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; - - params = zwp_linux_dmabuf_v1_create_params(display->dmabuf); - - if ((display->opts & OPT_FLAG_DIRECT_DISPLAY) && display->direct_display) { - weston_direct_display_v1_enable(display->direct_display, params); - - if (display->opts & OPT_FLAG_INVERT) { - flags &= ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; - fprintf(stdout, "dmabuf y-inverted attribute flag was removed" - ", as display-direct flag was set\n"); - } - } - - for (i = 0; i < display->format.num_planes; ++i) - zwp_linux_buffer_params_v1_add(params, - buffer->dmabuf_fds[i], - i, /* plane_idx */ - buffer->data_offsets[i], /* offset */ - display->format.strides[i], - modifier >> 32, - modifier & 0xffffffff); - zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, - buffer); - zwp_linux_buffer_params_v1_create(params, - display->format.width, - display->format.height, - display->drm_format, - flags); -} - -static int -buffer_export(struct display *display, int index, int dmafd[]) -{ - struct v4l2_exportbuffer expbuf; - unsigned i; - - CLEAR(expbuf); - - for (i = 0; i < display->format.num_planes; ++i) { - expbuf.type = display->format.type; - expbuf.index = index; - expbuf.plane = i; - if (xioctl(display->v4l_fd, VIDIOC_EXPBUF, &expbuf) == -1) { - perror("VIDIOC_EXPBUF"); - while (i) - close(dmafd[--i]); - return 0; - } - dmafd[i] = expbuf.fd; - } - - return 1; -} - -static int -queue_initial_buffers(struct display *display, - struct buffer buffers[NUM_BUFFERS]) -{ - struct buffer *buffer; - int index; - - for (index = 0; index < NUM_BUFFERS; ++index) { - buffer = &buffers[index]; - buffer->display = display; - buffer->index = index; - - if (!queue(display, buffer)) { - fprintf(stderr, "Failed to queue buffer\n"); - return 0; - } - - assert(!buffer->buffer); - if (!buffer_export(display, index, buffer->dmabuf_fds)) - return 0; - - create_dmabuf_buffer(display, buffer); - } - - return 1; -} - -static int -dequeue(struct display *display) -{ - struct v4l2_buffer buf; - struct v4l2_plane planes[VIDEO_MAX_PLANES]; - - CLEAR(buf); - buf.type = display->format.type; - buf.memory = V4L2_MEMORY_MMAP; - buf.length = VIDEO_MAX_PLANES; - buf.m.planes = planes; - - /* This ioctl is blocking until a buffer is ready to be displayed */ - if (xioctl(display->v4l_fd, VIDIOC_DQBUF, &buf) == -1) { - perror("VIDIOC_DQBUF"); - return -1; - } - - return buf.index; -} - -static int -fill_buffer_format(struct display *display) -{ - struct v4l2_format fmt; - struct v4l2_pix_format *pix; - struct v4l2_pix_format_mplane *pix_mp; - int i; - char buf[4]; - - CLEAR(fmt); - fmt.type = display->format.type; - - /* Preserve original settings as set by v4l2-ctl for example */ - if (xioctl(display->v4l_fd, VIDIOC_G_FMT, &fmt) == -1) { - perror("VIDIOC_G_FMT"); - return 0; - } - - if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { - pix = &fmt.fmt.pix; - - printf("%d×%d, %.4s\n", pix->width, pix->height, - dump_format(pix->pixelformat, buf)); - - display->format.num_planes = 1; - display->format.width = pix->width; - display->format.height = pix->height; - display->format.strides[0] = pix->bytesperline; - } else { - pix_mp = &fmt.fmt.pix_mp; - - display->format.num_planes = pix_mp->num_planes; - display->format.width = pix_mp->width; - display->format.height = pix_mp->height; - - for (i = 0; i < pix_mp->num_planes; ++i) - display->format.strides[i] = pix_mp->plane_fmt[i].bytesperline; - - printf("%d×%d, %.4s, %d planes\n", - pix_mp->width, pix_mp->height, - dump_format(pix_mp->pixelformat, buf), - pix_mp->num_planes); - } - - return 1; -} - -static int -v4l_init(struct display *display, struct buffer buffers[NUM_BUFFERS]) { - if (!fill_buffer_format(display)) { - fprintf(stderr, "Failed to fill buffer format\n"); - return 0; - } - - if (!queue_initial_buffers(display, buffers)) { - fprintf(stderr, "Failed to queue initial buffers\n"); - return 0; - } - - return 1; -} - -static int -start_capture(struct display *display) -{ - int type = display->format.type; - - if (xioctl(display->v4l_fd, VIDIOC_STREAMON, &type) == -1) { - perror("VIDIOC_STREAMON"); - return 0; - } - - return 1; -} - -static void -xdg_surface_handle_configure(void *data, struct xdg_surface *surface, - uint32_t serial) -{ - struct window *window = data; - - xdg_surface_ack_configure(surface, serial); - - if (window->initialized && window->wait_for_configure) - redraw(window, NULL, 0); - window->wait_for_configure = false; -} - -static const struct xdg_surface_listener xdg_surface_listener = { - xdg_surface_handle_configure, -}; - -static void -xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, - int32_t width, int32_t height, - struct wl_array *states) -{ -} - -static void -xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) -{ - running = 0; -} - -static const struct xdg_toplevel_listener xdg_toplevel_listener = { - xdg_toplevel_handle_configure, - xdg_toplevel_handle_close, -}; - -static struct window * -create_window(struct display *display) -{ - struct window *window; - - window = zalloc(sizeof *window); - if (!window) - return NULL; - - window->callback = NULL; - window->display = display; - window->surface = wl_compositor_create_surface(display->compositor); - - if (display->wm_base) { - window->xdg_surface = - xdg_wm_base_get_xdg_surface(display->wm_base, - window->surface); - - assert(window->xdg_surface); - - xdg_surface_add_listener(window->xdg_surface, - &xdg_surface_listener, window); - - window->xdg_toplevel = - xdg_surface_get_toplevel(window->xdg_surface); - - assert(window->xdg_toplevel); - - xdg_toplevel_add_listener(window->xdg_toplevel, - &xdg_toplevel_listener, window); - - xdg_toplevel_set_title(window->xdg_toplevel, "simple-dmabuf-v4l"); - - window->wait_for_configure = true; - wl_surface_commit(window->surface); - } else if (display->fshell) { - zwp_fullscreen_shell_v1_present_surface(display->fshell, - window->surface, - ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT, - NULL); - } else { - assert(0); - } - - return window; -} - -static void -destroy_window(struct window *window) -{ - int i; - unsigned j; - - if (window->callback) - wl_callback_destroy(window->callback); - - if (window->xdg_toplevel) - xdg_toplevel_destroy(window->xdg_toplevel); - if (window->xdg_surface) - xdg_surface_destroy(window->xdg_surface); - wl_surface_destroy(window->surface); - - for (i = 0; i < NUM_BUFFERS; i++) { - if (!window->buffers[i].buffer) - continue; - - wl_buffer_destroy(window->buffers[i].buffer); - for (j = 0; j < window->display->format.num_planes; ++j) - close(window->buffers[i].dmabuf_fds[j]); - } - - v4l_shutdown(window->display); - - free(window); -} - -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; - int index, num_busy = 0; - - /* Check for a deadlock situation where we would block forever trying - * to dequeue a buffer while all of them are locked by the compositor. - */ - for (index = 0; index < NUM_BUFFERS; ++index) - if (window->buffers[index].busy) - ++num_busy; - - /* A robust application would just postpone redraw until it has queued - * a buffer. - */ - assert(num_busy < NUM_BUFFERS); - - index = dequeue(window->display); - if (index < 0) { - /* We couldn’t get any buffer out of the camera, exiting. */ - running = false; - return; - } - - buffer = &window->buffers[index]; - assert(!buffer->busy); - - wl_surface_attach(window->surface, buffer->buffer, 0, 0); - wl_surface_damage(window->surface, 0, 0, - window->display->format.width, - window->display->format.height); - - 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 -dmabuf_modifier(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, - uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) -{ - struct display *d = data; - uint64_t modifier = ((uint64_t) modifier_hi << 32 ) | modifier_lo; - - if (format == d->drm_format && modifier == DRM_FORMAT_MOD_LINEAR) - d->requested_format_found = true; -} - - -static void -dmabuf_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, - uint32_t format) -{ - /* deprecated */ -} - -static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { - dmabuf_format, - dmabuf_modifier -}; - -static void -keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, - uint32_t format, int fd, uint32_t size) -{ - /* Just so we don’t leak the keymap fd */ - close(fd); -} - -static void -keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, - uint32_t serial, struct wl_surface *surface, - struct wl_array *keys) -{ -} - -static void -keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, - uint32_t serial, struct wl_surface *surface) -{ -} - -static void -keyboard_handle_key(void *data, struct wl_keyboard *keyboard, - uint32_t serial, uint32_t time, uint32_t key, - uint32_t state) -{ - struct display *d = data; - - if (!d->wm_base) - return; - - if (key == KEY_ESC && state) - running = false; -} - -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_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; - } -} - -static const struct wl_seat_listener seat_listener = { - seat_handle_capabilities, -}; - -static void -xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) -{ - xdg_wm_base_pong(shell, serial); -} - -static const struct xdg_wm_base_listener wm_base_listener = { - xdg_wm_base_ping, -}; - -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_seat") == 0) { - d->seat = wl_registry_bind(registry, - id, &wl_seat_interface, 1); - wl_seat_add_listener(d->seat, &seat_listener, d); - } else if (strcmp(interface, "xdg_wm_base") == 0) { - d->wm_base = wl_registry_bind(registry, - id, &xdg_wm_base_interface, 1); - xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d); - } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { - d->fshell = wl_registry_bind(registry, - id, &zwp_fullscreen_shell_v1_interface, - 1); - } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { - d->dmabuf = wl_registry_bind(registry, - id, &zwp_linux_dmabuf_v1_interface, 3); - zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, - d); - } else if (strcmp(interface, "weston_direct_display_v1") == 0) { - d->direct_display = wl_registry_bind(registry, - id, &weston_direct_display_v1_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 display * -create_display(uint32_t requested_format, uint32_t opt_flags) -{ - struct display *display; - - display = zalloc(sizeof *display); - if (display == NULL) { - fprintf(stderr, "out of memory\n"); - exit(1); - } - display->display = wl_display_connect(NULL); - assert(display->display); - - display->drm_format = requested_format; - - display->registry = wl_display_get_registry(display->display); - wl_registry_add_listener(display->registry, - ®istry_listener, display); - wl_display_roundtrip(display->display); - if (display->dmabuf == NULL) { - fprintf(stderr, "No zwp_linux_dmabuf global\n"); - exit(1); - } - - wl_display_roundtrip(display->display); - - if (!display->requested_format_found) { - fprintf(stderr, "0x%lx requested DRM format not available\n", - (unsigned long) requested_format); - exit(1); - } - - if (opt_flags) - display->opts = opt_flags; - return display; -} - -static void -destroy_display(struct display *display) -{ - if (display->dmabuf) - zwp_linux_dmabuf_v1_destroy(display->dmabuf); - - if (display->wm_base) - xdg_wm_base_destroy(display->wm_base); - - if (display->fshell) - zwp_fullscreen_shell_v1_release(display->fshell); - - if (display->compositor) - wl_compositor_destroy(display->compositor); - - wl_registry_destroy(display->registry); - wl_display_flush(display->display); - wl_display_disconnect(display->display); - free(display); -} - -static void -usage(const char *argv0) -{ - printf("Usage: %s [-v v4l2_device] [-f v4l2_format] [-d drm_format] [-i|--y-invert] [-g|--d-display]\n" - "\n" - "The default V4L2 device is /dev/video0\n" - "\n" - "Both formats are FOURCC values (see http://fourcc.org/)\n" - "V4L2 formats are defined in \n" - "DRM formats are defined in \n" - "The default for both formats is YUYV.\n" - "If the V4L2 and DRM formats differ, the data is simply " - "reinterpreted rather than converted.\n\n" - "Flags:\n" - "- y-invert force the image to be y-flipped;\n note will be " - "automatically added if we detect if the camera sensor is " - "y-flipped\n" - "- d-display skip importing dmabuf-based buffer into the GPU\n " - "and attempt pass the buffer straight to the display controller\n", - argv0); - - printf("\n" - "How to set up Vivid the virtual video driver for testing:\n" - "- build your kernel with CONFIG_VIDEO_VIVID=m\n" - "- add this to a /etc/modprobe.d/ file:\n" - " options vivid node_types=0x1 num_inputs=1 input_types=0x00\n" - "- modprobe vivid and check which device was created,\n" - " here we assume /dev/video0\n" - "- set the pixel format:\n" - " $ v4l2-ctl -d /dev/video0 --set-fmt-video=width=640,pixelformat=XR24\n" - "- optionally could add 'allocators=0x1' to options as to create" - " the buffer in a dmabuf-contiguous way\n" - " (as some display-controllers require it)\n" - "- launch the demo:\n" - " $ %s -v /dev/video0 -f XR24 -d XR24\n" - "You should see a test pattern with color bars, and some text.\n" - "\n" - "More about vivid: https://www.kernel.org/doc/Documentation/video4linux/vivid.txt\n" - "\n", argv0); - - exit(0); -} - -static void -signal_int(int signum) -{ - running = false; -} - -int -main(int argc, char **argv) -{ - struct sigaction sigint; - struct display *display; - struct window *window; - const char *v4l_device = NULL; - uint32_t v4l_format = 0x0; - uint32_t drm_format = 0x0; - uint32_t opts_flags = 0x0; - int c, opt_index, ret = 0; - - static struct option long_options[] = { - { "v4l2-device", required_argument, NULL, 'v' }, - { "v4l2-format", required_argument, NULL, 'f' }, - { "drm-format", required_argument, NULL, 'd' }, - { "y-invert", no_argument, NULL, 'i' }, - { "d-display", no_argument, NULL, 'g' }, - { "help", no_argument, NULL, 'h' }, - { 0, 0, NULL, 0 } - }; - - while ((c = getopt_long(argc, argv, "hiv:d:f:g", long_options, - &opt_index)) != -1) { - switch (c) { - case 'v': - v4l_device = optarg; - break; - case 'f': - v4l_format = parse_format(optarg); - break; - case 'd': - drm_format = parse_format(optarg); - break; - case 'i': - opts_flags |= OPT_FLAG_INVERT; - break; - case 'g': - opts_flags |= OPT_FLAG_DIRECT_DISPLAY; - break; - default: - case 'h': - usage(argv[0]); - break; - } - } - - if (!v4l_device) - v4l_device = "/dev/video0"; - - if (v4l_format == 0x0) - v4l_format = parse_format("YUYV"); - - if (drm_format == 0x0) - drm_format = v4l_format; - - display = create_display(drm_format, opts_flags); - display->format.format = v4l_format; - - window = create_window(display); - if (!window) - return 1; - - if (!v4l_connect(display, v4l_device)) - return 1; - - if (!v4l_init(display, window->buffers)) - return 1; - - sigint.sa_handler = signal_int; - sigemptyset(&sigint.sa_mask); - sigint.sa_flags = SA_RESETHAND; - sigaction(SIGINT, &sigint, NULL); - - /* Here we retrieve the linux-dmabuf objects, or error */ - wl_display_roundtrip(display->display); - - /* In case of error, running will be 0 */ - if (!running) - return 1; - - /* We got all of our buffers, we can start the capture! */ - if (!start_capture(display)) - return 1; - - window->initialized = true; - - if (!window->wait_for_configure) - redraw(window, NULL, 0); - - while (running && ret != -1) - ret = wl_display_dispatch(display->display); - - fprintf(stderr, "simple-dmabuf-v4l exiting\n"); - destroy_window(window); - destroy_display(display); - - return 0; -} diff --git a/clients/simple-egl.c b/clients/simple-egl.c deleted file mode 100644 index cd7408e..0000000 --- a/clients/simple-egl.c +++ /dev/null @@ -1,896 +0,0 @@ -/* - * Copyright © 2011 Benjamin Franzke - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include - -#include "xdg-shell-client-protocol.h" -#include -#include - -#include "shared/helpers.h" -#include "shared/platform.h" -#include "shared/weston-egl-ext.h" - -struct window; -struct seat; - -struct display { - struct wl_display *display; - struct wl_registry *registry; - struct wl_compositor *compositor; - struct xdg_wm_base *wm_base; - 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; - - uint32_t benchmark_time, frames; - struct wl_egl_window *native; - struct wl_surface *surface; - struct xdg_surface *xdg_surface; - struct xdg_toplevel *xdg_toplevel; - EGLSurface egl_surface; - struct wl_callback *callback; - int fullscreen, maximized, opaque, buffer_size, frame_sync, delay; - bool wait_for_configure; -}; - -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, struct window *window) -{ - static const struct { - char *extension, *entrypoint; - } swap_damage_ext_to_entrypoint[] = { - { - .extension = "EGL_EXT_swap_buffers_with_damage", - .entrypoint = "eglSwapBuffersWithDamageEXT", - }, - { - .extension = "EGL_KHR_swap_buffers_with_damage", - .entrypoint = "eglSwapBuffersWithDamageKHR", - }, - }; - - 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, count, i, size; - EGLConfig *configs; - EGLBoolean ret; - - if (window->opaque || window->buffer_size == 16) - config_attribs[9] = 0; - - display->egl.dpy = - weston_platform_get_egl_display(EGL_PLATFORM_WAYLAND_KHR, - display->display, NULL); - 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); - - if (!eglGetConfigs(display->egl.dpy, NULL, 0, &count) || count < 1) - assert(0); - - configs = calloc(count, sizeof *configs); - assert(configs); - - ret = eglChooseConfig(display->egl.dpy, config_attribs, - configs, count, &n); - assert(ret && n >= 1); - - for (i = 0; i < n; i++) { - eglGetConfigAttrib(display->egl.dpy, - configs[i], EGL_BUFFER_SIZE, &size); - if (window->buffer_size == size) { - display->egl.conf = configs[i]; - break; - } - } - free(configs); - if (display->egl.conf == NULL) { - fprintf(stderr, "did not find config with buffer size %d\n", - window->buffer_size); - exit(EXIT_FAILURE); - } - - 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 && - weston_check_egl_extension(extensions, "EGL_EXT_buffer_age")) { - for (i = 0; i < (int) ARRAY_LENGTH(swap_damage_ext_to_entrypoint); i++) { - if (weston_check_egl_extension(extensions, - swap_damage_ext_to_entrypoint[i].extension)) { - /* The EXTPROC is identical to the KHR one */ - display->swap_buffers_with_damage = - (PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC) - eglGetProcAddress(swap_damage_ext_to_entrypoint[i].entrypoint); - break; - } - } - } - - if (display->swap_buffers_with_damage) - printf("has EGL_EXT_buffer_age and %s\n", swap_damage_ext_to_entrypoint[i].extension); - -} - -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_surface_configure(void *data, struct xdg_surface *surface, - uint32_t serial) -{ - struct window *window = data; - - xdg_surface_ack_configure(surface, serial); - - window->wait_for_configure = false; -} - -static const struct xdg_surface_listener xdg_surface_listener = { - handle_surface_configure -}; - -static void -handle_toplevel_configure(void *data, struct xdg_toplevel *toplevel, - int32_t width, int32_t height, - struct wl_array *states) -{ - struct window *window = data; - uint32_t *p; - - window->fullscreen = 0; - window->maximized = 0; - wl_array_for_each(p, states) { - uint32_t state = *p; - switch (state) { - case XDG_TOPLEVEL_STATE_FULLSCREEN: - window->fullscreen = 1; - break; - case XDG_TOPLEVEL_STATE_MAXIMIZED: - window->maximized = 1; - break; - } - } - - if (width > 0 && height > 0) { - if (!window->fullscreen && !window->maximized) { - window->window_size.width = width; - window->window_size.height = height; - } - window->geometry.width = width; - window->geometry.height = height; - } else if (!window->fullscreen && !window->maximized) { - window->geometry = window->window_size; - } - - if (window->native) - wl_egl_window_resize(window->native, - window->geometry.width, - window->geometry.height, 0, 0); -} - -static void -handle_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) -{ - running = 0; -} - -static const struct xdg_toplevel_listener xdg_toplevel_listener = { - handle_toplevel_configure, - handle_toplevel_close, -}; - -static void -create_surface(struct window *window) -{ - struct display *display = window->display; - EGLBoolean ret; - - window->surface = wl_compositor_create_surface(display->compositor); - - window->native = - wl_egl_window_create(window->surface, - window->geometry.width, - window->geometry.height); - window->egl_surface = - weston_platform_create_egl_surface(display->egl.dpy, - display->egl.conf, - window->native, NULL); - - window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, - window->surface); - xdg_surface_add_listener(window->xdg_surface, - &xdg_surface_listener, window); - - window->xdg_toplevel = - xdg_surface_get_toplevel(window->xdg_surface); - xdg_toplevel_add_listener(window->xdg_toplevel, - &xdg_toplevel_listener, window); - - xdg_toplevel_set_title(window->xdg_toplevel, "simple-egl"); - - window->wait_for_configure = true; - wl_surface_commit(window->surface); - - ret = eglMakeCurrent(window->display->egl.dpy, window->egl_surface, - window->egl_surface, window->display->egl.ctx); - assert(ret == EGL_TRUE); - - if (!window->frame_sync) - eglSwapInterval(display->egl.dpy, 0); - - if (!display->wm_base) - return; - - if (window->fullscreen) - xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); -} - -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); - - weston_platform_destroy_egl_surface(window->display->egl.dpy, - window->egl_surface); - wl_egl_window_destroy(window->native); - - if (window->xdg_toplevel) - xdg_toplevel_destroy(window->xdg_toplevel); - if (window->xdg_surface) - xdg_surface_destroy(window->xdg_surface); - wl_surface_destroy(window->surface); - - if (window->callback) - wl_callback_destroy(window->callback); -} - -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 uint32_t speed_div = 5, benchmark_interval = 5; - struct wl_region *region; - EGLint rect[4]; - EGLint buffer_age = 0; - struct timeval tv; - - assert(window->callback == callback); - window->callback = NULL; - - if (callback) - wl_callback_destroy(callback); - - gettimeofday(&tv, NULL); - time = tv.tv_sec * 1000 + tv.tv_usec / 1000; - if (window->frames == 0) - window->benchmark_time = time; - if (time - window->benchmark_time > (benchmark_interval * 1000)) { - printf("%d frames in %d seconds: %f fps\n", - window->frames, - benchmark_interval, - (float) window->frames / benchmark_interval); - window->benchmark_time = time; - window->frames = 0; - } - - 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); - - 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); - - usleep(window->delay); - - 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); - } - - 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); - } - window->frames++; -} - -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); - if (!buffer) - return; - 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 (!display->window->xdg_toplevel) - return; - - if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) - xdg_toplevel_move(display->window->xdg_toplevel, - 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; - - if (!d->wm_base) - return; - - xdg_toplevel_move(d->window->xdg_toplevel, 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) -{ - /* Just so we don’t leak the keymap fd */ - close(fd); -} - -static void -keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, - uint32_t serial, struct wl_surface *surface, - struct wl_array *keys) -{ -} - -static void -keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, - uint32_t serial, struct wl_surface *surface) -{ -} - -static void -keyboard_handle_key(void *data, struct wl_keyboard *keyboard, - uint32_t serial, uint32_t time, uint32_t key, - uint32_t state) -{ - struct display *d = data; - - if (!d->wm_base) - return; - - if (key == KEY_F11 && state) { - if (d->window->fullscreen) - xdg_toplevel_unset_fullscreen(d->window->xdg_toplevel); - else - xdg_toplevel_set_fullscreen(d->window->xdg_toplevel, NULL); - } 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 -xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) -{ - xdg_wm_base_pong(shell, serial); -} - -static const struct xdg_wm_base_listener wm_base_listener = { - xdg_wm_base_ping, -}; - -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, - MIN(version, 4)); - } else if (strcmp(interface, "xdg_wm_base") == 0) { - d->wm_base = wl_registry_bind(registry, name, - &xdg_wm_base_interface, 1); - xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d); - } 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); - if (!d->cursor_theme) { - fprintf(stderr, "unable to load default theme\n"); - return; - } - d->default_cursor = - wl_cursor_theme_get_cursor(d->cursor_theme, "left_ptr"); - if (!d->default_cursor) { - fprintf(stderr, "unable to load default left pointer\n"); - // TODO: abort ? - } - } -} - -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" - " -d \tBuffer swap delay in microseconds\n" - " -f\tRun in fullscreen mode\n" - " -o\tCreate an opaque surface\n" - " -s\tUse a 16 bpp EGL config\n" - " -b\tDon't sync to compositor redraw (eglSwapInterval 0)\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.geometry.width = 250; - window.geometry.height = 250; - window.window_size = window.geometry; - window.buffer_size = 32; - window.frame_sync = 1; - window.delay = 0; - - for (i = 1; i < argc; i++) { - if (strcmp("-d", argv[i]) == 0 && i+1 < argc) - window.delay = atoi(argv[++i]); - else if (strcmp("-f", argv[i]) == 0) - window.fullscreen = 1; - else if (strcmp("-o", argv[i]) == 0) - window.opaque = 1; - else if (strcmp("-s", argv[i]) == 0) - window.buffer_size = 16; - else if (strcmp("-b", argv[i]) == 0) - window.frame_sync = 0; - 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_roundtrip(display.display); - - init_egl(&display, &window); - 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); - - /* The mainloop here is a little subtle. Redrawing will cause - * EGL to read events so we can just call - * wl_display_dispatch_pending() to handle any events that got - * queued up as a side effect. */ - while (running && ret != -1) { - if (window.wait_for_configure) { - ret = wl_display_dispatch(display.display); - } else { - ret = wl_display_dispatch_pending(display.display); - redraw(&window, NULL, 0); - } - } - - 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.wm_base) - xdg_wm_base_destroy(display.wm_base); - - if (display.compositor) - wl_compositor_destroy(display.compositor); - - wl_registry_destroy(display.registry); - wl_display_flush(display.display); - wl_display_disconnect(display.display); - - return 0; -} diff --git a/clients/simple-im.c b/clients/simple-im.c deleted file mode 100644 index a5b834d..0000000 --- a/clients/simple-im.c +++ /dev/null @@ -1,526 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "window.h" -#include "input-method-unstable-v1-client-protocol.h" -#include "shared/helpers.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 zwp_input_method_v1 *input_method; - struct zwp_input_method_context_v1 *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 zwp_input_method_context_v1 *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 zwp_input_method_context_v1 *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 zwp_input_method_context_v1 *context, - uint32_t hint, - uint32_t purpose) -{ -} - -static void -handle_invoke_action(void *data, - struct zwp_input_method_context_v1 *context, - uint32_t button, - uint32_t index) -{ -} - -static void -handle_commit_state(void *data, - struct zwp_input_method_context_v1 *context, - uint32_t serial) -{ - struct simple_im *keyboard = data; - - keyboard->serial = serial; -} - -static void -handle_preferred_language(void *data, - struct zwp_input_method_context_v1 *context, - const char *language) -{ -} - -static const struct zwp_input_method_context_v1_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_keymap_new_from_string(keyboard->xkb_context, - map_str, - XKB_KEYMAP_FORMAT_TEXT_V1, - XKB_KEYMAP_COMPILE_NO_FLAGS); - - 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_keymap_unref(keyboard->keymap); - return; - } - - keyboard->control_mask = - 1 << xkb_keymap_mod_get_index(keyboard->keymap, "Control"); - keyboard->alt_mask = - 1 << xkb_keymap_mod_get_index(keyboard->keymap, "Mod1"); - keyboard->shift_mask = - 1 << xkb_keymap_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_state_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 zwp_input_method_context_v1 *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_MODS_DEPRESSED | - XKB_STATE_MODS_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; - - zwp_input_method_context_v1_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 zwp_input_method_v1 *input_method, - struct zwp_input_method_context_v1 *context) -{ - struct simple_im *keyboard = data; - - if (keyboard->context) - zwp_input_method_context_v1_destroy(keyboard->context); - - keyboard->compose_state = state_normal; - - keyboard->serial = 0; - - keyboard->context = context; - zwp_input_method_context_v1_add_listener(context, - &input_method_context_listener, - keyboard); - keyboard->keyboard = zwp_input_method_context_v1_grab_keyboard(context); - wl_keyboard_add_listener(keyboard->keyboard, - &input_method_keyboard_listener, - keyboard); -} - -static void -input_method_deactivate(void *data, - struct zwp_input_method_v1 *input_method, - struct zwp_input_method_context_v1 *context) -{ - struct simple_im *keyboard = data; - - if (!keyboard->context) - return; - - zwp_input_method_context_v1_destroy(keyboard->context); - keyboard->context = NULL; -} - -static const struct zwp_input_method_v1_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, "zwp_input_method_v1")) { - keyboard->input_method = - wl_registry_bind(registry, name, - &zwp_input_method_v1_interface, 1); - zwp_input_method_v1_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 zwp_input_method_context_v1 *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 < ARRAY_LENGTH(ignore_keys_on_compose); i++) { - if (sym == ignore_keys_on_compose[i]) { - zwp_input_method_context_v1_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, - ARRAY_LENGTH(compose_seqs), - sizeof(compose_seqs[0]), compare_compose_keys); - - if (cs) { - if (cs->keys[i + 1] == 0) { - zwp_input_method_context_v1_preedit_cursor(keyboard->context, - 0); - zwp_input_method_context_v1_preedit_string(keyboard->context, - keyboard->serial, - "", ""); - zwp_input_method_context_v1_cursor_position(keyboard->context, - 0, 0); - zwp_input_method_context_v1_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); - } - - zwp_input_method_context_v1_preedit_cursor(keyboard->context, - strlen(text)); - zwp_input_method_context_v1_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); - } - zwp_input_method_context_v1_preedit_cursor(keyboard->context, - 0); - zwp_input_method_context_v1_preedit_string(keyboard->context, - keyboard->serial, - "", ""); - zwp_input_method_context_v1_cursor_position(keyboard->context, - 0, 0); - zwp_input_method_context_v1_commit_string(keyboard->context, - keyboard->serial, - text); - keyboard->compose_state = state_normal; - } - return; - } - - if (xkb_keysym_to_utf8(sym, text, sizeof(text)) <= 0) { - zwp_input_method_context_v1_key(context, serial, time, key, state); - return; - } - - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) - return; - - zwp_input_method_context_v1_cursor_position(keyboard->context, - 0, 0); - zwp_input_method_context_v1_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: %s\n", - strerror(errno)); - 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"); - return -1; - } - - simple_im.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - 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: %s\n", strerror(errno)); - return -1; - } - - return 0; -} diff --git a/clients/simple-shm.c b/clients/simple-shm.c deleted file mode 100644 index 90892c8..0000000 --- a/clients/simple-shm.c +++ /dev/null @@ -1,528 +0,0 @@ -/* - * Copyright © 2011 Benjamin Franzke - * Copyright © 2010 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "shared/os-compatibility.h" -#include -#include "xdg-shell-client-protocol.h" -#include "fullscreen-shell-unstable-v1-client-protocol.h" - -struct display { - struct wl_display *display; - struct wl_registry *registry; - struct wl_compositor *compositor; - struct xdg_wm_base *wm_base; - struct zwp_fullscreen_shell_v1 *fshell; - struct wl_shm *shm; - bool has_xrgb; -}; - -struct buffer { - struct wl_buffer *buffer; - void *shm_data; - int busy; -}; - -struct window { - struct display *display; - int width, height; - struct wl_surface *surface; - struct xdg_surface *xdg_surface; - struct xdg_toplevel *xdg_toplevel; - struct buffer buffers[2]; - struct buffer *prev_buffer; - struct wl_callback *callback; - bool wait_for_configure; -}; - -static int running = 1; - -static void -redraw(void *data, struct wl_callback *callback, uint32_t time); - -static void -buffer_release(void *data, struct wl_buffer *buffer) -{ - struct buffer *mybuf = data; - - mybuf->busy = 0; -} - -static const struct wl_buffer_listener buffer_listener = { - buffer_release -}; - -static int -create_shm_buffer(struct display *display, struct buffer *buffer, - int width, int height, uint32_t format) -{ - struct wl_shm_pool *pool; - int fd, size, stride; - void *data; - - stride = width * 4; - size = stride * height; - - fd = os_create_anonymous_file(size); - if (fd < 0) { - fprintf(stderr, "creating a buffer file for %d B failed: %s\n", - size, strerror(errno)); - return -1; - } - - data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (data == MAP_FAILED) { - fprintf(stderr, "mmap failed: %s\n", strerror(errno)); - close(fd); - return -1; - } - - pool = wl_shm_create_pool(display->shm, fd, size); - buffer->buffer = wl_shm_pool_create_buffer(pool, 0, - width, height, - stride, format); - wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); - wl_shm_pool_destroy(pool); - close(fd); - - buffer->shm_data = data; - - return 0; -} - -static void -handle_xdg_surface_configure(void *data, struct xdg_surface *surface, - uint32_t serial) -{ - struct window *window = data; - - xdg_surface_ack_configure(surface, serial); - - if (window->wait_for_configure) { - redraw(window, NULL, 0); - window->wait_for_configure = false; - } -} - -static const struct xdg_surface_listener xdg_surface_listener = { - handle_xdg_surface_configure, -}; - -static void -handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, - int32_t width, int32_t height, - struct wl_array *state) -{ -} - -static void -handle_xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) -{ - running = 0; -} - -static const struct xdg_toplevel_listener xdg_toplevel_listener = { - handle_xdg_toplevel_configure, - handle_xdg_toplevel_close, -}; - -static struct window * -create_window(struct display *display, int width, int height) -{ - struct window *window; - - window = zalloc(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); - - if (display->wm_base) { - window->xdg_surface = - xdg_wm_base_get_xdg_surface(display->wm_base, - window->surface); - assert(window->xdg_surface); - xdg_surface_add_listener(window->xdg_surface, - &xdg_surface_listener, window); - - window->xdg_toplevel = - xdg_surface_get_toplevel(window->xdg_surface); - assert(window->xdg_toplevel); - xdg_toplevel_add_listener(window->xdg_toplevel, - &xdg_toplevel_listener, window); - - xdg_toplevel_set_title(window->xdg_toplevel, "simple-shm"); - wl_surface_commit(window->surface); - window->wait_for_configure = true; - } else if (display->fshell) { - zwp_fullscreen_shell_v1_present_surface(display->fshell, - window->surface, - ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT, - NULL); - } else { - assert(0); - } - - return window; -} - -static void -destroy_window(struct window *window) -{ - if (window->callback) - wl_callback_destroy(window->callback); - - if (window->buffers[0].buffer) - wl_buffer_destroy(window->buffers[0].buffer); - if (window->buffers[1].buffer) - wl_buffer_destroy(window->buffers[1].buffer); - - if (window->xdg_toplevel) - xdg_toplevel_destroy(window->xdg_toplevel); - if (window->xdg_surface) - xdg_surface_destroy(window->xdg_surface); - wl_surface_destroy(window->surface); - free(window); -} - -static struct buffer * -window_next_buffer(struct window *window) -{ - struct buffer *buffer; - int ret = 0; - - if (!window->buffers[0].busy) - buffer = &window->buffers[0]; - else if (!window->buffers[1].busy) - buffer = &window->buffers[1]; - else - return NULL; - - if (!buffer->buffer) { - ret = create_shm_buffer(window->display, buffer, - window->width, window->height, - WL_SHM_FORMAT_XRGB8888); - - if (ret < 0) - return NULL; - - /* paint the padding */ - memset(buffer->shm_data, 0xff, - window->width * window->height * 4); - } - - return buffer; -} - -static void -paint_pixels(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; - - if (format == WL_SHM_FORMAT_XRGB8888) - d->has_xrgb = true; -} - -struct wl_shm_listener shm_listener = { - shm_format -}; - -static void -xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) -{ - xdg_wm_base_pong(shell, serial); -} - -static const struct xdg_wm_base_listener xdg_wm_base_listener = { - xdg_wm_base_ping, -}; - -static void -registry_handle_global(void *data, struct wl_registry *registry, - uint32_t id, const char *interface, uint32_t version) -{ - struct display *d = data; - - if (strcmp(interface, "wl_compositor") == 0) { - d->compositor = - wl_registry_bind(registry, - id, &wl_compositor_interface, 1); - } else if (strcmp(interface, "xdg_wm_base") == 0) { - d->wm_base = wl_registry_bind(registry, - id, &xdg_wm_base_interface, 1); - xdg_wm_base_add_listener(d->wm_base, &xdg_wm_base_listener, d); - } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { - d->fshell = wl_registry_bind(registry, - id, &zwp_fullscreen_shell_v1_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->has_xrgb = false; - display->registry = wl_display_get_registry(display->display); - wl_registry_add_listener(display->registry, - ®istry_listener, display); - wl_display_roundtrip(display->display); - if (display->shm == NULL) { - fprintf(stderr, "No wl_shm global\n"); - exit(1); - } - - wl_display_roundtrip(display->display); - - /* - * Why do we need two roundtrips here? - * - * wl_display_get_registry() sends a request to the server, to which - * the server replies by emitting the wl_registry.global events. - * The first wl_display_roundtrip() sends wl_display.sync. The server - * first processes the wl_display.get_registry which includes sending - * the global events, and then processes the sync. Therefore when the - * sync (roundtrip) returns, we are guaranteed to have received and - * processed all the global events. - * - * While we are inside the first wl_display_roundtrip(), incoming - * events are dispatched, which causes registry_handle_global() to - * be called for each global. One of these globals is wl_shm. - * registry_handle_global() sends wl_registry.bind request for the - * wl_shm global. However, wl_registry.bind request is sent after - * the first wl_display.sync, so the reply to the sync comes before - * the initial events of the wl_shm object. - * - * The initial events that get sent as a reply to binding to wl_shm - * include wl_shm.format. These tell us which pixel formats are - * supported, and we need them before we can create buffers. They - * don't change at runtime, so we receive them as part of init. - * - * When the reply to the first sync comes, the server may or may not - * have sent the initial wl_shm events. Therefore we need the second - * wl_display_roundtrip() call here. - * - * The server processes the wl_registry.bind for wl_shm first, and - * the second wl_display.sync next. During our second call to - * wl_display_roundtrip() the initial wl_shm events are received and - * processed. Finally, when the reply to the second wl_display.sync - * arrives, it guarantees we have processed all wl_shm initial events. - * - * This sequence contains two examples on how wl_display_roundtrip() - * can be used to guarantee, that all reply events to a request - * have been received and processed. This is a general Wayland - * technique. - */ - - if (!display->has_xrgb) { - fprintf(stderr, "WL_SHM_FORMAT_XRGB32 not available\n"); - exit(1); - } - - return display; -} - -static void -destroy_display(struct display *display) -{ - if (display->shm) - wl_shm_destroy(display->shm); - - if (display->wm_base) - xdg_wm_base_destroy(display->wm_base); - - if (display->fshell) - zwp_fullscreen_shell_v1_release(display->fshell); - - if (display->compositor) - wl_compositor_destroy(display->compositor); - - wl_registry_destroy(display->registry); - wl_display_flush(display->display); - wl_display_disconnect(display->display); - free(display); -} - -static void -signal_int(int signum) -{ - running = 0; -} - -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); - - if (!window->wait_for_configure) - 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 deleted file mode 100644 index 385188c..0000000 --- a/clients/simple-touch.c +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright © 2011 Benjamin Franzke - * Copyright © 2011 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "shared/helpers.h" -#include "shared/os-compatibility.h" - -struct seat { - struct touch *touch; - struct wl_seat *seat; - struct wl_touch *wl_touch; -}; - -struct touch { - struct wl_display *display; - struct wl_registry *registry; - struct wl_compositor *compositor; - struct wl_shell *shell; - struct wl_shm *shm; - 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: %s\n", - size, strerror(errno)); - exit(1); - } - - touch->data = - mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (touch->data == MAP_FAILED) { - fprintf(stderr, "mmap failed: %s\n", strerror(errno)); - 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 *wl_seat, - enum wl_seat_capability caps) -{ - struct seat *seat = data; - struct touch *touch = seat->touch; - - if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !seat->wl_touch) { - seat->wl_touch = wl_seat_get_touch(wl_seat); - wl_touch_set_user_data(seat->wl_touch, touch); - wl_touch_add_listener(seat->wl_touch, &touch_listener, touch); - } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && seat->wl_touch) { - wl_touch_destroy(seat->wl_touch); - seat->wl_touch = NULL; - } -} - -static const struct wl_seat_listener seat_listener = { - seat_handle_capabilities, -}; - -static void -add_seat(struct touch *touch, uint32_t name, uint32_t version) -{ - struct seat *seat; - - seat = malloc(sizeof *seat); - assert(seat); - - seat->touch = touch; - seat->wl_touch = NULL; - seat->seat = wl_registry_bind(touch->registry, name, - &wl_seat_interface, 1); - wl_seat_add_listener(seat->seat, &seat_listener, seat); -} - -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) { - add_seat(touch, name, version); - } -} - -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 deleted file mode 100644 index f1b90ec..0000000 --- a/clients/smoke.c +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright © 2010 Kristian Høgsberg - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#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; - 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 - 1.5) - px = smoke->width - 1.5; - if (py > smoke->height - 1.5) - py = smoke->height - 1.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 -redraw_handler(struct widget *widget, void *data) -{ - struct smoke *smoke = data; - uint32_t time = widget_get_last_time(smoke->widget); - 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); - - cairo_surface_destroy(surface); - - widget_schedule_redraw(smoke->widget); -} - -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; - - /* Don't 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: %s\n", - strerror(errno)); - 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); - - widget_destroy(smoke.widget); - window_destroy(smoke.window); - display_destroy(d); - - return 0; -} diff --git a/clients/stacking.c b/clients/stacking.c deleted file mode 100644 index 5e9084f..0000000 --- a/clients/stacking.c +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright © 2013 Collabora Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "shared/helpers.h" -#include "window.h" - -struct stacking { - struct display *display; - struct window *root_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); -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); -static void -keyboard_focus_handler(struct window *window, - struct input *device, void *data); -static void -fullscreen_handler(struct window *window, void *data); -static void -redraw_handler(struct widget *widget, void *data); - -/* Iff parent_window is set, the new window will be transient. */ -static struct window * -new_window(struct stacking *stacking, struct window *parent_window) -{ - struct window *new_window; - struct widget *new_widget; - - new_window = window_create(stacking->display); - window_set_parent(new_window, parent_window); - - new_widget = window_frame_create(new_window, new_window); - - window_set_title(new_window, "Stacking Test"); - window_set_key_handler(new_window, key_handler); - window_set_keyboard_focus_handler(new_window, keyboard_focus_handler); - window_set_fullscreen_handler(new_window, fullscreen_handler); - widget_set_button_handler(new_widget, button_handler); - widget_set_redraw_handler(new_widget, redraw_handler); - window_set_user_data(new_window, stacking); - - window_schedule_resize(new_window, 300, 300); - - return new_window; -} - -static void -show_popup_cb(void *data, struct input *input, int index) -{ - /* Ignore the selected menu item. */ -} - -static void -show_popup(struct stacking *stacking, struct input *input, uint32_t time, - struct window *window) -{ - int32_t x, y; - static const char *entries[] = { - "Test Entry", - "Another Test Entry", - }; - - input_get_position(input, &x, &y); - window_show_menu(stacking->display, input, time, window, x, y, - show_popup_cb, entries, ARRAY_LENGTH(entries)); -} - -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 stacking *stacking = data; - - switch (button) { - case BTN_RIGHT: - if (state == WL_POINTER_BUTTON_STATE_PRESSED) - show_popup(stacking, input, time, - widget_get_user_data(widget)); - break; - - case BTN_LEFT: - default: - break; - } -} - -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 stacking *stacking = data; - - if (state != WL_KEYBOARD_KEY_STATE_PRESSED) - return; - - switch (sym) { - case XKB_KEY_f: - fullscreen_handler(window, data); - break; - - case XKB_KEY_m: - window_set_maximized(window, !window_is_maximized(window)); - break; - - case XKB_KEY_n: - /* New top-level window. */ - new_window(stacking, NULL); - break; - - case XKB_KEY_p: - show_popup(stacking, input, time, window); - break; - - case XKB_KEY_q: - exit (0); - break; - - case XKB_KEY_t: - /* New transient window. */ - new_window(stacking, window); - break; - - default: - break; - } -} - -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) -{ - window_set_fullscreen(window, !window_is_fullscreen(window)); -} - -static void -draw_string(cairo_t *cr, - const char *fmt, ...) WL_PRINTF(2, 3); - -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 -set_window_background_colour(cairo_t *cr, struct window *window) -{ - if (window_get_parent(window)) - cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 0.4); - else if (window_is_maximized(window)) - cairo_set_source_rgba(cr, 1.0, 1.0, 0.0, 0.6); - else if (window_is_fullscreen(window)) - cairo_set_source_rgba(cr, 0.0, 1.0, 1.0, 0.6); - else - cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); -} - -static void -redraw_handler(struct widget *widget, void *data) -{ - struct window *window; - struct rectangle allocation; - cairo_t *cr; - - widget_get_allocation(widget, &allocation); - window = widget_get_user_data(widget); - - cr = widget_cairo_create(widget); - cairo_translate(cr, allocation.x, allocation.y); - - /* Draw background. */ - cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); - set_window_background_colour(cr, window); - cairo_rectangle(cr, 0, 0, allocation.width, allocation.height); - cairo_fill(cr); - - /* Print the instructions. */ - cairo_move_to(cr, 5, 15); - cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); - - draw_string(cr, - "Window: %p\n" - "Fullscreen? %u\n" - "Maximized? %u\n" - "Transient? %u\n" - "Keys: (f)ullscreen, (m)aximize,\n" - " (n)ew window, (p)opup,\n" - " (q)uit, (t)ransient window\n", - window, window_is_fullscreen(window), - window_is_maximized(window), window_get_parent(window) ? 1 : 0); - - cairo_destroy(cr); -} - -int -main(int argc, char *argv[]) -{ - struct stacking stacking; - - memset(&stacking, 0, sizeof stacking); - - stacking.display = display_create(&argc, argv); - if (stacking.display == NULL) { - fprintf(stderr, "Failed to create display: %s\n", - strerror(errno)); - return -1; - } - - display_set_user_data(stacking.display, &stacking); - - stacking.root_window = new_window(&stacking, NULL); - - display_run(stacking.display); - - window_destroy(stacking.root_window); - display_destroy(stacking.display); - - return 0; -} diff --git a/clients/subsurfaces.c b/clients/subsurfaces.c deleted file mode 100644 index df4372f..0000000 --- a/clients/subsurfaces.c +++ /dev/null @@ -1,811 +0,0 @@ -/* - * Copyright © 2010 Intel Corporation - * Copyright © 2011 Benjamin Franzke - * Copyright © 2012-2013 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include "shared/helpers.h" -#include "shared/xalloc.h" -#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 bool option_no_triangle; -static bool 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 = zalloc(sizeof *egl); - assert(egl); - - egl->dpy = - weston_platform_get_egl_display(EGL_PLATFORM_WAYLAND_KHR, - display, NULL); - 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 = weston_platform_create_egl_surface(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 = xzalloc(sizeof *tri); - - tri->egl = egl; - tri->widget = window_add_subsurface(window, tri, - int_to_mode(option_triangle_mode)); - widget_set_use_cairo(tri->widget, 0); - 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) - weston_platform_destroy_egl_surface(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 = xzalloc(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 = window_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; - - if (parse_options(options, ARRAY_LENGTH(options), &argc, argv) > 1 - || option_help) { - printf(help_text, argv[0]); - return 0; - } - - display = display_create(&argc, argv); - if (display == NULL) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - 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/terminal.c b/clients/terminal.c deleted file mode 100644 index 66e2bf5..0000000 --- a/clients/terminal.c +++ /dev/null @@ -1,3185 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include "shared/helpers.h" -#include "shared/xalloc.h" -#include "window.h" - -static bool option_fullscreen; -static bool option_maximize; -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; - machine->unicode = 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; - char *title; - 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, row, column, max_width; - uint32_t buffer_height; - uint32_t start, end, saved_start, log_size; - wl_fixed_t smooth_scroll; - int saved_row, saved_column; - int scrolling; - 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; - int size_in_title; - - struct wl_data_source *selection; - uint32_t click_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; - int pace_pipe; -}; - -/* 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->buffer_height - 1); - - return (void *) terminal->data + index * terminal->data_pitch; -} - -static struct attr* -terminal_get_attr_row(struct terminal *terminal, int row) -{ - int index; - - index = (row + terminal->start) & (terminal->buffer_height - 1); - - return (void *) terminal->data_attr + index * terminal->attr_pitch; -} - -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; - - terminal->start += d; - 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) -{ - union utf8_char *data; - struct attr *data_attr; - char *tab_ruler; - int data_pitch, attr_pitch; - int i, l, total_rows; - uint32_t d, uheight = height; - struct rectangle allocation; - struct winsize ws; - - if (uheight > terminal->buffer_height) - height = terminal->buffer_height; - - if (terminal->width == width && terminal->height == height) - return; - - if (terminal->data && width <= terminal->max_width) { - d = 0; - if (height < terminal->height && height <= terminal->row) - d = terminal->height - height; - else if (height > terminal->height && - terminal->height - 1 == terminal->row) { - d = terminal->height - height; - if (terminal->log_size < uheight) - d = -terminal->start; - } - - terminal->start += d; - terminal->row -= d; - } else { - terminal->max_width = width; - data_pitch = width * sizeof(union utf8_char); - data = xzalloc(data_pitch * terminal->buffer_height); - attr_pitch = width * sizeof(struct attr); - data_attr = xmalloc(attr_pitch * terminal->buffer_height); - tab_ruler = xzalloc(width); - attr_init(data_attr, terminal->curr_attr, - width * terminal->buffer_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 += i; - 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->data = data; - terminal->data_attr = data_attr; - terminal->tab_ruler = tab_ruler; - terminal->start = 0; - } - - terminal->margin_bottom = - height - (terminal->height - terminal->margin_bottom); - terminal->width = width; - terminal->height = height; - 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 -update_title(struct terminal *terminal) -{ - if (window_is_resizing(terminal->window)) { - char *p; - if (asprintf(&p, "%s — [%dx%d]", terminal->title, terminal->width, terminal->height) > 0) { - window_set_title(terminal->window, p); - free(p); - } - } else { - window_set_title(terminal->window, terminal->title); - } -} - -static void -resize_handler(struct widget *widget, - int32_t width, int32_t height, void *data) -{ - struct terminal *terminal = data; - int32_t columns, rows, m; - - if (terminal->pace_pipe >= 0) { - close(terminal->pace_pipe); - terminal->pace_pipe = -1; - } - 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); - update_title(terminal); -} - -static void -state_changed_handler(struct window *window, void *data) -{ - struct terminal *terminal = data; - update_title(terminal); -} - -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; - - window_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 = terminal->selection_start_row; 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); - } - - /* skip space glyph (RLE) we use as a placeholder of - the right half of a double-width character, - because RLE is not available in every font. */ - if (p_row[col].ch == 0x200B) - continue; - - 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*/ - free(terminal->title); - terminal->title = strdup(p); - window_set_title(terminal->window, p); - break; - case 7: /* shell cwd as uri */ - break; - case 777: /* Desktop notifications */ - 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 - Insert blank characters */ - count = set[0] ? args[0] : 1; - if (count == 0) count = 1; - terminal_shift_line(terminal, count); - break; - case 'A': /* CUU - Move cursor up rows */ - 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 - Move cursor down rows */ - 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 - Move cursor right by columns */ - 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 - Move cursor left columns */ - 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 - Move cursor down rows, to column 1 */ - 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 - Move cursour up rows, to column 1 */ - 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 - Move cursor to column in current row */ - y = set[0] ? args[0] : 1; - y = y <= 0 ? 1 : y > terminal->width ? terminal->width : y; - - terminal->column = y - 1; - break; - case 'f': /* HVP - Move cursor to */ - case 'H': /* CUP - Move cursor to (origin at 1,1) */ - 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 - Erase display */ - 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) { - /* Clear screen by scrolling contents out */ - terminal_scroll_buffer(terminal, - terminal->end - terminal->start); - } - break; - case 'K': /* EL - Erase line */ - 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 - Insert blank lines */ - 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 - Delete lines */ - 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 - Delete characters on current line */ - 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 - Erase characters on current line */ - 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 - Move cursor to column in current row */ - 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 - Answer "I am a VT102" */ - terminal_write(terminal, "\e[?6c", 5); - break; - case 'd': /* VPA - Move cursor to row, current column */ - x = set[0] ? args[0] : 1; - x = x <= 0 ? 1 : x > terminal->height ? terminal->height : x; - - terminal->row = x - 1; - break; - case 'g': /* TBC - Clear tab stop(s) */ - 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 - Set mode */ - for (i = 0; i < 10 && set[i]; i++) { - handle_term_parameter(terminal, args[i], 1); - } - break; - case 'l': /* RM - Reset mode */ - for (i = 0; i < 10 && set[i]; i++) { - handle_term_parameter(terminal, args[i], 0); - } - break; - case 'm': /* SGR - Set attributes */ - 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 - Status report */ - 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': /* DECSTBM - Set scrolling region */ - 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': /* Save cursor location */ - 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; - } - break; - case 'u': /* Restore cursor location */ - 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 - Reverse linefeed */ - terminal->row -= 1; - if (terminal->row < terminal->margin_top) { - terminal->row = terminal->margin_top; - terminal_scroll(terminal, -1); - } - break; - case 'E': /* NEL - Newline */ - terminal->column = 0; - // fallthrough - case 'D': /* IND - Linefeed */ - terminal->row += 1; - if (terminal->row > terminal->margin_bottom) { - terminal->row = terminal->margin_bottom; - terminal_scroll(terminal, +1); - } - break; - case 'c': /* RIS - Reset*/ - terminal_init(terminal); - break; - case 'H': /* HTS - Set tab stop at current column */ - terminal->tab_ruler[terminal->column] = 1; - break; - case '7': /* DECSC - Save current state */ - 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 - Restore state most recently saved by ESC 7 */ - 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 - Set application keypad mode */ - terminal->key_mode = KM_APPLICATION; - break; - case '>': /* DECPNM - Set numeric keypad mode */ - 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; - - if (terminal->row + terminal->start + 1 > terminal->end) - terminal->end = terminal->row + terminal->start + 1; - if (terminal->end == terminal->buffer_height) - terminal->log_size = terminal->buffer_height; - else if (terminal->log_size < terminal->buffer_height) - terminal->log_size = terminal->end; - - /* 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 void -data_source_dnd_drop_performed(void *data, struct wl_data_source *source) -{ -} - -static void -data_source_dnd_finished(void *data, struct wl_data_source *source) -{ -} - -static void -data_source_action(void *data, - struct wl_data_source *source, uint32_t dnd_action) -{ -} - -static const struct wl_data_source_listener data_source_listener = { - data_source_target, - data_source_send, - data_source_cancelled, - data_source_dnd_drop_performed, - data_source_dnd_finished, - data_source_action -}; - -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(void *data) -{ - struct terminal *terminal = data; - - terminal_destroy(terminal); -} - -static void -terminal_copy(struct terminal *terminal, struct input *input) -{ - terminal->selection = - display_create_data_source(terminal->display); - if (!terminal->selection) - return; - - 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)); -} - -static void -terminal_paste(struct terminal *terminal, struct input *input) -{ - input_receive_selection_data_to_fd(input, - "text/plain;charset=utf-8", - terminal->master); - -} - -static void -terminal_new_instance(struct terminal *terminal) -{ - struct terminal *new_terminal; - - new_terminal = terminal_create(terminal->display); - if (terminal_run(new_terminal, option_shell)) - terminal_destroy(new_terminal); -} - -static int -handle_bound_key(struct terminal *terminal, - struct input *input, uint32_t sym, uint32_t time) -{ - switch (sym) { - case XKB_KEY_X: - /* Cut selection; terminal doesn't do cut, fall - * through to copy. */ - case XKB_KEY_C: - terminal_copy(terminal, input); - return 1; - case XKB_KEY_V: - terminal_paste(terminal, input); - return 1; - case XKB_KEY_N: - terminal_new_instance(terminal); - return 1; - - case XKB_KEY_Up: - if (!terminal->scrolling) - terminal->saved_start = terminal->start; - if (terminal->start == terminal->end - terminal->log_size) - return 1; - - terminal->scrolling = 1; - terminal->start--; - terminal->row++; - terminal->selection_start_row++; - terminal->selection_end_row++; - widget_schedule_redraw(terminal->widget); - return 1; - - case XKB_KEY_Down: - if (!terminal->scrolling) - terminal->saved_start = terminal->start; - - if (terminal->start == terminal->saved_start) - return 1; - - terminal->scrolling = 1; - terminal->start++; - terminal->row--; - terminal->selection_start_row--; - terminal->selection_end_row--; - widget_schedule_redraw(terminal->widget); - 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, d; - 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) { - if (terminal->scrolling) { - d = terminal->saved_start - terminal->start; - terminal->row -= d; - terminal->selection_start_row -= d; - terminal->selection_end_row -= d; - terminal->start = terminal->saved_start; - terminal->scrolling = 0; - widget_schedule_redraw(terminal->widget); - } - - 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 || ch < 0) - 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 = NULL; - - 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 -terminal_minimize(struct terminal *terminal) -{ - window_set_minimized(terminal->window); -} - -static void -menu_func(void *data, struct input *input, int index) -{ - struct window *window = data; - struct terminal *terminal = window_get_user_data(window); - - fprintf(stderr, "picked entry %d\n", index); - - switch (index) { - case 0: - terminal_new_instance(terminal); - break; - case 1: - terminal_copy(terminal, input); - break; - case 2: - terminal_paste(terminal, input); - break; - case 3: - terminal_minimize(terminal); - break; - } -} - -static void -show_menu(struct terminal *terminal, struct input *input, uint32_t time) -{ - int32_t x, y; - static const char *entries[] = { - "Open Terminal", "Copy", "Paste", "Minimize" - }; - - input_get_position(input, &x, &y); - window_show_menu(terminal->display, input, time, terminal->window, - x - 10, y - 10, menu_func, - entries, ARRAY_LENGTH(entries)); -} - -static void -click_handler(struct widget *widget, struct terminal *terminal, - struct input *input, int32_t x, int32_t y, - uint32_t time) -{ - if (time - terminal->click_time < 500) - terminal->click_count++; - else - terminal->click_count = 1; - - terminal->click_time = time; - terminal->dragging = (terminal->click_count - 1) % 3 + SELECT_CHAR; - - terminal->selection_end_x = terminal->selection_start_x = x; - terminal->selection_end_y = terminal->selection_start_y = y; - if (recompute_selection(terminal)) - widget_schedule_redraw(widget); -} - -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; - int32_t x, y; - - switch (button) { - case BTN_LEFT: - if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - input_get_position(input, &x, &y); - click_handler(widget, terminal, input, x, y, time); - } else { - terminal->dragging = SELECT_NONE; - } - break; - - case BTN_RIGHT: - if (state == WL_POINTER_BUTTON_STATE_PRESSED) - show_menu(terminal, input, time); - 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; -} - -/* This magnitude is chosen rather arbitrarily. Really, the scrolling - * should happen on a (fractional) pixel basis, not a line basis. */ -#define AXIS_UNITS_PER_LINE 256 - -static void -axis_handler(struct widget *widget, - struct input *input, uint32_t time, - uint32_t axis, - wl_fixed_t value, - void *data) -{ - struct terminal *terminal = data; - int lines; - - if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) - return; - - terminal->smooth_scroll += value; - lines = terminal->smooth_scroll / AXIS_UNITS_PER_LINE; - terminal->smooth_scroll -= lines * AXIS_UNITS_PER_LINE; - - if (lines > 0) { - if (terminal->scrolling) { - if ((uint32_t)lines > terminal->saved_start - terminal->start) - lines = terminal->saved_start - terminal->start; - } else { - lines = 0; - } - } else if (lines < 0) { - uint32_t neg_lines = -lines; - - if (neg_lines > terminal->log_size + terminal->start - terminal->end) - lines = terminal->end - terminal->log_size - terminal->start; - } - - if (lines) { - if (!terminal->scrolling) - terminal->saved_start = terminal->start; - terminal->scrolling = 1; - - terminal->start += lines; - terminal->row -= lines; - terminal->selection_start_row -= lines; - terminal->selection_end_row -= lines; - - widget_schedule_redraw(widget); - } -} - -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); -} - -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 terminal *terminal = data; - - if (id == 0) - click_handler(widget, terminal, input, x, y, time); -} - -static void -touch_up_handler(struct widget *widget, struct input *input, - uint32_t serial, uint32_t time, int32_t id, void *data) -{ - struct terminal *terminal = data; - - if (id == 0) - terminal->dragging = SELECT_NONE; -} - -static void -touch_motion_handler(struct widget *widget, struct input *input, - uint32_t time, int32_t id, float x, float y, void *data) -{ - struct terminal *terminal = data; - - if (terminal->dragging && - id == 0) { - terminal->selection_end_x = (int)x; - terminal->selection_end_y = (int)y; - - if (recompute_selection(terminal)) - widget_schedule_redraw(widget); - } -} - -#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 = window_frame_create(terminal->window, terminal); - terminal->title = xstrdup("Wayland Terminal"); - window_set_title(terminal->window, terminal->title); - widget_set_transparent(terminal->widget, 0); - - init_state_machine(&terminal->state_machine); - init_color_table(terminal); - - terminal->display = display; - terminal->margin = 5; - terminal->buffer_height = 1024; - terminal->end = 1; - - 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_state_changed_handler(terminal->window, state_changed_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); - widget_set_axis_handler(terminal->widget, axis_handler); - widget_set_touch_up_handler(terminal->widget, touch_up_handler); - widget_set_touch_down_handler(terminal->widget, touch_down_handler); - widget_set_touch_motion_handler(terminal->widget, touch_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->title); - 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; - int pipes[2]; - - /* Awkwardness: There's a sticky race condition here. If - * anything prints after the forkpty() but before the window has - * a size then we'll segfault. So we make a pipe and wait on - * it before actually exec()ing the terminal. The resize - * handler closes it in the parent process and the child continues - * on to launch a shell. - * - * The reason we don't just do terminal_run() after the window - * has a size is that we'd prefer to perform the fork() before - * the process opens a wayland connection. - */ - if (pipe(pipes) == -1) { - fprintf(stderr, "Can't create pipe for pacing.\n"); - exit(EXIT_FAILURE); - } - - pid = forkpty(&master, NULL, NULL, NULL); - if (pid == 0) { - int ret; - - close(pipes[1]); - do { - char tmp; - ret = read(pipes[0], &tmp, 1); - } while (ret == -1 && errno == EINTR); - close(pipes[0]); - setenv("TERM", option_term, 1); - setenv("COLORTERM", option_term, 1); - if (execl(path, path, NULL)) { - printf("exec failed: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } - } else if (pid < 0) { - fprintf(stderr, "failed to fork and create pty (%s).\n", - strerror(errno)); - return -1; - } - - close(pipes[0]); - terminal->master = master; - terminal->pace_pipe = pipes[1]; - fcntl(master, F_SETFL, O_NONBLOCK); - terminal->io_task.run = io_handler; - display_watch_fd(terminal->display, terminal->master, - EPOLLIN | EPOLLHUP, &terminal->io_task); - - if (option_fullscreen) - window_set_fullscreen(terminal->window, 1); - else if (option_maximize) - window_set_maximized(terminal->window, 1); - else - terminal_resize(terminal, 80, 24); - - return 0; -} - -static const struct weston_option terminal_options[] = { - { WESTON_OPTION_BOOLEAN, "fullscreen", 'f', &option_fullscreen }, - { WESTON_OPTION_BOOLEAN, "maximized", 'm', &option_maximize }, - { WESTON_OPTION_STRING, "font", 0, &option_font }, - { WESTON_OPTION_INTEGER, "font-size", 0, &option_font_size }, - { WESTON_OPTION_STRING, "shell", 0, &option_shell }, -}; - -int main(int argc, char *argv[]) -{ - struct display *d; - struct terminal *terminal; - const char *config_file; - struct sigaction sigpipe; - 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_file = weston_config_get_name_from_env(); - config = weston_config_parse(config_file); - 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); - - if (parse_options(terminal_options, - ARRAY_LENGTH(terminal_options), &argc, argv) > 1) { - printf("Usage: %s [OPTIONS]\n" - " --fullscreen or -f\n" - " --maximized or -m\n" - " --font=NAME\n" - " --font-size=SIZE\n" - " --shell=NAME\n", argv[0]); - return 1; - } - - /* Disable SIGPIPE so that paste operations do not crash the program - * when the file descriptor provided to receive data is a pipe or - * socket whose reading end has been closed */ - sigpipe.sa_handler = SIG_IGN; - sigemptyset(&sigpipe.sa_mask); - sigpipe.sa_flags = 0; - sigaction(SIGPIPE, &sigpipe, NULL); - - d = display_create(&argc, argv); - if (d == NULL) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - 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/touch-calibrator.c b/clients/touch-calibrator.c deleted file mode 100644 index 66208d1..0000000 --- a/clients/touch-calibrator.c +++ /dev/null @@ -1,970 +0,0 @@ -/* - * Copyright 2012 Intel Corporation - * Copyright 2017-2018 Collabora, Ltd. - * Copyright 2017-2018 General Electric Company - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "clients/window.h" -#include "shared/helpers.h" -#include - -#include "weston-touch-calibration-client-protocol.h" - -enum exit_code { - CAL_EXIT_SUCCESS = 0, - CAL_EXIT_ERROR = 1, - CAL_EXIT_CANCELLED = 2, -}; - -static int debug_; -static int verbose_; - -#define pr_ver(...) do { \ - if (verbose_) \ - printf(__VA_ARGS__); \ -} while (0) - -#define pr_dbg(...) do { \ - if (debug_) \ - fprintf(stderr, __VA_ARGS__); \ -} while (0) - -static void -pr_err(const char *fmt, ...) WL_PRINTF(1, 2); - -/* Our points for the calibration must be not be on a line */ -static const struct { - float x_ratio, y_ratio; -} test_ratios[] = { - { 0.15, 0.10 }, /* three points for calibration */ - { 0.85, 0.13 }, - { 0.20, 0.80 }, - { 0.70, 0.75 } /* and one for verification */ -}; - -#define NR_SAMPLES ((int)ARRAY_LENGTH(test_ratios)) - -struct point { - double x; - double y; -}; - -struct sample { - int ind; - struct point drawn; /**< drawn point, pixels */ - struct weston_touch_coordinate *pending; - struct point drawn_cal; /**< drawn point, converted */ - bool conv_done; - struct point touched; /**< touch point, normalized */ - bool touch_done; -}; - -struct poly { - struct color { - double r, g, b, a; - } color; - int n_verts; - const struct point *verts; -}; - -/** Touch event handling state machine - * - * Only a complete down->up->frame sequence should be accepted with user - * feedback "right", and anything that deviates from that (invalid_touch, - * cancel, multiple touch-downs) needs to undo the current sample and - * possibly show user feedback "wrong". - * - * \ - * - \: \ - * - * IDLE - * - touch down: sample, -> DOWN - * - touch up: no-op - * - frame: no-op - * - invalid_touch: (undo), wrong, -> WAIT - * - cancel: no-op - * DOWN (first touch down) - * - touch down: undo, wrong, -> WAIT - * - touch up: -> UP - * - frame: no-op - * - invalid_touch: undo, wrong, -> WAIT - * - cancel: undo, -> IDLE - * UP (first touch was down and up) - * - touch down: undo, wrong, -> WAIT - * - touch up: no-op - * - frame: right, touch finish, -> WAIT - * - invalid_touch: undo, wrong, -> WAIT - * - cancel: undo, -> IDLE - * WAIT (show user feedback) - * - touch down: no-op - * - touch up: no-op - * - frame, cancel, timer: if num_tp == 0 && timer_done -> IDLE - * - invalid_touch: no-op - */ -enum touch_state { - STATE_IDLE, - STATE_DOWN, - STATE_UP, - STATE_WAIT -}; - -struct calibrator { - struct sample samples[NR_SAMPLES]; - int current_sample; - - struct display *display; - struct weston_touch_calibration *calibration; - struct weston_touch_calibrator *calibrator; - struct window *window; - struct widget *widget; - - int n_devices_listed; - char *match_name; - char *device_name; - - int width; - int height; - - bool cancelled; - - const struct poly *current_poly; - bool exiting; - - struct toytimer wait_timer; - bool timer_pending; - enum touch_state state; - - int num_tp; /* touch points down count */ -}; - -static struct sample * -current_sample(struct calibrator *cal) -{ - return &cal->samples[cal->current_sample]; -} - -static void -sample_start(struct calibrator *cal, int i) -{ - struct sample *s = &cal->samples[i]; - - assert(i >= 0 && i < NR_SAMPLES); - - s->ind = i; - s->drawn.x = round(test_ratios[i].x_ratio * cal->width); - s->drawn.y = round(test_ratios[i].y_ratio * cal->height); - s->pending = NULL; - s->conv_done = false; - s->touch_done = false; - - cal->current_sample = i; -} - -static struct point -wire_to_point(uint32_t xu, uint32_t yu) -{ - struct point p = { - .x = (double)xu / 0xffffffff, - .y = (double)yu / 0xffffffff - }; - - return p; -} - -static void -sample_touch_down(struct calibrator *cal, uint32_t xu, uint32_t yu) -{ - struct sample *s = current_sample(cal); - - s->touched = wire_to_point(xu, yu); - s->touch_done = true; - - pr_dbg("Down[%d] (%f, %f)\n", s->ind, s->touched.x, s->touched.y); -} - -static void -coordinate_result_handler(void *data, struct weston_touch_coordinate *interface, - uint32_t xu, uint32_t yu) -{ - struct sample *s = data; - - weston_touch_coordinate_destroy(s->pending); - s->pending = NULL; - - s->drawn_cal = wire_to_point(xu, yu); - s->conv_done = true; - - pr_dbg("Conv[%d] (%f, %f)\n", s->ind, s->drawn_cal.x, s->drawn_cal.y); -} - -struct weston_touch_coordinate_listener coordinate_listener = { - coordinate_result_handler -}; - -static void -sample_undo(struct calibrator *cal) -{ - struct sample *s = current_sample(cal); - - pr_dbg("Undo[%d]\n", s->ind); - - s->touch_done = false; - s->conv_done = false; - if (s->pending) { - weston_touch_coordinate_destroy(s->pending); - s->pending = NULL; - } -} - -static void -sample_finish(struct calibrator *cal) -{ - struct sample *s = current_sample(cal); - - pr_dbg("Finish[%d]\n", s->ind); - - assert(!s->pending && !s->conv_done); - - s->pending = weston_touch_calibrator_convert(cal->calibrator, - (int32_t)s->drawn.x, - (int32_t)s->drawn.y); - weston_touch_coordinate_add_listener(s->pending, - &coordinate_listener, s); - - if (cal->current_sample + 1 < NR_SAMPLES) { - sample_start(cal, cal->current_sample + 1); - } else { - pr_dbg("got all touches\n"); - cal->exiting = true; - } -} - -/* - * 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 int -compute_calibration(struct calibrator *cal, float *result) -{ - struct weston_matrix m; - struct weston_matrix inverse; - struct weston_vector x_calib; - struct weston_vector y_calib; - int i; - - assert(NR_SAMPLES >= 3); - - /* - * x1 y1 1 0 - * x2 y2 1 0 - * x3 y3 1 0 - * 0 0 0 1 - */ - weston_matrix_init(&m); - for (i = 0; i < 3; i++) { - m.d[i + 0] = cal->samples[i].touched.x; - m.d[i + 4] = cal->samples[i].touched.y; - m.d[i + 8] = 1.0f; - } - m.type = WESTON_MATRIX_TRANSFORM_OTHER; - - if (weston_matrix_invert(&inverse, &m) < 0) { - pr_err("non-invertible matrix during computation\n"); - return -1; - } - - for (i = 0; i < 3; i++) { - x_calib.f[i] = cal->samples[i].drawn_cal.x; - y_calib.f[i] = cal->samples[i].drawn_cal.y; - } - x_calib.f[3] = 0.0f; - y_calib.f[3] = 0.0f; - - /* Multiples into the vector */ - weston_matrix_transform(&inverse, &x_calib); - weston_matrix_transform(&inverse, &y_calib); - - for (i = 0; i < 3; i++) - result[i] = x_calib.f[i]; - for (i = 0; i < 3; i++) - result[i + 3] = y_calib.f[i]; - - return 0; -} - -static int -verify_calibration(struct calibrator *cal, const float *r) -{ - double thr = 0.1; /* accepted error radius */ - struct point e; /* expected value; error */ - const struct sample *s = &cal->samples[3]; - - /* transform raw touches through the matrix */ - e.x = r[0] * s->touched.x + r[1] * s->touched.y + r[2]; - e.y = r[3] * s->touched.x + r[4] * s->touched.y + r[5]; - - /* compute error */ - e.x -= s->drawn_cal.x; - e.y -= s->drawn_cal.y; - - pr_dbg("calibration test error: %f, %f\n", e.x, e.y); - - if (e.x * e.x + e.y * e.y < thr * thr) - return 0; - - pr_err("Calibration verification failed, too large error.\n"); - return -1; -} - -static void -send_calibration(struct calibrator *cal, float *values) -{ - struct wl_array matrix; - float *f; - int i; - - wl_array_init(&matrix); - for (i = 0; i < 6; i++) { - f = wl_array_add(&matrix, sizeof *f); - *f = values[i]; - } - weston_touch_calibration_save(cal->calibration, - cal->device_name, &matrix); - wl_array_release(&matrix); -} - -static const struct point cross_verts[] = { - { 0.1, 0.2 }, - { 0.2, 0.1 }, - { 0.5, 0.4 }, - { 0.8, 0.1 }, - { 0.9, 0.2 }, - { 0.6, 0.5 }, - { 0.9, 0.8 }, - { 0.8, 0.9 }, - { 0.5, 0.6 }, - { 0.2, 0.9 }, - { 0.1, 0.8 }, - { 0.4, 0.5 }, -}; - -/* a red cross, for "wrong" */ -static const struct poly cross = { - .color = { 0.7, 0.0, 0.0, 1.0 }, - .n_verts = ARRAY_LENGTH(cross_verts), - .verts = cross_verts -}; - -static const struct point check_verts[] = { - { 0.5, 0.7 }, - { 0.8, 0.1 }, - { 0.9, 0.1 }, - { 0.55, 0.8 }, - { 0.45, 0.8 }, - { 0.3, 0.5 }, - { 0.4, 0.5 } -}; - -/* a green check mark, for "right" */ -static const struct poly check = { - .color = { 0.0, 0.7, 0.0, 1.0 }, - .n_verts = ARRAY_LENGTH(check_verts), - .verts = check_verts -}; - -static void -draw_poly(cairo_t *cr, const struct poly *poly) -{ - int i; - - cairo_set_source_rgba(cr, poly->color.r, poly->color.g, - poly->color.b, poly->color.a); - cairo_move_to(cr, poly->verts[0].x, poly->verts[0].y); - for (i = 1; i < poly->n_verts; i++) - cairo_line_to(cr, poly->verts[i].x, poly->verts[i].y); - cairo_close_path(cr); - cairo_fill(cr); -} - -static void -feedback_show(struct calibrator *cal, const struct poly *what) -{ - cal->current_poly = what; - widget_schedule_redraw(cal->widget); - - toytimer_arm_once_usec(&cal->wait_timer, 1000 * 1000); - cal->timer_pending = true; -} - -static void -feedback_hide(struct calibrator *cal) -{ - cal->current_poly = NULL; - widget_schedule_redraw(cal->widget); -} - -static void -try_enter_state_idle(struct calibrator *cal) -{ - if (cal->num_tp != 0) - return; - - if (cal->timer_pending) - return; - - cal->state = STATE_IDLE; - - feedback_hide(cal); - - if (cal->exiting) - display_exit(cal->display); -} - -static void -enter_state_wait(struct calibrator *cal) -{ - assert(cal->timer_pending); - cal->state = STATE_WAIT; -} - -static void -wait_timer_done(struct toytimer *tt) -{ - struct calibrator *cal = container_of(tt, struct calibrator, wait_timer); - - assert(cal->state == STATE_WAIT); - cal->timer_pending = false; - try_enter_state_idle(cal); -} - -static void -redraw_handler(struct widget *widget, void *data) -{ - struct calibrator *cal = data; - struct sample *s = current_sample(cal); - struct rectangle allocation; - cairo_surface_t *surface; - cairo_t *cr; - - widget_get_allocation(cal->widget, &allocation); - assert(allocation.width == cal->width); - assert(allocation.height == cal->height); - - surface = window_get_surface(cal->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); - - if (!cal->current_poly) { - cairo_translate(cr, s->drawn.x, s->drawn.y); - cairo_set_line_width(cr, 2.0); - cairo_set_source_rgb(cr, 0.7, 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); - } else { - cairo_scale(cr, allocation.width, allocation.height); - draw_poly(cr, cal->current_poly); - } - - cairo_destroy(cr); - cairo_surface_destroy(surface); -} - -static struct calibrator * -calibrator_create(struct display *display, const char *match_name) -{ - struct calibrator *cal; - - cal = zalloc(sizeof *cal); - if (!cal) - abort(); - - cal->match_name = match_name ? strdup(match_name) : NULL; - cal->window = window_create_custom(display); - cal->widget = window_add_widget(cal->window, cal); - window_inhibit_redraw(cal->window); - window_set_title(cal->window, "Touchscreen calibrator"); - cal->display = display; - - widget_set_redraw_handler(cal->widget, redraw_handler); - - toytimer_init(&cal->wait_timer, CLOCK_MONOTONIC, - display, wait_timer_done); - - cal->state = STATE_IDLE; - cal->num_tp = 0; - - return cal; -} - -static void -configure_handler(void *data, struct weston_touch_calibrator *interface, - int32_t width, int32_t height) -{ - struct calibrator *cal = data; - - pr_dbg("Configure calibrator window to size %ix%i\n", width, height); - cal->width = width; - cal->height = height; - window_schedule_resize(cal->window, width, height); - window_uninhibit_redraw(cal->window); - - sample_start(cal, 0); - widget_schedule_redraw(cal->widget); -} - -static void -cancel_calibration_handler(void *data, struct weston_touch_calibrator *interface) -{ - struct calibrator *cal = data; - - pr_dbg("calibration cancelled by the display server, quitting.\n"); - cal->cancelled = true; - display_exit(cal->display); -} - -static void -invalid_touch_handler(void *data, struct weston_touch_calibrator *interface) -{ - struct calibrator *cal = data; - - pr_dbg("invalid touch\n"); - - switch (cal->state) { - case STATE_IDLE: - case STATE_DOWN: - case STATE_UP: - sample_undo(cal); - feedback_show(cal, &cross); - enter_state_wait(cal); - break; - case STATE_WAIT: - /* no-op */ - break; - } -} - -static void -down_handler(void *data, struct weston_touch_calibrator *interface, - uint32_t time, int32_t id, uint32_t xu, uint32_t yu) -{ - struct calibrator *cal = data; - - cal->num_tp++; - - switch (cal->state) { - case STATE_IDLE: - sample_touch_down(cal, xu, yu); - cal->state = STATE_DOWN; - break; - case STATE_DOWN: - case STATE_UP: - sample_undo(cal); - feedback_show(cal, &cross); - enter_state_wait(cal); - break; - case STATE_WAIT: - /* no-op */ - break; - } - - if (cal->current_poly) - return; -} - -static void -up_handler(void *data, struct weston_touch_calibrator *interface, - uint32_t time, int32_t id) -{ - struct calibrator *cal = data; - - cal->num_tp--; - if (cal->num_tp < 0) { - pr_dbg("Unmatched touch up.\n"); - cal->num_tp = 0; - } - - switch (cal->state) { - case STATE_DOWN: - cal->state = STATE_UP; - break; - case STATE_IDLE: - case STATE_UP: - case STATE_WAIT: - /* no-op */ - break; - } -} - -static void -motion_handler(void *data, struct weston_touch_calibrator *interface, - uint32_t time, int32_t id, uint32_t xu, uint32_t yu) -{ - /* motion is ignored */ -} - -static void -frame_handler(void *data, struct weston_touch_calibrator *interface) -{ - struct calibrator *cal = data; - - switch (cal->state) { - case STATE_IDLE: - case STATE_DOWN: - /* no-op */ - break; - case STATE_UP: - feedback_show(cal, &check); - sample_finish(cal); - enter_state_wait(cal); - break; - case STATE_WAIT: - try_enter_state_idle(cal); - break; - } -} - -static void -cancel_handler(void *data, struct weston_touch_calibrator *interface) -{ - struct calibrator *cal = data; - - cal->num_tp = 0; - - switch (cal->state) { - case STATE_IDLE: - /* no-op */ - break; - case STATE_DOWN: - case STATE_UP: - sample_undo(cal); - try_enter_state_idle(cal); - break; - case STATE_WAIT: - try_enter_state_idle(cal); - break; - } -} - -struct weston_touch_calibrator_listener calibrator_listener = { - configure_handler, - cancel_calibration_handler, - invalid_touch_handler, - down_handler, - up_handler, - motion_handler, - frame_handler, - cancel_handler -}; - -static void -calibrator_show(struct calibrator *cal) -{ - struct wl_surface *surface = window_get_wl_surface(cal->window); - - cal->calibrator = - weston_touch_calibration_create_calibrator(cal->calibration, - surface, - cal->device_name); - weston_touch_calibrator_add_listener(cal->calibrator, - &calibrator_listener, cal); -} - -static void -calibrator_destroy(struct calibrator *cal) -{ - toytimer_fini(&cal->wait_timer); - if (cal->calibrator) - weston_touch_calibrator_destroy(cal->calibrator); - if (cal->calibration) - weston_touch_calibration_destroy(cal->calibration); - if (cal->widget) - widget_destroy(cal->widget); - if (cal->window) - window_destroy(cal->window); - free(cal->match_name); - free(cal->device_name); - free(cal); -} - -static void -touch_device_handler(void *data, struct weston_touch_calibration *c, - const char *device, const char *head) -{ - struct calibrator *cal = data; - - cal->n_devices_listed++; - - if (!cal->match_name) { - printf("device \"%s\" - head \"%s\"\n", device, head); - return; - } - - if (cal->device_name) - return; - - if (strcmp(cal->match_name, device) == 0 || - strcmp(cal->match_name, head) == 0) - cal->device_name = strdup(device); -} - -struct weston_touch_calibration_listener touch_calibration_listener = { - touch_device_handler -}; - -static void -global_handler(struct display *display, uint32_t name, - const char *interface, uint32_t version, void *data) -{ - struct calibrator *cal = data; - - if (strcmp(interface, "weston_touch_calibration") == 0) { - cal->calibration = display_bind(display, name, - &weston_touch_calibration_interface, 1); - weston_touch_calibration_add_listener(cal->calibration, - &touch_calibration_listener, - cal); - } -} - -static int -calibrator_run(struct calibrator *cal) -{ - struct wl_display *dpy; - struct sample *s; - bool wait; - int i; - int ret; - float result[6]; - - calibrator_show(cal); - display_run(cal->display); - - if (cal->cancelled) - return CAL_EXIT_CANCELLED; - - /* remove the window, no more input events */ - widget_destroy(cal->widget); - cal->widget = NULL; - window_destroy(cal->window); - cal->window = NULL; - - /* wait for all conversions to return */ - dpy = display_get_display(cal->display); - do { - wait = false; - - for (i = 0; i < NR_SAMPLES; i++) - if (cal->samples[i].pending) - wait = true; - - if (wait) { - ret = wl_display_roundtrip(dpy); - if (ret < 0) - return CAL_EXIT_ERROR; - } - } while (wait); - - for (i = 0; i < NR_SAMPLES; i++) { - s = &cal->samples[i]; - if (!s->conv_done || !s->touch_done) - return CAL_EXIT_ERROR; - } - - if (compute_calibration(cal, result) < 0) - return CAL_EXIT_ERROR; - - if (verify_calibration(cal, result) < 0) - return CAL_EXIT_ERROR; - - pr_ver("Calibration values:"); - for (i = 0; i < 6; i++) - pr_ver(" %f", result[i]); - pr_ver("\n"); - - send_calibration(cal, result); - ret = wl_display_roundtrip(dpy); - if (ret < 0) - return CAL_EXIT_ERROR; - - return CAL_EXIT_SUCCESS; -} - -static void -pr_err(const char *fmt, ...) -{ - va_list argp; - - va_start(argp, fmt); - fprintf(stderr, "%s error: ", program_invocation_short_name); - vfprintf(stderr, fmt, argp); - va_end(argp); -} - -static void -help(void) -{ - fprintf(stderr, "Compute a touchscreen calibration matrix for " - "a Wayland compositor by\n" - "having the user touch points on the screen.\n\n"); - fprintf(stderr, "Usage: %s [options...] name\n\n", - program_invocation_short_name); - fprintf(stderr, - "Where 'name' can be a touch device sys path or a head name.\n" - "If 'name' is not given, all devices available for " - "calibration will be listed.\n" - "If 'name' is given, it must be exactly as listed.\n" - "Options:\n" - " --debug Print messages to help debugging.\n" - " -h, --help Display this help message\n" - " -v, --verbose Print list header and calibration result.\n"); -} - -int -main(int argc, char *argv[]) -{ - struct display *display; - struct calibrator *cal; - int c; - char *match_name = NULL; - int exit_code = CAL_EXIT_SUCCESS; - static const struct option opts[] = { - { "help", no_argument, NULL, 'h' }, - { "debug", no_argument, &debug_, 1 }, - { "verbose", no_argument, &verbose_, 1 }, - { 0, 0, NULL, 0 } - }; - - while ((c = getopt_long(argc, argv, "hv", opts, NULL)) != -1) { - switch (c) { - case 'h': - help(); - return CAL_EXIT_SUCCESS; - case 'v': - verbose_ = 1; - break; - case 0: - break; - default: - return CAL_EXIT_ERROR; - } - } - - if (optind < argc) - match_name = argv[optind++]; - - if (optind < argc) { - pr_err("extra arguments given.\n\n"); - help(); - return CAL_EXIT_ERROR; - } - - display = display_create(&argc, argv); - if (!display) - return CAL_EXIT_ERROR; - - cal = calibrator_create(display, match_name); - if (!cal) - return CAL_EXIT_ERROR; - - display_set_user_data(display, cal); - display_set_global_handler(display, global_handler); - - if (!match_name) - pr_ver("Available touch devices:\n"); - - /* Roundtrip to get list of available touch devices, - * first globals, then touch_device events */ - wl_display_roundtrip(display_get_display(display)); - wl_display_roundtrip(display_get_display(display)); - - if (!cal->calibration) { - exit_code = CAL_EXIT_ERROR; - pr_err("the Wayland server does not expose the calibration interface.\n"); - } else if (cal->device_name) { - exit_code = calibrator_run(cal); - } else if (match_name) { - exit_code = CAL_EXIT_ERROR; - pr_err("\"%s\" was not found.\n", match_name); - } else if (cal->n_devices_listed == 0) { - fprintf(stderr, "No devices listed.\n"); - } - - calibrator_destroy(cal); - display_destroy(display); - - return exit_code; -} diff --git a/clients/transformed.c b/clients/transformed.c deleted file mode 100644 index 59f44bc..0000000 --- a/clients/transformed.c +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#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; -}; - -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 -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_move(transformed->window, input, display_get_serial(transformed->display)); -} - -static void -usage(int error_code) -{ - fprintf(stderr, "Usage: transformed [OPTIONS]\n\n" - " -w \tSet window width to \n" - " -h \tSet window height to \n" - " --help\tShow this help text\n\n"); - - fprintf(stderr, "This version has been fixed for " - "https://gitlab.freedesktop.org/wayland/weston/issues/99 .\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; - - for (i = 1; i < argc; i++) { - 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: %s\n", - strerror(errno)); - return -1; - } - - transformed.display = d; - transformed.window = window_create(d); - transformed.widget = - window_add_widget(transformed.window, &transformed); - - window_set_title(transformed.window, "Transformed"); - - widget_set_transparent(transformed.widget, 0); - widget_set_default_cursor(transformed.widget, CURSOR_BLANK); - - 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); - widget_destroy(transformed.widget); - window_destroy(transformed.window); - display_destroy(d); - - return 0; -} diff --git a/clients/weston-debug.c b/clients/weston-debug.c deleted file mode 100644 index 3060dec..0000000 --- a/clients/weston-debug.c +++ /dev/null @@ -1,501 +0,0 @@ -/* - * Copyright © 2017 Pekka Paalanen - * Copyright © 2018 Zodiac Inflight Innovations - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "shared/helpers.h" -#include -#include "weston-debug-client-protocol.h" - -struct debug_app { - struct { - bool help; - bool list; - bool bind_all; - char *output; - char *outfd; - } opt; - - int out_fd; - struct wl_display *dpy; - struct wl_registry *registry; - struct weston_debug_v1 *debug_iface; - struct wl_list stream_list; -}; - -struct debug_stream { - struct wl_list link; - bool should_bind; - char *name; - char *desc; - struct weston_debug_stream_v1 *obj; -}; - -/** - * Called either through stream_find in response to an advertisement - * event (see comment on stream_find) when we have all the information, - * or directly from option parsing to make a placeholder entry when the - * stream was explicitly named on the command line to bind to. - */ -static struct debug_stream * -stream_alloc(struct debug_app *app, const char *name, const char *desc) -{ - struct debug_stream *stream; - - stream = zalloc(sizeof *stream); - if (!stream) - return NULL; - - stream->name = strdup(name); - if (!stream->name) { - free(stream); - return NULL; - } - - if (desc) { - stream->desc = strdup(desc); - if (!stream->desc) { - free(stream->name); - free(stream); - return NULL; - } - } - - stream->should_bind = app->opt.bind_all; - wl_list_insert(app->stream_list.prev, &stream->link); - - return stream; -} - -/** - * Called in response to a stream advertisement event. If our stream was - * manually specified on the command line, then it will already have a - * dummy entry in stream_list: we fill in its description and return. - * If there's no entry in the list, we make a new one and return that. - */ -static struct debug_stream * -stream_find(struct debug_app *app, const char *name, const char *desc) -{ - struct debug_stream *stream; - - wl_list_for_each(stream, &app->stream_list, link) { - if (strcmp(stream->name, name) == 0) { - assert(stream->desc == NULL); - if (desc) - stream->desc = strdup(desc); - return stream; - } - } - - return stream_alloc(app, name, desc); -} - -static void -stream_destroy(struct debug_stream *stream) -{ - if (stream->obj) - weston_debug_stream_v1_destroy(stream->obj); - - wl_list_remove(&stream->link); - free(stream->name); - free(stream); -} - -static void -destroy_streams(struct debug_app *app) -{ - struct debug_stream *stream; - struct debug_stream *tmp; - - wl_list_for_each_safe(stream, tmp, &app->stream_list, link) - stream_destroy(stream); -} - -static void -debug_advertise(void *data, struct weston_debug_v1 *debug, const char *name, - const char *desc) -{ - struct debug_app *app = data; - (void) stream_find(app, name, desc); -} - -static const struct weston_debug_v1_listener debug_listener = { - debug_advertise, -}; - -static void -global_handler(void *data, struct wl_registry *registry, uint32_t id, - const char *interface, uint32_t version) -{ - struct debug_app *app = data; - uint32_t myver; - - assert(app->registry == registry); - - if (!strcmp(interface, weston_debug_v1_interface.name)) { - if (app->debug_iface) - return; - - myver = MIN(1, version); - app->debug_iface = - wl_registry_bind(registry, id, - &weston_debug_v1_interface, myver); - weston_debug_v1_add_listener(app->debug_iface, &debug_listener, - app); - } -} - -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 -handle_stream_complete(void *data, struct weston_debug_stream_v1 *obj) -{ - struct debug_stream *stream = data; - - assert(stream->obj == obj); - - stream_destroy(stream); -} - -static void -handle_stream_failure(void *data, struct weston_debug_stream_v1 *obj, - const char *msg) -{ - struct debug_stream *stream = data; - - assert(stream->obj == obj); - - fprintf(stderr, "Debug stream '%s' aborted: %s\n", stream->name, msg); - - stream_destroy(stream); -} - -static const struct weston_debug_stream_v1_listener stream_listener = { - handle_stream_complete, - handle_stream_failure -}; - -static void -start_streams(struct debug_app *app) -{ - struct debug_stream *stream; - - wl_list_for_each(stream, &app->stream_list, link) { - if (!stream->should_bind) - continue; - - stream->obj = weston_debug_v1_subscribe(app->debug_iface, - stream->name, - app->out_fd); - weston_debug_stream_v1_add_listener(stream->obj, - &stream_listener, stream); - } -} - -static void -list_streams(struct debug_app *app) -{ - struct debug_stream *stream; - - fprintf(stderr, "Available debug streams:\n"); - - wl_list_for_each(stream, &app->stream_list, link) { - if (stream->should_bind && stream->desc) { - fprintf(stderr, " %s [will bind]\n", stream->name); - fprintf(stderr, " %s\n", stream->desc); - } else if (stream->should_bind) { - fprintf(stderr, " %s [wanted but not found]\n", - stream->name); - } else { - fprintf(stderr, " %s [will not bind]\n", - stream->name); - fprintf(stderr, " %s\n", stream->desc); - } - } -} - -static int -setup_out_fd(const char *output, const char *outfd) -{ - int fd = -1; - int flags; - - assert(!(output && outfd)); - - if (output) { - if (strcmp(output, "-") == 0) { - fd = STDOUT_FILENO; - } else { - fd = open(output, - O_WRONLY | O_APPEND | O_CREAT, 0644); - if (fd < 0) { - fprintf(stderr, - "Error: opening file '%s' failed: %s\n", - output, strerror(errno)); - } - return fd; - } - } else if (outfd) { - fd = atoi(outfd); - } else { - fd = STDOUT_FILENO; - } - - flags = fcntl(fd, F_GETFL); - if (flags == -1) { - fprintf(stderr, - "Error: cannot use file descriptor %d: %s\n", fd, - strerror(errno)); - return -1; - } - - if ((flags & O_ACCMODE) != O_WRONLY && - (flags & O_ACCMODE) != O_RDWR) { - fprintf(stderr, - "Error: file descriptor %d is not writable.\n", fd); - return -1; - } - - return fd; -} - -static void -print_help(void) -{ - fprintf(stderr, - "Usage: weston-debug [options] [names]\n" - "Where options may be:\n" - " -h, --help\n" - " This help text, and exit with success.\n" - " -l, --list\n" - " Print a list of available debug streams to stderr.\n" - " -a, --all-streams\n" - " Bind to all available streams.\n" - " -o FILE, --output FILE\n" - " Direct output to file named FILE. Use - for stdout.\n" - " Stdout is the default. Mutually exclusive with -f.\n" - " -f FD, --outfd FD\n" - " Direct output to the file descriptor FD.\n" - " Stdout (1) is the default. Mutually exclusive with -o.\n" - "Names are whatever debug stream names the compositor supports.\n" - ); -} - -static int -parse_cmdline(struct debug_app *app, int argc, char **argv) -{ - static const struct option opts[] = { - { "help", no_argument, NULL, 'h' }, - { "list", no_argument, NULL, 'l' }, - { "all-streams", no_argument, NULL, 'a' }, - { "output", required_argument, NULL, 'o' }, - { "outfd", required_argument, NULL, 'f' }, - { 0 } - }; - static const char optstr[] = "hlao:f:"; - int c; - bool failed = false; - - while (1) { - c = getopt_long(argc, argv, optstr, opts, NULL); - if (c == -1) - break; - - switch (c) { - case 'h': - app->opt.help = true; - break; - case 'l': - app->opt.list = true; - break; - case 'a': - app->opt.bind_all = true; - break; - case 'o': - free(app->opt.output); - app->opt.output = strdup(optarg); - break; - case 'f': - free(app->opt.outfd); - app->opt.outfd = strdup(optarg); - break; - case '?': - failed = true; - break; - default: - fprintf(stderr, "huh? getopt => %c (%d)\n", c, c); - failed = true; - } - } - - if (failed) - return -1; - - while (optind < argc) { - struct debug_stream *stream = - stream_alloc(app, argv[optind++], NULL); - stream->should_bind = true; - } - - return 0; -} - -int -main(int argc, char **argv) -{ - struct debug_app app = {}; - int ret = 0; - - wl_list_init(&app.stream_list); - app.out_fd = -1; - - if (parse_cmdline(&app, argc, argv) < 0) { - ret = 1; - goto out_parse; - } - - if (app.opt.help) { - print_help(); - goto out_parse; - } - - if (!app.opt.list && !app.opt.bind_all && - wl_list_empty(&app.stream_list)) { - fprintf(stderr, "Error: no options given.\n\n"); - ret = 1; - print_help(); - goto out_parse; - } - - if (app.opt.bind_all && !wl_list_empty(&app.stream_list)) { - fprintf(stderr, "Error: --all and specific stream names cannot be used simultaneously.\n"); - ret = 1; - goto out_parse; - } - - if (app.opt.output && app.opt.outfd) { - fprintf(stderr, "Error: options --output and --outfd cannot be used simultaneously.\n"); - ret = 1; - goto out_parse; - } - - app.out_fd = setup_out_fd(app.opt.output, app.opt.outfd); - if (app.out_fd < 0) { - ret = 1; - goto out_parse; - } - - app.dpy = wl_display_connect(NULL); - if (!app.dpy) { - fprintf(stderr, "Error: Could not connect to Wayland display: %s\n", - strerror(errno)); - ret = 1; - goto out_parse; - } - - app.registry = wl_display_get_registry(app.dpy); - wl_registry_add_listener(app.registry, ®istry_listener, &app); - wl_display_roundtrip(app.dpy); - - if (!app.debug_iface) { - ret = 1; - fprintf(stderr, - "The Wayland server does not support %s interface.\n", - weston_debug_v1_interface.name); - goto out_conn; - } - - wl_display_roundtrip(app.dpy); /* for weston_debug_v1::advertise */ - - if (app.opt.list) - list_streams(&app); - - start_streams(&app); - - weston_debug_v1_destroy(app.debug_iface); - - while (1) { - struct debug_stream *stream; - bool empty = true; - - wl_list_for_each(stream, &app.stream_list, link) { - if (stream->obj) { - empty = false; - break; - } - } - - if (empty) - break; - - if (wl_display_dispatch(app.dpy) < 0) { - ret = 1; - break; - } - } - -out_conn: - destroy_streams(&app); - - /* Wait for server to close all files */ - wl_display_roundtrip(app.dpy); - - wl_registry_destroy(app.registry); - wl_display_disconnect(app.dpy); - -out_parse: - if (app.out_fd != -1) - close(app.out_fd); - - destroy_streams(&app); - free(app.opt.output); - free(app.opt.outfd); - - return ret; -} diff --git a/clients/weston-info.c b/clients/weston-info.c deleted file mode 100644 index 9772527..0000000 --- a/clients/weston-info.c +++ /dev/null @@ -1,1890 +0,0 @@ -/* - * Copyright © 2012 Philipp Brüschweiler - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "shared/helpers.h" -#include "shared/os-compatibility.h" -#include "shared/xalloc.h" -#include -#include "presentation-time-client-protocol.h" -#include "linux-dmabuf-unstable-v1-client-protocol.h" -#include "tablet-unstable-v2-client-protocol.h" -#include "xdg-output-unstable-v1-client-protocol.h" - -typedef void (*print_info_t)(void *info); -typedef void (*destroy_info_t)(void *info); - -struct global_info { - struct wl_list link; - - uint32_t id; - uint32_t version; - char *interface; - - print_info_t print; - destroy_info_t destroy; -}; - -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_list global_link; - - struct wl_output *output; - - int32_t version; - - struct { - int32_t x, y; - int32_t scale; - 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 linux_dmabuf_modifier { - struct wl_list link; - - uint32_t format; - uint64_t modifier; -}; - -struct linux_dmabuf_info { - struct global_info global; - struct zwp_linux_dmabuf_v1 *dmabuf; - - struct wl_list modifiers; -}; - -struct seat_info { - struct global_info global; - struct wl_list global_link; - struct wl_seat *seat; - struct weston_info *info; - - struct wl_keyboard *keyboard; - uint32_t capabilities; - char *name; - - int32_t repeat_rate; - int32_t repeat_delay; -}; - -struct tablet_v2_path { - struct wl_list link; - char *path; -}; - -struct tablet_tool_info { - struct wl_list link; - struct zwp_tablet_tool_v2 *tool; - - uint64_t hardware_serial; - uint64_t hardware_id_wacom; - enum zwp_tablet_tool_v2_type type; - - bool has_tilt; - bool has_pressure; - bool has_distance; - bool has_rotation; - bool has_slider; - bool has_wheel; -}; - -struct tablet_pad_group_info { - struct wl_list link; - struct zwp_tablet_pad_group_v2 *group; - - uint32_t modes; - size_t button_count; - int *buttons; - size_t strips; - size_t rings; -}; - -struct tablet_pad_info { - struct wl_list link; - struct zwp_tablet_pad_v2 *pad; - - uint32_t buttons; - struct wl_list paths; - struct wl_list groups; -}; - -struct tablet_info { - struct wl_list link; - struct zwp_tablet_v2 *tablet; - - char *name; - uint32_t vid, pid; - struct wl_list paths; -}; - -struct tablet_seat_info { - struct wl_list link; - - struct zwp_tablet_seat_v2 *seat; - struct seat_info *seat_info; - - struct wl_list tablets; - struct wl_list tools; - struct wl_list pads; -}; - -struct tablet_v2_info { - struct global_info global; - struct zwp_tablet_manager_v2 *manager; - struct weston_info *info; - - struct wl_list seats; -}; - -struct xdg_output_v1_info { - struct wl_list link; - - struct zxdg_output_v1 *xdg_output; - struct output_info *output; - - struct { - int32_t x, y; - int32_t width, height; - } logical; - - char *name, *description; -}; - -struct xdg_output_manager_v1_info { - struct global_info global; - struct zxdg_output_manager_v1 *manager; - struct weston_info *info; - - struct wl_list outputs; -}; - -struct presentation_info { - struct global_info global; - struct wp_presentation *presentation; - - clockid_t clk_id; -}; - -struct weston_info { - struct wl_display *display; - struct wl_registry *registry; - - struct wl_list infos; - bool roundtrip_needed; - - /* required for tablet-unstable-v2 */ - struct wl_list seats; - struct tablet_v2_info *tablet_info; - - /* required for xdg-output-unstable-v1 */ - struct wl_list outputs; - struct xdg_output_manager_v1_info *xdg_output_manager_v1_info; -}; - -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 = xstrdup(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,", - output->geometry.x, output->geometry.y); - if (output->version >= 2) - printf(" scale: %d,", output->geometry.scale); - printf("\n"); - - 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_transform: %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: %.3f 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 char -bits2graph(uint32_t value, unsigned bitoffset) -{ - int c = (value >> bitoffset) & 0xff; - - if (isgraph(c) || isspace(c)) - return c; - - return '?'; -} - -static void -fourcc2str(uint32_t format, char *str, int len) -{ - int i; - - assert(len >= 5); - - for (i = 0; i < 4; i++) - str[i] = bits2graph(format, i * 8); - str[i] = '\0'; -} - -static void -print_shm_info(void *data) -{ - char str[5]; - struct shm_info *shm = data; - struct shm_format *format; - - print_global_info(data); - - printf("\tformats:"); - - wl_list_for_each(format, &shm->formats, link) - switch (format->format) { - case WL_SHM_FORMAT_ARGB8888: - printf(" ARGB8888"); - break; - case WL_SHM_FORMAT_XRGB8888: - printf(" XRGB8888"); - break; - case WL_SHM_FORMAT_RGB565: - printf(" RGB565"); - break; - default: - fourcc2str(format->format, str, sizeof(str)); - printf(" '%s'(0x%08x)", str, format->format); - break; - } - - printf("\n"); -} - -static void -print_linux_dmabuf_info(void *data) -{ - char str[5]; - struct linux_dmabuf_info *dmabuf = data; - struct linux_dmabuf_modifier *modifier; - - print_global_info(data); - - printf("\tformats:"); - - wl_list_for_each(modifier, &dmabuf->modifiers, link) { - fourcc2str(modifier->format, str, sizeof(str)); - printf("\n\t'%s'(0x%08x), modifier: 0x%016"PRIx64, str, modifier->format, modifier->modifier); - } - - 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"); - - if (seat->repeat_rate > 0) - printf("\tkeyboard repeat rate: %d\n", seat->repeat_rate); - if (seat->repeat_delay > 0) - printf("\tkeyboard repeat delay: %d\n", seat->repeat_delay); -} - -static void -keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, - uint32_t format, int fd, uint32_t size) -{ - /* Just so we don’t leak the keymap fd */ - close(fd); -} - -static void -keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, - uint32_t serial, struct wl_surface *surface, - struct wl_array *keys) -{ -} - -static void -keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, - uint32_t serial, struct wl_surface *surface) -{ -} - -static void -keyboard_handle_key(void *data, struct wl_keyboard *keyboard, - uint32_t serial, uint32_t time, uint32_t key, - uint32_t state) -{ -} - -static void -keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, - uint32_t serial, uint32_t mods_depressed, - uint32_t mods_latched, uint32_t mods_locked, - uint32_t group) -{ -} - -static void -keyboard_handle_repeat_info(void *data, struct wl_keyboard *keyboard, - int32_t rate, int32_t delay) -{ - struct seat_info *seat = data; - - seat->repeat_rate = rate; - seat->repeat_delay = delay; -} - -static const struct wl_keyboard_listener keyboard_listener = { - keyboard_handle_keymap, - keyboard_handle_enter, - keyboard_handle_leave, - keyboard_handle_key, - keyboard_handle_modifiers, - keyboard_handle_repeat_info, -}; - -static void -seat_handle_capabilities(void *data, struct wl_seat *wl_seat, - enum wl_seat_capability caps) -{ - struct seat_info *seat = data; - - seat->capabilities = caps; - - /* we want listen for repeat_info from wl_keyboard, but only - * do so if the seat info is >= 4 and if we actually have a - * keyboard */ - if (seat->global.version < 4) - return; - - if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { - seat->keyboard = wl_seat_get_keyboard(seat->seat); - wl_keyboard_add_listener(seat->keyboard, &keyboard_listener, - seat); - - seat->info->roundtrip_needed = true; - } -} - -static void -seat_handle_name(void *data, struct wl_seat *wl_seat, - const char *name) -{ - struct seat_info *seat = data; - seat->name = xstrdup(name); -} - -static const struct wl_seat_listener seat_listener = { - seat_handle_capabilities, - seat_handle_name, -}; - -static void -destroy_seat_info(void *data) -{ - struct seat_info *seat = data; - - wl_seat_destroy(seat->seat); - - if (seat->name != NULL) - free(seat->name); - - if (seat->keyboard) - wl_keyboard_destroy(seat->keyboard); - - wl_list_remove(&seat->global_link); -} - -static const char * -tablet_tool_type_to_str(enum zwp_tablet_tool_v2_type type) -{ - switch (type) { - case ZWP_TABLET_TOOL_V2_TYPE_PEN: - return "pen"; - case ZWP_TABLET_TOOL_V2_TYPE_ERASER: - return "eraser"; - case ZWP_TABLET_TOOL_V2_TYPE_BRUSH: - return "brush"; - case ZWP_TABLET_TOOL_V2_TYPE_PENCIL: - return "pencil"; - case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH: - return "airbrush"; - case ZWP_TABLET_TOOL_V2_TYPE_FINGER: - return "finger"; - case ZWP_TABLET_TOOL_V2_TYPE_MOUSE: - return "mouse"; - case ZWP_TABLET_TOOL_V2_TYPE_LENS: - return "lens"; - } - - return "Unknown type"; -} - -static void -print_tablet_tool_info(const struct tablet_tool_info *info) -{ - printf("\t\ttablet_tool: %s\n", tablet_tool_type_to_str(info->type)); - if (info->hardware_serial) { - printf("\t\t\thardware serial: %" PRIx64 "\n", info->hardware_serial); - } - if (info->hardware_id_wacom) { - printf("\t\t\thardware wacom: %" PRIx64 "\n", info->hardware_id_wacom); - } - - printf("\t\t\tcapabilities:"); - - if (info->has_tilt) { - printf(" tilt"); - } - if (info->has_pressure) { - printf(" pressure"); - } - if (info->has_distance) { - printf(" distance"); - } - if (info->has_rotation) { - printf(" rotation"); - } - if (info->has_slider) { - printf(" slider"); - } - if (info->has_wheel) { - printf(" wheel"); - } - printf("\n"); -} - -static void -destroy_tablet_tool_info(struct tablet_tool_info *info) -{ - wl_list_remove(&info->link); - zwp_tablet_tool_v2_destroy(info->tool); - free(info); -} - -static void -print_tablet_pad_group_info(const struct tablet_pad_group_info *info) -{ - size_t i; - printf("\t\t\tgroup:\n"); - printf("\t\t\t\tmodes: %u\n", info->modes); - printf("\t\t\t\tstrips: %zu\n", info->strips); - printf("\t\t\t\trings: %zu\n", info->rings); - printf("\t\t\t\tbuttons:"); - - for (i = 0; i < info->button_count; ++i) { - printf(" %d", info->buttons[i]); - } - - printf("\n"); -} - -static void -destroy_tablet_pad_group_info(struct tablet_pad_group_info *info) -{ - wl_list_remove(&info->link); - zwp_tablet_pad_group_v2_destroy(info->group); - - if (info->buttons) { - free(info->buttons); - } - free(info); -} - -static void -print_tablet_pad_info(const struct tablet_pad_info *info) -{ - const struct tablet_v2_path *path; - const struct tablet_pad_group_info *group; - - printf("\t\tpad:\n"); - printf("\t\t\tbuttons: %u\n", info->buttons); - - wl_list_for_each(path, &info->paths, link) { - printf("\t\t\tpath: %s\n", path->path); - } - - wl_list_for_each(group, &info->groups, link) { - print_tablet_pad_group_info(group); - } -} - -static void -destroy_tablet_pad_info(struct tablet_pad_info *info) -{ - struct tablet_v2_path *path; - struct tablet_v2_path *tmp_path; - struct tablet_pad_group_info *group; - struct tablet_pad_group_info *tmp_group; - - wl_list_remove(&info->link); - zwp_tablet_pad_v2_destroy(info->pad); - - wl_list_for_each_safe(path, tmp_path, &info->paths, link) { - wl_list_remove(&path->link); - free(path->path); - free(path); - } - - wl_list_for_each_safe(group, tmp_group, &info->groups, link) { - destroy_tablet_pad_group_info(group); - } - - free(info); -} - -static void -print_tablet_info(const struct tablet_info *info) -{ - const struct tablet_v2_path *path; - - printf("\t\ttablet: %s\n", info->name); - printf("\t\t\tvendor: %u\n", info->vid); - printf("\t\t\tproduct: %u\n", info->pid); - - wl_list_for_each(path, &info->paths, link) { - printf("\t\t\tpath: %s\n", path->path); - } -} - -static void -destroy_tablet_info(struct tablet_info *info) -{ - struct tablet_v2_path *path; - struct tablet_v2_path *tmp; - - wl_list_remove(&info->link); - zwp_tablet_v2_destroy(info->tablet); - - if (info->name) { - free(info->name); - } - - wl_list_for_each_safe(path, tmp, &info->paths, link) { - wl_list_remove(&path->link); - free(path->path); - free(path); - } - - free(info); -} - -static void -print_tablet_seat_info(const struct tablet_seat_info *info) -{ - const struct tablet_info *tablet; - const struct tablet_pad_info *pad; - const struct tablet_tool_info *tool; - - printf("\ttablet_seat: %s\n", info->seat_info->name); - - wl_list_for_each(tablet, &info->tablets, link) { - print_tablet_info(tablet); - } - - wl_list_for_each(pad, &info->pads, link) { - print_tablet_pad_info(pad); - } - - wl_list_for_each(tool, &info->tools, link) { - print_tablet_tool_info(tool); - } -} - -static void -destroy_tablet_seat_info(struct tablet_seat_info *info) -{ - struct tablet_info *tablet; - struct tablet_info *tmp_tablet; - struct tablet_pad_info *pad; - struct tablet_pad_info *tmp_pad; - struct tablet_tool_info *tool; - struct tablet_tool_info *tmp_tool; - - wl_list_remove(&info->link); - zwp_tablet_seat_v2_destroy(info->seat); - - wl_list_for_each_safe(tablet, tmp_tablet, &info->tablets, link) { - destroy_tablet_info(tablet); - } - - wl_list_for_each_safe(pad, tmp_pad, &info->pads, link) { - destroy_tablet_pad_info(pad); - } - - wl_list_for_each_safe(tool, tmp_tool, &info->tools, link) { - destroy_tablet_tool_info(tool); - } - - free(info); -} - -static void -print_tablet_v2_info(void *data) -{ - struct tablet_v2_info *info = data; - struct tablet_seat_info *seat; - print_global_info(data); - - wl_list_for_each(seat, &info->seats, link) { - /* Skip tablet_seats without a tablet, they are irrelevant */ - if (wl_list_empty(&seat->pads) && - wl_list_empty(&seat->tablets) && - wl_list_empty(&seat->tools)) { - continue; - } - - print_tablet_seat_info(seat); - } -} - -static void -destroy_tablet_v2_info(void *data) -{ - struct tablet_v2_info *info = data; - struct tablet_seat_info *seat; - struct tablet_seat_info *tmp; - - zwp_tablet_manager_v2_destroy(info->manager); - - wl_list_for_each_safe(seat, tmp, &info->seats, link) { - destroy_tablet_seat_info(seat); - } -} - -static void -handle_tablet_v2_tablet_tool_done(void *data, struct zwp_tablet_tool_v2 *tool) -{ - /* don't bother waiting for this; there's no good reason a - * compositor will wait more than one roundtrip before sending - * these initial events. */ -} - -static void -handle_tablet_v2_tablet_tool_removed(void *data, struct zwp_tablet_tool_v2 *tool) -{ - /* don't bother waiting for this; we never make any request either way. */ -} - -static void -handle_tablet_v2_tablet_tool_type(void *data, struct zwp_tablet_tool_v2 *tool, - uint32_t tool_type) -{ - struct tablet_tool_info *info = data; - info->type = tool_type; -} - -static void -handle_tablet_v2_tablet_tool_hardware_serial(void *data, - struct zwp_tablet_tool_v2 *tool, - uint32_t serial_hi, - uint32_t serial_lo) -{ - struct tablet_tool_info *info = data; - - info->hardware_serial = ((uint64_t) serial_hi) << 32 | - (uint64_t) serial_lo; -} - -static void -handle_tablet_v2_tablet_tool_hardware_id_wacom(void *data, - struct zwp_tablet_tool_v2 *tool, - uint32_t id_hi, uint32_t id_lo) -{ - struct tablet_tool_info *info = data; - - info->hardware_id_wacom = ((uint64_t) id_hi) << 32 | (uint64_t) id_lo; -} - -static void -handle_tablet_v2_tablet_tool_capability(void *data, - struct zwp_tablet_tool_v2 *tool, - uint32_t capability) -{ - struct tablet_tool_info *info = data; - enum zwp_tablet_tool_v2_capability cap = capability; - - switch(cap) { - case ZWP_TABLET_TOOL_V2_CAPABILITY_TILT: - info->has_tilt = true; - break; - case ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE: - info->has_pressure = true; - break; - case ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE: - info->has_distance = true; - break; - case ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION: - info->has_rotation = true; - break; - case ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER: - info->has_slider = true; - break; - case ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL: - info->has_wheel = true; - break; - } -} - -static void -handle_tablet_v2_tablet_tool_proximity_in(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - uint32_t serial, struct zwp_tablet_v2 *tablet, - struct wl_surface *surface) -{ - -} - -static void -handle_tablet_v2_tablet_tool_proximity_out(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) -{ - -} - -static void -handle_tablet_v2_tablet_tool_down(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - uint32_t serial) -{ - -} - -static void -handle_tablet_v2_tablet_tool_up(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) -{ - -} - - -static void -handle_tablet_v2_tablet_tool_motion(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - wl_fixed_t x, - wl_fixed_t y) -{ - -} - -static void -handle_tablet_v2_tablet_tool_pressure(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - uint32_t pressure) -{ - -} - -static void -handle_tablet_v2_tablet_tool_distance(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - uint32_t distance) -{ - -} - -static void -handle_tablet_v2_tablet_tool_tilt(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - wl_fixed_t tilt_x, - wl_fixed_t tilt_y) -{ - -} - -static void -handle_tablet_v2_tablet_tool_rotation(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - wl_fixed_t degrees) -{ - -} - -static void -handle_tablet_v2_tablet_tool_slider(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - int32_t position) -{ - -} - -static void -handle_tablet_v2_tablet_tool_wheel(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - wl_fixed_t degrees, - int32_t clicks) -{ - -} - -static void -handle_tablet_v2_tablet_tool_button(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - uint32_t serial, - uint32_t button, - uint32_t state) -{ - -} - -static void -handle_tablet_v2_tablet_tool_frame(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - uint32_t time) -{ - -} - -static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = { - .removed = handle_tablet_v2_tablet_tool_removed, - .done = handle_tablet_v2_tablet_tool_done, - .type = handle_tablet_v2_tablet_tool_type, - .hardware_serial = handle_tablet_v2_tablet_tool_hardware_serial, - .hardware_id_wacom = handle_tablet_v2_tablet_tool_hardware_id_wacom, - .capability = handle_tablet_v2_tablet_tool_capability, - - .proximity_in = handle_tablet_v2_tablet_tool_proximity_in, - .proximity_out = handle_tablet_v2_tablet_tool_proximity_out, - .down = handle_tablet_v2_tablet_tool_down, - .up = handle_tablet_v2_tablet_tool_up, - - .motion = handle_tablet_v2_tablet_tool_motion, - .pressure = handle_tablet_v2_tablet_tool_pressure, - .distance = handle_tablet_v2_tablet_tool_distance, - .tilt = handle_tablet_v2_tablet_tool_tilt, - .rotation = handle_tablet_v2_tablet_tool_rotation, - .slider = handle_tablet_v2_tablet_tool_slider, - .wheel = handle_tablet_v2_tablet_tool_wheel, - .button = handle_tablet_v2_tablet_tool_button, - .frame = handle_tablet_v2_tablet_tool_frame, -}; - -static void add_tablet_v2_tablet_tool_info(void *data, - struct zwp_tablet_seat_v2 *tablet_seat_v2, - struct zwp_tablet_tool_v2 *tool) -{ - struct tablet_seat_info *tablet_seat = data; - struct tablet_tool_info *tool_info = xzalloc(sizeof *tool_info); - - tool_info->tool = tool; - wl_list_insert(&tablet_seat->tools, &tool_info->link); - - zwp_tablet_tool_v2_add_listener(tool, &tablet_tool_listener, tool_info); -} - -static void -handle_tablet_v2_tablet_pad_group_mode_switch(void *data, - struct zwp_tablet_pad_group_v2 *zwp_tablet_pad_group_v2, - uint32_t time, uint32_t serial, uint32_t mode) -{ - /* This shouldn't ever happen */ -} - -static void -handle_tablet_v2_tablet_pad_group_done(void *data, - struct zwp_tablet_pad_group_v2 *group) -{ - /* don't bother waiting for this; there's no good reason a - * compositor will wait more than one roundtrip before sending - * these initial events. */ -} - -static void -handle_tablet_v2_tablet_pad_group_modes(void *data, - struct zwp_tablet_pad_group_v2 *group, - uint32_t modes) -{ - struct tablet_pad_group_info *info = data; - info->modes = modes; -} - -static void -handle_tablet_v2_tablet_pad_group_buttons(void *data, - struct zwp_tablet_pad_group_v2 *group, - struct wl_array *buttons) -{ - struct tablet_pad_group_info *info = data; - - info->button_count = buttons->size / sizeof(int); - info->buttons = xzalloc(buttons->size); - memcpy(info->buttons, buttons->data, buttons->size); -} - -static void -handle_tablet_v2_tablet_pad_group_ring(void *data, - struct zwp_tablet_pad_group_v2 *group, - struct zwp_tablet_pad_ring_v2 *ring) -{ - struct tablet_pad_group_info *info = data; - ++info->rings; - - zwp_tablet_pad_ring_v2_destroy(ring); -} - -static void -handle_tablet_v2_tablet_pad_group_strip(void *data, - struct zwp_tablet_pad_group_v2 *group, - struct zwp_tablet_pad_strip_v2 *strip) -{ - struct tablet_pad_group_info *info = data; - ++info->strips; - - zwp_tablet_pad_strip_v2_destroy(strip); -} - -static const struct zwp_tablet_pad_group_v2_listener tablet_pad_group_listener = { - .buttons = handle_tablet_v2_tablet_pad_group_buttons, - .modes = handle_tablet_v2_tablet_pad_group_modes, - .ring = handle_tablet_v2_tablet_pad_group_ring, - .strip = handle_tablet_v2_tablet_pad_group_strip, - .done = handle_tablet_v2_tablet_pad_group_done, - .mode_switch = handle_tablet_v2_tablet_pad_group_mode_switch, -}; - -static void -handle_tablet_v2_tablet_pad_group(void *data, - struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2, - struct zwp_tablet_pad_group_v2 *pad_group) -{ - struct tablet_pad_info *pad_info = data; - struct tablet_pad_group_info *group = xzalloc(sizeof *group); - - wl_list_insert(&pad_info->groups, &group->link); - group->group = pad_group; - zwp_tablet_pad_group_v2_add_listener(pad_group, - &tablet_pad_group_listener, group); -} - -static void -handle_tablet_v2_tablet_pad_path(void *data, struct zwp_tablet_pad_v2 *pad, - const char *path) -{ - struct tablet_pad_info *pad_info = data; - struct tablet_v2_path *path_elem = xzalloc(sizeof *path_elem); - path_elem->path = xstrdup(path); - - wl_list_insert(&pad_info->paths, &path_elem->link); -} - -static void -handle_tablet_v2_tablet_pad_buttons(void *data, struct zwp_tablet_pad_v2 *pad, - uint32_t buttons) -{ - struct tablet_pad_info *pad_info = data; - - pad_info->buttons = buttons; -} - -static void -handle_tablet_v2_tablet_pad_done(void *data, struct zwp_tablet_pad_v2 *pad) -{ - /* don't bother waiting for this; there's no good reason a - * compositor will wait more than one roundtrip before sending - * these initial events. */ -} - -static void -handle_tablet_v2_tablet_pad_removed(void *data, struct zwp_tablet_pad_v2 *pad) -{ - /* don't bother waiting for this; We never make any request that's not - * allowed to be issued either way. */ -} - -static void -handle_tablet_v2_tablet_pad_button(void *data, struct zwp_tablet_pad_v2 *pad, - uint32_t time, uint32_t button, uint32_t state) -{ - /* we don't have a surface, so this can't ever happen */ -} - -static void -handle_tablet_v2_tablet_pad_enter(void *data, struct zwp_tablet_pad_v2 *pad, - uint32_t serial, - struct zwp_tablet_v2 *tablet, - struct wl_surface *surface) -{ - /* we don't have a surface, so this can't ever happen */ -} - -static void -handle_tablet_v2_tablet_pad_leave(void *data, struct zwp_tablet_pad_v2 *pad, - uint32_t serial, struct wl_surface *surface) -{ - /* we don't have a surface, so this can't ever happen */ -} - -static const struct zwp_tablet_pad_v2_listener tablet_pad_listener = { - .group = handle_tablet_v2_tablet_pad_group, - .path = handle_tablet_v2_tablet_pad_path, - .buttons = handle_tablet_v2_tablet_pad_buttons, - .done = handle_tablet_v2_tablet_pad_done, - .removed = handle_tablet_v2_tablet_pad_removed, - .button = handle_tablet_v2_tablet_pad_button, - .enter = handle_tablet_v2_tablet_pad_enter, - .leave = handle_tablet_v2_tablet_pad_leave, -}; - -static void add_tablet_v2_tablet_pad_info(void *data, - struct zwp_tablet_seat_v2 *tablet_seat_v2, - struct zwp_tablet_pad_v2 *pad) -{ - struct tablet_seat_info *tablet_seat = data; - struct tablet_pad_info *pad_info = xzalloc(sizeof *pad_info); - - wl_list_init(&pad_info->paths); - wl_list_init(&pad_info->groups); - pad_info->pad = pad; - wl_list_insert(&tablet_seat->pads, &pad_info->link); - - zwp_tablet_pad_v2_add_listener(pad, &tablet_pad_listener, pad_info); -} - -static void -handle_tablet_v2_tablet_name(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, - const char *name) -{ - struct tablet_info *tablet_info = data; - tablet_info->name = xstrdup(name); -} - -static void -handle_tablet_v2_tablet_path(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, - const char *path) -{ - struct tablet_info *tablet_info = data; - struct tablet_v2_path *path_elem = xzalloc(sizeof *path_elem); - path_elem->path = xstrdup(path); - - wl_list_insert(&tablet_info->paths, &path_elem->link); -} - -static void -handle_tablet_v2_tablet_id(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, - uint32_t vid, uint32_t pid) -{ - struct tablet_info *tablet_info = data; - - tablet_info->vid = vid; - tablet_info->pid = pid; -} - -static void -handle_tablet_v2_tablet_done(void *data, struct zwp_tablet_v2 *zwp_tablet_v2) -{ - /* don't bother waiting for this; there's no good reason a - * compositor will wait more than one roundtrip before sending - * these initial events. */ -} - -static void -handle_tablet_v2_tablet_removed(void *data, struct zwp_tablet_v2 *zwp_tablet_v2) -{ - /* don't bother waiting for this; We never make any request that's not - * allowed to be issued either way. */ -} - -static const struct zwp_tablet_v2_listener tablet_listener = { - .name = handle_tablet_v2_tablet_name, - .id = handle_tablet_v2_tablet_id, - .path = handle_tablet_v2_tablet_path, - .done = handle_tablet_v2_tablet_done, - .removed = handle_tablet_v2_tablet_removed -}; - -static void -add_tablet_v2_tablet_info(void *data, struct zwp_tablet_seat_v2 *tablet_seat_v2, - struct zwp_tablet_v2 *tablet) -{ - struct tablet_seat_info *tablet_seat = data; - struct tablet_info *tablet_info = xzalloc(sizeof *tablet_info); - - wl_list_init(&tablet_info->paths); - tablet_info->tablet = tablet; - wl_list_insert(&tablet_seat->tablets, &tablet_info->link); - - zwp_tablet_v2_add_listener(tablet, &tablet_listener, tablet_info); -} - -static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { - .tablet_added = add_tablet_v2_tablet_info, - .pad_added = add_tablet_v2_tablet_pad_info, - .tool_added = add_tablet_v2_tablet_tool_info, -}; - -static void -add_tablet_seat_info(struct tablet_v2_info *tablet_info, struct seat_info *seat) -{ - struct tablet_seat_info *tablet_seat = xzalloc(sizeof *tablet_seat); - - wl_list_insert(&tablet_info->seats, &tablet_seat->link); - tablet_seat->seat = zwp_tablet_manager_v2_get_tablet_seat( - tablet_info->manager, seat->seat); - zwp_tablet_seat_v2_add_listener(tablet_seat->seat, - &tablet_seat_listener, tablet_seat); - - wl_list_init(&tablet_seat->pads); - wl_list_init(&tablet_seat->tablets); - wl_list_init(&tablet_seat->tools); - tablet_seat->seat_info = seat; - - tablet_info->info->roundtrip_needed = true; -} - -static void -add_tablet_v2_info(struct weston_info *info, uint32_t id, uint32_t version) -{ - struct seat_info *seat; - struct tablet_v2_info *tablet = xzalloc(sizeof *tablet); - - wl_list_init(&tablet->seats); - tablet->info = info; - - init_global_info(info, &tablet->global, id, - zwp_tablet_manager_v2_interface.name, version); - tablet->global.print = print_tablet_v2_info; - tablet->global.destroy = destroy_tablet_v2_info; - - tablet->manager = wl_registry_bind(info->registry, - id, &zwp_tablet_manager_v2_interface, 1); - - wl_list_for_each(seat, &info->seats, global_link) { - add_tablet_seat_info(tablet, seat); - } - - info->tablet_info = tablet; -} - -static void -destroy_xdg_output_v1_info(struct xdg_output_v1_info *info) -{ - wl_list_remove(&info->link); - zxdg_output_v1_destroy(info->xdg_output); - free(info->name); - free(info->description); - free(info); -} - -static void -print_xdg_output_v1_info(const struct xdg_output_v1_info *info) -{ - printf("\txdg_output_v1\n"); - printf("\t\toutput: %d\n", info->output->global.id); - if (info->name) - printf("\t\tname: '%s'\n", info->name); - if (info->description) - printf("\t\tdescription: '%s'\n", info->description); - printf("\t\tlogical_x: %d, logical_y: %d\n", - info->logical.x, info->logical.y); - printf("\t\tlogical_width: %d, logical_height: %d\n", - info->logical.width, info->logical.height); -} - -static void -print_xdg_output_manager_v1_info(void *data) -{ - struct xdg_output_manager_v1_info *info = data; - struct xdg_output_v1_info *output; - - print_global_info(data); - - wl_list_for_each(output, &info->outputs, link) - print_xdg_output_v1_info(output); -} - -static void -destroy_xdg_output_manager_v1_info(void *data) -{ - struct xdg_output_manager_v1_info *info = data; - struct xdg_output_v1_info *output, *tmp; - - zxdg_output_manager_v1_destroy(info->manager); - - wl_list_for_each_safe(output, tmp, &info->outputs, link) - destroy_xdg_output_v1_info(output); -} - -static void -handle_xdg_output_v1_logical_position(void *data, struct zxdg_output_v1 *output, - int32_t x, int32_t y) -{ - struct xdg_output_v1_info *xdg_output = data; - xdg_output->logical.x = x; - xdg_output->logical.y = y; -} - -static void -handle_xdg_output_v1_logical_size(void *data, struct zxdg_output_v1 *output, - int32_t width, int32_t height) -{ - struct xdg_output_v1_info *xdg_output = data; - xdg_output->logical.width = width; - xdg_output->logical.height = height; -} - -static void -handle_xdg_output_v1_done(void *data, struct zxdg_output_v1 *output) -{ - /* Don't bother waiting for this; there's no good reason a - * compositor will wait more than one roundtrip before sending - * these initial events. */ -} - -static void -handle_xdg_output_v1_name(void *data, struct zxdg_output_v1 *output, - const char *name) -{ - struct xdg_output_v1_info *xdg_output = data; - xdg_output->name = strdup(name); -} - -static void -handle_xdg_output_v1_description(void *data, struct zxdg_output_v1 *output, - const char *description) -{ - struct xdg_output_v1_info *xdg_output = data; - xdg_output->description = strdup(description); -} - -static const struct zxdg_output_v1_listener xdg_output_v1_listener = { - .logical_position = handle_xdg_output_v1_logical_position, - .logical_size = handle_xdg_output_v1_logical_size, - .done = handle_xdg_output_v1_done, - .name = handle_xdg_output_v1_name, - .description = handle_xdg_output_v1_description, -}; - -static void -add_xdg_output_v1_info(struct xdg_output_manager_v1_info *manager_info, - struct output_info *output) -{ - struct xdg_output_v1_info *xdg_output = xzalloc(sizeof *xdg_output); - - wl_list_insert(&manager_info->outputs, &xdg_output->link); - xdg_output->xdg_output = zxdg_output_manager_v1_get_xdg_output( - manager_info->manager, output->output); - zxdg_output_v1_add_listener(xdg_output->xdg_output, - &xdg_output_v1_listener, xdg_output); - - xdg_output->output = output; - - manager_info->info->roundtrip_needed = true; -} - -static void -add_xdg_output_manager_v1_info(struct weston_info *info, uint32_t id, - uint32_t version) -{ - struct output_info *output; - struct xdg_output_manager_v1_info *manager = xzalloc(sizeof *manager); - - wl_list_init(&manager->outputs); - manager->info = info; - - init_global_info(info, &manager->global, id, - zxdg_output_manager_v1_interface.name, version); - manager->global.print = print_xdg_output_manager_v1_info; - manager->global.destroy = destroy_xdg_output_manager_v1_info; - - manager->manager = wl_registry_bind(info->registry, id, - &zxdg_output_manager_v1_interface, version > 2 ? 2 : version); - - wl_list_for_each(output, &info->outputs, global_link) - add_xdg_output_v1_info(manager, output); - - info->xdg_output_manager_v1_info = manager; -} - -static void -add_seat_info(struct weston_info *info, uint32_t id, uint32_t version) -{ - struct seat_info *seat = xzalloc(sizeof *seat); - - /* required to set roundtrip_needed to true in capabilities - * handler */ - seat->info = info; - - init_global_info(info, &seat->global, id, "wl_seat", version); - seat->global.print = print_seat_info; - seat->global.destroy = destroy_seat_info; - - seat->seat = wl_registry_bind(info->registry, - id, &wl_seat_interface, MIN(version, 4)); - wl_seat_add_listener(seat->seat, &seat_listener, seat); - - seat->repeat_rate = seat->repeat_delay = -1; - - info->roundtrip_needed = true; - wl_list_insert(&info->seats, &seat->global_link); - - if (info->tablet_info) { - add_tablet_seat_info(info->tablet_info, seat); - } -} - -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 = xzalloc(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 -destroy_shm_info(void *data) -{ - struct shm_info *shm = data; - struct shm_format *format, *tmp; - - wl_list_for_each_safe(format, tmp, &shm->formats, link) { - wl_list_remove(&format->link); - free(format); - } - - wl_shm_destroy(shm->shm); -} - -static void -add_shm_info(struct weston_info *info, uint32_t id, uint32_t version) -{ - struct shm_info *shm = xzalloc(sizeof *shm); - - init_global_info(info, &shm->global, id, "wl_shm", version); - shm->global.print = print_shm_info; - shm->global.destroy = destroy_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 -linux_dmabuf_handle_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format) -{ - /* This is a deprecated event, don’t use it. */ -} - -static void -linux_dmabuf_handle_modifier(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) -{ - struct linux_dmabuf_info *dmabuf = data; - struct linux_dmabuf_modifier *linux_dmabuf_modifier = xzalloc(sizeof *linux_dmabuf_modifier); - - wl_list_insert(&dmabuf->modifiers, &linux_dmabuf_modifier->link); - linux_dmabuf_modifier->format = format; - linux_dmabuf_modifier->modifier = ((uint64_t)modifier_hi) << 32 | modifier_lo; -} - -static const struct zwp_linux_dmabuf_v1_listener linux_dmabuf_listener = { - linux_dmabuf_handle_format, - linux_dmabuf_handle_modifier, -}; - -static void -destroy_linux_dmabuf_info(void *data) -{ - struct linux_dmabuf_info *dmabuf = data; - struct linux_dmabuf_modifier *modifier, *tmp; - - wl_list_for_each_safe(modifier, tmp, &dmabuf->modifiers, link) { - wl_list_remove(&modifier->link); - free(modifier); - } - - zwp_linux_dmabuf_v1_destroy(dmabuf->dmabuf); -} - -static void -add_linux_dmabuf_info(struct weston_info *info, uint32_t id, uint32_t version) -{ - struct linux_dmabuf_info *dmabuf = xzalloc(sizeof *dmabuf); - - init_global_info(info, &dmabuf->global, id, "zwp_linux_dmabuf_v1", version); - dmabuf->global.print = print_linux_dmabuf_info; - dmabuf->global.destroy = destroy_linux_dmabuf_info; - - wl_list_init(&dmabuf->modifiers); - - if (version >= 3) { - dmabuf->dmabuf = wl_registry_bind(info->registry, - id, &zwp_linux_dmabuf_v1_interface, 3); - zwp_linux_dmabuf_v1_add_listener(dmabuf->dmabuf, &linux_dmabuf_listener, dmabuf); - - 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 = xstrdup(make); - output->geometry.model = xstrdup(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 void -output_handle_done(void *data, struct wl_output *wl_output) -{ - /* don't bother waiting for this; there's no good reason a - * compositor will wait more than one roundtrip before sending - * these initial events. */ -} - -static void -output_handle_scale(void *data, struct wl_output *wl_output, - int32_t scale) -{ - struct output_info *output = data; - - output->geometry.scale = scale; -} - -static const struct wl_output_listener output_listener = { - output_handle_geometry, - output_handle_mode, - output_handle_done, - output_handle_scale, -}; - -static void -destroy_output_info(void *data) -{ - struct output_info *output = data; - struct output_mode *mode, *tmp; - - wl_output_destroy(output->output); - - if (output->geometry.make != NULL) - free(output->geometry.make); - if (output->geometry.model != NULL) - free(output->geometry.model); - - wl_list_for_each_safe(mode, tmp, &output->modes, link) { - wl_list_remove(&mode->link); - free(mode); - } -} - -static void -add_output_info(struct weston_info *info, uint32_t id, uint32_t version) -{ - struct output_info *output = xzalloc(sizeof *output); - - init_global_info(info, &output->global, id, "wl_output", version); - output->global.print = print_output_info; - output->global.destroy = destroy_output_info; - - output->version = MIN(version, 2); - output->geometry.scale = 1; - wl_list_init(&output->modes); - - output->output = wl_registry_bind(info->registry, id, - &wl_output_interface, output->version); - wl_output_add_listener(output->output, &output_listener, - output); - - info->roundtrip_needed = true; - wl_list_insert(&info->outputs, &output->global_link); - - if (info->xdg_output_manager_v1_info) - add_xdg_output_v1_info(info->xdg_output_manager_v1_info, - output); -} - -static void -destroy_presentation_info(void *info) -{ - struct presentation_info *prinfo = info; - - wp_presentation_destroy(prinfo->presentation); -} - -static const char * -clock_name(clockid_t clk_id) -{ - static const char *names[] = { - [CLOCK_REALTIME] = "CLOCK_REALTIME", - [CLOCK_MONOTONIC] = "CLOCK_MONOTONIC", - [CLOCK_MONOTONIC_RAW] = "CLOCK_MONOTONIC_RAW", - [CLOCK_REALTIME_COARSE] = "CLOCK_REALTIME_COARSE", - [CLOCK_MONOTONIC_COARSE] = "CLOCK_MONOTONIC_COARSE", -#ifdef CLOCK_BOOTTIME - [CLOCK_BOOTTIME] = "CLOCK_BOOTTIME", -#endif - }; - - if (clk_id < 0 || (unsigned)clk_id >= ARRAY_LENGTH(names)) - return "unknown"; - - return names[clk_id]; -} - -static void -print_presentation_info(void *info) -{ - struct presentation_info *prinfo = info; - - print_global_info(info); - - printf("\tpresentation clock id: %d (%s)\n", - prinfo->clk_id, clock_name(prinfo->clk_id)); -} - -static void -presentation_handle_clock_id(void *data, struct wp_presentation *presentation, - uint32_t clk_id) -{ - struct presentation_info *prinfo = data; - - prinfo->clk_id = clk_id; -} - -static const struct wp_presentation_listener presentation_listener = { - presentation_handle_clock_id -}; - -static void -add_presentation_info(struct weston_info *info, uint32_t id, uint32_t version) -{ - struct presentation_info *prinfo = xzalloc(sizeof *prinfo); - - init_global_info(info, &prinfo->global, id, - wp_presentation_interface.name, version); - prinfo->global.print = print_presentation_info; - prinfo->global.destroy = destroy_presentation_info; - - prinfo->clk_id = -1; - prinfo->presentation = wl_registry_bind(info->registry, id, - &wp_presentation_interface, 1); - wp_presentation_add_listener(prinfo->presentation, - &presentation_listener, prinfo); - - info->roundtrip_needed = true; -} - -static void -destroy_global_info(void *data) -{ -} - -static void -add_global_info(struct weston_info *info, uint32_t id, - const char *interface, uint32_t version) -{ - struct global_info *global = xzalloc(sizeof *global); - - init_global_info(info, global, id, interface, version); - global->print = print_global_info; - global->destroy = destroy_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, "zwp_linux_dmabuf_v1")) - add_linux_dmabuf_info(info, id, version); - else if (!strcmp(interface, "wl_output")) - add_output_info(info, id, version); - else if (!strcmp(interface, wp_presentation_interface.name)) - add_presentation_info(info, id, version); - else if (!strcmp(interface, zwp_tablet_manager_v2_interface.name)) - add_tablet_v2_info(info, id, version); - else if (!strcmp(interface, zxdg_output_manager_v1_interface.name)) - add_xdg_output_manager_v1_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); -} - -static void -destroy_info(void *data) -{ - struct global_info *global = data; - - global->destroy(data); - wl_list_remove(&global->link); - free(global->interface); - free(data); -} - -static void -destroy_infos(struct wl_list *infos) -{ - struct global_info *info, *tmp; - wl_list_for_each_safe(info, tmp, infos, link) - destroy_info(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: %s\n", - strerror(errno)); - return -1; - } - - fprintf(stderr, "\n"); - fprintf(stderr, "*** Please use wayland-info instead\n"); - fprintf(stderr, "*** weston-info is deprecated and will be removed in a future version\n"); - fprintf(stderr, "\n"); - - info.tablet_info = NULL; - info.xdg_output_manager_v1_info = NULL; - wl_list_init(&info.infos); - wl_list_init(&info.seats); - wl_list_init(&info.outputs); - - 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); - destroy_infos(&info.infos); - - wl_registry_destroy(info.registry); - wl_display_disconnect(info.display); - - return 0; -} diff --git a/clients/window.c b/clients/window.c deleted file mode 100644 index ca7e62d..0000000 --- a/clients/window.c +++ /dev/null @@ -1,6673 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * Copyright © 2012-2013 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#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 -#elif !defined(ENABLE_EGL) /* platform.h defines these if EGL is enabled */ -typedef void *EGLDisplay; -typedef void *EGLConfig; -typedef void *EGLContext; -#define EGL_NO_DISPLAY ((EGLDisplay)0) -#endif /* no HAVE_CAIRO_EGL */ - -#include -#ifdef HAVE_XKBCOMMON_COMPOSE -#include -#endif -#include - -#include -#include -#include "shared/cairo-util.h" -#include "shared/helpers.h" -#include "shared/xalloc.h" -#include -#include "xdg-shell-client-protocol.h" -#include "text-cursor-position-client-protocol.h" -#include "pointer-constraints-unstable-v1-client-protocol.h" -#include "relative-pointer-unstable-v1-client-protocol.h" -#include "shared/os-compatibility.h" -#include "shared/string-helpers.h" - -#include "window.h" -#include "viewporter-client-protocol.h" - -#define ZWP_RELATIVE_POINTER_MANAGER_V1_VERSION 1 -#define ZWP_POINTER_CONSTRAINTS_V1_VERSION 1 - -#define DEFAULT_XCURSOR_SIZE 32 - -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_shm *shm; - struct wl_data_device_manager *data_device_manager; - struct text_cursor_position *text_cursor_position; - struct xdg_wm_base *xdg_shell; - struct zwp_relative_pointer_manager_v1 *relative_pointer_manager; - struct zwp_pointer_constraints_v1 *pointer_constraints; - 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; - display_global_handler_t global_handler_remove; - - void *user_data; - - struct xkb_context *xkb_context; - - /* A hack to get text extents for tooltips */ - cairo_surface_t *dummy_surface; - void *dummy_surface_data; - - int data_device_manager_version; - struct wp_viewporter *viewporter; -}; - -struct window_output { - struct output *output; - struct wl_list link; -}; - -struct toysurface { - /* - * Prepare the surface for drawing. Ensure there is a surface - * of the right size available for rendering, and return 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 on 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 wp_viewport *viewport; -}; - -struct window { - struct display *display; - struct wl_list window_output_list; - char *title; - struct rectangle saved_allocation; - struct rectangle min_allocation; - struct rectangle pending_allocation; - struct rectangle last_geometry; - int x, y; - int redraw_inhibited; - int redraw_needed; - int redraw_task_scheduled; - struct task redraw_task; - int resize_needed; - int custom; - int focused; - - int resizing; - - int fullscreen; - int maximized; - - 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; - window_state_changed_handler_t state_changed_handler; - - window_locked_pointer_motion_handler_t locked_pointer_motion_handler; - - struct surface *main_surface; - struct xdg_surface *xdg_surface; - struct xdg_toplevel *xdg_toplevel; - struct xdg_popup *xdg_popup; - - struct window *parent; - struct window *last_parent; - - struct window_frame *frame; - - /* struct surface::link, contains also main_surface */ - struct wl_list subsurface_list; - - struct zwp_relative_pointer_v1 *relative_pointer; - struct zwp_locked_pointer_v1 *locked_pointer; - bool pointer_locked; - locked_pointer_locked_handler_t pointer_locked_handler; - locked_pointer_unlocked_handler_t pointer_unlocked_handler; - confined_pointer_confined_handler_t pointer_confined_handler; - confined_pointer_unconfined_handler_t pointer_unconfined_handler; - - struct zwp_confined_pointer_v1 *confined_pointer; - struct widget *confined_widget; - bool confined; - - 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; - widget_pointer_frame_handler_t pointer_frame_handler; - widget_axis_source_handler_t axis_source_handler; - widget_axis_stop_handler_t axis_stop_handler; - widget_axis_discrete_handler_t axis_discrete_handler; - void *user_data; - int opaque; - int tooltip_count; - int default_cursor; - /* If this is set to false then no cairo surface will be - * created before redrawing the surface. This is useful if the - * redraw handler is going to do completely custom rendering - * such as using EGL directly */ - int use_cairo; - int viewport_dest_width; - int viewport_dest_height; -}; - -struct touch_point { - int32_t id; - float x, y; - 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; - struct window *locked_window; - struct window *confined_window; - int current_cursor; - uint32_t cursor_anim_start; - struct wl_callback *cursor_frame_cb; - uint32_t cursor_timer_start; - uint32_t cursor_anim_current; - struct toytimer cursor_timer; - bool cursor_timer_running; - 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; - uint32_t touch_grab; - int32_t touch_grab_id; - float drag_x, drag_y; - struct window *drag_focus; - uint32_t drag_enter_serial; - - struct { - struct xkb_keymap *keymap; - struct xkb_state *state; -#ifdef HAVE_XKBCOMMON_COMPOSE - struct xkb_compose_table *compose_table; - struct xkb_compose_state *compose_state; -#endif - xkb_mod_mask_t control_mask; - xkb_mod_mask_t alt_mask; - xkb_mod_mask_t shift_mask; - } xkb; - - int32_t repeat_rate_sec; - int32_t repeat_rate_nsec; - int32_t repeat_delay_sec; - int32_t repeat_delay_nsec; - - struct toytimer repeat_timer; - uint32_t repeat_sym; - uint32_t repeat_key; - uint32_t repeat_time; - int seat_version; -}; - -struct output { - struct display *display; - struct wl_output *output; - uint32_t server_output_id; - struct rectangle allocation; - struct wl_list link; - int transform; - int scale; - char *make; - char *model; - - display_output_handler_t destroy_handler; - void *user_data; -}; - -struct window_frame { - struct widget *widget; - struct widget *child; - struct frame *frame; - - uint32_t last_time; - uint32_t did_double, double_click; - int32_t last_id, double_id; -}; - -struct menu { - void *user_data; - struct window *window; - struct widget *widget; - struct input *input; - struct frame *frame; - const char **entries; - uint32_t time; - int current; - int count; - int release_count; - menu_func_t func; -}; - -struct tooltip { - struct widget *parent; - struct widget *widget; - char *entry; - struct toytimer timer; - 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 -}; - -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, - EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) - 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); - weston_platform_destroy_egl_surface(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 = zalloc(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 = - weston_platform_create_egl_surface(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: %s\n", - size, strerror(errno)); - return NULL; - } - - *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (*data == MAP_FAILED) { - fprintf(stderr, "mmap failed: %s\n", strerror(errno)); - close(fd); - return 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; - int stride, length, offset; - void *map; - - data = malloc(sizeof *data); - if (data == NULL) - return NULL; - - stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, - 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_ARGB32, - rectangle->width, - rectangle->height, - stride); - - cairo_surface_set_user_data(surface, &shm_surface_data_key, - data, shm_surface_data_destroy); - - 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, preferably 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); - if (!leaf->cairo_surface) - return NULL; - - 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 = xzalloc(sizeof *surface); - 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" -}; - -static const char *move_draggings[] = { - "dnd-move" -}; - -static const char *copy_draggings[] = { - "dnd-copy" -}; - -static const char *forbidden_draggings[] = { - "dnd-none", - "dnd-no-drop" -}; - -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)}, - {move_draggings, ARRAY_LENGTH(move_draggings)}, - {copy_draggings, ARRAY_LENGTH(copy_draggings)}, - {forbidden_draggings, ARRAY_LENGTH(forbidden_draggings)}, -}; - -static void -create_cursors(struct display *display) -{ - const char *config_file; - struct weston_config *config; - struct weston_config_section *s; - int size = DEFAULT_XCURSOR_SIZE; - char *theme = NULL, *size_str; - unsigned int i, j; - struct wl_cursor *cursor; - - theme = getenv("XCURSOR_THEME"); - - size_str = getenv("XCURSOR_SIZE"); - if (size_str) { - safe_strtoint(size_str, &size); - if (size <= 0) - size = DEFAULT_XCURSOR_SIZE; - } - - config_file = weston_config_get_name_from_env(); - config = weston_config_parse(config_file); - s = weston_config_get_section(config, "shell", NULL, NULL); - weston_config_section_get_string(s, "cursor-theme", &theme, theme); - weston_config_section_get_int(s, "cursor-size", &size, size); - weston_config_destroy(config); - - display->cursor_theme = wl_cursor_theme_load(theme, size, display->shm); - if (!display->cursor_theme) { - fprintf(stderr, "could not load theme '%s'\n", theme); - return; - } - 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) -{ - struct widget *widget = surface->widget; - 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; - } - - if (surface->viewport) { - wp_viewport_set_destination(surface->viewport, - widget->viewport_dest_width, - widget->viewport_dest_height); - } - - 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->focused; -} - -static void -window_close(struct window *window) -{ - if (window->close_handler) - window->close_handler(window->user_data); - else - display_exit(window->display); -} - -struct display * -window_get_display(struct window *window) -{ - return window->display; -} - -static void -surface_create_surface(struct surface *surface, 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, 0, 0, - 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; - - if (window->resizing) - flags |= SURFACE_HINT_RESIZE; - - surface_create_surface(surface, 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 window_frame_destroy(struct window_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) { - struct touch_point *tp, *tmp; - - wl_list_for_each_safe(tp, tmp, - &input->touch_point_list, - link) { - wl_list_remove(&tp->link); - free(tp); - } - - input->touch_focus = NULL; - } - if (input->pointer_focus == window) - input->pointer_focus = NULL; - if (input->keyboard_focus == window) - input->keyboard_focus = NULL; - if (input->locked_window == window) - input->locked_window = NULL; - if (input->confined_window == window) - input->confined_window = 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) - window_frame_destroy(window->frame); - - if (window->xdg_toplevel) - xdg_toplevel_destroy(window->xdg_toplevel); - if (window->xdg_popup) - xdg_popup_destroy(window->xdg_popup); - if (window->xdg_surface) - xdg_surface_destroy(window->xdg_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; - int alloc_x, alloc_y, width, height; - double scale; - - wl_list_for_each(child, &widget->child_list, link) { - target = widget_find_widget(child, x, y); - if (target) - return target; - } - - alloc_x = widget->allocation.x; - alloc_y = widget->allocation.y; - width = widget->allocation.width; - height = widget->allocation.height; - - if (widget->viewport_dest_width != -1 && - widget->viewport_dest_height != -1) { - scale = widget->viewport_dest_width / (double) width; - alloc_x = alloc_x * scale; - width = widget->viewport_dest_width; - - scale = widget->viewport_dest_height / (double) height; - alloc_y = alloc_y * scale; - height = widget->viewport_dest_height; - } - - if (alloc_x <= x && x < alloc_x + width && - alloc_y <= y && y < alloc_y + 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; - widget->use_cairo = 1; - widget->viewport_dest_width = -1; - widget->viewport_dest_height = -1; - - 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) - widget_destroy_tooltip(widget); - - 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; - - assert(widget->use_cairo); - - if (!surface->cairo_surface) { - if (surface == window->main_surface) - window_create_main_surface(window); - else - surface_create_surface(surface, 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 + M_PI_2; - translate_x = 0; - translate_y = surface_width; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - angle = M_PI + M_PI_2; - translate_x = 0; - translate_y = 0; - 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_2; - translate_x = surface_height; - translate_y = 0; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - angle = M_PI_2; - translate_x = surface_height; - translate_y = surface_width; - 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; -} - -struct wl_subsurface * -widget_get_wl_subsurface(struct widget *widget) -{ - return widget->surface->subsurface; -} - -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; -} - -void -widget_set_pointer_frame_handler(struct widget *widget, - widget_pointer_frame_handler_t handler) -{ - widget->pointer_frame_handler = handler; -} - -void -widget_set_axis_handlers(struct widget *widget, - widget_axis_handler_t axis_handler, - widget_axis_source_handler_t axis_source_handler, - widget_axis_stop_handler_t axis_stop_handler, - widget_axis_discrete_handler_t axis_discrete_handler) -{ - widget->axis_handler = axis_handler; - widget->axis_source_handler = axis_source_handler; - widget->axis_stop_handler = axis_stop_handler; - widget->axis_discrete_handler = axis_discrete_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); -} - -void -widget_set_use_cairo(struct widget *widget, - int use_cairo) -{ - widget->use_cairo = use_cairo; -} - -int -widget_set_viewport_destination(struct widget *widget, int width, int height) -{ - struct window *window = widget->window; - struct display *display = window->display; - struct surface *surface = widget->surface; - if (!display->viewporter) - return -1; - - if (width == -1 && height == -1) { - if (surface->viewport) { - wp_viewport_destroy(surface->viewport); - surface->viewport = NULL; - } - - widget->viewport_dest_width = -1; - widget->viewport_dest_height = -1; - return 0; - } - - if (!surface->viewport) { - surface->viewport = wp_viewporter_get_viewport(display->viewporter, - surface->surface); - if (!surface->viewport) - return -1; - } - - widget->viewport_dest_width = width; - widget->viewport_dest_height = height; - - return 0; -} - -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; -} - -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_translate(cr, widget->allocation.x, widget->allocation.y); - 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_rgba(cr, 1.0, 1.0, 1.0, 0.85); - cairo_move_to(cr, 10, 17); - cairo_set_font_size(cr, 14); - cairo_show_text(cr, tooltip->entry); - cairo_destroy(cr); -} - -static cairo_text_extents_t -get_text_extents(struct display *display, struct tooltip *tooltip) -{ - cairo_t *cr; - cairo_text_extents_t extents; - - /* Use the dummy_surface because the tooltip's surface was not - * created yet, and parent does not have a valid surface - * outside repaint, either. - */ - cr = cairo_create(display->dummy_surface); - cairo_set_font_size(cr, 14); - 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; - const int offset_y = 27; - const int margin = 3; - cairo_text_extents_t extents; - - if (tooltip->widget) - return 0; - - tooltip->widget = window_add_subsurface(parent->window, tooltip, SUBSURFACE_DESYNCHRONIZED); - - extents = get_text_extents(display, tooltip); - widget_set_redraw_handler(tooltip->widget, tooltip_redraw_handler); - widget_set_allocation(tooltip->widget, - tooltip->x, tooltip->y + offset_y, - 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); - tooltip->widget = NULL; - } - - toytimer_fini(&tooltip->timer); - free(tooltip->entry); - free(tooltip); - parent->tooltip = NULL; -} - -static void -tooltip_func(struct toytimer *tt) -{ - struct tooltip *tooltip = container_of(tt, struct tooltip, timer); - - window_create_tooltip(tooltip); -} - -#define TOOLTIP_TIMEOUT 500 -static int -tooltip_timer_reset(struct tooltip *tooltip) -{ - toytimer_arm_once_usec(&tooltip->timer, TOOLTIP_TIMEOUT * 1000); - - 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->x = x; - tooltip->y = y; - tooltip->entry = strdup(entry); - toytimer_init(&tooltip->timer, CLOCK_MONOTONIC, - parent->window->display, tooltip_func); - tooltip_timer_reset(tooltip); - - return 0; -} - -static void -frame_resize_handler(struct widget *widget, - int32_t width, int32_t height, void *data) -{ - struct window_frame *frame = data; - struct widget *child = frame->child; - struct rectangle interior; - struct rectangle input; - struct rectangle opaque; - - if (widget->window->fullscreen) { - interior.x = 0; - interior.y = 0; - interior.width = width; - interior.height = height; - } else { - frame_resize(frame->frame, width, height); - frame_interior(frame->frame, &interior.x, &interior.y, - &interior.width, &interior.height); - } - - widget_set_allocation(child, interior.x, interior.y, - interior.width, interior.height); - - if (child->resize_handler) { - child->resize_handler(child, interior.width, interior.height, - child->user_data); - - if (widget->window->fullscreen) { - width = child->allocation.width; - height = child->allocation.height; - } else { - frame_resize_inside(frame->frame, - child->allocation.width, - child->allocation.height); - width = frame_width(frame->frame); - height = frame_height(frame->frame); - } - } - - widget_set_allocation(widget, 0, 0, width, height); - - widget->surface->input_region = - wl_compositor_create_region(widget->window->display->compositor); - if (!widget->window->fullscreen) { - frame_input_rect(frame->frame, &input.x, &input.y, - &input.width, &input.height); - wl_region_add(widget->surface->input_region, - input.x, input.y, input.width, input.height); - } else { - wl_region_add(widget->surface->input_region, 0, 0, width, height); - } - - widget_set_allocation(widget, 0, 0, width, height); - - if (child->opaque) { - if (!widget->window->fullscreen) { - frame_opaque_rect(frame->frame, &opaque.x, &opaque.y, - &opaque.width, &opaque.height); - - wl_region_add(widget->surface->opaque_region, - opaque.x, opaque.y, - opaque.width, opaque.height); - } else { - wl_region_add(widget->surface->opaque_region, - 0, 0, width, height); - } - } - - - widget_schedule_redraw(widget); -} - -static void -frame_redraw_handler(struct widget *widget, void *data) -{ - cairo_t *cr; - struct window_frame *frame = data; - struct window *window = widget->window; - - if (window->fullscreen) - return; - - cr = widget_cairo_create(widget); - - frame_repaint(frame->frame, cr); - - cairo_destroy(cr); -} - -static int -frame_get_pointer_image_for_location(struct window_frame *frame, - enum theme_location location) -{ - struct window *window = frame->widget->window; - - if (window->custom) - return CURSOR_LEFT_PTR; - - 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(void *data, struct input *input, int index) -{ - struct window *window = data; - - switch (index) { - case 0: /* close */ - window_close(window); - break; - case 1: /* 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", - "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) -{ - struct window_frame *frame = data; - enum theme_location location; - - location = frame_pointer_enter(frame->frame, input, x, y); - if (frame_status(frame->frame) & FRAME_STATUS_REPAINT) - widget_schedule_redraw(frame->widget); - - return frame_get_pointer_image_for_location(data, location); -} - -static int -frame_motion_handler(struct widget *widget, - struct input *input, uint32_t time, - float x, float y, void *data) -{ - struct window_frame *frame = data; - enum theme_location location; - - location = frame_pointer_motion(frame->frame, input, x, y); - if (frame_status(frame->frame) & FRAME_STATUS_REPAINT) - widget_schedule_redraw(frame->widget); - - return frame_get_pointer_image_for_location(data, location); -} - -static void -frame_leave_handler(struct widget *widget, - struct input *input, void *data) -{ - struct window_frame *frame = data; - - frame_pointer_leave(frame->frame, input); - if (frame_status(frame->frame) & FRAME_STATUS_REPAINT) - widget_schedule_redraw(frame->widget); -} - -static void -frame_handle_status(struct window_frame *frame, struct input *input, - uint32_t time, enum theme_location location) -{ - struct window *window = frame->widget->window; - uint32_t status; - - status = frame_status(frame->frame); - if (status & FRAME_STATUS_REPAINT) - widget_schedule_redraw(frame->widget); - - if (status & FRAME_STATUS_MINIMIZE) { - window_set_minimized(window); - frame_status_clear(frame->frame, FRAME_STATUS_MINIMIZE); - } - - if (status & FRAME_STATUS_MENU) { - window_show_frame_menu(window, input, time); - frame_status_clear(frame->frame, FRAME_STATUS_MENU); - } - - if (status & FRAME_STATUS_MAXIMIZE) { - window_set_maximized(window, !window->maximized); - frame_status_clear(frame->frame, FRAME_STATUS_MAXIMIZE); - } - - if (status & FRAME_STATUS_CLOSE) { - window_close(window); - return; - } - - if ((status & FRAME_STATUS_MOVE) && window->xdg_toplevel) { - input_ungrab(input); - xdg_toplevel_move(window->xdg_toplevel, - input_get_seat(input), - window->display->serial); - - frame_status_clear(frame->frame, FRAME_STATUS_MOVE); - } - - if ((status & FRAME_STATUS_RESIZE) && window->xdg_toplevel) { - input_ungrab(input); - - xdg_toplevel_resize(window->xdg_toplevel, - input_get_seat(input), - window->display->serial, - location); - - frame_status_clear(frame->frame, FRAME_STATUS_RESIZE); - } -} - -#define DOUBLE_CLICK_PERIOD 250 -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 window_frame *frame = data; - enum theme_location location; - - frame->double_click = 0; - if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - if (time - frame->last_time <= DOUBLE_CLICK_PERIOD) { - frame->double_click = 1; - frame->did_double = 1; - } else - frame->did_double = 0; - - frame->last_time = time; - } else if (frame->did_double == 1) { - frame->double_click = 1; - frame->did_double = 0; - } - - if (frame->double_click) - location = frame_double_click(frame->frame, input, - button, state); - else - location = frame_pointer_button(frame->frame, input, - button, state); - - frame_handle_status(frame, input, time, location); -} - -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_frame *frame = data; - - frame->double_click = 0; - if (time - frame->last_time <= DOUBLE_CLICK_PERIOD && - frame->last_id == id) { - frame->double_click = 1; - frame->did_double = 1; - frame->double_id = id; - } else - frame->did_double = 0; - - frame->last_time = time; - frame->last_id = id; - - if (frame->double_click) - frame_double_touch_down(frame->frame, input, id, x, y); - else - frame_touch_down(frame->frame, input, id, x, y); - - frame_handle_status(frame, input, time, THEME_LOCATION_CLIENT_AREA); -} - -static void -frame_touch_up_handler(struct widget *widget, - struct input *input, uint32_t serial, uint32_t time, - int32_t id, void *data) -{ - struct window_frame *frame = data; - - if (frame->double_id == id && frame->did_double) { - frame->did_double = 0; - frame->double_id = 0; - frame_double_touch_up(frame->frame, input, id); - } else - frame_touch_up(frame->frame, input, id); - frame_handle_status(frame, input, time, THEME_LOCATION_CLIENT_AREA); -} - -struct widget * -window_frame_create(struct window *window, void *data) -{ - struct window_frame *frame; - uint32_t buttons; - - if (window->custom) { - buttons = FRAME_BUTTON_NONE; - } else { - buttons = FRAME_BUTTON_ALL; - } - - frame = xzalloc(sizeof *frame); - frame->frame = frame_create(window->display->theme, 0, 0, - buttons, window->title, NULL); - if (!frame->frame) { - free(frame); - return NULL; - } - - 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_leave_handler(frame->widget, frame_leave_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); - widget_set_touch_up_handler(frame->widget, frame_touch_up_handler); - - window->frame = frame; - - return frame->child; -} - -void -window_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->maximized ? 0 : t->margin; - - if (!widget->window->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 -window_frame_destroy(struct window_frame *frame) -{ - frame_destroy(frame->frame); - - /* 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 -touch_grab(struct input *input, int32_t touch_id) -{ - input->touch_grab = 1; - input->touch_grab_id = touch_id; -} - -void -touch_ungrab(struct input *input) -{ - struct touch_point *tp, *tmp; - - input->touch_grab = 0; - - wl_list_for_each_safe(tp, tmp, - &input->touch_point_list, link) { - if (tp->id != input->touch_grab_id) - continue; - wl_list_remove(&tp->link); - free(tp); - - return; - } -} - -void -input_grab(struct input *input, struct widget *widget, uint32_t button) -{ - input->grab = widget; - input->grab_button = button; - - input_set_focus_widget(input, widget, input->sx, input->sy); -} - -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 -cursor_delay_timer_reset(struct input *input, uint32_t duration) -{ - if (!duration) - input->cursor_timer_running = false; - else - input->cursor_timer_running = true; - - toytimer_arm_once_usec(&input->cursor_timer, duration * 1000); -} - -static void cancel_pointer_image_update(struct input *input) -{ - if (input->cursor_timer_running) - cursor_delay_timer_reset(input, 0); -} - -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; - cancel_pointer_image_update(input); -} - -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; - } - - window = wl_surface_get_user_data(surface); - if (surface != window->main_surface->surface) { - DBG("Ignoring input event from subsurface %p\n", surface); - return; - } - - input->display->serial = serial; - input->pointer_enter_serial = serial; - input->pointer_focus = 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); - - if (!window) - return; - - input->sx = sx; - input->sy = sy; - - /* when making the window smaller - e.g. after an unmaximise we might - * still have a pending motion event that the compositor has picked - * based on the old surface dimensions. However, if we have an active - * grab, we expect to see input from outside the window anyway. - */ - if (!input->grab && (sx < window->main_surface->allocation.x || - sy < window->main_surface->allocation.y || - 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 void -pointer_handle_frame(void *data, struct wl_pointer *pointer) -{ - struct input *input = data; - struct widget *widget; - - widget = input->focus_widget; - if (input->grab) - widget = input->grab; - if (widget && widget->pointer_frame_handler) - (*widget->pointer_frame_handler)(widget, - input, - widget->user_data); -} - -static void -pointer_handle_axis_source(void *data, struct wl_pointer *pointer, - uint32_t source) -{ - struct input *input = data; - struct widget *widget; - - widget = input->focus_widget; - if (input->grab) - widget = input->grab; - if (widget && widget->axis_source_handler) - (*widget->axis_source_handler)(widget, - input, - source, - widget->user_data); -} - -static void -pointer_handle_axis_stop(void *data, struct wl_pointer *pointer, - uint32_t time, uint32_t axis) -{ - struct input *input = data; - struct widget *widget; - - widget = input->focus_widget; - if (input->grab) - widget = input->grab; - if (widget && widget->axis_stop_handler) - (*widget->axis_stop_handler)(widget, - input, time, - axis, - widget->user_data); -} - -static void -pointer_handle_axis_discrete(void *data, struct wl_pointer *pointer, - uint32_t axis, int32_t discrete) -{ - struct input *input = data; - struct widget *widget; - - widget = input->focus_widget; - if (input->grab) - widget = input->grab; - if (widget && widget->axis_discrete_handler) - (*widget->axis_discrete_handler)(widget, - input, - axis, - discrete, - 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, - pointer_handle_frame, - pointer_handle_axis_source, - pointer_handle_axis_stop, - pointer_handle_axis_discrete, -}; - -static void -input_remove_keyboard_focus(struct input *input) -{ - struct window *window = input->keyboard_focus; - - toytimer_disarm(&input->repeat_timer); - - if (!window) - return; - - if (window->keyboard_focus_handler) - (*window->keyboard_focus_handler)(window, NULL, - window->user_data); - - input->keyboard_focus = NULL; -} - -static void -keyboard_repeat_func(struct toytimer *tt) -{ - struct input *input = container_of(tt, struct input, repeat_timer); - struct window *window = input->keyboard_focus; - - 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; - struct xkb_keymap *keymap; - struct xkb_state *state; -#ifdef HAVE_XKBCOMMON_COMPOSE - struct xkb_compose_table *compose_table; - struct xkb_compose_state *compose_state; -#endif - char *locale; - 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_PRIVATE, fd, 0); - if (map_str == MAP_FAILED) { - close(fd); - return; - } - - /* Set up XKB keymap */ - keymap = xkb_keymap_new_from_string(input->display->xkb_context, - map_str, - XKB_KEYMAP_FORMAT_TEXT_V1, - 0); - munmap(map_str, size); - close(fd); - - if (!keymap) { - fprintf(stderr, "failed to compile keymap\n"); - return; - } - - /* Set up XKB state */ - state = xkb_state_new(keymap); - if (!state) { - fprintf(stderr, "failed to create XKB state\n"); - xkb_keymap_unref(keymap); - return; - } - - /* Look up the preferred locale, falling back to "C" as default */ - if (!(locale = getenv("LC_ALL"))) - if (!(locale = getenv("LC_CTYPE"))) - if (!(locale = getenv("LANG"))) - locale = "C"; - - /* Set up XKB compose table */ -#ifdef HAVE_XKBCOMMON_COMPOSE - compose_table = - xkb_compose_table_new_from_locale(input->display->xkb_context, - locale, - XKB_COMPOSE_COMPILE_NO_FLAGS); - if (compose_table) { - /* Set up XKB compose state */ - compose_state = xkb_compose_state_new(compose_table, - XKB_COMPOSE_STATE_NO_FLAGS); - if (compose_state) { - xkb_compose_state_unref(input->xkb.compose_state); - xkb_compose_table_unref(input->xkb.compose_table); - input->xkb.compose_state = compose_state; - input->xkb.compose_table = compose_table; - } else { - fprintf(stderr, "could not create XKB compose state. " - "Disabiling compose.\n"); - xkb_compose_table_unref(compose_table); - compose_table = NULL; - } - } else { - fprintf(stderr, "could not create XKB compose table for locale '%s'. " - "Disabiling compose\n", locale); - } -#endif - - xkb_keymap_unref(input->xkb.keymap); - xkb_state_unref(input->xkb.state); - input->xkb.keymap = keymap; - input->xkb.state = state; - - input->xkb.control_mask = - 1 << xkb_keymap_mod_get_index(input->xkb.keymap, "Control"); - input->xkb.alt_mask = - 1 << xkb_keymap_mod_get_index(input->xkb.keymap, "Mod1"); - input->xkb.shift_mask = - 1 << xkb_keymap_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; - - if (!surface) { - /* enter event for a window we've just destroyed */ - return; - } - - input->display->serial = serial; - input->keyboard_focus = wl_surface_get_user_data(surface); - - window = input->keyboard_focus; - 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); -} - -/* Translate symbols appropriately if a compose sequence is being entered */ -static xkb_keysym_t -process_key_press(xkb_keysym_t sym, struct input *input) -{ -#ifdef HAVE_XKBCOMMON_COMPOSE - if (!input->xkb.compose_state) - return sym; - if (sym == XKB_KEY_NoSymbol) - return sym; - if (xkb_compose_state_feed(input->xkb.compose_state, - sym) != XKB_COMPOSE_FEED_ACCEPTED) - return sym; - - switch (xkb_compose_state_get_status(input->xkb.compose_state)) { - case XKB_COMPOSE_COMPOSING: - return XKB_KEY_NoSymbol; - case XKB_COMPOSE_COMPOSED: - return xkb_compose_state_get_one_sym(input->xkb.compose_state); - case XKB_COMPOSE_CANCELLED: - return XKB_KEY_NoSymbol; - case XKB_COMPOSE_NOTHING: - return sym; - default: - return sym; - } -#else - return sym; -#endif -} - -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; - - /* We only use input grabs for pointer events for now, so just - * ignore key presses if a grab is active. We expand the key - * event delivery mechanism to route events to widgets to - * properly handle key grabs. In the meantime, this prevents - * key event delivery while a grab is active. */ - if (input->grab && input->grab_button == 0) - return; - - num_syms = xkb_state_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->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) { - window_close(window); - } else if (window->key_handler) { - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) - sym = process_key_press(sym, input); - - (*window->key_handler)(window, input, time, key, - sym, state, window->user_data); - } - - if (state == WL_KEYBOARD_KEY_STATE_RELEASED && - key == input->repeat_key) { - toytimer_disarm(&input->repeat_timer); - } else if (state == WL_KEYBOARD_KEY_STATE_PRESSED && - xkb_keymap_key_repeats(input->xkb.keymap, code)) { - input->repeat_sym = sym; - input->repeat_key = key; - input->repeat_time = time; - its.it_interval.tv_sec = input->repeat_rate_sec; - its.it_interval.tv_nsec = input->repeat_rate_nsec; - its.it_value.tv_sec = input->repeat_delay_sec; - its.it_value.tv_nsec = input->repeat_delay_nsec; - toytimer_arm(&input->repeat_timer, &its); - } -} - -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_MODS_DEPRESSED | - XKB_STATE_MODS_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 void -set_repeat_info(struct input *input, int32_t rate, int32_t delay) -{ - input->repeat_rate_sec = input->repeat_rate_nsec = 0; - input->repeat_delay_sec = input->repeat_delay_nsec = 0; - - /* a rate of zero disables any repeating, regardless of the delay's - * value */ - if (rate == 0) - return; - - if (rate == 1) - input->repeat_rate_sec = 1; - else - input->repeat_rate_nsec = 1000000000 / rate; - - input->repeat_delay_sec = delay / 1000; - delay -= (input->repeat_delay_sec * 1000); - input->repeat_delay_nsec = delay * 1000 * 1000; -} - -static void -keyboard_handle_repeat_info(void *data, struct wl_keyboard *keyboard, - int32_t rate, int32_t delay) -{ - struct input *input = data; - - set_repeat_info(input, rate, delay); -} - -static const struct wl_keyboard_listener keyboard_listener = { - keyboard_handle_keymap, - keyboard_handle_enter, - keyboard_handle_leave, - keyboard_handle_key, - keyboard_handle_modifiers, - keyboard_handle_repeat_info - -}; - -static void -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); - - if (!surface) { - /* down event for a window we've just destroyed */ - return; - } - - 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; - } - - if (surface != input->touch_focus->main_surface->surface) { - DBG("Ignoring input event from subsurface %p\n", surface); - input->touch_focus = NULL; - return; - } - - if (input->grab) - widget = input->grab; - else - 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; - tp->x = sx; - tp->y = sy; - 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; - - tp->x = sx; - tp->y = sy; - 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); - } -} - -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 void -touch_handle_shape(void *data, struct wl_touch *wl_touch, int32_t id, - wl_fixed_t major, wl_fixed_t minor) -{ -} - -static void -touch_handle_orientation(void *data, struct wl_touch *wl_touch, int32_t id, - wl_fixed_t orientation) -{ -} - -static const struct wl_touch_listener touch_listener = { - touch_handle_down, - touch_handle_up, - touch_handle_motion, - touch_handle_frame, - touch_handle_cancel, - touch_handle_shape, - touch_handle_orientation, -}; - -static void -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) { - if (input->seat_version >= WL_POINTER_RELEASE_SINCE_VERSION) - wl_pointer_release(input->pointer); - else - 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) { - if (input->seat_version >= WL_KEYBOARD_RELEASE_SINCE_VERSION) - wl_keyboard_release(input->keyboard); - else - 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) { - if (input->seat_version >= WL_TOUCH_RELEASE_SINCE_VERSION) - wl_touch_release(input->touch); - else - 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; -} - -int -input_get_touch(struct input *input, int32_t id, float *x, float *y) -{ - struct touch_point *tp; - - wl_list_for_each(tp, &input->touch_point_list, link) { - if (tp->id != id) - continue; - - *x = tp->x; - *y = tp->y; - return 0; - } - - return -1; -} - -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; - uint32_t dnd_action; - uint32_t source_actions; - 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 void -data_offer_source_actions(void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions) -{ - struct data_offer *offer = data; - - offer->source_actions = source_actions; -} - -static void -data_offer_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) -{ - struct data_offer *offer = data; - - offer->dnd_action = dnd_action; -} - -static const struct wl_data_offer_listener data_offer_listener = { - data_offer_offer, - data_offer_source_actions, - data_offer_action -}; - -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; - - if (!surface) { - /* enter event for a window we've just destroyed */ - return; - } - - window = wl_surface_get_user_data(surface); - input->drag_enter_serial = serial; - input->drag_focus = window, - input->drag_x = x; - input->drag_y = y; - - if (!input->touch_grab) - input->pointer_enter_serial = serial; - - 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; - - if (input->display->data_device_manager_version >= - WL_DATA_OFFER_SET_ACTIONS_SINCE_VERSION) { - wl_data_offer_set_actions(offer, - WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | - WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE, - WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE); - } - } else { - input->drag_offer = NULL; - types_data = NULL; - } - - 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->drag_focus; - float x = wl_fixed_to_double(x_w); - float y = wl_fixed_to_double(y_w); - void *types_data; - - input->drag_x = x; - input->drag_y = 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->drag_focus; - float x, y; - - x = input->drag_x; - y = input->drag_y; - - if (window->drop_handler) - window->drop_handler(window, input, - x, y, window->user_data); - - if (input->touch_grab) - touch_ungrab(input); -} - -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_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); - wl_pointer_set_cursor(input->pointer, input->pointer_enter_serial, - input->pointer_surface, - image->hotspot_x, image->hotspot_y); -} - -static const struct wl_callback_listener pointer_surface_listener; - -static bool -input_set_pointer_special(struct input *input) -{ - if (input->current_cursor == CURSOR_BLANK) { - wl_pointer_set_cursor(input->pointer, - input->pointer_enter_serial, - NULL, 0, 0); - return true; - } - - if (input->current_cursor == CURSOR_UNSET) - return true; - - return false; -} - -static void -schedule_pointer_image_update(struct input *input, - struct wl_cursor *cursor, - uint32_t duration, - bool force_frame) -{ - /* Some silly cursor sets have enormous pauses in them. In these - * cases it's better to use a timer even if it results in less - * accurate presentation, since it will save us having to set the - * same cursor image over and over again. - * - * This is really not the way we're supposed to time any kind of - * animation, but we're pretending it's OK here because we don't - * want animated cursors with long delays to needlessly hog CPU. - * - * We use force_frame to ensure we don't accumulate large timing - * errors by running off the wrong clock. - */ - if (!force_frame && duration > 100) { - struct timespec tp; - - clock_gettime(CLOCK_MONOTONIC, &tp); - input->cursor_timer_start = tp.tv_sec * 1000 - + tp.tv_nsec / 1000000; - cursor_delay_timer_reset(input, duration); - return; - } - - /* for short durations we'll just spin on frame callbacks for - * accurate timing - the way any kind of timing sensitive animation - * should really be done. */ - input->cursor_frame_cb = wl_surface_frame(input->pointer_surface); - wl_callback_add_listener(input->cursor_frame_cb, - &pointer_surface_listener, input); - -} - -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; - uint32_t duration; - bool force_frame = true; - - cancel_pointer_image_update(input); - - if (callback) { - assert(callback == input->cursor_frame_cb); - wl_callback_destroy(callback); - input->cursor_frame_cb = NULL; - force_frame = false; - } - - if (!input->pointer) - return; - - if (input_set_pointer_special(input)) - 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; - - input->cursor_anim_current = time; - - if (time == 0 || input->cursor_anim_start == 0) { - duration = 0; - i = 0; - } else - i = wl_cursor_frame_and_duration( - cursor, - time - input->cursor_anim_start, - &duration); - - if (cursor->image_count > 1) - schedule_pointer_image_update(input, cursor, duration, - force_frame); - - input_set_pointer_image_index(input, i); -} - -static void -cursor_timer_func(struct toytimer *tt) -{ - struct input *input = container_of(tt, struct input, cursor_timer); - struct timespec tp; - struct wl_cursor *cursor; - uint32_t time; - - if (!input->cursor_timer_running) - return; - - cursor = input->display->cursors[input->current_cursor]; - if (!cursor) - return; - - clock_gettime(CLOCK_MONOTONIC, &tp); - time = tp.tv_sec * 1000 + tp.tv_nsec / 1000000 - input->cursor_timer_start; - pointer_surface_frame_callback(input, NULL, input->cursor_anim_current + time); -} - -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 && !input_set_pointer_special(input)) { - /* 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) -{ - if (input->data_device) - 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->drag_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); - struct display *display = offer->input->display; - 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) { - if ((offer != offer->input->selection_offer) && - (display->data_device_manager_version >= - WL_DATA_OFFER_FINISH_SINCE_VERSION)) - wl_data_offer_finish(offer->offer); - 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->drag_x; - input->drag_offer->y = input->drag_y; -} - -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->xdg_toplevel) - return; - - xdg_toplevel_move(window->xdg_toplevel, input->seat, 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 -window_do_resize(struct window *window) -{ - struct surface *surface; - - 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); - } - - if (!window->fullscreen && !window->maximized) - window->saved_allocation = window->pending_allocation; - - if (window->confined && window->confined_widget) { - struct wl_compositor *compositor = window->display->compositor; - struct wl_region *region; - struct widget *widget = window->confined_widget; - - region = wl_compositor_create_region(compositor); - wl_region_add(region, - widget->allocation.x, - widget->allocation.y, - widget->allocation.width, - widget->allocation.height); - zwp_confined_pointer_v1_set_region(window->confined_pointer, - region); - wl_region_destroy(region); - } -} - -static void -idle_resize(struct window *window) -{ - 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); - - window_do_resize(window); -} - -static void -undo_resize(struct window *window) -{ - window->pending_allocation.width = - window->main_surface->server_allocation.width; - window->pending_allocation.height = - window->main_surface->server_allocation.height; - - DBG("back to %dx%d\n", - window->main_surface->server_allocation.width, - window->main_surface->server_allocation.height); - - window_do_resize(window); - - if (window->pending_allocation.width == 0 && - window->pending_allocation.height == 0) { - fprintf(stderr, "Error: Could not draw a surface, " - "most likely due to insufficient disk space in " - "%s (XDG_RUNTIME_DIR).\n", getenv("XDG_RUNTIME_DIR")); - exit(EXIT_FAILURE); - } -} - -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 int -window_get_shadow_margin(struct window *window) -{ - if (window->frame && !window->fullscreen) - return frame_get_shadow_margin(window->frame->frame); - else - return 0; -} - -void -window_inhibit_redraw(struct window *window) -{ - window->redraw_inhibited = 1; - wl_list_remove(&window->redraw_task.link); - wl_list_init(&window->redraw_task.link); - window->redraw_task_scheduled = 0; -} - -void -window_uninhibit_redraw(struct window *window) -{ - window->redraw_inhibited = 0; - if (window->redraw_needed || window->resize_needed) - window_schedule_redraw_task(window); -} - -static void -xdg_surface_handle_configure(void *data, - struct xdg_surface *xdg_surface, - uint32_t serial) -{ - struct window *window = data; - - xdg_surface_ack_configure(window->xdg_surface, serial); - - if (window->state_changed_handler) - window->state_changed_handler(window, window->user_data); - - window_uninhibit_redraw(window); -} - -static const struct xdg_surface_listener xdg_surface_listener = { - xdg_surface_handle_configure -}; - -static void -xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *xdg_toplevel, - int32_t width, int32_t height, - struct wl_array *states) -{ - struct window *window = data; - uint32_t *p; - - window->maximized = 0; - window->fullscreen = 0; - window->resizing = 0; - window->focused = 0; - - wl_array_for_each(p, states) { - uint32_t state = *p; - switch (state) { - case XDG_TOPLEVEL_STATE_MAXIMIZED: - window->maximized = 1; - break; - case XDG_TOPLEVEL_STATE_FULLSCREEN: - window->fullscreen = 1; - break; - case XDG_TOPLEVEL_STATE_RESIZING: - window->resizing = 1; - break; - case XDG_TOPLEVEL_STATE_ACTIVATED: - window->focused = 1; - break; - default: - /* Unknown state */ - break; - } - } - - if (window->frame) { - if (window->maximized) { - frame_set_flag(window->frame->frame, FRAME_FLAG_MAXIMIZED); - } else { - frame_unset_flag(window->frame->frame, FRAME_FLAG_MAXIMIZED); - } - - if (window->focused) { - frame_set_flag(window->frame->frame, FRAME_FLAG_ACTIVE); - } else { - frame_unset_flag(window->frame->frame, FRAME_FLAG_ACTIVE); - } - } - - if (width > 0 && height > 0) { - /* The width / height params are for window geometry, - * but window_schedule_resize takes allocation. Add - * on the shadow margin to get the difference. */ - int margin = window_get_shadow_margin(window); - - window_schedule_resize(window, - width + margin * 2, - height + margin * 2); - } else if (window->saved_allocation.width > 0 && - window->saved_allocation.height > 0) { - window_schedule_resize(window, - window->saved_allocation.width, - window->saved_allocation.height); - } -} - -static void -xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_surface) -{ - struct window *window = data; - window_close(window); -} - -static const struct xdg_toplevel_listener xdg_toplevel_listener = { - xdg_toplevel_handle_configure, - xdg_toplevel_handle_close, -}; - -static void -window_sync_parent(struct window *window) -{ - struct xdg_toplevel *parent_toplevel; - - if (!window->xdg_surface) - return; - - if (window->parent == window->last_parent) - return; - - if (window->parent) - parent_toplevel = window->parent->xdg_toplevel; - else - parent_toplevel = NULL; - - xdg_toplevel_set_parent(window->xdg_toplevel, parent_toplevel); - window->last_parent = window->parent; -} - -static void -window_get_geometry(struct window *window, struct rectangle *geometry) -{ - if (window->frame && !window->fullscreen) - frame_input_rect(window->frame->frame, - &geometry->x, - &geometry->y, - &geometry->width, - &geometry->height); - else - window_get_allocation(window, geometry); -} - -static void -window_sync_geometry(struct window *window) -{ - struct rectangle geometry; - - if (!window->xdg_surface) - return; - - window_get_geometry(window, &geometry); - if (geometry.x == window->last_geometry.x && - geometry.y == window->last_geometry.y && - geometry.width == window->last_geometry.width && - geometry.height == window->last_geometry.height) - return; - - xdg_surface_set_window_geometry(window->xdg_surface, - geometry.x, - geometry.y, - geometry.width, - geometry.height); - window->last_geometry = geometry; -} - -static void -window_flush(struct window *window) -{ - struct surface *surface; - - assert(!window->redraw_inhibited); - - if (!window->custom) { - if (window->xdg_surface) - window_sync_geometry(window); - if (window->xdg_toplevel) - window_sync_parent(window); - } - - wl_list_for_each(surface, &window->subsurface_list, link) { - if (surface == window->main_surface) - continue; - - surface_flush(surface); - } - - surface_flush(window->main_surface); -} - -static void -menu_destroy(struct menu *menu) -{ - widget_destroy(menu->widget); - window_destroy(menu->window); - frame_destroy(menu->frame); - free(menu); -} - -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 int -surface_redraw(struct surface *surface) -{ - DBG_OBJ(surface->surface, "begin\n"); - - if (!surface->window->redraw_needed && !surface->redraw_needed) - return 0; - - /* 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 0; - - DBG_OBJ(surface->frame_cb, "cancelled\n"); - wl_callback_destroy(surface->frame_cb); - } - - if (surface->widget->use_cairo && - !widget_get_cairo_surface(surface->widget)) { - DBG_OBJ(surface->surface, "cancelled due to buffer failure\n"); - return -1; - } - - 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"); - return 0; -} - -static void -idle_redraw(struct task *task, uint32_t events) -{ - struct window *window = container_of(task, struct window, redraw_task); - struct surface *surface; - int failed = 0; - int resized = 0; - - 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); - resized = 1; - } - - if (surface_redraw(window->main_surface) < 0) { - /* - * Only main_surface failure will cause us to undo the resize. - * If sub-surfaces fail, they will just be broken with old - * content. - */ - failed = 1; - } else { - wl_list_for_each(surface, &window->subsurface_list, link) { - if (surface == window->main_surface) - continue; - - surface_redraw(surface); - } - } - - window->redraw_needed = 0; - window_flush(window); - - wl_list_for_each(surface, &window->subsurface_list, link) - surface_set_synchronized_default(surface); - - if (resized && failed) { - /* Restore widget tree to correspond to what is on screen. */ - undo_resize(window); - } -} - -static void -window_schedule_redraw_task(struct window *window) -{ - if (window->redraw_inhibited) - 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->fullscreen; -} - -void -window_set_fullscreen(struct window *window, int fullscreen) -{ - if (!window->xdg_toplevel) - return; - - if (window->fullscreen == fullscreen) - return; - - if (fullscreen) - xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); - else - xdg_toplevel_unset_fullscreen(window->xdg_toplevel); -} - -int -window_is_maximized(struct window *window) -{ - return window->maximized; -} - -void -window_set_maximized(struct window *window, int maximized) -{ - if (!window->xdg_toplevel) - return; - - if (window->maximized == maximized) - return; - - if (maximized) - xdg_toplevel_set_maximized(window->xdg_toplevel); - else - xdg_toplevel_unset_maximized(window->xdg_toplevel); -} - -int -window_is_resizing(struct window *window) -{ - return window->resizing; -} - -void -window_set_minimized(struct window *window) -{ - if (!window->xdg_toplevel) - return; - - xdg_toplevel_set_minimized(window->xdg_toplevel); -} - -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_state_changed_handler(struct window *window, - window_state_changed_handler_t handler) -{ - window->state_changed_handler = handler; -} - -void -window_set_pointer_locked_handler(struct window *window, - locked_pointer_locked_handler_t locked, - locked_pointer_unlocked_handler_t unlocked) -{ - window->pointer_unlocked_handler = unlocked; - window->pointer_locked_handler = locked; -} - -void -window_set_pointer_confined_handler(struct window *window, - confined_pointer_confined_handler_t confined, - confined_pointer_unconfined_handler_t unconfined) -{ - window->pointer_confined_handler = confined; - window->pointer_unconfined_handler = unconfined; -} - -void -window_set_locked_pointer_motion_handler(struct window *window, - window_locked_pointer_motion_handler_t handler) -{ - window->locked_pointer_motion_handler = handler; -} - -void -window_set_title(struct window *window, const char *title) -{ - free(window->title); - window->title = strdup(title); - if (window->frame) { - frame_set_title(window->frame->frame, title); - widget_schedule_redraw(window->frame->widget); - } - if (window->xdg_toplevel) - xdg_toplevel_set_title(window->xdg_toplevel, 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)); -} - -static void -relative_pointer_handle_motion(void *data, struct zwp_relative_pointer_v1 *pointer, - uint32_t utime_hi, - uint32_t utime_lo, - wl_fixed_t dx, - wl_fixed_t dy, - wl_fixed_t dx_unaccel, - wl_fixed_t dy_unaccel) -{ - struct input *input = data; - struct window *window = input->pointer_focus; - uint32_t ms = (((uint64_t) utime_hi) << 32 | utime_lo) / 1000; - - if (window->locked_pointer_motion_handler && - window->pointer_locked) { - window->locked_pointer_motion_handler( - window, input, ms, - wl_fixed_to_double(dx), - wl_fixed_to_double(dy), - window->user_data); - } -} - -static const struct zwp_relative_pointer_v1_listener relative_pointer_listener = { - relative_pointer_handle_motion, -}; - -static void -locked_pointer_locked(void *data, - struct zwp_locked_pointer_v1 *locked_pointer) -{ - struct input *input = data; - struct window *window = input->locked_window; - - if (!window) - return; - - window->pointer_locked = true; - - if (window->pointer_locked_handler) { - window->pointer_locked_handler(window, - input, - window->user_data); - } -} - -static void -locked_pointer_unlocked(void *data, - struct zwp_locked_pointer_v1 *locked_pointer) -{ - struct input *input = data; - struct window *window = input->locked_window; - - if (!window) - return; - - window_unlock_pointer(window); - - input->locked_window = NULL; - - if (window->pointer_unlocked_handler) { - window->pointer_unlocked_handler(window, - input, - window->user_data); - } -} - -static const struct zwp_locked_pointer_v1_listener locked_pointer_listener = { - locked_pointer_locked, - locked_pointer_unlocked, -}; - -int -window_lock_pointer(struct window *window, struct input *input) -{ - struct zwp_relative_pointer_manager_v1 *relative_pointer_manager = - window->display->relative_pointer_manager; - struct zwp_pointer_constraints_v1 *pointer_constraints = - window->display->pointer_constraints; - struct zwp_relative_pointer_v1 *relative_pointer; - struct zwp_locked_pointer_v1 *locked_pointer; - - if (!window->display->relative_pointer_manager) - return -1; - - if (!window->display->pointer_constraints) - return -1; - - if (window->locked_pointer) - return -1; - - if (window->confined_pointer) - return -1; - - if (!input->pointer) - return -1; - - relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer( - relative_pointer_manager, input->pointer); - zwp_relative_pointer_v1_add_listener(relative_pointer, - &relative_pointer_listener, - input); - - locked_pointer = - zwp_pointer_constraints_v1_lock_pointer(pointer_constraints, - window->main_surface->surface, - input->pointer, - NULL, - ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); - zwp_locked_pointer_v1_add_listener(locked_pointer, - &locked_pointer_listener, - input); - - window->locked_pointer = locked_pointer; - window->relative_pointer = relative_pointer; - input->locked_window = window; - - return 0; -} - -void -window_unlock_pointer(struct window *window) -{ - if (!window->locked_pointer) - return; - - zwp_locked_pointer_v1_destroy(window->locked_pointer); - zwp_relative_pointer_v1_destroy(window->relative_pointer); - window->locked_pointer = NULL; - window->relative_pointer = NULL; - window->pointer_locked = false; -} - -void -widget_set_locked_pointer_cursor_hint(struct widget *widget, - float x, float y) -{ - struct window *window = widget->window; - - if (!window->locked_pointer) - return; - - zwp_locked_pointer_v1_set_cursor_position_hint(window->locked_pointer, - wl_fixed_from_double(x), - wl_fixed_from_double(y)); - wl_surface_commit(window->main_surface->surface); -} - -static void -confined_pointer_confined(void *data, - struct zwp_confined_pointer_v1 *confined_pointer) -{ - struct input *input = data; - struct window *window = input->confined_window; - - if (!window) - return; - - window->confined = true; - - if (window->pointer_confined_handler) { - window->pointer_confined_handler(window, - input, - window->user_data); - } -} - -static void -confined_pointer_unconfined(void *data, - struct zwp_confined_pointer_v1 *confined_pointer) -{ - struct input *input = data; - struct window *window = input->confined_window; - - if (!window) - return; - - window_unconfine_pointer(window); - - window->confined = false; - input->confined_window = NULL; - - if (window->pointer_unconfined_handler) { - window->pointer_unconfined_handler(window, - input, - window->user_data); - } -} - -static const struct zwp_confined_pointer_v1_listener confined_pointer_listener = { - confined_pointer_confined, - confined_pointer_unconfined, -}; - -int -window_confine_pointer_to_rectangles(struct window *window, - struct input *input, - struct rectangle *rectangles, - int num_rectangles) -{ - struct zwp_pointer_constraints_v1 *pointer_constraints = - window->display->pointer_constraints; - struct zwp_confined_pointer_v1 *confined_pointer; - struct wl_compositor *compositor = window->display->compositor; - struct wl_region *region = NULL; - int i; - - if (!window->display->pointer_constraints) - return -1; - - if (window->locked_pointer) - return -1; - - if (window->confined_pointer) - return -1; - - if (!input->pointer) - return -1; - - if (num_rectangles >= 1) { - region = wl_compositor_create_region(compositor); - for (i = 0; i < num_rectangles; i++) { - wl_region_add(region, - rectangles[i].x, - rectangles[i].y, - rectangles[i].width, - rectangles[i].height); - } - } - - confined_pointer = - zwp_pointer_constraints_v1_confine_pointer(pointer_constraints, - window->main_surface->surface, - input->pointer, - region, - ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); - if (region) - wl_region_destroy(region); - - zwp_confined_pointer_v1_add_listener(confined_pointer, - &confined_pointer_listener, - input); - - window->confined_pointer = confined_pointer; - window->confined_widget = NULL; - input->confined_window = window; - - return 0; -} - -void -window_update_confine_rectangles(struct window *window, - struct rectangle *rectangles, - int num_rectangles) -{ - struct wl_compositor *compositor = window->display->compositor; - struct wl_region *region; - int i; - - region = wl_compositor_create_region(compositor); - for (i = 0; i < num_rectangles; i++) { - wl_region_add(region, - rectangles[i].x, - rectangles[i].y, - rectangles[i].width, - rectangles[i].height); - } - - zwp_confined_pointer_v1_set_region(window->confined_pointer, region); - - wl_region_destroy(region); -} - -int -window_confine_pointer_to_widget(struct window *window, - struct widget *widget, - struct input *input) -{ - int ret; - - if (widget) { - ret = window_confine_pointer_to_rectangles(window, - input, - &widget->allocation, - 1); - window->confined_widget = widget; - return ret; - } else { - return window_confine_pointer_to_rectangles(window, - input, - NULL, - 0); - } -} - -void -window_unconfine_pointer(struct window *window) -{ - if (!window->confined_pointer) - return; - - zwp_confined_pointer_v1_destroy(window->confined_pointer); - window->confined_pointer = NULL; - window->confined = false; -} - -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 = xzalloc(sizeof *surface); - 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); - surface->viewport = NULL; - - return surface; -} - -static enum window_buffer_type -get_preferred_buffer_type(struct display *display) -{ -#ifdef HAVE_CAIRO_EGL - if (display->argb_device && !getenv("TOYTOOLKIT_NO_EGL")) - return WINDOW_BUFFER_TYPE_EGL_WINDOW; -#endif - - return WINDOW_BUFFER_TYPE_SHM; -} - -static struct window * -window_create_internal(struct display *display, int custom) -{ - struct window *window; - struct surface *surface; - - window = xzalloc(sizeof *window); - wl_list_init(&window->subsurface_list); - window->display = display; - - surface = surface_create(window); - window->main_surface = surface; - - assert(custom || display->xdg_shell); - - window->custom = custom; - - surface->buffer_type = get_preferred_buffer_type(display); - - wl_surface_set_user_data(surface->surface, window); - wl_list_insert(display->window_list.prev, &window->link); - wl_list_init(&window->redraw_task.link); - - wl_list_init (&window->window_output_list); - - return window; -} - -struct window * -window_create(struct display *display) -{ - struct window *window; - - window = window_create_internal(display, 0); - - if (window->display->xdg_shell) { - window->xdg_surface = - xdg_wm_base_get_xdg_surface(window->display->xdg_shell, - window->main_surface->surface); - fail_on_null(window->xdg_surface, 0, __FILE__, __LINE__); - - xdg_surface_add_listener(window->xdg_surface, - &xdg_surface_listener, window); - - window->xdg_toplevel = - xdg_surface_get_toplevel(window->xdg_surface); - fail_on_null(window->xdg_toplevel, 0, __FILE__, __LINE__); - - xdg_toplevel_add_listener(window->xdg_toplevel, - &xdg_toplevel_listener, window); - - window_inhibit_redraw(window); - - wl_surface_commit(window->main_surface->surface); - } - - return window; -} - -struct window * -window_create_custom(struct display *display) -{ - return window_create_internal(display, 1); -} - -void -window_set_parent(struct window *window, - struct window *parent_window) -{ - window->parent = parent_window; - window_sync_parent(window); -} - -struct window * -window_get_parent(struct window *window) -{ - return window->parent; -} - -static void -menu_set_item(struct menu *menu, int sy) -{ - int32_t x, y, width, height; - int next; - - frame_interior(menu->frame, &x, &y, &width, &height); - next = (sy - y) / 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 release after press-drag-release or - * click-motion-click. */ - menu->func(menu->user_data, input, menu->current); - input_ungrab(menu->input); - menu_destroy(menu); - } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { - menu->release_count++; - } -} - -static void -menu_touch_up_handler(struct widget *widget, - struct input *input, - uint32_t serial, - uint32_t time, - int32_t id, - void *data) -{ - struct menu *menu = data; - - input_ungrab(input); - menu_destroy(menu); -} - -static void -menu_redraw_handler(struct widget *widget, void *data) -{ - cairo_t *cr; - struct menu *menu = data; - int32_t x, y, width, height, i; - - cr = widget_cairo_create(widget); - - frame_repaint(menu->frame, cr); - frame_interior(menu->frame, &x, &y, &width, &height); - - theme_set_background_source(menu->window->display->theme, - cr, THEME_FRAME_ACTIVE); - cairo_rectangle(cr, x, y, width, height); - cairo_fill(cr); - - cairo_select_font_face(cr, "sans", - CAIRO_FONT_SLANT_NORMAL, - CAIRO_FONT_WEIGHT_NORMAL); - cairo_set_font_size(cr, 12); - - 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, x, y + i * 20, width, 20); - cairo_fill(cr); - cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); - cairo_move_to(cr, x + 10, y + i * 20 + 16); - cairo_show_text(cr, menu->entries[i]); - } else { - cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); - cairo_move_to(cr, x + 10, y + i * 20 + 16); - cairo_show_text(cr, menu->entries[i]); - } - } - - cairo_destroy(cr); -} - -static void -xdg_popup_handle_configure(void *data, - struct xdg_popup *xdg_popup, - int32_t x, - int32_t y, - int32_t width, - int32_t height) -{ -} - -static void -xdg_popup_handle_popup_done(void *data, struct xdg_popup *xdg_popup) -{ - struct window *window = data; - struct menu *menu = window->main_surface->widget->user_data; - - input_ungrab(menu->input); - menu_destroy(menu); -} - -static const struct xdg_popup_listener xdg_popup_listener = { - xdg_popup_handle_configure, - xdg_popup_handle_popup_done, -}; - -static struct menu * -create_menu(struct display *display, - struct input *input, uint32_t time, - menu_func_t func, const char **entries, int count, - void *user_data) -{ - struct window *window; - struct menu *menu; - - menu = malloc(sizeof *menu); - if (!menu) - return NULL; - - window = window_create_internal(display, 0); - if (!window) { - free(menu); - return NULL; - } - - menu->window = window; - menu->user_data = user_data; - menu->widget = window_add_widget(menu->window, menu); - menu->frame = frame_create(window->display->theme, 0, 0, - FRAME_BUTTON_NONE, NULL, NULL); - fail_on_null(menu->frame, 0, __FILE__, __LINE__); - menu->entries = entries; - menu->count = count; - menu->release_count = 0; - menu->current = -1; - menu->time = time; - menu->func = func; - menu->input = input; - - input_ungrab(input); - - 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); - widget_set_touch_up_handler(menu->widget, menu_touch_up_handler); - - input_grab(input, menu->widget, 0); - frame_resize_inside(menu->frame, 200, count * 20); - frame_set_flag(menu->frame, FRAME_FLAG_ACTIVE); - window_schedule_resize(window, frame_width(menu->frame), - frame_height(menu->frame)); - - return menu; -} - -static struct xdg_positioner * -create_simple_positioner(struct display *display, - int x, int y, int w, int h) -{ - struct xdg_positioner *positioner; - - positioner = xdg_wm_base_create_positioner(display->xdg_shell); - fail_on_null(positioner, 0, __FILE__, __LINE__); - xdg_positioner_set_anchor_rect(positioner, x, y, 1, 1); - xdg_positioner_set_size(positioner, w, h); - xdg_positioner_set_anchor(positioner, - XDG_POSITIONER_ANCHOR_TOP_LEFT); - xdg_positioner_set_gravity(positioner, - XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT); - - return positioner; -} - -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 menu *menu; - struct window *window; - int32_t ix, iy; - struct rectangle parent_geometry; - struct xdg_positioner *positioner; - - menu = create_menu(display, input, time, func, entries, count, parent); - - if (menu == NULL) - return; - - window = menu->window; - - window_set_buffer_scale (menu->window, window_get_buffer_scale (parent)); - window_set_buffer_transform (menu->window, window_get_buffer_transform (parent)); - - window->x = x; - window->y = y; - - frame_interior(menu->frame, &ix, &iy, NULL, NULL); - window_get_geometry(parent, &parent_geometry); - - if (!display->xdg_shell) - return; - - window->xdg_surface = - xdg_wm_base_get_xdg_surface(display->xdg_shell, - window->main_surface->surface); - fail_on_null(window->xdg_surface, 0, __FILE__, __LINE__); - - xdg_surface_add_listener(window->xdg_surface, - &xdg_surface_listener, window); - - positioner = create_simple_positioner(display, - window->x - (ix + parent_geometry.x), - window->y - (iy + parent_geometry.y), - frame_width(menu->frame), - frame_height(menu->frame)); - window->xdg_popup = xdg_surface_get_popup(window->xdg_surface, - parent->xdg_surface, - positioner); - fail_on_null(window->xdg_popup, 0, __FILE__, __LINE__); - xdg_positioner_destroy(positioner); - xdg_popup_grab(window->xdg_popup, input->seat, - display_get_serial(window->display)); - xdg_popup_add_listener(window->xdg_popup, - &xdg_popup_listener, window); - - window_inhibit_redraw(window); - - wl_surface_commit(window->main_surface->surface); -} - -void -window_set_buffer_type(struct window *window, enum window_buffer_type type) -{ - window->main_surface->buffer_type = type; -} - -enum window_buffer_type -window_get_buffer_type(struct window *window) -{ - return window->main_surface->buffer_type; -} - -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); - surface->buffer_type = window_get_buffer_type(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"); - } - - window->resize_needed = 1; - window_schedule_redraw(window); - - 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; - - if (output->make) - free(output->make); - output->make = strdup(make); - - if (output->model) - free(output->model); - output->model = strdup(model); -} - -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); - output->server_output_id = id; - 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); -} - -static void -display_destroy_output(struct display *d, uint32_t id) -{ - struct output *output; - - wl_list_for_each(output, &d->output_list, link) { - if (output->server_output_id == id) { - output_destroy(output); - break; - } - } -} - -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_global_handler_remove(struct display *display, - display_global_handler_t remove_handler) -{ - display->global_handler_remove = remove_handler; - if (!remove_handler) - return; -} - -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; -} - -const char * -output_get_make(struct output *output) -{ - return output->make; -} - -const char * -output_get_model(struct output *output) -{ - return output->model; -} - -static void -fini_xkb(struct input *input) -{ - xkb_state_unref(input->xkb.state); - xkb_keymap_unref(input->xkb.keymap); -} - -static void -display_add_input(struct display *d, uint32_t id, int display_seat_version) -{ - struct input *input; - int seat_version = MIN(display_seat_version, 7); - - input = xzalloc(sizeof *input); - input->display = d; - input->seat = wl_registry_bind(d->registry, id, &wl_seat_interface, - seat_version); - input->touch_focus = NULL; - input->pointer_focus = NULL; - input->keyboard_focus = NULL; - input->seat_version = seat_version; - - 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); - - if (d->data_device_manager) { - 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); - - toytimer_init(&input->cursor_timer, CLOCK_MONOTONIC, d, - cursor_timer_func); - - set_repeat_info(input, 40, 400); - toytimer_init(&input->repeat_timer, CLOCK_MONOTONIC, d, - keyboard_repeat_func); -} - -static void -display_add_data_device(struct display *d, uint32_t id, int ddm_version) -{ - struct input *input; - - d->data_device_manager_version = MIN(ddm_version, 3); - d->data_device_manager = - wl_registry_bind(d->registry, id, - &wl_data_device_manager_interface, - d->data_device_manager_version); - - wl_list_for_each(input, &d->input_list, link) { - if (!input->data_device) { - 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); - } - } -} - -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); - - if (input->data_device) { - if (input->display->data_device_manager_version >= 2) - wl_data_device_release(input->data_device); - else - wl_data_device_destroy(input->data_device); - } - if (input->seat_version >= WL_POINTER_RELEASE_SINCE_VERSION) { - if (input->touch) - wl_touch_release(input->touch); - if (input->pointer) - wl_pointer_release(input->pointer); - if (input->keyboard) - wl_keyboard_release(input->keyboard); - } else { - if (input->touch) - wl_touch_destroy(input->touch); - if (input->pointer) - wl_pointer_destroy(input->pointer); - if (input->keyboard) - wl_keyboard_destroy(input->keyboard); - } - - fini_xkb(input); - - wl_surface_destroy(input->pointer_surface); - - wl_list_remove(&input->link); - wl_seat_destroy(input->seat); - toytimer_fini(&input->repeat_timer); - toytimer_fini(&input->cursor_timer); - free(input); -} - -static void -xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) -{ - xdg_wm_base_pong(shell, serial); -} - -static const struct xdg_wm_base_listener wm_base_listener = { - xdg_wm_base_ping, -}; - -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) { - display_add_input(d, id, version); - } else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0 && - version == ZWP_RELATIVE_POINTER_MANAGER_V1_VERSION) { - d->relative_pointer_manager = - wl_registry_bind(registry, id, - &zwp_relative_pointer_manager_v1_interface, - 1); - } else if (strcmp(interface, "zwp_pointer_constraints_v1") == 0 && - version == ZWP_POINTER_CONSTRAINTS_V1_VERSION) { - d->pointer_constraints = - wl_registry_bind(registry, id, - &zwp_pointer_constraints_v1_interface, - 1); - } else if (strcmp(interface, "wl_shm") == 0) { - d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); - } else if (strcmp(interface, "wl_data_device_manager") == 0) { - display_add_data_device(d, id, version); - } else if (strcmp(interface, "xdg_wm_base") == 0) { - d->xdg_shell = wl_registry_bind(registry, id, - &xdg_wm_base_interface, 1); - xdg_wm_base_add_listener(d->xdg_shell, &wm_base_listener, d); - } 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, "wl_subcompositor") == 0) { - d->subcompositor = - wl_registry_bind(registry, id, - &wl_subcompositor_interface, 1); - } else if (!strcmp(interface, "wp_viewporter")) { - d->viewporter = - wl_registry_bind(registry, id, - &wp_viewporter_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; - - if (strcmp(global->interface, "wl_output") == 0) - display_destroy_output(d, name); - - /* XXX: Should destroy remaining bound globals */ - - if (d->global_handler_remove) - d->global_handler_remove(d, name, global->interface, - global->version, d->user_data); - - 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 = - weston_platform_get_egl_display(EGL_PLATFORM_WAYLAND_KHR, - d->display, NULL); - - 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 = xmalloc(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: %s\n", - strerror(errno)); - free(d); - return NULL; - } - - d->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - 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->registry = wl_display_get_registry(d->display); - wl_registry_add_listener(d->registry, ®istry_listener, d); - - if (wl_display_roundtrip(d->display) < 0) { - fprintf(stderr, "Failed to process Wayland connection: %s\n", - strerror(errno)); - 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->xdg_shell) - xdg_wm_base_destroy(display->xdg_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) -{ - if (wl_list_empty(&display->output_list)) - return NULL; - - 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) -{ - if (display->data_device_manager) - return wl_data_device_manager_create_data_source(display->data_device_manager); - else - return NULL; -} - -EGLConfig -display_get_argb_egl_config(struct display *d) -{ - return d->argb_config; -} - -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; -} - -int -display_get_data_device_manager_version(struct display *display) -{ - return display->data_device_manager_version; -} - -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; -} - -static void -toytimer_fire(struct task *tsk, uint32_t events) -{ - uint64_t e; - struct toytimer *tt; - - tt = container_of(tsk, struct toytimer, tsk); - - if (events != EPOLLIN) - fprintf(stderr, "unexpected timerfd events %x\n", events); - - if (!(events & EPOLLIN)) - return; - - if (read(tt->fd, &e, sizeof e) != sizeof e) { - /* If we change the timer between the fd becoming - * readable and getting here, there'll be nothing to - * read and we get EAGAIN. */ - if (errno != EAGAIN) - fprintf(stderr, "timer read failed: %s\n", - strerror(errno)); - return; - } - - tt->callback(tt); -} - -void -toytimer_init(struct toytimer *tt, clockid_t clock, struct display *display, - toytimer_cb callback) -{ - memset(tt, 0, sizeof *tt); - - tt->fd = timerfd_create(clock, TFD_CLOEXEC | TFD_NONBLOCK); - if (tt->fd == -1) { - fprintf(stderr, "creating timer failed: %s\n", - strerror(errno)); - abort(); - } - - tt->display = display; - tt->callback = callback; - tt->tsk.run = toytimer_fire; - display_watch_fd(display, tt->fd, EPOLLIN, &tt->tsk); -} - -void -toytimer_fini(struct toytimer *tt) -{ - display_unwatch_fd(tt->display, tt->fd); - close(tt->fd); - tt->fd = -1; -} - -void -toytimer_arm(struct toytimer *tt, const struct itimerspec *its) -{ - int ret; - - ret = timerfd_settime(tt->fd, 0, its, NULL); - if (ret < 0) { - fprintf(stderr, "timer setup failed: %s\n", strerror(errno)); - abort(); - } -} - -#define USEC_PER_SEC 1000000 - -void -toytimer_arm_once_usec(struct toytimer *tt, uint32_t usec) -{ - struct itimerspec its; - - its.it_interval.tv_sec = 0; - its.it_interval.tv_nsec = 0; - its.it_value.tv_sec = usec / USEC_PER_SEC; - its.it_value.tv_nsec = (usec % USEC_PER_SEC) * 1000; - toytimer_arm(tt, &its); -} - -void -toytimer_disarm(struct toytimer *tt) -{ - struct itimerspec its = {}; - - toytimer_arm(tt, &its); -} diff --git a/clients/window.h b/clients/window.h deleted file mode 100644 index c66dd06..0000000 --- a/clients/window.h +++ /dev/null @@ -1,744 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#ifndef _WINDOW_H_ -#define _WINDOW_H_ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include "shared/platform.h" - -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; -}; - -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 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_set_global_handler_remove(struct display *display, - display_global_handler_t remove_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 - -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); - -int -display_get_data_device_manager_version(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_DND_MOVE, - CURSOR_DND_COPY, - CURSOR_DND_FORBIDDEN, - - 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)(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 (*window_state_changed_handler_t)(struct window *window, - void *data); - - -typedef void (*window_locked_pointer_motion_handler_t)(struct window *window, - struct input *input, - uint32_t time, - float x, float y, - void *data); - -typedef void (*locked_pointer_locked_handler_t)(struct window *window, - struct input *input, - void *data); - -typedef void (*locked_pointer_unlocked_handler_t)(struct window *window, - struct input *input, - void *data); - -typedef void (*confined_pointer_confined_handler_t)(struct window *window, - struct input *input, - void *data); - -typedef void (*confined_pointer_unconfined_handler_t)(struct window *window, - struct input *input, - 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); - -typedef void (*widget_pointer_frame_handler_t)(struct widget *widget, - struct input *input, - void *data); - -typedef void (*widget_axis_source_handler_t)(struct widget *widget, - struct input *input, - uint32_t source, - void *data); - -typedef void (*widget_axis_stop_handler_t)(struct widget *widget, - struct input *input, - uint32_t time, - uint32_t axis, - void *data); - -typedef void (*widget_axis_discrete_handler_t)(struct widget *widget, - struct input *input, - uint32_t axis, - int32_t discrete, - void *data); - -struct window * -window_create(struct display *display); -struct window * -window_create_custom(struct display *display); - -void -window_set_parent(struct window *window, struct window *parent_window); -struct window * -window_get_parent(struct window *window); - -int -window_has_focus(struct window *window); - -typedef void (*menu_func_t)(void *data, struct input *input, int index); - -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_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); - -int -window_lock_pointer(struct window *window, struct input *input); - -void -window_unlock_pointer(struct window *window); - -void -widget_set_locked_pointer_cursor_hint(struct widget *widget, - float x, float y); - -int -window_confine_pointer_to_rectangles(struct window *window, - struct input *input, - struct rectangle *rectangles, - int num_rectangles); - -void -window_update_confine_rectangles(struct window *window, - struct rectangle *rectangles, - int num_rectangles); - -int -window_confine_pointer_to_widget(struct window *window, - struct widget *widget, - struct input *input); - -void -window_unconfine_pointer(struct window *window); - -cairo_surface_t * -window_get_surface(struct window *window); - -struct wl_surface * -window_get_wl_surface(struct window *window); - -struct wl_subsurface * -widget_get_wl_subsurface(struct widget *widget); - -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); - -enum window_buffer_type -window_get_buffer_type(struct window *window); - -int -window_is_fullscreen(struct window *window); - -void -window_set_fullscreen(struct window *window, int fullscreen); - -int -window_is_maximized(struct window *window); - -void -window_set_maximized(struct window *window, int maximized); - -int -window_is_resizing(struct window *window); - -void -window_set_minimized(struct window *window); - -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_state_changed_handler(struct window *window, - window_state_changed_handler_t handler); - -void -window_set_pointer_locked_handler(struct window *window, - locked_pointer_locked_handler_t locked, - locked_pointer_unlocked_handler_t unlocked); - -void -window_set_pointer_confined_handler(struct window *window, - confined_pointer_confined_handler_t confined, - confined_pointer_unconfined_handler_t unconfined); - -void -window_set_locked_pointer_motion_handler( - struct window *window, window_locked_pointer_motion_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); - -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_set_pointer_frame_handler(struct widget *widget, - widget_pointer_frame_handler_t handler); -void -widget_set_axis_handlers(struct widget *widget, - widget_axis_handler_t axis_handler, - widget_axis_source_handler_t axis_source_handler, - widget_axis_stop_handler_t axis_stop_handler, - widget_axis_discrete_handler_t axis_discrete_handler); - -void -window_inhibit_redraw(struct window *window); -void -window_uninhibit_redraw(struct window *window); -void -widget_schedule_redraw(struct widget *widget); -void -widget_set_use_cairo(struct widget *widget, int use_cairo); - -/* - * Sets the viewport destination for the widget's surface - * return 0 on success and -1 on failure. Set width and height to - * -1 to reset the viewport. - */ -int -widget_set_viewport_destination(struct widget *widget, int width, int height); - -struct widget * -window_frame_create(struct window *window, void *data); - -void -window_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); - -int -input_get_touch(struct input *input, int32_t id, float *x, float *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 -touch_grab(struct input *input, int32_t touch_id); - -void -touch_ungrab(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); - -const char * -output_get_make(struct output *output); - -const char * -output_get_model(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); - -struct toytimer; -typedef void (*toytimer_cb)(struct toytimer *); - -struct toytimer { - struct display *display; - struct task tsk; - int fd; - toytimer_cb callback; -}; - -void -toytimer_init(struct toytimer *tt, clockid_t clock, struct display *display, - toytimer_cb callback); - -void -toytimer_fini(struct toytimer *tt); - -void -toytimer_arm(struct toytimer *tt, const struct itimerspec *its); - -void -toytimer_arm_once_usec(struct toytimer *tt, uint32_t usec); - -void -toytimer_disarm(struct toytimer *tt); - -#endif diff --git a/compositor/cms-colord.c b/compositor/cms-colord.c deleted file mode 100644 index d4efdb4..0000000 --- a/compositor/cms-colord.c +++ /dev/null @@ -1,588 +0,0 @@ -/* - * Copyright © 2013 Richard Hughes - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include "weston.h" -#include "cms-helper.h" -#include "shared/helpers.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 bool -edid_value_valid(const char *str) -{ - if (str == NULL) - return false; - if (str[0] == '\0') - return false; - if (strcmp(str, "unknown") == 0) - return false; - return true; -} - -static gchar * -get_output_id(struct cms_colord *cms, struct weston_output *o) -{ - struct weston_head *head; - const gchar *tmp; - GString *device_id; - - /* XXX: What to do with multiple heads? - * This is potentially unstable, if head configuration is changed - * while the output is enabled. */ - head = weston_output_get_first_head(o); - - if (wl_list_length(&o->head_list) > 1) { - weston_log("colord: WARNING: multiple heads are not supported (output %s).\n", - o->name); - } - - /* 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(head->make)) { - tmp = g_hash_table_lookup(cms->pnp_ids, head->make); - if (tmp == NULL) - tmp = head->make; - g_string_append_printf(device_id, "-%s", tmp); - } - if (edid_value_valid(head->model)) - g_string_append_printf(device_id, "-%s", head->model); - if (edid_value_valid(head->serial_number)) - g_string_append_printf(device_id, "-%s", head->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\n"); - } -} - -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_output *ocms = - container_of(listener, struct cms_output, destroy_listener); - struct weston_output *o = (struct weston_output *) data; - struct cms_colord *cms = ocms->cms; - gchar *device_id; - - device_id = get_output_id(cms, o); - 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) -{ - struct weston_head *head; - CdDevice *device; - const gchar *tmp; - gchar *device_id; - GError *error = NULL; - GHashTable *device_props; - struct cms_output *ocms; - - /* XXX: What to do with multiple heads? */ - head = weston_output_get_first_head(o); - - /* 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(head->make)) { - tmp = g_hash_table_lookup(cms->pnp_ids, head->make); - if (tmp == NULL) - tmp = head->make; - g_hash_table_insert (device_props, - g_strdup(CD_DEVICE_PROPERTY_VENDOR), - g_strdup(tmp)); - } - if (edid_value_valid(head->model)) { - g_hash_table_insert (device_props, - g_strdup(CD_DEVICE_PROPERTY_MODEL), - g_strdup(head->model)); - } - if (edid_value_valid(head->serial_number)) { - g_hash_table_insert (device_props, - g_strdup(CD_DEVICE_PROPERTY_SERIAL), - g_strdup(head->serial_number)); - } - if (head->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\n"); - 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) -{ - if (cms->loop) { - g_main_loop_quit(cms->loop); - g_main_loop_unref(cms->loop); - } - if (cms->thread) - g_thread_join(cms->thread); - - /* cms->devices must be destroyed before other resources, as - * the other resources are needed during output cleanup in - * cms->devices unref. - */ - 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); - - g_free(cms->pnp_ids_data); - g_hash_table_unref(cms->pnp_ids); - - wl_list_remove(&cms->destroy_listener.link); - 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; - struct cms_colord *cms = ocms->cms; - struct weston_output *o = ocms->o; - 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 unplugged %s\n", device_id); - - wl_list_remove(&ocms->destroy_listener.link); - 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); - } - - g_object_unref(ocms->device); - g_slice_free(struct cms_output, ocms); - g_free (device_id); -} - -WL_EXPORT int -wet_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 (!weston_compositor_add_destroy_listener_once(ec, - &cms->destroy_listener, - colord_notifier_destroy)) { - free(cms); - return 0; - } - -#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); - - /* 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/compositor/cms-helper.c b/compositor/cms-helper.c deleted file mode 100644 index bc56a9d..0000000 --- a/compositor/cms-helper.c +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright © 2013 Richard Hughes - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include - -#ifdef HAVE_LCMS -#include -#endif - -#include -#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 = zalloc(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/compositor/cms-helper.h b/compositor/cms-helper.h deleted file mode 100644 index 4a5b711..0000000 --- a/compositor/cms-helper.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright © 2013 Richard Hughes - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _WESTON_CMS_H_ -#define _WESTON_CMS_H_ - -#include "config.h" - -#include - -/* 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 embedded 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/compositor/cms-static.c b/compositor/cms-static.c deleted file mode 100644 index 540d6ad..0000000 --- a/compositor/cms-static.c +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright © 2013 Richard Hughes - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include -#include "cms-helper.h" -#include "shared/helpers.h" -#include "weston.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(wet_get_config(cms->ec), - "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 && strlen(profile) > 0) { - weston_log("cms-static: failed to load %s\n", profile); - } else { - weston_log("cms-static: loading %s for %s\n", - (p != NULL) ? profile : "identity LUT", - 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 -wet_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; - - if (!weston_compositor_add_destroy_listener_once(ec, - &cms->destroy_listener, - cms_notifier_destroy)) { - free(cms); - return 0; - } - - 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/compositor/executable.c b/compositor/executable.c deleted file mode 100644 index 0644077..0000000 --- a/compositor/executable.c +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright © 2019 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include "weston.h" - -int -main(int argc, char *argv[]) -{ - return wet_main(argc, argv); -} diff --git a/compositor/main.c b/compositor/main.c deleted file mode 100755 index efaa7b2..0000000 --- a/compositor/main.c +++ /dev/null @@ -1,3463 +0,0 @@ -/* - * Copyright © 2010-2011 Intel Corporation - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2012-2018 Collabora, Ltd. - * Copyright © 2010-2011 Benjamin Franzke - * Copyright © 2013 Jason Ekstrand - * Copyright © 2017, 2018 General Electric Company - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "weston.h" -#include -#include "shared/os-compatibility.h" -#include "shared/helpers.h" -#include "shared/string-helpers.h" -#include "git-version.h" -#include -#include "weston.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include "../remoting/remoting-plugin.h" -#include "../pipewire/pipewire-plugin.h" - -#define WINDOW_TITLE "Weston Compositor" -/* flight recorder size (in bytes) */ -#define DEFAULT_FLIGHT_REC_SIZE (5 * 1024 * 1024) - -struct wet_output_config { - int width; - int height; - int32_t scale; - uint32_t transform; -}; - -struct wet_compositor; -struct wet_layoutput; - -struct wet_head_tracker { - struct wl_listener head_destroy_listener; -}; - -/** User data for each weston_output */ -struct wet_output { - struct weston_output *output; - struct wl_listener output_destroy_listener; - struct wet_layoutput *layoutput; - struct wl_list link; /**< in wet_layoutput::output_list */ -}; - -#define MAX_CLONE_HEADS 16 - -struct wet_head_array { - struct weston_head *heads[MAX_CLONE_HEADS]; /**< heads to add */ - unsigned n; /**< the number of heads */ -}; - -/** A layout output - * - * Contains wet_outputs that are all clones (independent CRTCs). - * Stores output layout information in the future. - */ -struct wet_layoutput { - struct wet_compositor *compositor; - struct wl_list compositor_link; /**< in wet_compositor::layoutput_list */ - struct wl_list output_list; /**< wet_output::link */ - char *name; - struct weston_config_section *section; - struct wet_head_array add; /**< tmp: heads to add as clones */ -}; - -struct wet_compositor { - struct weston_compositor *compositor; - struct weston_config *config; - struct wet_output_config *parsed_options; - bool drm_use_current_mode; - struct wl_listener heads_changed_listener; - int (*simple_output_configure)(struct weston_output *output); - bool init_failed; - struct wl_list layoutput_list; /**< wet_layoutput::compositor_link */ -}; - -static FILE *weston_logfile = NULL; -// OHOS remove logger -//static struct weston_log_scope *log_scope; -//static struct weston_log_scope *protocol_scope; -static int cached_tm_mday = -1; - -// OHOS remove logger -//static char * -//weston_log_timestamp(char *buf, size_t len) -//{ -// struct timeval tv; -// struct tm *brokendown_time; -// char datestr[128]; -// char timestr[128]; -// -// gettimeofday(&tv, NULL); -// -// brokendown_time = localtime(&tv.tv_sec); -// if (brokendown_time == NULL) { -// snprintf(buf, len, "%s", "[(NULL)localtime] "); -// return buf; -// } -// -// memset(datestr, 0, sizeof(datestr)); -// if (brokendown_time->tm_mday != cached_tm_mday) { -// strftime(datestr, sizeof(datestr), "Date: %Y-%m-%d %Z\n", -// brokendown_time); -// cached_tm_mday = brokendown_time->tm_mday; -// } -// -// strftime(timestr, sizeof(timestr), "%H:%M:%S", brokendown_time); -// /* if datestr is empty it prints only timestr*/ -// snprintf(buf, len, "%s[%s.%03li]", datestr, -// timestr, (tv.tv_usec / 1000)); -// -// return buf; -//} -// -//static void -//custom_handler(const char *fmt, va_list arg) -//{ -// char timestr[512]; -// -// weston_log_scope_printf(log_scope, "%s libwayland: ", -// weston_log_timestamp(timestr, -// sizeof(timestr))); -// weston_log_scope_vprintf(log_scope, fmt, arg); -//} -// -//static bool -//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) { -// os_fd_set_cloexec(fileno(weston_logfile)); -// } else { -// fprintf(stderr, "Failed to open %s: %s\n", filename, strerror(errno)); -// return false; -// } -// } -// -// if (weston_logfile == NULL) -// weston_logfile = stderr; -// else -// setvbuf(weston_logfile, NULL, _IOLBF, 256); -// -// return true; -//} -// -//static void -//weston_log_file_close(void) -//{ -// if ((weston_logfile != stderr) && (weston_logfile != NULL)) -// fclose(weston_logfile); -// weston_logfile = stderr; -//} -// -//static int -//vlog(const char *fmt, va_list ap) -//{ -// const char *oom = "Out of memory"; -// char timestr[128]; -// int len = 0; -// char *str; -// -// if (weston_log_scope_is_enabled(log_scope)) { -// int len_va; -// char *log_timestamp = weston_log_timestamp(timestr, -// sizeof(timestr)); -// len_va = vasprintf(&str, fmt, ap); -// if (len_va >= 0) { -// len = weston_log_scope_printf(log_scope, "%s %s", -// log_timestamp, str); -// free(str); -// } else { -// len = weston_log_scope_printf(log_scope, "%s %s", -// log_timestamp, oom); -// } -// } -// -// return len; -//} -// -//static int -//vlog_continue(const char *fmt, va_list argp) -//{ -// return weston_log_scope_vprintf(log_scope, fmt, argp); -//} -// -//static const char * -//get_next_argument(const char *signature, char* type) -//{ -// for(; *signature; ++signature) { -// switch(*signature) { -// case 'i': -// case 'u': -// case 'f': -// case 's': -// case 'o': -// case 'n': -// case 'a': -// case 'h': -// *type = *signature; -// return signature + 1; -// } -// } -// *type = '\0'; -// return signature; -//} -// -//static void -//protocol_log_fn(void *user_data, -// enum wl_protocol_logger_type direction, -// const struct wl_protocol_logger_message *message) -//{ -// FILE *fp; -// char *logstr; -// size_t logsize; -// char timestr[128]; -// struct wl_resource *res = message->resource; -// const char *signature = message->message->signature; -// int i; -// char type; -// -// if (!weston_log_scope_is_enabled(protocol_scope)) -// return; -// -// fp = open_memstream(&logstr, &logsize); -// if (!fp) -// return; -// -// weston_log_scope_timestamp(protocol_scope, -// timestr, sizeof timestr); -// fprintf(fp, "%s ", timestr); -// fprintf(fp, "client %p %s ", wl_resource_get_client(res), -// direction == WL_PROTOCOL_LOGGER_REQUEST ? "rq" : "ev"); -// fprintf(fp, "%s@%u.%s(", -// wl_resource_get_class(res), -// wl_resource_get_id(res), -// message->message->name); -// -// for (i = 0; i < message->arguments_count; i++) { -// signature = get_next_argument(signature, &type); -// -// if (i > 0) -// fprintf(fp, ", "); -// -// switch (type) { -// case 'u': -// fprintf(fp, "%u", message->arguments[i].u); -// break; -// case 'i': -// fprintf(fp, "%d", message->arguments[i].i); -// break; -// case 'f': -// fprintf(fp, "%f", -// wl_fixed_to_double(message->arguments[i].f)); -// break; -// case 's': -// fprintf(fp, "\"%s\"", message->arguments[i].s); -// break; -// case 'o': -// if (message->arguments[i].o) { -// struct wl_resource* resource; -// resource = (struct wl_resource*) message->arguments[i].o; -// fprintf(fp, "%s@%u", -// wl_resource_get_class(resource), -// wl_resource_get_id(resource)); -// } -// else -// fprintf(fp, "nil"); -// break; -// case 'n': -// fprintf(fp, "new id %s@", -// (message->message->types[i]) ? -// message->message->types[i]->name : -// "[unknown]"); -// if (message->arguments[i].n != 0) -// fprintf(fp, "%u", message->arguments[i].n); -// else -// fprintf(fp, "nil"); -// break; -// case 'a': -// fprintf(fp, "array"); -// break; -// case 'h': -// fprintf(fp, "fd %d", message->arguments[i].h); -// break; -// } -// } -// -// fprintf(fp, ")\n"); -// -// if (fclose(fp) == 0) -// weston_log_scope_write(protocol_scope, logstr, logsize); -// -// free(logstr); -//} - -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; - - while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { - 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"); - continue; - } - - wl_list_remove(&p->link); - p->cleanup(p, status); - } - - if (pid < 0 && errno != ECHILD) - weston_log("waitpid error %s\n", strerror(errno)); - - return 1; -} - -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 launch 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: %s\n", strerror(errno)); - return; - } - - snprintf(s, sizeof s, "%d", clientfd); - setenv("WAYLAND_SOCKET", s, 1); - - if (execl(path, path, NULL) < 0) - weston_log("compositor: executing '%s' failed: %s\n", - path, strerror(errno)); -} - -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': %s\n", - path, strerror(errno)); - return NULL; - } - - pid = fork(); - if (pid == -1) { - close(sv[0]); - close(sv[1]); - weston_log("weston_client_launch: " - "fork failed while launching '%s': %s\n", path, - strerror(errno)); - 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; -} - -WL_EXPORT void -weston_watch_process(struct weston_process *process) -{ - wl_list_insert(&child_process_list, &process->link); -} - -struct process_info { - struct weston_process proc; - char *path; -}; - -static void -process_handle_sigchld(struct weston_process *process, int status) -{ - struct process_info *pinfo = - container_of(process, struct process_info, proc); - - /* - * There are no guarantees whether this runs before or after - * the wl_client destructor. - */ - - if (WIFEXITED(status)) { - weston_log("%s exited with status %d\n", pinfo->path, - WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - weston_log("%s died on signal %d\n", pinfo->path, - WTERMSIG(status)); - } else { - weston_log("%s disappeared\n", pinfo->path); - } - - free(pinfo->path); - free(pinfo); -} - -WL_EXPORT struct wl_client * -weston_client_start(struct weston_compositor *compositor, const char *path) -{ - struct process_info *pinfo; - struct wl_client *client; - - pinfo = zalloc(sizeof *pinfo); - if (!pinfo) - return NULL; - - pinfo->path = strdup(path); - if (!pinfo->path) - goto out_free; - - client = weston_client_launch(compositor, &pinfo->proc, path, - process_handle_sigchld); - if (!client) - goto out_str; - - return client; - -out_str: - free(pinfo->path); - -out_free: - free(pinfo); - - return 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); -} - -static struct wet_compositor * -to_wet_compositor(struct weston_compositor *compositor) -{ - return weston_compositor_get_user_data(compositor); -} - -static struct wet_output_config * -wet_init_parsed_options(struct weston_compositor *ec) -{ - struct wet_compositor *compositor = to_wet_compositor(ec); - struct wet_output_config *config; - - config = zalloc(sizeof *config); - - if (!config) { - perror("out of memory"); - return NULL; - } - - config->width = 0; - config->height = 0; - config->scale = 0; - config->transform = UINT32_MAX; - - compositor->parsed_options = config; - - return config; -} - -WL_EXPORT struct weston_config * -wet_get_config(struct weston_compositor *ec) -{ - struct wet_compositor *compositor = to_wet_compositor(ec); - - return compositor->config; -} - -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 (current mode is %o),\n" - "and must be owned by the user (current owner is 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) -{ - FILE *out = error_code == EXIT_SUCCESS ? stdout : stderr; - - fprintf(out, - "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\n" -#if defined(BUILD_DRM_COMPOSITOR) - "\t\t\t\tdrm-backend.so\n" -#endif -#if defined(BUILD_FBDEV_COMPOSITOR) - "\t\t\t\tfbdev-backend.so\n" -#endif -#if defined(BUILD_HEADLESS_COMPOSITOR) - "\t\t\t\theadless-backend.so\n" -#endif -#if defined(BUILD_RDP_COMPOSITOR) - "\t\t\t\trdp-backend.so\n" -#endif -#if defined(BUILD_WAYLAND_COMPOSITOR) - "\t\t\t\twayland-backend.so\n" -#endif -#if defined(BUILD_X11_COMPOSITOR) - "\t\t\t\tx11-backend.so\n" -#endif - " --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" -#if defined(BUILD_XWAYLAND) - " --xwayland\t\tLoad the xwayland module\n" -#endif - " --modules\t\tLoad the comma-separated list of modules\n" - " --log=FILE\t\tLog to the given file\n" - " -c, --config=FILE\tConfig file to load, defaults to weston.ini\n" - " --no-config\t\tDo not read weston.ini\n" - " --wait-for-debugger\tRaise SIGSTOP on start-up\n" - " --debug\t\tEnable debug extension\n" - " -l, --logger-scopes=SCOPE\n\t\t\tSpecify log scopes to " - "subscribe to.\n\t\t\tCan specify multiple scopes, " - "each followed by comma\n" - " -f, --flight-rec-scopes=SCOPE\n\t\t\tSpecify log scopes to " - "subscribe to.\n\t\t\tCan specify multiple scopes, " - "each followed by comma\n" - " -h, --help\t\tThis help message\n\n"); - -#if defined(BUILD_DRM_COMPOSITOR) - fprintf(out, - "Options for drm-backend.so:\n\n" - " --seat=SEAT\t\tThe seat that weston should run on, instead of the seat defined in XDG_SEAT\n" - " --tty=TTY\t\tThe tty to use\n" - " --drm-device=CARD\tThe DRM device to use, e.g. \"card0\".\n" - " --use-pixman\t\tUse the pixman (CPU) renderer\n" - " --current-mode\tPrefer current KMS mode over EDID preferred mode\n" - " --continue-without-input\tAllow the compositor to start without input devices\n\n"); -#endif - -#if defined(BUILD_FBDEV_COMPOSITOR) - fprintf(out, - "Options for fbdev-backend.so:\n\n" - " --tty=TTY\t\tThe tty to use\n" - " --device=DEVICE\tThe framebuffer device to use\n" - " --seat=SEAT\t\tThe seat that weston should run on, instead of the seat defined in XDG_SEAT\n" - "\n"); -#endif - -#if defined(BUILD_HEADLESS_COMPOSITOR) - fprintf(out, - "Options for headless-backend.so:\n\n" - " --width=WIDTH\t\tWidth of memory surface\n" - " --height=HEIGHT\tHeight of memory surface\n" - " --scale=SCALE\t\tScale factor of output\n" - " --transform=TR\tThe output transformation, TR is one of:\n" - "\tnormal 90 180 270 flipped flipped-90 flipped-180 flipped-270\n" - " --use-pixman\t\tUse the pixman (CPU) renderer (default: no rendering)\n" - " --use-gl\t\tUse the GL renderer (default: no rendering)\n" - " --no-outputs\t\tDo not create any virtual outputs\n" - "\n"); -#endif - -#if defined(BUILD_RDP_COMPOSITOR) - fprintf(out, - "Options for rdp-backend.so:\n\n" - " --width=WIDTH\t\tWidth of desktop\n" - " --height=HEIGHT\tHeight of desktop\n" - " --env-socket\t\tUse socket defined in RDP_FD env variable as peer connection\n" - " --address=ADDR\tThe address to bind\n" - " --port=PORT\t\tThe port to listen on\n" - " --no-clients-resize\tThe RDP peers will be forced to the size of the desktop\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 - -#if defined(BUILD_WAYLAND_COMPOSITOR) - fprintf(out, - "Options for wayland-backend.so:\n\n" - " --width=WIDTH\t\tWidth of Wayland surface\n" - " --height=HEIGHT\tHeight of Wayland surface\n" - " --scale=SCALE\t\tScale factor of output\n" - " --fullscreen\t\tRun in fullscreen mode\n" - " --use-pixman\t\tUse the pixman (CPU) renderer\n" - " --output-count=COUNT\tCreate multiple outputs\n" - " --sprawl\t\tCreate one fullscreen output for every parent output\n" - " --display=DISPLAY\tWayland display to connect to\n\n"); -#endif - -#if defined(BUILD_X11_COMPOSITOR) - fprintf(out, - "Options for x11-backend.so:\n\n" - " --width=WIDTH\t\tWidth of X window\n" - " --height=HEIGHT\tHeight of X window\n" - " --scale=SCALE\t\tScale factor of output\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"); -#endif - - exit(error_code); -} - -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; -} - -static const char * -clock_name(clockid_t clk_id) -{ - static const char *names[] = { - [CLOCK_REALTIME] = "CLOCK_REALTIME", - [CLOCK_MONOTONIC] = "CLOCK_MONOTONIC", - [CLOCK_MONOTONIC_RAW] = "CLOCK_MONOTONIC_RAW", - [CLOCK_REALTIME_COARSE] = "CLOCK_REALTIME_COARSE", - [CLOCK_MONOTONIC_COARSE] = "CLOCK_MONOTONIC_COARSE", -#ifdef CLOCK_BOOTTIME - [CLOCK_BOOTTIME] = "CLOCK_BOOTTIME", -#endif - }; - - if (clk_id < 0 || (unsigned)clk_id >= ARRAY_LENGTH(names)) - return "unknown"; - - return names[clk_id]; -} - -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; - struct timespec res; - - 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"); - } - - weston_log_continue(STAMP_SPACE "presentation clock: %s, id %d\n", - clock_name(compositor->presentation_clock), - compositor->presentation_clock); - - if (clock_getres(compositor->presentation_clock, &res) == 0) - weston_log_continue(STAMP_SPACE - "presentation clock resolution: %d.%09ld s\n", - (int)res.tv_sec, res.tv_nsec); - else - weston_log_continue(STAMP_SPACE - "presentation clock resolution: N/A\n"); -} - -static void -handle_primary_client_destroyed(struct wl_listener *listener, void *data) -{ - struct wl_client *client = data; - - weston_log("Primary client died. Closing...\n"); - - wl_display_terminate(wl_client_get_display(client)); -} - -static int -weston_create_listening_socket(struct wl_display *display, const char *socket_name) -{ - if (socket_name) { - if (wl_display_add_socket(display, socket_name)) { - weston_log("fatal: failed to add socket: %s\n", - strerror(errno)); - return -1; - } - } else { - socket_name = wl_display_add_socket_auto(display); - if (!socket_name) { - weston_log("fatal: failed to add socket: %s\n", - strerror(errno)); - return -1; - } - } - - setenv("WAYLAND_DISPLAY", socket_name, 1); - - return 0; -} - -WL_EXPORT void * -wet_load_module_entrypoint(const char *name, const char *entrypoint) -{ - char path[PATH_MAX]; - void *module, *init; - size_t len; - - if (name == NULL) - return NULL; - - if (name[0] != '/') { - len = weston_module_path_from_env(name, path, sizeof path); - if (len == 0) - len = snprintf(path, sizeof path, "%s/%s", MODULEDIR, - name); - } else { - len = snprintf(path, sizeof path, "%s", name); - } - - /* snprintf returns the length of the string it would've written, - * _excluding_ the NUL byte. So even being equal to the size of - * our buffer is an error here. */ - if (len >= sizeof path) - return NULL; - - module = dlopen(path, RTLD_NOW | RTLD_NOLOAD); - if (module) { - weston_log("Module '%s' already loaded\n", path); - } else { - 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; -} - -WL_EXPORT int -wet_load_module(struct weston_compositor *compositor, - const char *name, int *argc, char *argv[]) -{ - int (*module_init)(struct weston_compositor *ec, - int *argc, char *argv[]); - - module_init = wet_load_module_entrypoint(name, "wet_module_init"); - if (!module_init) - return -1; - if (module_init(compositor, argc, argv) < 0) - return -1; - return 0; -} - -static int -wet_load_shell(struct weston_compositor *compositor, - const char *name, int *argc, char *argv[]) -{ - int (*shell_init)(struct weston_compositor *ec, - int *argc, char *argv[]); - - shell_init = wet_load_module_entrypoint(name, "wet_shell_init"); - if (!shell_init) - return -1; - if (shell_init(compositor, argc, argv) < 0) - return -1; - return 0; -} - -static char * -wet_get_binary_path(const char *name, const char *dir) -{ - char path[PATH_MAX]; - size_t len; - - len = weston_module_path_from_env(name, path, sizeof path); - if (len > 0) - return strdup(path); - - len = snprintf(path, sizeof path, "%s/%s", dir, name); - if (len >= sizeof path) - return NULL; - - return strdup(path); -} - -WL_EXPORT char * -wet_get_libexec_path(const char *name) -{ - return wet_get_binary_path(name, LIBEXECDIR); -} - -WL_EXPORT char * -wet_get_bindir_path(const char *name) -{ - return wet_get_binary_path(name, BINDIR); -} - -static int -load_modules(struct weston_compositor *ec, const char *modules, - int *argc, char *argv[], bool *xwayland) -{ - const char *p, *end; - char buffer[256]; - - if (modules == NULL) - return 0; - - p = modules; - while (*p) { - end = strchrnul(p, ','); - snprintf(buffer, sizeof buffer, "%.*s", (int) (end - p), p); - - if (strstr(buffer, "xwayland.so")) { - weston_log("Old Xwayland module loading detected: " - "Please use --xwayland command line option " - "or set xwayland=true in the [core] section " - "in weston.ini\n"); - *xwayland = true; - } else { - if (wet_load_module(ec, buffer, argc, argv) < 0) - return -1; - } - - p = end; - while (*p == ',') - p++; - } - - return 0; -} - -static int -save_touch_device_calibration(struct weston_compositor *compositor, - struct weston_touch_device *device, - const struct weston_touch_device_matrix *calibration) -{ - struct weston_config_section *s; - struct weston_config *config = wet_get_config(compositor); - char *helper = NULL; - char *helper_cmd = NULL; - int ret = -1; - int status; - const float *m = calibration->m; - - s = weston_config_get_section(config, - "libinput", NULL, NULL); - - weston_config_section_get_string(s, "calibration_helper", - &helper, NULL); - - if (!helper || strlen(helper) == 0) { - ret = 0; - goto out; - } - - if (asprintf(&helper_cmd, "\"%s\" '%s' %f %f %f %f %f %f", - helper, device->syspath, - m[0], m[1], m[2], - m[3], m[4], m[5]) < 0) - goto out; - - status = system(helper_cmd); - free(helper_cmd); - - if (status < 0) { - weston_log("Error: failed to run calibration helper '%s'.\n", - helper); - goto out; - } - - if (!WIFEXITED(status)) { - weston_log("Error: calibration helper '%s' possibly killed.\n", - helper); - goto out; - } - - if (WEXITSTATUS(status) == 0) { - ret = 0; - } else { - weston_log("Calibration helper '%s' exited with status %d.\n", - helper, WEXITSTATUS(status)); - } - -out: - free(helper); - - return ret; -} - -static int -weston_compositor_init_config(struct weston_compositor *ec, - struct weston_config *config) -{ - struct xkb_rule_names xkb_names; - struct weston_config_section *s; - int repaint_msec; - bool cal; - - /* weston.ini [keyboard] */ - s = weston_config_get_section(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_set_xkb_rule_names(ec, &xkb_names) < 0) - return -1; - - weston_config_section_get_int(s, "repeat-rate", - &ec->kb_repeat_rate, 40); - weston_config_section_get_int(s, "repeat-delay", - &ec->kb_repeat_delay, 400); - - weston_config_section_get_bool(s, "vt-switching", - &ec->vt_switching, true); - - /* weston.ini [core] */ - s = weston_config_get_section(config, "core", NULL, NULL); - weston_config_section_get_int(s, "repaint-window", &repaint_msec, - ec->repaint_msec); - if (repaint_msec < -10 || repaint_msec > 1000) { - weston_log("Invalid repaint_window value in config: %d\n", - repaint_msec); - } else { - ec->repaint_msec = repaint_msec; - } - weston_log("Output repaint window is %d ms maximum.\n", - ec->repaint_msec); - - /* weston.ini [libinput] */ - s = weston_config_get_section(config, "libinput", NULL, NULL); - weston_config_section_get_bool(s, "touchscreen_calibrator", &cal, 0); - if (cal) - weston_compositor_enable_touch_calibrator(ec, - save_touch_device_calibration); - - return 0; -} - -static char * -weston_choose_default_backend(void) -{ - char *backend = NULL; - - if (getenv("WAYLAND_DISPLAY") || getenv("WAYLAND_SOCKET")) - backend = strdup("wayland-backend.so"); - else if (getenv("DISPLAY")) - backend = strdup("x11-backend.so"); - else - backend = strdup(WESTON_NATIVE_BACKEND); - - return backend; -} - -static const struct { const char *name; uint32_t token; } transforms[] = { - { "normal", WL_OUTPUT_TRANSFORM_NORMAL }, - { "rotate-90", WL_OUTPUT_TRANSFORM_90 }, - { "rotate-180", WL_OUTPUT_TRANSFORM_180 }, - { "rotate-270", WL_OUTPUT_TRANSFORM_270 }, - { "flipped", WL_OUTPUT_TRANSFORM_FLIPPED }, - { "flipped-rotate-90", WL_OUTPUT_TRANSFORM_FLIPPED_90 }, - { "flipped-rotate-180", WL_OUTPUT_TRANSFORM_FLIPPED_180 }, - { "flipped-rotate-270", WL_OUTPUT_TRANSFORM_FLIPPED_270 }, -}; - -WL_EXPORT int -weston_parse_transform(const char *transform, uint32_t *out) -{ - unsigned int i; - - for (i = 0; i < ARRAY_LENGTH(transforms); i++) - if (strcmp(transforms[i].name, transform) == 0) { - *out = transforms[i].token; - return 0; - } - - *out = WL_OUTPUT_TRANSFORM_NORMAL; - return -1; -} - -WL_EXPORT const char * -weston_transform_to_string(uint32_t output_transform) -{ - unsigned int i; - - for (i = 0; i < ARRAY_LENGTH(transforms); i++) - if (transforms[i].token == output_transform) - return transforms[i].name; - - return ""; -} - -static int -load_configuration(struct weston_config **config, int32_t noconfig, - const char *config_file) -{ - const char *file = "weston.ini"; - const char *full_path; - - *config = NULL; - - if (config_file) - file = config_file; - - if (noconfig == 0) - *config = weston_config_parse(file); - - if (*config) { - full_path = weston_config_get_full_path(*config); - - weston_log("Using config file '%s'\n", full_path); - setenv(WESTON_CONFIG_FILE_ENV_VAR, full_path, 1); - - return 0; - } - - if (config_file && noconfig == 0) { - weston_log("fatal: error opening or reading config file" - " '%s'.\n", config_file); - - return -1; - } - - weston_log("Starting with no config file.\n"); - setenv(WESTON_CONFIG_FILE_ENV_VAR, "", 1); - - return 0; -} - -static void -handle_exit(struct weston_compositor *c) -{ - wl_display_terminate(c->wl_display); -} - -static void -wet_output_set_scale(struct weston_output *output, - struct weston_config_section *section, - int32_t default_scale, - int32_t parsed_scale) -{ - int32_t scale = default_scale; - - if (section) - weston_config_section_get_int(section, "scale", &scale, default_scale); - - if (parsed_scale) - scale = parsed_scale; - - weston_output_set_scale(output, scale); -} - -/* UINT32_MAX is treated as invalid because 0 is a valid - * enumeration value and the parameter is unsigned - */ -static int -wet_output_set_transform(struct weston_output *output, - struct weston_config_section *section, - uint32_t default_transform, - uint32_t parsed_transform) -{ - char *t = NULL; - uint32_t transform = default_transform; - - if (section) { - weston_config_section_get_string(section, - "transform", &t, NULL); - } - - if (t) { - if (weston_parse_transform(t, &transform) < 0) { - weston_log("Invalid transform \"%s\" for output %s\n", - t, output->name); - return -1; - } - free(t); - } - - if (parsed_transform != UINT32_MAX) - transform = parsed_transform; - - weston_output_set_transform(output, transform); - - return 0; -} - -static void -allow_content_protection(struct weston_output *output, - struct weston_config_section *section) -{ - bool allow_hdcp = true; - - if (section) - weston_config_section_get_bool(section, "allow_hdcp", - &allow_hdcp, true); - - weston_output_allow_protection(output, allow_hdcp); -} - -static int -wet_configure_windowed_output_from_config(struct weston_output *output, - struct wet_output_config *defaults) -{ - const struct weston_windowed_output_api *api = - weston_windowed_output_get_api(output->compositor); - - struct weston_config *wc = wet_get_config(output->compositor); - struct weston_config_section *section = NULL; - struct wet_compositor *compositor = to_wet_compositor(output->compositor); - struct wet_output_config *parsed_options = compositor->parsed_options; - int width = defaults->width; - int height = defaults->height; - - assert(parsed_options); - - if (!api) { - weston_log("Cannot use weston_windowed_output_api.\n"); - return -1; - } - - section = weston_config_get_section(wc, "output", "name", output->name); - - if (section) { - char *mode; - - weston_config_section_get_string(section, "mode", &mode, NULL); - if (!mode || sscanf(mode, "%dx%d", &width, - &height) != 2) { - weston_log("Invalid mode for output %s. Using defaults.\n", - output->name); - width = defaults->width; - height = defaults->height; - } - free(mode); - } - - allow_content_protection(output, section); - - if (parsed_options->width) - width = parsed_options->width; - - if (parsed_options->height) - height = parsed_options->height; - - wet_output_set_scale(output, section, defaults->scale, parsed_options->scale); - if (wet_output_set_transform(output, section, defaults->transform, - parsed_options->transform) < 0) { - return -1; - } - - if (api->output_set_size(output, width, height) < 0) { - weston_log("Cannot configure output \"%s\" using weston_windowed_output_api.\n", - output->name); - return -1; - } - - return 0; -} - -static int -count_remaining_heads(struct weston_output *output, struct weston_head *to_go) -{ - struct weston_head *iter = NULL; - int n = 0; - - while ((iter = weston_output_iterate_heads(output, iter))) { - if (iter != to_go) - n++; - } - - return n; -} - -static void -wet_head_tracker_destroy(struct wet_head_tracker *track) -{ - wl_list_remove(&track->head_destroy_listener.link); - free(track); -} - -static void -handle_head_destroy(struct wl_listener *listener, void *data) -{ - struct weston_head *head = data; - struct weston_output *output; - struct wet_head_tracker *track = - container_of(listener, struct wet_head_tracker, - head_destroy_listener); - - wet_head_tracker_destroy(track); - - output = weston_head_get_output(head); - - /* On shutdown path, the output might be already gone. */ - if (!output) - return; - - if (count_remaining_heads(output, head) > 0) - return; - - weston_output_destroy(output); -} - -static struct wet_head_tracker * -wet_head_tracker_from_head(struct weston_head *head) -{ - struct wl_listener *lis; - - lis = weston_head_get_destroy_listener(head, handle_head_destroy); - if (!lis) - return NULL; - - return container_of(lis, struct wet_head_tracker, - head_destroy_listener); -} - -/* Listen for head destroy signal. - * - * If a head is destroyed and it was the last head on the output, we - * destroy the associated output. - * - * Do not bother destroying the head trackers on shutdown, the backend will - * destroy the heads which calls our handler to destroy the trackers. - */ -static void -wet_head_tracker_create(struct wet_compositor *compositor, - struct weston_head *head) -{ - struct wet_head_tracker *track; - - track = zalloc(sizeof *track); - if (!track) - return; - - track->head_destroy_listener.notify = handle_head_destroy; - weston_head_add_destroy_listener(head, &track->head_destroy_listener); -} - -static void -simple_head_enable(struct wet_compositor *wet, struct weston_head *head) -{ - struct weston_output *output; - int ret = 0; - - output = weston_compositor_create_output_with_head(wet->compositor, - head); - if (!output) { - weston_log("Could not create an output for head \"%s\".\n", - weston_head_get_name(head)); - wet->init_failed = true; - - return; - } - - if (wet->simple_output_configure) - ret = wet->simple_output_configure(output); - if (ret < 0) { - weston_log("Cannot configure output \"%s\".\n", - weston_head_get_name(head)); - weston_output_destroy(output); - wet->init_failed = true; - - return; - } - - if (weston_output_enable(output) < 0) { - weston_log("Enabling output \"%s\" failed.\n", - weston_head_get_name(head)); - weston_output_destroy(output); - wet->init_failed = true; - - return; - } - - wet_head_tracker_create(wet, head); - - /* The weston_compositor will track and destroy the output on exit. */ -} - -static void -simple_head_disable(struct weston_head *head) -{ - struct weston_output *output; - struct wet_head_tracker *track; - - track = wet_head_tracker_from_head(head); - if (track) - wet_head_tracker_destroy(track); - - output = weston_head_get_output(head); - assert(output); - weston_output_destroy(output); -} - -static void -simple_heads_changed(struct wl_listener *listener, void *arg) -{ - struct weston_compositor *compositor = arg; - struct wet_compositor *wet = to_wet_compositor(compositor); - struct weston_head *head = NULL; - bool connected; - bool enabled; - bool changed; - bool non_desktop; - - while ((head = weston_compositor_iterate_heads(wet->compositor, head))) { - connected = weston_head_is_connected(head); - enabled = weston_head_is_enabled(head); - changed = weston_head_is_device_changed(head); - non_desktop = weston_head_is_non_desktop(head); - - if (connected && !enabled && !non_desktop) { - simple_head_enable(wet, head); - } else if (!connected && enabled) { - simple_head_disable(head); - } else if (enabled && changed) { - weston_log("Detected a monitor change on head '%s', " - "not bothering to do anything about it.\n", - weston_head_get_name(head)); - } - weston_head_reset_device_changed(head); - } -} - -static void -wet_set_simple_head_configurator(struct weston_compositor *compositor, - int (*fn)(struct weston_output *)) -{ - struct wet_compositor *wet = to_wet_compositor(compositor); - - wet->simple_output_configure = fn; - - wet->heads_changed_listener.notify = simple_heads_changed; - weston_compositor_add_heads_changed_listener(compositor, - &wet->heads_changed_listener); -} - -static void -configure_input_device_accel(struct weston_config_section *s, - struct libinput_device *device) -{ - char *profile_string = NULL; - int is_a_profile = 1; - uint32_t profiles; - enum libinput_config_accel_profile profile; - double speed; - - if (weston_config_section_get_string(s, "accel-profile", - &profile_string, NULL) == 0) { - if (strcmp(profile_string, "flat") == 0) - profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; - else if (strcmp(profile_string, "adaptive") == 0) - profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; - else { - weston_log("warning: no such accel-profile: %s\n", - profile_string); - is_a_profile = 0; - } - - profiles = libinput_device_config_accel_get_profiles(device); - if (is_a_profile && (profile & profiles) != 0) { - weston_log(" accel-profile=%s\n", - profile_string); - libinput_device_config_accel_set_profile(device, - profile); - } - } - - if (weston_config_section_get_double(s, "accel-speed", - &speed, 0) == 0 && - speed >= -1. && speed <= 1.) { - weston_log(" accel-speed=%.3f\n", speed); - libinput_device_config_accel_set_speed(device, speed); - } - - free(profile_string); -} - -static void -configure_input_device_scroll(struct weston_config_section *s, - struct libinput_device *device) -{ - bool natural; - char *method_string = NULL; - uint32_t methods; - enum libinput_config_scroll_method method; - char *button_string = NULL; - int button; - - if (libinput_device_config_scroll_has_natural_scroll(device) && - weston_config_section_get_bool(s, "natural-scroll", - &natural, false) == 0) { - weston_log(" natural-scroll=%s\n", - natural ? "true" : "false"); - libinput_device_config_scroll_set_natural_scroll_enabled( - device, natural); - } - - if (weston_config_section_get_string(s, "scroll-method", - &method_string, NULL) != 0) - goto done; - if (strcmp(method_string, "two-finger") == 0) - method = LIBINPUT_CONFIG_SCROLL_2FG; - else if (strcmp(method_string, "edge") == 0) - method = LIBINPUT_CONFIG_SCROLL_EDGE; - else if (strcmp(method_string, "button") == 0) - method = LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; - else if (strcmp(method_string, "none") == 0) - method = LIBINPUT_CONFIG_SCROLL_NO_SCROLL; - else { - weston_log("warning: no such scroll-method: %s\n", - method_string); - goto done; - } - - methods = libinput_device_config_scroll_get_methods(device); - if (method != LIBINPUT_CONFIG_SCROLL_NO_SCROLL && - (method & methods) == 0) - goto done; - - weston_log(" scroll-method=%s\n", method_string); - libinput_device_config_scroll_set_method(device, method); - - if (method == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) { - if (weston_config_section_get_string(s, "scroll-button", - &button_string, - NULL) != 0) - goto done; - - button = libevdev_event_code_from_name(EV_KEY, button_string); - if (button == -1) { - weston_log(" Bad scroll-button: %s\n", - button_string); - goto done; - } - - weston_log(" scroll-button=%s\n", button_string); - libinput_device_config_scroll_set_button(device, button); - } - -done: - free(method_string); - free(button_string); -} - -static void -configure_input_device(struct weston_compositor *compositor, - struct libinput_device *device) -{ - struct weston_config_section *s; - struct weston_config *config = wet_get_config(compositor); - bool has_enable_tap = false; - bool enable_tap; - bool disable_while_typing; - bool middle_emulation; - bool tap_and_drag; - bool tap_and_drag_lock; - bool left_handed; - unsigned int rotation; - - weston_log("libinput: configuring device \"%s\".\n", - libinput_device_get_name(device)); - - s = weston_config_get_section(config, - "libinput", NULL, NULL); - - if (libinput_device_config_tap_get_finger_count(device) > 0) { - if (weston_config_section_get_bool(s, "enable_tap", - &enable_tap, false) == 0) { - weston_log("!!DEPRECATION WARNING!!: In weston.ini, " - "enable_tap is deprecated in favour of " - "enable-tap. Support for it may be removed " - "at any time!"); - has_enable_tap = true; - } - if (weston_config_section_get_bool(s, "enable-tap", - &enable_tap, false) == 0) - has_enable_tap = true; - if (has_enable_tap) { - weston_log(" enable-tap=%s.\n", - enable_tap ? "true" : "false"); - libinput_device_config_tap_set_enabled(device, - enable_tap); - } - if (weston_config_section_get_bool(s, "tap-and-drag", - &tap_and_drag, false) == 0) { - weston_log(" tap-and-drag=%s.\n", - tap_and_drag ? "true" : "false"); - libinput_device_config_tap_set_drag_enabled(device, - tap_and_drag); - } - if (weston_config_section_get_bool(s, "tap-and-drag-lock", - &tap_and_drag_lock, false) == 0) { - weston_log(" tap-and-drag-lock=%s.\n", - tap_and_drag_lock ? "true" : "false"); - libinput_device_config_tap_set_drag_lock_enabled( - device, tap_and_drag_lock); - } - } - - if (libinput_device_config_dwt_is_available(device) && - weston_config_section_get_bool(s, "disable-while-typing", - &disable_while_typing, false) == 0) { - weston_log(" disable-while-typing=%s.\n", - disable_while_typing ? "true" : "false"); - libinput_device_config_dwt_set_enabled(device, - disable_while_typing); - } - - if (libinput_device_config_middle_emulation_is_available(device) && - weston_config_section_get_bool(s, "middle-button-emulation", - &middle_emulation, false) == 0) { - weston_log(" middle-button-emulation=%s\n", - middle_emulation ? "true" : "false"); - libinput_device_config_middle_emulation_set_enabled( - device, middle_emulation); - } - - if (libinput_device_config_left_handed_is_available(device) && - weston_config_section_get_bool(s, "left-handed", - &left_handed, false) == 0) { - weston_log(" left-handed=%s\n", - left_handed ? "true" : "false"); - libinput_device_config_left_handed_set(device, left_handed); - } - - if (libinput_device_config_rotation_is_available(device) && - weston_config_section_get_uint(s, "rotation", - &rotation, false) == 0) { - weston_log(" rotation=%u\n", rotation); - libinput_device_config_rotation_set_angle(device, rotation); - } - - if (libinput_device_config_accel_is_available(device)) - configure_input_device_accel(s, device); - - configure_input_device_scroll(s, device); -} - -static int -drm_backend_output_configure(struct weston_output *output, - struct weston_config_section *section) -{ - struct wet_compositor *wet = to_wet_compositor(output->compositor); - const struct weston_drm_output_api *api; - enum weston_drm_backend_output_mode mode = - WESTON_DRM_BACKEND_OUTPUT_PREFERRED; - uint32_t transform = WL_OUTPUT_TRANSFORM_NORMAL; - char *s; - char *modeline = NULL; - char *gbm_format = NULL; - char *seat = NULL; - - api = weston_drm_output_get_api(output->compositor); - if (!api) { - weston_log("Cannot use weston_drm_output_api.\n"); - return -1; - } - - weston_config_section_get_string(section, "mode", &s, "preferred"); - - if (strcmp(s, "off") == 0) { - assert(0 && "off was supposed to be pruned"); - return -1; - } else if (wet->drm_use_current_mode || strcmp(s, "current") == 0) { - mode = WESTON_DRM_BACKEND_OUTPUT_CURRENT; - } else if (strcmp(s, "preferred") != 0) { - modeline = s; - s = NULL; - } - free(s); - - if (api->set_mode(output, mode, modeline) < 0) { - weston_log("Cannot configure an output using weston_drm_output_api.\n"); - free(modeline); - return -1; - } - free(modeline); - - if (count_remaining_heads(output, NULL) == 1) { - struct weston_head *head = weston_output_get_first_head(output); - transform = weston_head_get_transform(head); - } - - wet_output_set_scale(output, section, 1, 0); - if (wet_output_set_transform(output, section, transform, - UINT32_MAX) < 0) { - return -1; - } - - weston_config_section_get_string(section, - "gbm-format", &gbm_format, NULL); - - api->set_gbm_format(output, gbm_format); - free(gbm_format); - - weston_config_section_get_string(section, "seat", &seat, ""); - - api->set_seat(output, seat); - free(seat); - - allow_content_protection(output, section); - - return 0; -} - -/* Find the output section to use for configuring the output with the - * named head. If an output section with the given name contains - * a "same-as" key, ignore all other settings in the output section and - * instead find an output section named by the "same-as". Do this - * recursively. - */ -static struct weston_config_section * -drm_config_find_controlling_output_section(struct weston_config *config, - const char *head_name) -{ - struct weston_config_section *section; - char *same_as; - int depth = 0; - - same_as = strdup(head_name); - do { - section = weston_config_get_section(config, "output", - "name", same_as); - if (!section && depth > 0) - weston_log("Configuration error: " - "output section referred to with " - "'same-as=%s' not found.\n", same_as); - - free(same_as); - - if (!section) - return NULL; - - if (++depth > 10) { - weston_log("Configuration error: " - "'same-as' nested too deep for output '%s'.\n", - head_name); - return NULL; - } - - weston_config_section_get_string(section, "same-as", - &same_as, NULL); - } while (same_as); - - return section; -} - -static struct wet_layoutput * -wet_compositor_create_layoutput(struct wet_compositor *compositor, - const char *name, - struct weston_config_section *section) -{ - struct wet_layoutput *lo; - - lo = zalloc(sizeof *lo); - if (!lo) - return NULL; - - lo->compositor = compositor; - wl_list_insert(compositor->layoutput_list.prev, &lo->compositor_link); - wl_list_init(&lo->output_list); - lo->name = strdup(name); - lo->section = section; - - return lo; -} - -static void -wet_layoutput_destroy(struct wet_layoutput *lo) -{ - wl_list_remove(&lo->compositor_link); - assert(wl_list_empty(&lo->output_list)); - free(lo->name); - free(lo); -} - -static void -wet_output_handle_destroy(struct wl_listener *listener, void *data) -{ - struct wet_output *output; - - output = wl_container_of(listener, output, output_destroy_listener); - assert(output->output == data); - - output->output = NULL; - wl_list_remove(&output->output_destroy_listener.link); -} - -static struct wet_output * -wet_layoutput_create_output(struct wet_layoutput *lo, const char *name) -{ - struct wet_output *output; - - output = zalloc(sizeof *output); - if (!output) - return NULL; - - output->output = - weston_compositor_create_output(lo->compositor->compositor, - name); - if (!output->output) { - free(output); - return NULL; - } - - output->layoutput = lo; - wl_list_insert(lo->output_list.prev, &output->link); - output->output_destroy_listener.notify = wet_output_handle_destroy; - weston_output_add_destroy_listener(output->output, - &output->output_destroy_listener); - - return output; -} - -static struct wet_output * -wet_output_from_weston_output(struct weston_output *base) -{ - struct wl_listener *lis; - - lis = weston_output_get_destroy_listener(base, - wet_output_handle_destroy); - if (!lis) - return NULL; - - return container_of(lis, struct wet_output, output_destroy_listener); -} - -static void -wet_output_destroy(struct wet_output *output) -{ - if (output->output) { - /* output->output destruction may be deferred in some cases (see - * drm_output_destroy()), so we need to forcibly trigger the - * destruction callback now, or otherwise would later access - * data that we are about to free - */ - struct weston_output *save = output->output; - wet_output_handle_destroy(&output->output_destroy_listener, save); - weston_output_destroy(save); - } - - wl_list_remove(&output->link); - free(output); -} - -static struct wet_layoutput * -wet_compositor_find_layoutput(struct wet_compositor *wet, const char *name) -{ - struct wet_layoutput *lo; - - wl_list_for_each(lo, &wet->layoutput_list, compositor_link) - if (strcmp(lo->name, name) == 0) - return lo; - - return NULL; -} - -static void -wet_compositor_layoutput_add_head(struct wet_compositor *wet, - const char *output_name, - struct weston_config_section *section, - struct weston_head *head) -{ - struct wet_layoutput *lo; - - lo = wet_compositor_find_layoutput(wet, output_name); - if (!lo) { - lo = wet_compositor_create_layoutput(wet, output_name, section); - if (!lo) - return; - } - - if (lo->add.n + 1 >= ARRAY_LENGTH(lo->add.heads)) - return; - - lo->add.heads[lo->add.n++] = head; -} - -static void -wet_compositor_destroy_layout(struct wet_compositor *wet) -{ - struct wet_layoutput *lo, *lo_tmp; - struct wet_output *output, *output_tmp; - - wl_list_for_each_safe(lo, lo_tmp, - &wet->layoutput_list, compositor_link) { - wl_list_for_each_safe(output, output_tmp, - &lo->output_list, link) { - wet_output_destroy(output); - } - wet_layoutput_destroy(lo); - } -} - -static void -drm_head_prepare_enable(struct wet_compositor *wet, - struct weston_head *head) -{ - const char *name = weston_head_get_name(head); - struct weston_config_section *section; - char *output_name = NULL; - char *mode = NULL; - - section = drm_config_find_controlling_output_section(wet->config, name); - if (section) { - /* skip outputs that are explicitly off, or non-desktop and not - * explicitly enabled. The backend turns them off automatically. - */ - weston_config_section_get_string(section, "mode", &mode, NULL); - if (mode && strcmp(mode, "off") == 0) { - free(mode); - return; - } - if (!mode && weston_head_is_non_desktop(head)) - return; - free(mode); - - weston_config_section_get_string(section, "name", - &output_name, NULL); - assert(output_name); - - wet_compositor_layoutput_add_head(wet, output_name, - section, head); - free(output_name); - } else { - wet_compositor_layoutput_add_head(wet, name, NULL, head); - } -} - -static bool -drm_head_should_force_enable(struct wet_compositor *wet, - struct weston_head *head) -{ - const char *name = weston_head_get_name(head); - struct weston_config_section *section; - bool force; - - section = drm_config_find_controlling_output_section(wet->config, name); - if (!section) - return false; - - weston_config_section_get_bool(section, "force-on", &force, false); - return force; -} - -static void -drm_try_attach(struct weston_output *output, - struct wet_head_array *add, - struct wet_head_array *failed) -{ - unsigned i; - - /* try to attach all heads, this probably succeeds */ - for (i = 0; i < add->n; i++) { - if (!add->heads[i]) - continue; - - if (weston_output_attach_head(output, add->heads[i]) < 0) { - assert(failed->n < ARRAY_LENGTH(failed->heads)); - - failed->heads[failed->n++] = add->heads[i]; - add->heads[i] = NULL; - } - } -} - -static int -drm_try_enable(struct weston_output *output, - struct wet_head_array *undo, - struct wet_head_array *failed) -{ - /* Try to enable, and detach heads one by one until it succeeds. */ - while (!output->enabled) { - if (weston_output_enable(output) == 0) - return 0; - - /* the next head to drop */ - while (undo->n > 0 && undo->heads[--undo->n] == NULL) - ; - - /* No heads left to undo and failed to enable. */ - if (undo->heads[undo->n] == NULL) - return -1; - - assert(failed->n < ARRAY_LENGTH(failed->heads)); - - /* undo one head */ - weston_head_detach(undo->heads[undo->n]); - failed->heads[failed->n++] = undo->heads[undo->n]; - undo->heads[undo->n] = NULL; - } - - return 0; -} - -static int -drm_try_attach_enable(struct weston_output *output, struct wet_layoutput *lo) -{ - struct wet_head_array failed = {}; - unsigned i; - - assert(!output->enabled); - - drm_try_attach(output, &lo->add, &failed); - if (drm_backend_output_configure(output, lo->section) < 0) - return -1; - - if (drm_try_enable(output, &lo->add, &failed) < 0) - return -1; - - /* For all successfully attached/enabled heads */ - for (i = 0; i < lo->add.n; i++) - if (lo->add.heads[i]) - wet_head_tracker_create(lo->compositor, - lo->add.heads[i]); - - /* Push failed heads to the next round. */ - lo->add = failed; - - return 0; -} - -static int -drm_process_layoutput(struct wet_compositor *wet, struct wet_layoutput *lo) -{ - struct wet_output *output, *tmp; - char *name = NULL; - int ret; - - /* - * For each existing wet_output: - * try attach - * While heads left to enable: - * Create output - * try attach, try enable - */ - - wl_list_for_each_safe(output, tmp, &lo->output_list, link) { - struct wet_head_array failed = {}; - - if (!output->output) { - /* Clean up left-overs from destroyed heads. */ - wet_output_destroy(output); - continue; - } - - assert(output->output->enabled); - - drm_try_attach(output->output, &lo->add, &failed); - lo->add = failed; - if (lo->add.n == 0) - return 0; - } - - if (!weston_compositor_find_output_by_name(wet->compositor, lo->name)) - name = strdup(lo->name); - - while (lo->add.n > 0) { - if (!wl_list_empty(&lo->output_list)) { - weston_log("Error: independent-CRTC clone mode is not implemented.\n"); - return -1; - } - - if (!name) { - ret = asprintf(&name, "%s:%s", lo->name, - weston_head_get_name(lo->add.heads[0])); - if (ret < 0) - return -1; - } - output = wet_layoutput_create_output(lo, name); - free(name); - name = NULL; - - if (!output) - return -1; - - if (drm_try_attach_enable(output->output, lo) < 0) { - wet_output_destroy(output); - return -1; - } - } - - return 0; -} - -static int -drm_process_layoutputs(struct wet_compositor *wet) -{ - struct wet_layoutput *lo; - int ret = 0; - - wl_list_for_each(lo, &wet->layoutput_list, compositor_link) { - if (lo->add.n == 0) - continue; - - if (drm_process_layoutput(wet, lo) < 0) { - lo->add = (struct wet_head_array){}; - ret = -1; - } - } - - return ret; -} - -static void -drm_head_disable(struct weston_head *head) -{ - struct weston_output *output_base; - struct wet_output *output; - struct wet_head_tracker *track; - - track = wet_head_tracker_from_head(head); - if (track) - wet_head_tracker_destroy(track); - - output_base = weston_head_get_output(head); - assert(output_base); - output = wet_output_from_weston_output(output_base); - assert(output && output->output == output_base); - - weston_head_detach(head); - if (count_remaining_heads(output->output, NULL) == 0) - wet_output_destroy(output); -} - -static void -drm_heads_changed(struct wl_listener *listener, void *arg) -{ - struct weston_compositor *compositor = arg; - struct wet_compositor *wet = to_wet_compositor(compositor); - struct weston_head *head = NULL; - bool connected; - bool enabled; - bool changed; - bool forced; - - /* We need to collect all cloned heads into outputs before enabling the - * output. - */ - while ((head = weston_compositor_iterate_heads(compositor, head))) { - connected = weston_head_is_connected(head); - enabled = weston_head_is_enabled(head); - changed = weston_head_is_device_changed(head); - forced = drm_head_should_force_enable(wet, head); - - if ((connected || forced) && !enabled) { - drm_head_prepare_enable(wet, head); - } else if (!(connected || forced) && enabled) { - drm_head_disable(head); - } else if (enabled && changed) { - weston_log("Detected a monitor change on head '%s', " - "not bothering to do anything about it.\n", - weston_head_get_name(head)); - } - weston_head_reset_device_changed(head); - } - - if (drm_process_layoutputs(wet) < 0) - wet->init_failed = true; -} - -static int -drm_backend_remoted_output_configure(struct weston_output *output, - struct weston_config_section *section, - char *modeline, - const struct weston_remoting_api *api) -{ - char *gbm_format = NULL; - char *seat = NULL; - char *host = NULL; - char *pipeline = NULL; - int port, ret; - - ret = api->set_mode(output, modeline); - if (ret < 0) { - weston_log("Cannot configure an output \"%s\" using " - "weston_remoting_api. Invalid mode\n", - output->name); - return -1; - } - - wet_output_set_scale(output, section, 1, 0); - if (wet_output_set_transform(output, section, - WL_OUTPUT_TRANSFORM_NORMAL, - UINT32_MAX) < 0) { - return -1; - }; - - weston_config_section_get_string(section, "gbm-format", &gbm_format, - NULL); - api->set_gbm_format(output, gbm_format); - free(gbm_format); - - weston_config_section_get_string(section, "seat", &seat, ""); - - api->set_seat(output, seat); - free(seat); - - weston_config_section_get_string(section, "gst-pipeline", &pipeline, - NULL); - if (pipeline) { - api->set_gst_pipeline(output, pipeline); - free(pipeline); - return 0; - } - - weston_config_section_get_string(section, "host", &host, NULL); - weston_config_section_get_int(section, "port", &port, 0); - if (!host || port <= 0 || 65533 < port) { - weston_log("Cannot configure an output \"%s\". " - "Need to specify gst-pipeline or " - "host and port (1-65533).\n", output->name); - } - api->set_host(output, host); - free(host); - api->set_port(output, port); - - return 0; -} - -static void -remoted_output_init(struct weston_compositor *c, - struct weston_config_section *section, - const struct weston_remoting_api *api) -{ - struct weston_output *output = NULL; - char *output_name, *modeline = NULL; - int ret; - - weston_config_section_get_string(section, "name", &output_name, - NULL); - if (!output_name) - return; - - weston_config_section_get_string(section, "mode", &modeline, "off"); - if (strcmp(modeline, "off") == 0) - goto err; - - output = api->create_output(c, output_name); - if (!output) { - weston_log("Cannot create remoted output \"%s\".\n", - output_name); - goto err; - } - - ret = drm_backend_remoted_output_configure(output, section, modeline, - api); - if (ret < 0) { - weston_log("Cannot configure remoted output \"%s\".\n", - output_name); - goto err; - } - - if (weston_output_enable(output) < 0) { - weston_log("Enabling remoted output \"%s\" failed.\n", - output_name); - goto err; - } - - free(modeline); - free(output_name); - weston_log("remoted output '%s' enabled\n", output->name); - return; - -err: - free(modeline); - free(output_name); - if (output) - weston_output_destroy(output); -} - -static void -load_remoting(struct weston_compositor *c, struct weston_config *wc) -{ - const struct weston_remoting_api *api = NULL; - int (*module_init)(struct weston_compositor *ec); - struct weston_config_section *section = NULL; - const char *section_name; - - /* read remote-output section in weston.ini */ - while (weston_config_next_section(wc, §ion, §ion_name)) { - if (strcmp(section_name, "remote-output")) - continue; - - if (!api) { - char *module_name; - struct weston_config_section *core_section = - weston_config_get_section(wc, "core", NULL, - NULL); - - weston_config_section_get_string(core_section, - "remoting", - &module_name, - "remoting-plugin.so"); - module_init = weston_load_module(module_name, - "weston_module_init"); - free(module_name); - if (!module_init) { - weston_log("Can't load remoting-plugin\n"); - return; - } - if (module_init(c) < 0) { - weston_log("Remoting-plugin init failed\n"); - return; - } - - api = weston_remoting_get_api(c); - if (!api) - return; - } - - remoted_output_init(c, section, api); - } -} - -static int -drm_backend_pipewire_output_configure(struct weston_output *output, - struct weston_config_section *section, - char *modeline, - const struct weston_pipewire_api *api) -{ - char *seat = NULL; - int ret; - - ret = api->set_mode(output, modeline); - if (ret < 0) { - weston_log("Cannot configure an output \"%s\" using " - "weston_pipewire_api. Invalid mode\n", - output->name); - return -1; - } - - wet_output_set_scale(output, section, 1, 0); - if (wet_output_set_transform(output, section, - WL_OUTPUT_TRANSFORM_NORMAL, - UINT32_MAX) < 0) { - return -1; - } - - weston_config_section_get_string(section, "seat", &seat, ""); - - api->set_seat(output, seat); - free(seat); - - return 0; -} - -static void -pipewire_output_init(struct weston_compositor *c, - struct weston_config_section *section, - const struct weston_pipewire_api *api) -{ - struct weston_output *output = NULL; - char *output_name, *modeline = NULL; - int ret; - - weston_config_section_get_string(section, "name", &output_name, - NULL); - if (!output_name) - return; - - weston_config_section_get_string(section, "mode", &modeline, "off"); - if (strcmp(modeline, "off") == 0) - goto err; - - output = api->create_output(c, output_name); - if (!output) { - weston_log("Cannot create pipewire output \"%s\".\n", - output_name); - goto err; - } - - ret = drm_backend_pipewire_output_configure(output, section, modeline, - api); - if (ret < 0) { - weston_log("Cannot configure pipewire output \"%s\".\n", - output_name); - goto err; - } - - if (weston_output_enable(output) < 0) { - weston_log("Enabling pipewire output \"%s\" failed.\n", - output_name); - goto err; - } - - free(modeline); - free(output_name); - weston_log("pipewire output '%s' enabled\n", output->name); - return; - -err: - free(modeline); - free(output_name); - if (output) - weston_output_destroy(output); -} - -static void -load_pipewire(struct weston_compositor *c, struct weston_config *wc) -{ - const struct weston_pipewire_api *api = NULL; - int (*module_init)(struct weston_compositor *ec); - struct weston_config_section *section = NULL; - const char *section_name; - - /* read pipewire-output section in weston.ini */ - while (weston_config_next_section(wc, §ion, §ion_name)) { - if (strcmp(section_name, "pipewire-output")) - continue; - - if (!api) { - char *module_name; - struct weston_config_section *core_section = - weston_config_get_section(wc, "core", NULL, - NULL); - - weston_config_section_get_string(core_section, - "pipewire", - &module_name, - "pipewire-plugin.so"); - module_init = weston_load_module(module_name, - "weston_module_init"); - free(module_name); - if (!module_init) { - weston_log("Can't load pipewire-plugin\n"); - return; - } - if (module_init(c) < 0) { - weston_log("Pipewire-plugin init failed\n"); - return; - } - - api = weston_pipewire_get_api(c); - if (!api) - return; - } - - pipewire_output_init(c, section, api); - } -} - -static int -load_drm_backend(struct weston_compositor *c, - int *argc, char **argv, struct weston_config *wc) -{ - struct weston_drm_backend_config config = {{ 0, }}; - struct weston_config_section *section; - struct wet_compositor *wet = to_wet_compositor(c); - int ret = 0; - - wet->drm_use_current_mode = false; - - section = weston_config_get_section(wc, "core", NULL, NULL); - weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, - false); - - const struct weston_option options[] = { - { WESTON_OPTION_STRING, "seat", 0, &config.seat_id }, - { WESTON_OPTION_INTEGER, "tty", 0, &config.tty }, - { WESTON_OPTION_STRING, "drm-device", 0, &config.specific_device }, - { WESTON_OPTION_BOOLEAN, "current-mode", 0, &wet->drm_use_current_mode }, - { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, - { WESTON_OPTION_BOOLEAN, "continue-without-input", 0, &config.continue_without_input }, - }; - - parse_options(options, ARRAY_LENGTH(options), argc, argv); - - section = weston_config_get_section(wc, "core", NULL, NULL); - weston_config_section_get_string(section, - "gbm-format", &config.gbm_format, - NULL); - weston_config_section_get_uint(section, "pageflip-timeout", - &config.pageflip_timeout, 0); - weston_config_section_get_bool(section, "pixman-shadow", - &config.use_pixman_shadow, true); - - config.base.struct_version = WESTON_DRM_BACKEND_CONFIG_VERSION; - config.base.struct_size = sizeof(struct weston_drm_backend_config); - config.configure_device = configure_input_device; - - wet->heads_changed_listener.notify = drm_heads_changed; - weston_compositor_add_heads_changed_listener(c, - &wet->heads_changed_listener); - - ret = weston_compositor_load_backend(c, WESTON_BACKEND_DRM, - &config.base); - - /* remoting */ - load_remoting(c, wc); - - /* pipewire */ - load_pipewire(c, wc); - - free(config.gbm_format); - free(config.seat_id); - - return ret; -} - -static int -headless_backend_output_configure(struct weston_output *output) -{ - struct wet_output_config defaults = { - .width = 1024, - .height = 640, - .scale = 1, - .transform = WL_OUTPUT_TRANSFORM_NORMAL - }; - - return wet_configure_windowed_output_from_config(output, &defaults); -} - -static int -load_headless_backend(struct weston_compositor *c, - int *argc, char **argv, struct weston_config *wc) -{ - const struct weston_windowed_output_api *api; - struct weston_headless_backend_config config = {{ 0, }}; - struct weston_config_section *section; - bool no_outputs = false; - int ret = 0; - char *transform = NULL; - - struct wet_output_config *parsed_options = wet_init_parsed_options(c); - if (!parsed_options) - return -1; - - section = weston_config_get_section(wc, "core", NULL, NULL); - weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, - false); - weston_config_section_get_bool(section, "use-gl", &config.use_gl, - false); - - const struct weston_option options[] = { - { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, - { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, - { WESTON_OPTION_INTEGER, "scale", 0, &parsed_options->scale }, - { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, - { WESTON_OPTION_BOOLEAN, "use-gl", 0, &config.use_gl }, - { WESTON_OPTION_STRING, "transform", 0, &transform }, - { WESTON_OPTION_BOOLEAN, "no-outputs", 0, &no_outputs }, - }; - - parse_options(options, ARRAY_LENGTH(options), argc, argv); - - if (transform) { - if (weston_parse_transform(transform, &parsed_options->transform) < 0) { - weston_log("Invalid transform \"%s\"\n", transform); - return -1; - } - free(transform); - } - - config.base.struct_version = WESTON_HEADLESS_BACKEND_CONFIG_VERSION; - config.base.struct_size = sizeof(struct weston_headless_backend_config); - - wet_set_simple_head_configurator(c, headless_backend_output_configure); - - /* load the actual wayland backend and configure it */ - ret = weston_compositor_load_backend(c, WESTON_BACKEND_HEADLESS, - &config.base); - - if (ret < 0) - return ret; - - if (!no_outputs) { - api = weston_windowed_output_get_api(c); - - if (!api) { - weston_log("Cannot use weston_windowed_output_api.\n"); - return -1; - } - - if (api->create_head(c, "headless") < 0) - return -1; - } - - return 0; -} - -static int -rdp_backend_output_configure(struct weston_output *output) -{ - struct wet_compositor *compositor = to_wet_compositor(output->compositor); - struct wet_output_config *parsed_options = compositor->parsed_options; - const struct weston_rdp_output_api *api = weston_rdp_output_get_api(output->compositor); - int width = 640; - int height = 480; - - assert(parsed_options); - - if (!api) { - weston_log("Cannot use weston_rdp_output_api.\n"); - return -1; - } - - if (parsed_options->width) - width = parsed_options->width; - - if (parsed_options->height) - height = parsed_options->height; - - weston_output_set_scale(output, 1); - weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); - - if (api->output_set_size(output, width, height) < 0) { - weston_log("Cannot configure output \"%s\" using weston_rdp_output_api.\n", - output->name); - return -1; - } - - return 0; -} - -static void -weston_rdp_backend_config_init(struct weston_rdp_backend_config *config) -{ - config->base.struct_version = WESTON_RDP_BACKEND_CONFIG_VERSION; - config->base.struct_size = sizeof(struct weston_rdp_backend_config); - - config->bind_address = NULL; - config->port = 3389; - config->rdp_key = NULL; - config->server_cert = NULL; - config->server_key = NULL; - config->env_socket = 0; - config->no_clients_resize = 0; - config->force_no_compression = 0; -} - -static int -load_rdp_backend(struct weston_compositor *c, - int *argc, char *argv[], struct weston_config *wc) -{ - struct weston_rdp_backend_config config = {{ 0, }}; - int ret = 0; - - struct wet_output_config *parsed_options = wet_init_parsed_options(c); - if (!parsed_options) - return -1; - - weston_rdp_backend_config_init(&config); - - const struct weston_option rdp_options[] = { - { WESTON_OPTION_BOOLEAN, "env-socket", 0, &config.env_socket }, - { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, - { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, - { WESTON_OPTION_STRING, "address", 0, &config.bind_address }, - { WESTON_OPTION_INTEGER, "port", 0, &config.port }, - { WESTON_OPTION_BOOLEAN, "no-clients-resize", 0, &config.no_clients_resize }, - { 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 }, - { WESTON_OPTION_BOOLEAN, "force-no-compression", 0, &config.force_no_compression }, - }; - - parse_options(rdp_options, ARRAY_LENGTH(rdp_options), argc, argv); - - wet_set_simple_head_configurator(c, rdp_backend_output_configure); - - ret = weston_compositor_load_backend(c, WESTON_BACKEND_RDP, - &config.base); - - free(config.bind_address); - free(config.rdp_key); - free(config.server_cert); - free(config.server_key); - - return ret; -} - -static int -fbdev_backend_output_configure(struct weston_output *output) -{ - struct weston_config *wc = wet_get_config(output->compositor); - struct weston_config_section *section; - - section = weston_config_get_section(wc, "output", "name", "fbdev"); - - if (wet_output_set_transform(output, section, - WL_OUTPUT_TRANSFORM_NORMAL, - UINT32_MAX) < 0) { - return -1; - } - - weston_output_set_scale(output, 1); - - return 0; -} - -static int -load_fbdev_backend(struct weston_compositor *c, - int *argc, char **argv, struct weston_config *wc) -{ - struct weston_fbdev_backend_config config = {{ 0, }}; - int ret = 0; - - const struct weston_option fbdev_options[] = { - { WESTON_OPTION_INTEGER, "tty", 0, &config.tty }, - { WESTON_OPTION_STRING, "device", 0, &config.device }, - { WESTON_OPTION_STRING, "seat", 0, &config.seat_id }, - }; - - parse_options(fbdev_options, ARRAY_LENGTH(fbdev_options), argc, argv); - - config.base.struct_version = WESTON_FBDEV_BACKEND_CONFIG_VERSION; - config.base.struct_size = sizeof(struct weston_fbdev_backend_config); - config.configure_device = configure_input_device; - - wet_set_simple_head_configurator(c, fbdev_backend_output_configure); - - /* load the actual wayland backend and configure it */ - ret = weston_compositor_load_backend(c, WESTON_BACKEND_FBDEV, - &config.base); - - free(config.device); - return ret; -} - -static int -x11_backend_output_configure(struct weston_output *output) -{ - struct wet_output_config defaults = { - .width = 1024, - .height = 600, - .scale = 1, - .transform = WL_OUTPUT_TRANSFORM_NORMAL - }; - - return wet_configure_windowed_output_from_config(output, &defaults); -} - -static int -load_x11_backend(struct weston_compositor *c, - int *argc, char **argv, struct weston_config *wc) -{ - char *default_output; - const struct weston_windowed_output_api *api; - struct weston_x11_backend_config config = {{ 0, }}; - struct weston_config_section *section; - int ret = 0; - int option_count = 1; - int output_count = 0; - char const *section_name; - int i; - - struct wet_output_config *parsed_options = wet_init_parsed_options(c); - if (!parsed_options) - return -1; - - section = weston_config_get_section(wc, "core", NULL, NULL); - weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, - false); - - const struct weston_option options[] = { - { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, - { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, - { WESTON_OPTION_INTEGER, "scale", 0, &parsed_options->scale }, - { WESTON_OPTION_BOOLEAN, "fullscreen", 'f', &config.fullscreen }, - { WESTON_OPTION_INTEGER, "output-count", 0, &option_count }, - { WESTON_OPTION_BOOLEAN, "no-input", 0, &config.no_input }, - { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, - }; - - parse_options(options, ARRAY_LENGTH(options), argc, argv); - - config.base.struct_version = WESTON_X11_BACKEND_CONFIG_VERSION; - config.base.struct_size = sizeof(struct weston_x11_backend_config); - - wet_set_simple_head_configurator(c, x11_backend_output_configure); - - /* load the actual backend and configure it */ - ret = weston_compositor_load_backend(c, WESTON_BACKEND_X11, - &config.base); - - if (ret < 0) - return ret; - - api = weston_windowed_output_get_api(c); - - if (!api) { - weston_log("Cannot use weston_windowed_output_api.\n"); - return -1; - } - - section = NULL; - while (weston_config_next_section(wc, §ion, §ion_name)) { - char *output_name; - - if (output_count >= option_count) - break; - - if (strcmp(section_name, "output") != 0) { - continue; - } - - weston_config_section_get_string(section, "name", &output_name, NULL); - if (output_name == NULL || output_name[0] != 'X') { - free(output_name); - continue; - } - - if (api->create_head(c, output_name) < 0) { - free(output_name); - return -1; - } - free(output_name); - - output_count++; - } - - default_output = NULL; - - for (i = output_count; i < option_count; i++) { - if (asprintf(&default_output, "screen%d", i) < 0) { - return -1; - } - - if (api->create_head(c, default_output) < 0) { - free(default_output); - return -1; - } - free(default_output); - } - - return 0; -} - -static int -wayland_backend_output_configure(struct weston_output *output) -{ - struct wet_output_config defaults = { - .width = 1024, - .height = 640, - .scale = 1, - .transform = WL_OUTPUT_TRANSFORM_NORMAL - }; - - return wet_configure_windowed_output_from_config(output, &defaults); -} - -static int -load_wayland_backend(struct weston_compositor *c, - int *argc, char **argv, struct weston_config *wc) -{ - struct weston_wayland_backend_config config = {{ 0, }}; - struct weston_config_section *section; - const struct weston_windowed_output_api *api; - const char *section_name; - char *output_name = NULL; - int count = 1; - int ret = 0; - int i; - - struct wet_output_config *parsed_options = wet_init_parsed_options(c); - if (!parsed_options) - return -1; - - config.cursor_size = 32; - config.cursor_theme = NULL; - config.display_name = NULL; - - section = weston_config_get_section(wc, "core", NULL, NULL); - weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, - false); - - const struct weston_option wayland_options[] = { - { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, - { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, - { WESTON_OPTION_INTEGER, "scale", 0, &parsed_options->scale }, - { WESTON_OPTION_STRING, "display", 0, &config.display_name }, - { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, - { WESTON_OPTION_BOOLEAN, "use-tde", 0, &config.use_tde }, // OHOS tde - { WESTON_OPTION_INTEGER, "output-count", 0, &count }, - { WESTON_OPTION_BOOLEAN, "fullscreen", 0, &config.fullscreen }, - { WESTON_OPTION_BOOLEAN, "sprawl", 0, &config.sprawl }, - }; - - parse_options(wayland_options, ARRAY_LENGTH(wayland_options), argc, argv); - - section = weston_config_get_section(wc, "shell", NULL, NULL); - weston_config_section_get_string(section, "cursor-theme", - &config.cursor_theme, NULL); - weston_config_section_get_int(section, "cursor-size", - &config.cursor_size, 32); - - config.base.struct_size = sizeof(struct weston_wayland_backend_config); - config.base.struct_version = WESTON_WAYLAND_BACKEND_CONFIG_VERSION; - - /* load the actual wayland backend and configure it */ - ret = weston_compositor_load_backend(c, WESTON_BACKEND_WAYLAND, - &config.base); - - free(config.cursor_theme); - free(config.display_name); - - if (ret < 0) - return ret; - - api = weston_windowed_output_get_api(c); - - if (api == NULL) { - /* We will just assume if load_backend() finished cleanly and - * windowed_output_api is not present that wayland backend is - * started with --sprawl or runs on fullscreen-shell. - * In this case, all values are hardcoded, so nothing can be - * configured; simply create and enable an output. */ - wet_set_simple_head_configurator(c, NULL); - - return 0; - } - - wet_set_simple_head_configurator(c, wayland_backend_output_configure); - - section = NULL; - while (weston_config_next_section(wc, §ion, §ion_name)) { - if (count == 0) - break; - - if (strcmp(section_name, "output") != 0) { - continue; - } - - weston_config_section_get_string(section, "name", &output_name, NULL); - - if (output_name == NULL) - continue; - - if (output_name[0] != 'W' || output_name[1] != 'L') { - free(output_name); - continue; - } - - if (api->create_head(c, output_name) < 0) { - free(output_name); - return -1; - } - free(output_name); - - --count; - } - - for (i = 0; i < count; i++) { - if (asprintf(&output_name, "wayland%d", i) < 0) - return -1; - - if (api->create_head(c, output_name) < 0) { - free(output_name); - return -1; - } - free(output_name); - } - - return 0; -} - - -static int -load_backend(struct weston_compositor *compositor, const char *backend, - int *argc, char **argv, struct weston_config *config) -{ - if (strstr(backend, "headless-backend.so")) - return load_headless_backend(compositor, argc, argv, config); - else if (strstr(backend, "rdp-backend.so")) - return load_rdp_backend(compositor, argc, argv, config); - else if (strstr(backend, "fbdev-backend.so")) - return load_fbdev_backend(compositor, argc, argv, config); - else if (strstr(backend, "drm-backend.so")) - return load_drm_backend(compositor, argc, argv, config); - else if (strstr(backend, "x11-backend.so")) - return load_x11_backend(compositor, argc, argv, config); - else if (strstr(backend, "wayland-backend.so")) - return load_wayland_backend(compositor, argc, argv, config); - - weston_log("Error: unknown backend \"%s\"\n", backend); - return -1; -} - -static char * -copy_command_line(int argc, char * const argv[]) -{ - FILE *fp; - char *str = NULL; - size_t size = 0; - int i; - - fp = open_memstream(&str, &size); - if (!fp) - return NULL; - - fprintf(fp, "%s", argv[0]); - for (i = 1; i < argc; i++) - fprintf(fp, " %s", argv[i]); - fclose(fp); - - return str; -} - -#if !defined(BUILD_XWAYLAND) -int -wet_load_xwayland(struct weston_compositor *comp) -{ - return -1; -} -#endif - -// OHOS remove logger -//static void -//weston_log_setup_scopes(struct weston_log_context *log_ctx, -// struct weston_log_subscriber *subscriber, -// const char *names) -//{ -// assert(log_ctx); -// assert(subscriber); -// -// char *tokenize = strdup(names); -// char *token = strtok(tokenize, ","); -// while (token) { -// weston_log_subscribe(log_ctx, subscriber, token); -// token = strtok(NULL, ","); -// } -// free(tokenize); -//} -// -//static void -//flight_rec_key_binding_handler(struct weston_keyboard *keyboard, -// const struct timespec *time, uint32_t key, -// void *data) -//{ -// struct weston_log_subscriber *flight_rec = data; -// weston_log_subscriber_display_flight_rec(flight_rec); -//} -// -//static void -//weston_log_subscribe_to_scopes(struct weston_log_context *log_ctx, -// struct weston_log_subscriber *logger, -// struct weston_log_subscriber *flight_rec, -// const char *log_scopes, -// const char *flight_rec_scopes) -//{ -// if (log_scopes) -// weston_log_setup_scopes(log_ctx, logger, log_scopes); -// else -// weston_log_subscribe(log_ctx, logger, "log"); -// -// if (flight_rec_scopes) { -// weston_log_setup_scopes(log_ctx, flight_rec, flight_rec_scopes); -// } else { -// /* by default subscribe to 'log', and 'drm-backend' */ -// weston_log_subscribe(log_ctx, flight_rec, "log"); -// weston_log_subscribe(log_ctx, flight_rec, "drm-backend"); -// } -//} - -WL_EXPORT int -wet_main(int argc, char *argv[]) -{ - int ret = EXIT_FAILURE; - char *cmdline; - struct wl_display *display; - struct wl_event_source *signals[4]; - struct wl_event_loop *loop; - int i, fd; - char *backend = NULL; - char *shell = NULL; - bool xwayland = false; - char *modules = NULL; - char *option_modules = NULL; - char *log = NULL; - char *log_scopes = NULL; - char *flight_rec_scopes = NULL; - char *server_socket = NULL; - int32_t idle_time = -1; - int32_t help = 0; - char *socket_name = NULL; - int32_t version = 0; - int32_t noconfig = 0; - int32_t debug_protocol = 0; - bool numlock_on; - char *config_file = NULL; - struct weston_config *config = NULL; - struct weston_config_section *section; - struct wl_client *primary_client; - struct wl_listener primary_client_destroyed; - struct weston_seat *seat; - struct wet_compositor wet = { 0 }; -// OHOS remove logger -// struct weston_log_context *log_ctx = NULL; -// struct weston_log_subscriber *logger = NULL; -// struct weston_log_subscriber *flight_rec = NULL; - sigset_t mask; - - bool wait_for_debugger = false; - struct wl_protocol_logger *protologger = NULL; - - 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 }, -#if defined(BUILD_XWAYLAND) - { WESTON_OPTION_BOOLEAN, "xwayland", 0, &xwayland }, -#endif - { 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 }, - { WESTON_OPTION_BOOLEAN, "no-config", 0, &noconfig }, - { WESTON_OPTION_STRING, "config", 'c', &config_file }, - { WESTON_OPTION_BOOLEAN, "wait-for-debugger", 0, &wait_for_debugger }, - { WESTON_OPTION_BOOLEAN, "debug", 0, &debug_protocol }, - { WESTON_OPTION_STRING, "logger-scopes", 'l', &log_scopes }, - { WESTON_OPTION_STRING, "flight-rec-scopes", 'f', &flight_rec_scopes }, - }; - - wl_list_init(&wet.layoutput_list); - - os_fd_set_cloexec(fileno(stdin)); - - cmdline = copy_command_line(argc, argv); - parse_options(core_options, ARRAY_LENGTH(core_options), &argc, argv); - - if (help) { - free(cmdline); - usage(EXIT_SUCCESS); - } - - if (version) { - printf(PACKAGE_STRING "\n"); - free(cmdline); - - return EXIT_SUCCESS; - } - -// OHOS remove logger -// log_ctx = weston_log_ctx_create(); -// if (!log_ctx) { -// fprintf(stderr, "Failed to initialize weston debug framework.\n"); -// return EXIT_FAILURE; -// } -// -// log_scope = weston_log_ctx_add_log_scope(log_ctx, "log", -// "Weston and Wayland log\n", NULL, NULL, NULL); -// -// if (!weston_log_file_open(log)) -// return EXIT_FAILURE; -// -// weston_log_set_handler(vlog, vlog_continue); -// -// logger = weston_log_subscriber_create_log(weston_logfile); -// flight_rec = weston_log_subscriber_create_flight_rec(DEFAULT_FLIGHT_REC_SIZE); -// -// weston_log_subscribe_to_scopes(log_ctx, logger, flight_rec, -// log_scopes, flight_rec_scopes); - - 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); - weston_log("Command line: %s\n", cmdline); - free(cmdline); - log_uname(); - - verify_xdg_runtime_dir(); - - display = wl_display_create(); - if (display == NULL) { - weston_log("fatal: failed to create display\n"); - goto out_display; - } - - 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 (!signals[0] || !signals[1] || !signals[2] || !signals[3]) - goto out_signals; - - /* Xwayland uses SIGUSR1 for communicating with weston. Since some - weston plugins may create additional threads, set up any necessary - signal blocking early so that these threads can inherit the settings - when created. */ - sigemptyset(&mask); - sigaddset(&mask, SIGUSR1); - pthread_sigmask(SIG_BLOCK, &mask, NULL); - - if (load_configuration(&config, noconfig, config_file) < 0) - goto out_signals; - wet.config = config; - wet.parsed_options = NULL; - - section = weston_config_get_section(config, "core", NULL, NULL); - -// OHOS remove debugger -// if (!wait_for_debugger) { -// weston_config_section_get_bool(section, "wait-for-debugger", -// &wait_for_debugger, false); -// } -// if (wait_for_debugger) { -// weston_log("Weston PID is %ld - " -// "waiting for debugger, send SIGCONT to continue...\n", -// (long)getpid()); -// raise(SIGSTOP); -// } - - if (!backend) { - weston_config_section_get_string(section, "backend", &backend, - NULL); - if (!backend) - backend = weston_choose_default_backend(); - } - -// OHOS remove logger -// wet.compositor = weston_compositor_create(display, log_ctx, &wet); - wet.compositor = weston_compositor_create(display, &wet); - if (wet.compositor == NULL) { - weston_log("fatal: failed to create compositor\n"); - goto out; - } - segv_compositor = wet.compositor; - -// OHOS remove log, debugger -// protocol_scope = -// weston_log_ctx_add_log_scope(log_ctx, "proto", -// "Wayland protocol dump for all clients.\n", -// NULL, NULL, NULL); -// -// protologger = wl_display_add_protocol_logger(display, -// protocol_log_fn, -// NULL); -// if (debug_protocol) -// weston_compositor_enable_debug_protocol(wet.compositor); -// -// weston_compositor_add_debug_binding(wet.compositor, KEY_D, -// flight_rec_key_binding_handler, -// flight_rec); - - if (weston_compositor_init_config(wet.compositor, config) < 0) - goto out; - - weston_config_section_get_bool(section, "require-input", - &wet.compositor->require_input, true); - - if (load_backend(wet.compositor, backend, &argc, argv, config) < 0) { - weston_log("fatal: failed to create compositor backend\n"); - goto out; - } - - weston_compositor_flush_heads_changed(wet.compositor); - // OHOS - // if (wet.init_failed) - // goto out; - if (wet.init_failed && wl_list_empty(&wet.compositor->output_list)) - goto out; - - if (idle_time < 0) - weston_config_section_get_int(section, "idle-time", &idle_time, -1); - if (idle_time < 0) - idle_time = 300; /* default idle timeout, in seconds */ - - wet.compositor->idle_time = idle_time; - wet.compositor->default_pointer_grab = NULL; - wet.compositor->exit = handle_exit; - - weston_compositor_log_capabilities(wet.compositor); - - server_socket = getenv("WAYLAND_SERVER_SOCKET"); - if (server_socket) { - weston_log("Running with single client\n"); - if (!safe_strtoint(server_socket, &fd)) - fd = -1; - } else { - fd = -1; - } - - if (fd != -1) { - primary_client = wl_client_create(display, fd); - if (!primary_client) { - weston_log("fatal: failed to add client: %s\n", - strerror(errno)); - goto out; - } - primary_client_destroyed.notify = - handle_primary_client_destroyed; - wl_client_add_destroy_listener(primary_client, - &primary_client_destroyed); - } else if (weston_create_listening_socket(display, socket_name)) { - goto out; - } - - if (!shell) - weston_config_section_get_string(section, "shell", &shell, - "desktop-shell.so"); - - if (wet_load_shell(wet.compositor, shell, &argc, argv) < 0) - goto out; - - weston_config_section_get_string(section, "modules", &modules, ""); - if (load_modules(wet.compositor, modules, &argc, argv, &xwayland) < 0) - goto out; - - if (load_modules(wet.compositor, option_modules, &argc, argv, &xwayland) < 0) - goto out; - - if (!xwayland) { - weston_config_section_get_bool(section, "xwayland", &xwayland, - false); - } - if (xwayland) { - if (wet_load_xwayland(wet.compositor) < 0) - goto out; - } - - section = weston_config_get_section(config, "keyboard", NULL, NULL); - weston_config_section_get_bool(section, "numlock-on", &numlock_on, false); - if (numlock_on) { - wl_list_for_each(seat, &wet.compositor->seat_list, link) { - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(seat); - - if (keyboard) - weston_keyboard_set_locks(keyboard, - WESTON_NUM_LOCK, - WESTON_NUM_LOCK); - } - } - - for (i = 1; i < argc; i++) - weston_log("fatal: unhandled option: %s\n", argv[i]); - if (argc > 1) - goto out; - - weston_compositor_wake(wet.compositor); - - wl_display_run(display); - - /* Allow for setting return exit code after - * wl_display_run returns normally. This is - * useful for devs/testers and automated tests - * that want to indicate failure status to - * testing infrastructure above - */ - ret = wet.compositor->exit_code; - -out: - wet_compositor_destroy_layout(&wet); - - /* free(NULL) is valid, and it won't be NULL if it's used */ - free(wet.parsed_options); - -// OHOS remove logger -// if (protologger) -// wl_protocol_logger_destroy(protologger); - - weston_compositor_destroy(wet.compositor); -// OHOS remove logger -// weston_log_scope_destroy(protocol_scope); -// protocol_scope = NULL; -// weston_log_scope_destroy(log_scope); -// log_scope = NULL; -// weston_log_subscriber_destroy(logger); -// weston_log_subscriber_destroy(flight_rec); -// weston_log_ctx_destroy(log_ctx); - -out_signals: - for (i = ARRAY_LENGTH(signals) - 1; i >= 0; i--) - if (signals[i]) - wl_event_source_remove(signals[i]); - - wl_display_destroy(display); - -out_display: -// OHOS remove logger -// weston_log_file_close(); - - if (config) - weston_config_destroy(config); - free(config_file); - free(backend); - free(shell); - free(socket_name); - free(option_modules); - free(log); - free(modules); - - return ret; -} diff --git a/compositor/meson.build b/compositor/meson.build deleted file mode 100644 index 9dc95f3..0000000 --- a/compositor/meson.build +++ /dev/null @@ -1,187 +0,0 @@ -srcs_weston = [ - git_version_h, - 'main.c', - 'testsuite-util.c', - 'text-backend.c', - 'weston-screenshooter.c', - text_input_unstable_v1_server_protocol_h, - text_input_unstable_v1_protocol_c, - input_method_unstable_v1_server_protocol_h, - input_method_unstable_v1_protocol_c, - weston_screenshooter_server_protocol_h, - weston_screenshooter_protocol_c, -] -deps_weston = [ - dep_libshared, - dep_libweston_public, - dep_libinput, - dep_libevdev, - dep_libdl, - dep_threads, -] - -if get_option('xwayland') - config_h.set('BUILD_XWAYLAND', '1') - - srcs_weston += 'xwayland.c' - config_h.set_quoted('XSERVER_PATH', get_option('xwayland-path')) -endif - -libexec_weston = shared_library( - 'exec_weston', - sources: srcs_weston, - include_directories: common_inc, - dependencies: deps_weston, - install_dir: dir_module_weston, - install: true, - version: '0.0.0', - soversion: 0 -) -dep_libexec_weston = declare_dependency( - link_with: libexec_weston, - include_directories: [ include_directories('.'), public_inc ], - dependencies: dep_libweston_public -) -exe_weston = executable( - 'weston', - 'executable.c', - include_directories: common_inc, - dependencies: dep_libexec_weston, - install_rpath: dir_module_weston, - install: true -) -install_headers('weston.h', subdir: 'weston') - -pkgconfig.generate( - filebase: 'weston', - name: 'Weston Plugin API', - version: version_weston, - description: 'Header files for Weston plugin development', - requires_private: [ lib_weston ], - variables: [ - 'libexecdir=' + join_paths('${prefix}', get_option('libexecdir')), - 'pkglibexecdir=${libexecdir}/weston' - ], - subdirs: 'weston' -) - -install_data( - 'weston.desktop', - install_dir: join_paths(dir_data, 'wayland-sessions') -) - -if get_option('screenshare') - srcs_screenshare = [ - 'screen-share.c', - fullscreen_shell_unstable_v1_client_protocol_h, - fullscreen_shell_unstable_v1_protocol_c, - ] - deps_screenshare = [ - dep_libexec_weston, - dep_libshared, - dep_libweston_public, - dep_libweston_private_h, # XXX: https://gitlab.freedesktop.org/wayland/weston/issues/292 - dep_wayland_client, - ] - plugin_screenshare = shared_library( - 'screen-share', - srcs_screenshare, - include_directories: common_inc, - dependencies: deps_screenshare, - name_prefix: '', - install: true, - install_dir: dir_module_weston, - install_rpath: '$ORIGIN' - ) - env_modmap += 'screen-share.so=@0@;'.format(plugin_screenshare.full_path()) -endif - -if get_option('color-management-lcms') - config_h.set('HAVE_LCMS', '1') - - srcs_lcms = [ - 'cms-static.c', - 'cms-helper.c', - ] - - dep_lcms2 = dependency('lcms2', required: false) - if not dep_lcms2.found() - error('cms-static requires lcms2 which was not found. Or, you can use \'-Dcolor-management-lcms=false\'.') - endif - - plugin_lcms = shared_library( - 'cms-static', - srcs_lcms, - include_directories: common_inc, - dependencies: [ dep_libexec_weston, dep_libweston_public, dep_lcms2 ], - name_prefix: '', - install: true, - install_dir: dir_module_weston, - install_rpath: '$ORIGIN' - ) - env_modmap += 'cms-static.so=@0@;'.format(plugin_lcms.full_path()) -endif - -if get_option('color-management-colord') - if not get_option('color-management-lcms') - error('LCMS must be enabled to support colord') - endif - - srcs_colord = [ - 'cms-colord.c', - 'cms-helper.c', - ] - - dep_colord = dependency('colord', version: '>= 0.1.27', required: false) - if not dep_colord.found() - error('cms-colord requires colord >= 0.1.27 which was not found. Or, you can use \'-Dcolor-management-colord=false\'.') - endif - - plugin_colord_deps = [ dep_libweston_public, dep_colord, dep_lcms2 ] - - foreach depname : [ 'glib-2.0', 'gobject-2.0' ] - dep = dependency(depname, required: false) - if not dep.found() - error('cms-colord requires \'@0@\' which was not found. If you rather not build this, set \'-Dcolor-management-colord=false\'.'.format(depname)) - endif - plugin_colord_deps += dep - endforeach - - plugin_colord = shared_library( - 'cms-colord', - srcs_colord, - include_directories: common_inc, - dependencies: plugin_colord_deps, - name_prefix: '', - install: true, - install_dir: dir_module_weston - ) - env_modmap += 'cms-colord.so=@0@;'.format(plugin_colord.full_path()) -endif - -if get_option('systemd') - dep_libsystemd = dependency('libsystemd', required: false) - if not dep_libsystemd.found() - error('systemd-notify requires libsystemd which was not found. Or, you can use \'-Dsystemd=false\'.') - endif - - plugin_systemd_notify = shared_library( - 'systemd-notify', - 'systemd-notify.c', - include_directories: common_inc, - dependencies: [ dep_libweston_public, dep_libsystemd ], - name_prefix: '', - install: true, - install_dir: dir_module_weston - ) - env_modmap += 'systemd-notify.so=@0@;'.format(plugin_systemd_notify.full_path()) -endif - -weston_ini_config = configuration_data() -weston_ini_config.set('bindir', dir_bin) -weston_ini_config.set('libexecdir', dir_libexec) -configure_file( - input: '../weston.ini.in', - output: 'weston.ini', - configuration: weston_ini_config -) diff --git a/compositor/screen-share.c b/compositor/screen-share.c deleted file mode 100644 index 8c37452..0000000 --- a/compositor/screen-share.c +++ /dev/null @@ -1,1184 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2014 Jason Ekstrand - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include "backend.h" -#include "libweston-internal.h" -#include "weston.h" -#include "shared/helpers.h" -#include "shared/os-compatibility.h" -#include "shared/timespec-util.h" -#include "fullscreen-shell-unstable-v1-client-protocol.h" - -struct shared_output { - struct weston_output *output; - struct wl_listener output_destroyed; - struct wl_list seat_list; - - struct { - struct wl_display *display; - struct wl_registry *registry; - struct wl_compositor *compositor; - struct wl_shm *shm; - uint32_t shm_formats; - struct zwp_fullscreen_shell_v1 *fshell; - struct wl_output *output; - struct wl_surface *surface; - struct wl_callback *frame_cb; - struct zwp_fullscreen_shell_mode_feedback_v1 *mode_feedback; - } parent; - - struct wl_event_source *event_source; - struct wl_listener frame_listener; - - struct { - int32_t width, height; - - struct wl_list buffers; - struct wl_list free_buffers; - } shm; - - int cache_dirty; - pixman_image_t *cache_image; - uint32_t *tmp_data; - size_t tmp_data_size; -}; - -struct ss_seat { - struct weston_seat base; - struct shared_output *output; - struct wl_list link; - uint32_t id; - - struct { - struct wl_seat *seat; - struct wl_pointer *pointer; - struct wl_keyboard *keyboard; - } parent; - - enum weston_key_state_update keyboard_state_update; - uint32_t key_serial; -}; - -struct ss_shm_buffer { - struct shared_output *output; - struct wl_list link; - struct wl_list free_link; - - struct wl_buffer *buffer; - void *data; - size_t size; - pixman_region32_t damage; - - pixman_image_t *pm_image; -}; - -struct screen_share { - struct weston_compositor *compositor; - /* XXX: missing compositor destroy listener - * https://gitlab.freedesktop.org/wayland/weston/issues/298 - */ - char *command; -}; - -static void -ss_seat_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 ss_seat *seat = data; - - /* No transformation of input position is required here because we are - * always receiving the input in the same coordinates as the output. */ - - notify_pointer_focus(&seat->base, NULL, 0, 0); -} - -static void -ss_seat_handle_pointer_leave(void *data, struct wl_pointer *pointer, - uint32_t serial, struct wl_surface *surface) -{ - struct ss_seat *seat = data; - - notify_pointer_focus(&seat->base, NULL, 0, 0); -} - -static void -ss_seat_handle_motion(void *data, struct wl_pointer *pointer, - uint32_t time, wl_fixed_t x, wl_fixed_t y) -{ - struct ss_seat *seat = data; - struct timespec ts; - - timespec_from_msec(&ts, time); - - /* No transformation of input position is required here because we are - * always receiving the input in the same coordinates as the output. */ - - notify_motion_absolute(&seat->base, &ts, - wl_fixed_to_double(x), wl_fixed_to_double(y)); - notify_pointer_frame(&seat->base); -} - -static void -ss_seat_handle_button(void *data, struct wl_pointer *pointer, - uint32_t serial, uint32_t time, uint32_t button, - uint32_t state) -{ - struct ss_seat *seat = data; - struct timespec ts; - - timespec_from_msec(&ts, time); - - notify_button(&seat->base, &ts, button, state); - notify_pointer_frame(&seat->base); -} - -static void -ss_seat_handle_axis(void *data, struct wl_pointer *pointer, - uint32_t time, uint32_t axis, wl_fixed_t value) -{ - struct ss_seat *seat = data; - struct weston_pointer_axis_event weston_event; - struct timespec ts; - - weston_event.axis = axis; - weston_event.value = wl_fixed_to_double(value); - weston_event.has_discrete = false; - - timespec_from_msec(&ts, time); - - notify_axis(&seat->base, &ts, &weston_event); - notify_pointer_frame(&seat->base); -} - -static const struct wl_pointer_listener ss_seat_pointer_listener = { - ss_seat_handle_pointer_enter, - ss_seat_handle_pointer_leave, - ss_seat_handle_motion, - ss_seat_handle_button, - ss_seat_handle_axis, -}; - -static void -ss_seat_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, - uint32_t format, int fd, uint32_t size) -{ - struct ss_seat *seat = data; - struct xkb_keymap *keymap; - char *map_str; - - if (!data) - goto error_no_seat; - - if (format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { - map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); - if (map_str == MAP_FAILED) { - weston_log("mmap failed: %s\n", strerror(errno)); - goto error; - } - - keymap = xkb_keymap_new_from_string(seat->base.compositor->xkb_context, - map_str, - XKB_KEYMAP_FORMAT_TEXT_V1, - 0); - munmap(map_str, size); - - if (!keymap) { - weston_log("failed to compile keymap\n"); - goto error; - } - - seat->keyboard_state_update = STATE_UPDATE_NONE; - } else if (format == WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP) { - weston_log("No keymap provided; falling back to default\n"); - keymap = NULL; - seat->keyboard_state_update = STATE_UPDATE_AUTOMATIC; - } else { - weston_log("Invalid keymap\n"); - goto error; - } - - close(fd); - - if (seat->base.keyboard_device_count) - weston_seat_update_keymap(&seat->base, keymap); - else - weston_seat_init_keyboard(&seat->base, keymap); - - xkb_keymap_unref(keymap); - - return; - -error: - wl_keyboard_release(seat->parent.keyboard); -error_no_seat: - close(fd); -} - -static void -ss_seat_handle_keyboard_enter(void *data, struct wl_keyboard *keyboard, - uint32_t serial, struct wl_surface *surface, - struct wl_array *keys) -{ - struct ss_seat *seat = data; - - /* XXX: If we get a modifier event immediately before the focus, - * we should try to keep the same serial. */ - notify_keyboard_focus_in(&seat->base, keys, - STATE_UPDATE_AUTOMATIC); -} - -static void -ss_seat_handle_keyboard_leave(void *data, struct wl_keyboard *keyboard, - uint32_t serial, struct wl_surface *surface) -{ - struct ss_seat *seat = data; - - notify_keyboard_focus_out(&seat->base); -} - -static void -ss_seat_handle_key(void *data, struct wl_keyboard *keyboard, - uint32_t serial, uint32_t time, - uint32_t key, uint32_t state) -{ - struct ss_seat *seat = data; - struct timespec ts; - - timespec_from_msec(&ts, time); - seat->key_serial = serial; - notify_key(&seat->base, &ts, key, - state ? WL_KEYBOARD_KEY_STATE_PRESSED : - WL_KEYBOARD_KEY_STATE_RELEASED, - seat->keyboard_state_update); -} - -static void -ss_seat_handle_modifiers(void *data, struct wl_keyboard *wl_keyboard, - uint32_t serial_in, uint32_t mods_depressed, - uint32_t mods_latched, uint32_t mods_locked, - uint32_t group) -{ - struct ss_seat *seat = data; - struct weston_compositor *c = seat->base.compositor; - struct weston_keyboard *keyboard; - 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 == seat->key_serial) - serial_out = wl_display_get_serial(c->wl_display); - else - serial_out = wl_display_next_serial(c->wl_display); - - keyboard = weston_seat_get_keyboard(&seat->base); - xkb_state_update_mask(keyboard->xkb_state.state, - mods_depressed, mods_latched, - mods_locked, 0, 0, group); - notify_modifiers(&seat->base, serial_out); -} - -static const struct wl_keyboard_listener ss_seat_keyboard_listener = { - ss_seat_handle_keymap, - ss_seat_handle_keyboard_enter, - ss_seat_handle_keyboard_leave, - ss_seat_handle_key, - ss_seat_handle_modifiers, -}; - -static void -ss_seat_handle_capabilities(void *data, struct wl_seat *seat, - enum wl_seat_capability caps) -{ - struct ss_seat *ss_seat = data; - - if ((caps & WL_SEAT_CAPABILITY_POINTER) && !ss_seat->parent.pointer) { - ss_seat->parent.pointer = wl_seat_get_pointer(seat); - wl_pointer_set_user_data(ss_seat->parent.pointer, ss_seat); - wl_pointer_add_listener(ss_seat->parent.pointer, - &ss_seat_pointer_listener, ss_seat); - weston_seat_init_pointer(&ss_seat->base); - } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && ss_seat->parent.pointer) { - wl_pointer_destroy(ss_seat->parent.pointer); - ss_seat->parent.pointer = NULL; - } - - if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !ss_seat->parent.keyboard) { - ss_seat->parent.keyboard = wl_seat_get_keyboard(seat); - wl_keyboard_set_user_data(ss_seat->parent.keyboard, ss_seat); - wl_keyboard_add_listener(ss_seat->parent.keyboard, - &ss_seat_keyboard_listener, ss_seat); - } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && ss_seat->parent.keyboard) { - wl_keyboard_destroy(ss_seat->parent.keyboard); - ss_seat->parent.keyboard = NULL; - } -} - -static const struct wl_seat_listener ss_seat_listener = { - ss_seat_handle_capabilities, -}; - -static struct ss_seat * -ss_seat_create(struct shared_output *so, uint32_t id) -{ - struct ss_seat *seat; - - seat = zalloc(sizeof *seat); - if (seat == NULL) - return NULL; - - weston_seat_init(&seat->base, so->output->compositor, "default"); - seat->output = so; - seat->id = id; - seat->parent.seat = wl_registry_bind(so->parent.registry, id, - &wl_seat_interface, 1); - wl_list_insert(so->seat_list.prev, &seat->link); - - wl_seat_add_listener(seat->parent.seat, &ss_seat_listener, seat); - wl_seat_set_user_data(seat->parent.seat, seat); - - return seat; -} - -static void -ss_seat_destroy(struct ss_seat *seat) -{ - if (seat->parent.pointer) - wl_pointer_release(seat->parent.pointer); - if (seat->parent.keyboard) - wl_keyboard_release(seat->parent.keyboard); - wl_seat_destroy(seat->parent.seat); - - wl_list_remove(&seat->link); - - weston_seat_release(&seat->base); - - free(seat); -} - -static void -ss_shm_buffer_destroy(struct ss_shm_buffer *buffer) -{ - pixman_image_unref(buffer->pm_image); - - wl_buffer_destroy(buffer->buffer); - munmap(buffer->data, buffer->size); - - pixman_region32_fini(&buffer->damage); - - wl_list_remove(&buffer->link); - wl_list_remove(&buffer->free_link); - free(buffer); -} - -static void -buffer_release(void *data, struct wl_buffer *buffer) -{ - struct ss_shm_buffer *sb = data; - - if (sb->output) { - wl_list_insert(&sb->output->shm.free_buffers, &sb->free_link); - } else { - ss_shm_buffer_destroy(sb); - } -} - -static const struct wl_buffer_listener buffer_listener = { - buffer_release -}; - -static struct ss_shm_buffer * -shared_output_get_shm_buffer(struct shared_output *so) -{ - struct ss_shm_buffer *sb, *bnext; - struct wl_shm_pool *pool; - int width, height, stride; - int fd; - unsigned char *data; - - width = so->output->width; - height = so->output->height; - stride = width * 4; - - /* If the size of the output changed, we free the old buffers and - * make new ones. */ - if (so->shm.width != width || - so->shm.height != height) { - - /* Destroy free buffers */ - wl_list_for_each_safe(sb, bnext, &so->shm.free_buffers, free_link) - ss_shm_buffer_destroy(sb); - - /* Orphan in-use buffers so they get destroyed */ - wl_list_for_each(sb, &so->shm.buffers, link) - sb->output = NULL; - - so->shm.width = width; - so->shm.height = height; - } - - if (!wl_list_empty(&so->shm.free_buffers)) { - sb = container_of(so->shm.free_buffers.next, - struct ss_shm_buffer, free_link); - wl_list_remove(&sb->free_link); - wl_list_init(&sb->free_link); - - return sb; - } - - fd = os_create_anonymous_file(height * stride); - if (fd < 0) { - weston_log("os_create_anonymous_file: %s\n", strerror(errno)); - return NULL; - } - - data = mmap(NULL, height * stride, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (data == MAP_FAILED) { - weston_log("mmap: %s\n", strerror(errno)); - goto out_close; - } - - sb = zalloc(sizeof *sb); - if (!sb) - goto out_unmap; - - sb->output = so; - wl_list_init(&sb->free_link); - wl_list_insert(&so->shm.buffers, &sb->link); - - pixman_region32_init_rect(&sb->damage, 0, 0, width, height); - - sb->data = data; - sb->size = height * stride; - - pool = wl_shm_create_pool(so->parent.shm, fd, sb->size); - - sb->buffer = wl_shm_pool_create_buffer(pool, 0, - width, height, stride, - WL_SHM_FORMAT_ARGB8888); - wl_buffer_add_listener(sb->buffer, &buffer_listener, sb); - wl_shm_pool_destroy(pool); - close(fd); - fd = -1; - - memset(data, 0, sb->size); - - sb->pm_image = - pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, - (uint32_t *)data, stride); - if (!sb->pm_image) - goto out_pixman_error; - - return sb; - -out_pixman_error: - pixman_region32_fini(&sb->damage); -out_unmap: - munmap(data, height * stride); -out_close: - if (fd != -1) - close(fd); - return NULL; -} - -static void -output_compute_transform(struct weston_output *output, - pixman_transform_t *transform) -{ - pixman_fixed_t fw, fh; - - pixman_transform_init_identity(transform); - - fw = pixman_int_to_fixed(output->width); - fh = pixman_int_to_fixed(output->height); - - 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); - } - - 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, fw); - 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, fh, 0); - break; - } - - pixman_transform_scale(transform, NULL, - pixman_fixed_1 * output->current_scale, - pixman_fixed_1 * output->current_scale); -} - -static void -shared_output_destroy(struct shared_output *so); - -static int -shared_output_ensure_tmp_data(struct shared_output *so, - pixman_region32_t *region) -{ - pixman_box32_t *ext; - size_t size; - - if (!pixman_region32_not_empty(region)) - return 0; - - ext = pixman_region32_extents(region); - - /* Damage is in output coordinates. - * - * We are multiplying by 4 because the temporary data needs to be able - * to store an 32 bit-per-pixel buffer. - */ - size = 4 * (ext->x2 - ext->x1) * (ext->y2 - ext->y1) - * so->output->current_scale * so->output->current_scale; - - if (so->tmp_data != NULL && size <= so->tmp_data_size) - return 0; - - free(so->tmp_data); - so->tmp_data = malloc(size); - if (so->tmp_data == NULL) { - so->tmp_data_size = 0; - errno = ENOMEM; - return -1; - } - - so->tmp_data_size = size; - - return 0; -} - -static void -shared_output_update(struct shared_output *so); - -static void -shared_output_frame_callback(void *data, struct wl_callback *cb, uint32_t time) -{ - struct shared_output *so = data; - - if (cb != so->parent.frame_cb) - return; - - wl_callback_destroy(cb); - so->parent.frame_cb = NULL; - - shared_output_update(so); -} - -static const struct wl_callback_listener shared_output_frame_listener = { - shared_output_frame_callback -}; - -static void -shared_output_update(struct shared_output *so) -{ - struct ss_shm_buffer *sb; - pixman_box32_t *r; - int i, nrects; - pixman_transform_t transform; - - /* Only update if we need to */ - if (!so->cache_dirty || so->parent.frame_cb) - return; - - sb = shared_output_get_shm_buffer(so); - if (sb == NULL) { - shared_output_destroy(so); - return; - } - - output_compute_transform(so->output, &transform); - pixman_image_set_transform(so->cache_image, &transform); - - pixman_image_set_clip_region32(sb->pm_image, &sb->damage); - - if (so->output->current_scale == 1) { - pixman_image_set_filter(so->cache_image, - PIXMAN_FILTER_NEAREST, NULL, 0); - } else { - pixman_image_set_filter(so->cache_image, - PIXMAN_FILTER_BILINEAR, NULL, 0); - } - - pixman_image_composite32(PIXMAN_OP_SRC, - so->cache_image, /* src */ - NULL, /* mask */ - sb->pm_image, /* dest */ - 0, 0, /* src_x, src_y */ - 0, 0, /* mask_x, mask_y */ - 0, 0, /* dest_x, dest_y */ - so->output->width, /* width */ - so->output->height /* height */); - - pixman_image_set_transform(sb->pm_image, NULL); - pixman_image_set_clip_region32(sb->pm_image, NULL); - - r = pixman_region32_rectangles(&sb->damage, &nrects); - for (i = 0; i < nrects; ++i) - wl_surface_damage(so->parent.surface, r[i].x1, r[i].y1, - r[i].x2 - r[i].x1, r[i].y2 - r[i].y1); - - wl_surface_attach(so->parent.surface, sb->buffer, 0, 0); - - so->parent.frame_cb = wl_surface_frame(so->parent.surface); - wl_callback_add_listener(so->parent.frame_cb, - &shared_output_frame_listener, so); - - wl_surface_commit(so->parent.surface); - wl_callback_destroy(wl_display_sync(so->parent.display)); - wl_display_flush(so->parent.display); - - /* Clear the buffer damage */ - pixman_region32_fini(&sb->damage); - pixman_region32_init(&sb->damage); -} - -static void -shm_handle_format(void *data, struct wl_shm *wl_shm, uint32_t format) -{ - struct shared_output *so = data; - - so->parent.shm_formats |= (1 << format); -} - -struct wl_shm_listener shm_listener = { - shm_handle_format -}; - -static void -registry_handle_global(void *data, struct wl_registry *registry, - uint32_t id, const char *interface, uint32_t version) -{ - struct shared_output *so = data; - - if (strcmp(interface, "wl_compositor") == 0) { - so->parent.compositor = - wl_registry_bind(registry, - id, &wl_compositor_interface, 1); - } else if (strcmp(interface, "wl_output") == 0 && !so->parent.output) { - so->parent.output = - wl_registry_bind(registry, - id, &wl_output_interface, 1); - } else if (strcmp(interface, "wl_seat") == 0) { - ss_seat_create(so, id); - } else if (strcmp(interface, "wl_shm") == 0) { - so->parent.shm = - wl_registry_bind(registry, - id, &wl_shm_interface, 1); - wl_shm_add_listener(so->parent.shm, &shm_listener, so); - } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { - so->parent.fshell = - wl_registry_bind(registry, - id, - &zwp_fullscreen_shell_v1_interface, - 1); - } -} - -static void -registry_handle_global_remove(void *data, struct wl_registry *registry, - uint32_t name) -{ - struct shared_output *so = data; - struct ss_seat *seat, *next; - - wl_list_for_each_safe(seat, next, &so->seat_list, link) - if (seat->id == name) - ss_seat_destroy(seat); -} - -static const struct wl_registry_listener registry_listener = { - registry_handle_global, - registry_handle_global_remove -}; - -static int -shared_output_handle_event(int fd, uint32_t mask, void *data) -{ - struct shared_output *so = data; - int count = 0; - - if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { - shared_output_destroy(so); - return 0; - } - - if (mask & WL_EVENT_READABLE) - count = wl_display_dispatch(so->parent.display); - if (mask & WL_EVENT_WRITABLE) - wl_display_flush(so->parent.display); - - if (mask == 0) { - count = wl_display_dispatch_pending(so->parent.display); - wl_display_flush(so->parent.display); - } - - return count; -} - -static void -output_destroyed(struct wl_listener *l, void *data) -{ - struct shared_output *so; - - so = container_of(l, struct shared_output, output_destroyed); - - shared_output_destroy(so); -} - -static void -mode_feedback_ok(void *data, struct zwp_fullscreen_shell_mode_feedback_v1 *fb) -{ - struct shared_output *so = data; - - zwp_fullscreen_shell_mode_feedback_v1_destroy(so->parent.mode_feedback); -} - -static void -mode_feedback_failed(void *data, struct zwp_fullscreen_shell_mode_feedback_v1 *fb) -{ - struct shared_output *so = data; - - zwp_fullscreen_shell_mode_feedback_v1_destroy(so->parent.mode_feedback); - - weston_log("Screen share failed: present_surface_for_mode failed\n"); - shared_output_destroy(so); -} - -struct zwp_fullscreen_shell_mode_feedback_v1_listener mode_feedback_listener = { - mode_feedback_ok, - mode_feedback_failed, - mode_feedback_ok, -}; - -static void -shared_output_repainted(struct wl_listener *listener, void *data) -{ - struct shared_output *so = - container_of(listener, struct shared_output, frame_listener); - pixman_region32_t damage; - pixman_region32_t *current_damage = data; - struct ss_shm_buffer *sb; - int32_t x, y, width, height, stride; - int i, nrects, do_yflip, y_orig; - pixman_box32_t *r; - pixman_image_t *damaged_image; - pixman_transform_t transform; - - width = so->output->current_mode->width; - height = so->output->current_mode->height; - stride = width; - - if (!so->cache_image || - pixman_image_get_width(so->cache_image) != width || - pixman_image_get_height(so->cache_image) != height) { - if (so->cache_image) - pixman_image_unref(so->cache_image); - - so->cache_image = - pixman_image_create_bits(PIXMAN_a8r8g8b8, - width, height, NULL, - stride); - if (!so->cache_image) - goto err_shared_output; - - pixman_region32_init_rect(&damage, 0, 0, width, height); - } else { - /* Damage in output coordinates */ - pixman_region32_init(&damage); - pixman_region32_intersect(&damage, &so->output->region, current_damage); - pixman_region32_translate(&damage, -so->output->x, -so->output->y); - } - - /* Apply damage to all buffers */ - wl_list_for_each(sb, &so->shm.buffers, link) - pixman_region32_union(&sb->damage, &sb->damage, &damage); - - /* Transform to buffer coordinates */ - weston_transformed_region(so->output->width, so->output->height, - so->output->transform, - so->output->current_scale, - &damage, &damage); - - if (shared_output_ensure_tmp_data(so, &damage) < 0) - goto err_pixman_init; - - do_yflip = !!(so->output->compositor->capabilities & WESTON_CAP_CAPTURE_YFLIP); - - r = pixman_region32_rectangles(&damage, &nrects); - for (i = 0; i < nrects; ++i) { - x = r[i].x1; - y = r[i].y1; - width = r[i].x2 - r[i].x1; - height = r[i].y2 - r[i].y1; - - if (do_yflip) - y_orig = so->output->current_mode->height - r[i].y2; - else - y_orig = y; - - so->output->compositor->renderer->read_pixels( - so->output, PIXMAN_a8r8g8b8, so->tmp_data, - x, y_orig, width, height); - - damaged_image = pixman_image_create_bits(PIXMAN_a8r8g8b8, - width, height, - so->tmp_data, - (PIXMAN_FORMAT_BPP(PIXMAN_a8r8g8b8) / 8) * width); - if (!damaged_image) - goto err_pixman_init; - - if (do_yflip) { - pixman_transform_init_scale(&transform, - pixman_fixed_1, - pixman_fixed_minus_1); - - pixman_transform_translate(&transform, NULL, - 0, - pixman_int_to_fixed(height)); - - pixman_image_set_transform(damaged_image, &transform); - } - - pixman_image_composite32(PIXMAN_OP_SRC, - damaged_image, - NULL, - so->cache_image, - 0, 0, - 0, 0, - x, y, - width, height); - pixman_image_unref(damaged_image); - } - - so->cache_dirty = 1; - - pixman_region32_fini(&damage); - shared_output_update(so); - - return; - -err_pixman_init: - pixman_region32_fini(&damage); -err_shared_output: - shared_output_destroy(so); -} - -static struct shared_output * -shared_output_create(struct weston_output *output, int parent_fd) -{ - struct shared_output *so; - struct wl_event_loop *loop; - struct ss_seat *seat, *tmp; - int epoll_fd; - - so = zalloc(sizeof *so); - if (so == NULL) - goto err_close; - - wl_list_init(&so->seat_list); - - so->parent.display = wl_display_connect_to_fd(parent_fd); - if (!so->parent.display) - goto err_alloc; - - so->parent.registry = wl_display_get_registry(so->parent.display); - if (!so->parent.registry) - goto err_display; - wl_registry_add_listener(so->parent.registry, - ®istry_listener, so); - wl_display_roundtrip(so->parent.display); - if (so->parent.shm == NULL) { - weston_log("Screen share failed: No wl_shm found\n"); - goto err_display; - } - if (so->parent.fshell == NULL) { - weston_log("Screen share failed: " - "Parent does not support wl_fullscreen_shell\n"); - goto err_display; - } - if (so->parent.compositor == NULL) { - weston_log("Screen share failed: No wl_compositor found\n"); - goto err_display; - } - - /* Get SHM formats */ - wl_display_roundtrip(so->parent.display); - if (!(so->parent.shm_formats & (1 << WL_SHM_FORMAT_XRGB8888))) { - weston_log("Screen share failed: " - "WL_SHM_FORMAT_XRGB8888 not available\n"); - goto err_display; - } - - so->parent.surface = - wl_compositor_create_surface(so->parent.compositor); - if (!so->parent.surface) { - weston_log("Screen share failed: %s\n", strerror(errno)); - goto err_display; - } - - so->parent.mode_feedback = - zwp_fullscreen_shell_v1_present_surface_for_mode(so->parent.fshell, - so->parent.surface, - so->parent.output, - output->current_mode->refresh); - if (!so->parent.mode_feedback) { - weston_log("Screen share failed: %s\n", strerror(errno)); - goto err_display; - } - zwp_fullscreen_shell_mode_feedback_v1_add_listener(so->parent.mode_feedback, - &mode_feedback_listener, - so); - - loop = wl_display_get_event_loop(output->compositor->wl_display); - - epoll_fd = wl_display_get_fd(so->parent.display); - so->event_source = - wl_event_loop_add_fd(loop, epoll_fd, WL_EVENT_READABLE, - shared_output_handle_event, so); - if (!so->event_source) { - weston_log("Screen share failed: %s\n", strerror(errno)); - goto err_display; - } - - /* Ok, everything's created. We should be good to go */ - wl_list_init(&so->shm.buffers); - wl_list_init(&so->shm.free_buffers); - - so->output = output; - so->output_destroyed.notify = output_destroyed; - wl_signal_add(&so->output->destroy_signal, &so->output_destroyed); - - so->frame_listener.notify = shared_output_repainted; - wl_signal_add(&output->frame_signal, &so->frame_listener); - weston_output_disable_planes_incr(output); - weston_output_damage(output); - - return so; - -err_display: - wl_list_for_each_safe(seat, tmp, &so->seat_list, link) - ss_seat_destroy(seat); - wl_display_disconnect(so->parent.display); -err_alloc: - free(so); -err_close: - close(parent_fd); - return NULL; -} - -static void -shared_output_destroy(struct shared_output *so) -{ - struct ss_shm_buffer *buffer, *bnext; - - weston_output_disable_planes_decr(so->output); - - wl_list_for_each_safe(buffer, bnext, &so->shm.buffers, link) - ss_shm_buffer_destroy(buffer); - wl_list_for_each_safe(buffer, bnext, &so->shm.free_buffers, free_link) - ss_shm_buffer_destroy(buffer); - - wl_display_disconnect(so->parent.display); - wl_event_source_remove(so->event_source); - - wl_list_remove(&so->output_destroyed.link); - wl_list_remove(&so->frame_listener.link); - - pixman_image_unref(so->cache_image); - free(so->tmp_data); - - free(so); -} - -static struct shared_output * -weston_output_share(struct weston_output *output, const char* command) -{ - int sv[2]; - char str[32]; - pid_t pid; - sigset_t allsigs; - char *const argv[] = { - "/bin/sh", - "-c", - (char*)command, - NULL - }; - - if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) { - weston_log("weston_output_share: socketpair failed: %s\n", - strerror(errno)); - return NULL; - } - - pid = fork(); - - if (pid == -1) { - close(sv[0]); - close(sv[1]); - weston_log("weston_output_share: fork failed: %s\n", - strerror(errno)); - return NULL; - } - - if (pid == 0) { - /* do not give our signal mask to the new process */ - sigfillset(&allsigs); - sigprocmask(SIG_UNBLOCK, &allsigs, NULL); - - /* Launch clients as the user. Do not launch clients with - * wrong euid. */ - if (seteuid(getuid()) == -1) { - weston_log("weston_output_share: setuid failed: %s\n", - strerror(errno)); - abort(); - } - - sv[1] = dup(sv[1]); - if (sv[1] == -1) { - weston_log("weston_output_share: dup failed: %s\n", - strerror(errno)); - abort(); - } - - snprintf(str, sizeof str, "%d", sv[1]); - setenv("WAYLAND_SERVER_SOCKET", str, 1); - - execv(argv[0], argv); - weston_log("weston_output_share: exec failed: %s\n", - strerror(errno)); - abort(); - } else { - close(sv[1]); - return shared_output_create(output, sv[0]); - } - - return NULL; -} - -static struct weston_output * -weston_output_find(struct weston_compositor *c, int32_t x, int32_t y) -{ - struct weston_output *output; - - wl_list_for_each(output, &c->output_list, link) { - if (x >= output->x && y >= output->y && - x < output->x + output->width && - y < output->y + output->height) - return output; - } - - return NULL; -} - -static void -share_output_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data) -{ - struct weston_output *output; - struct weston_pointer *pointer; - struct screen_share *ss = data; - - pointer = weston_seat_get_pointer(keyboard->seat); - if (!pointer) { - weston_log("Cannot pick output: Seat does not have pointer\n"); - return; - } - - output = weston_output_find(pointer->seat->compositor, - wl_fixed_to_int(pointer->x), - wl_fixed_to_int(pointer->y)); - if (!output) { - weston_log("Cannot pick output: Pointer not on any output\n"); - return; - } - - weston_output_share(output, ss->command); -} - -WL_EXPORT int -wet_module_init(struct weston_compositor *compositor, - int *argc, char *argv[]) -{ - struct screen_share *ss; - struct weston_config *config; - struct weston_config_section *section; - - ss = zalloc(sizeof *ss); - if (ss == NULL) - return -1; - ss->compositor = compositor; - - config = wet_get_config(compositor); - - section = weston_config_get_section(config, "screen-share", NULL, NULL); - - weston_config_section_get_string(section, "command", &ss->command, ""); - - weston_compositor_add_key_binding(compositor, KEY_S, - MODIFIER_CTRL | MODIFIER_ALT, - share_output_binding, ss); - return 0; -} diff --git a/compositor/systemd-notify.c b/compositor/systemd-notify.c deleted file mode 100644 index 61196d8..0000000 --- a/compositor/systemd-notify.c +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (c) 2015 General Electric Company. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include - -#include "shared/helpers.h" -#include "shared/string-helpers.h" -#include -#include -#include "weston.h" - -struct systemd_notifier { - int watchdog_time; - struct wl_event_source *watchdog_source; - struct wl_listener compositor_destroy_listener; -}; - -static int -add_systemd_sockets(struct weston_compositor *compositor) -{ - int fd; - int cnt_systemd_sockets; - int current_fd = 0; - - cnt_systemd_sockets = sd_listen_fds(1); - - if (cnt_systemd_sockets < 0) { - weston_log("sd_listen_fds failed with: %d\n", - cnt_systemd_sockets); - return -1; - } - - /* socket-based activation not used, return silently */ - if (cnt_systemd_sockets == 0) - return 0; - - while (current_fd < cnt_systemd_sockets) { - fd = SD_LISTEN_FDS_START + current_fd; - - if (sd_is_socket(fd, AF_UNIX, SOCK_STREAM,1) <= 0) { - weston_log("invalid socket provided from systemd\n"); - return -1; - } - - if (wl_display_add_socket_fd(compositor->wl_display, fd)) { - weston_log("wl_display_add_socket_fd failed" - "for systemd provided socket\n"); - return -1; - } - current_fd++; - } - - weston_log("info: add %d socket(s) provided by systemd\n", - current_fd); - - return current_fd; -} - -static int -watchdog_handler(void *data) -{ - struct systemd_notifier *notifier = data; - - wl_event_source_timer_update(notifier->watchdog_source, - notifier->watchdog_time); - - sd_notify(0, "WATCHDOG=1"); - - return 1; -} - -static void -weston_compositor_destroy_listener(struct wl_listener *listener, void *data) -{ - struct systemd_notifier *notifier; - - sd_notify(0, "STOPPING=1"); - - notifier = container_of(listener, - struct systemd_notifier, - compositor_destroy_listener); - - if (notifier->watchdog_source) - wl_event_source_remove(notifier->watchdog_source); - - wl_list_remove(¬ifier->compositor_destroy_listener.link); - free(notifier); -} - -WL_EXPORT int -wet_module_init(struct weston_compositor *compositor, - int *argc, char *argv[]) -{ - char *watchdog_time_env; - struct wl_event_loop *loop; - int32_t watchdog_time_conv; - struct systemd_notifier *notifier; - - notifier = zalloc(sizeof *notifier); - if (notifier == NULL) - return -1; - - if (!weston_compositor_add_destroy_listener_once(compositor, - ¬ifier->compositor_destroy_listener, - weston_compositor_destroy_listener)) { - free(notifier); - return 0; - } - - if (add_systemd_sockets(compositor) < 0) - return -1; - - sd_notify(0, "READY=1"); - - /* 'WATCHDOG_USEC' is environment variable that is set - * by systemd to transfer 'WatchdogSec' watchdog timeout - * setting from service file.*/ - watchdog_time_env = getenv("WATCHDOG_USEC"); - if (!watchdog_time_env) - return 0; - - if (!safe_strtoint(watchdog_time_env, &watchdog_time_conv)) - return 0; - - /* Convert 'WATCHDOG_USEC' to milliseconds and notify - * systemd every half of that time.*/ - watchdog_time_conv /= 1000 * 2; - if (watchdog_time_conv <= 0) - return 0; - - notifier->watchdog_time = watchdog_time_conv; - - loop = wl_display_get_event_loop(compositor->wl_display); - notifier->watchdog_source = - wl_event_loop_add_timer(loop, watchdog_handler, notifier); - wl_event_source_timer_update(notifier->watchdog_source, - notifier->watchdog_time); - - return 0; -} - diff --git a/compositor/testsuite-util.c b/compositor/testsuite-util.c deleted file mode 100644 index 34882d1..0000000 --- a/compositor/testsuite-util.c +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2019 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include "weston.h" - -static struct wet_testsuite_data *wet_testsuite_data_global; - -/** Set global test suite data - * - * \param data Custom test suite data. - * - * The type struct wet_testsuite_data is free to be defined by any test suite - * in any way they want. This function stores a single pointer to that data - * in a global variable. - * - * The data is expected to be fetched from a test suite specific plugin that - * knows how to interpret it. - * - * \sa wet_testsuite_data_get - */ -WL_EXPORT void -wet_testsuite_data_set(struct wet_testsuite_data *data) -{ - wet_testsuite_data_global = data; -} - -/** Get global test suite data - * - * \return Custom test suite data. - * - * Returns the value last set with wet_testsuite_data_set(). - */ -WL_EXPORT struct wet_testsuite_data * -wet_testsuite_data_get(void) -{ - return wet_testsuite_data_global; -} diff --git a/compositor/text-backend.c b/compositor/text-backend.c deleted file mode 100644 index 722dcb2..0000000 --- a/compositor/text-backend.c +++ /dev/null @@ -1,1099 +0,0 @@ -/* - * Copyright © 2012 Openismus GmbH - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include -#include "weston.h" -#include "text-input-unstable-v1-server-protocol.h" -#include "input-method-unstable-v1-server-protocol.h" -#include "shared/helpers.h" -#include "shared/timespec-util.h" - -struct text_input_manager; -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; - - bool input_panel_visible; - - struct text_input_manager *manager; -}; - -struct text_input_manager { - struct wl_global *text_input_manager_global; - struct wl_listener destroy_listener; - - struct text_input *current_text_input; - - 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 *input; - - struct wl_list link; - - struct wl_listener keyboard_focus_listener; - - bool focus_listener_initialized; - - struct input_method_context *context; - - struct text_backend *text_backend; -}; - -struct input_method_context { - struct wl_resource *resource; - - struct text_input *input; - struct input_method *input_method; - - struct wl_resource *keyboard; -}; - -struct text_backend { - struct weston_compositor *compositor; - - struct { - char *path; - struct wl_client *client; - - unsigned deathcount; - struct timespec deathstamp; - } input_method; - - struct wl_listener client_listener; - struct wl_listener seat_created_listener; -}; - -static void -input_method_context_create(struct text_input *input, - 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_input_method(struct input_method *input_method) -{ - struct text_input *text_input = input_method->input; - struct weston_compositor *ec = text_input->ec; - - if (input_method->context && input_method->input_method_binding) { - input_method_context_end_keyboard_grab(input_method->context); - zwp_input_method_v1_send_deactivate( - input_method->input_method_binding, - input_method->context->resource); - input_method->context->input = NULL; - } - - wl_list_remove(&input_method->link); - input_method->input = NULL; - input_method->context = NULL; - - if (wl_list_empty(&text_input->input_methods) && - text_input->input_panel_visible && - text_input->manager->current_text_input == text_input) { - wl_signal_emit(&ec->hide_input_panel_signal, ec); - text_input->input_panel_visible = false; - } - - if (text_input->manager->current_text_input == text_input) - text_input->manager->current_text_input = NULL; - - zwp_text_input_v1_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_input_method(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; - zwp_input_method_context_v1_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; - struct weston_compositor *ec = text_input->ec; - struct text_input *current; - - if (!weston_seat) - return; - - input_method = weston_seat->input_method; - if (input_method->input == text_input) - return; - - if (input_method->input) - deactivate_input_method(input_method); - - input_method->input = 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); - - current = text_input->manager->current_text_input; - - if (current && current != text_input) { - current->input_panel_visible = false; - wl_signal_emit(&ec->hide_input_panel_signal, ec); - } - - 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); - } - text_input->manager->current_text_input = text_input; - - zwp_text_input_v1_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 weston_seat *weston_seat = wl_resource_get_user_data(seat); - - if (weston_seat && weston_seat->input_method->input) - deactivate_input_method(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; - zwp_input_method_context_v1_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; - zwp_input_method_context_v1_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; - zwp_input_method_context_v1_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; - zwp_input_method_context_v1_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 = true; - - if (!wl_list_empty(&text_input->input_methods) && - text_input == text_input->manager->current_text_input) { - 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 = false; - - if (!wl_list_empty(&text_input->input_methods) && - text_input == text_input->manager->current_text_input) - 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; - zwp_input_method_context_v1_send_preferred_language( - input_method->context->resource, language); - } -} - -static const struct zwp_text_input_v1_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 = zalloc(sizeof *text_input); - if (text_input == NULL) - return; - - text_input->resource = - wl_resource_create(client, &zwp_text_input_v1_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; - text_input->manager = text_input_manager; - - wl_list_init(&text_input->input_methods); -}; - -static const struct zwp_text_input_manager_v1_interface 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, - &zwp_text_input_manager_v1_interface, 1, id); - if (resource) - wl_resource_set_implementation(resource, - &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_list_remove(&text_input_manager->destroy_listener.link); - 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 = zalloc(sizeof *text_input_manager); - if (text_input_manager == NULL) - return; - - text_input_manager->ec = ec; - - text_input_manager->text_input_manager_global = - wl_global_create(ec->wl_display, - &zwp_text_input_manager_v1_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); - - if (context->input) - zwp_text_input_v1_send_commit_string(context->input->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); - - if (context->input) - zwp_text_input_v1_send_preedit_string(context->input->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); - - if (context->input) - zwp_text_input_v1_send_preedit_styling(context->input->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); - - if (context->input) - zwp_text_input_v1_send_preedit_cursor(context->input->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); - - if (context->input) - zwp_text_input_v1_send_delete_surrounding_text( - context->input->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); - - if (context->input) - zwp_text_input_v1_send_cursor_position(context->input->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); - - if (context->input) - zwp_text_input_v1_send_modifiers_map(context->input->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); - - if (context->input) - zwp_text_input_v1_send_keysym(context->input->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, - const struct timespec *time, uint32_t key, - uint32_t state_w) -{ - struct weston_keyboard *keyboard = grab->keyboard; - struct wl_display *display; - uint32_t serial; - uint32_t msecs; - - 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); - msecs = timespec_to_msec(time); - wl_keyboard_send_key(keyboard->input_method_resource, - serial, msecs, 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 = weston_seat_get_keyboard(seat); - - cr = wl_resource_create(client, &wl_keyboard_interface, 1, id); - wl_resource_set_implementation(cr, NULL, context, unbind_keyboard); - - context->keyboard = cr; - - weston_keyboard_send_keymap(keyboard, cr); - - 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 = weston_seat_get_keyboard(seat); - struct weston_keyboard_grab *default_grab = &keyboard->default_grab; - struct timespec ts; - - timespec_from_msec(&ts, time); - - default_grab->interface->key(default_grab, &ts, 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 = weston_seat_get_keyboard(seat); - 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); - - if (context->input) - zwp_text_input_v1_send_language(context->input->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); - - if (context->input) - zwp_text_input_v1_send_text_direction(context->input->resource, - serial, direction); -} - - -static const struct zwp_input_method_context_v1_interface 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); - - if (context->input_method && context->input_method->context == context) - context->input_method->context = NULL; - - free(context); -} - -static void -input_method_context_create(struct text_input *input, - struct input_method *input_method) -{ - struct input_method_context *context; - struct wl_resource *binding; - - if (!input_method->input_method_binding) - return; - - context = zalloc(sizeof *context); - if (context == NULL) - return; - - binding = input_method->input_method_binding; - context->resource = - wl_resource_create(wl_resource_get_client(binding), - &zwp_input_method_context_v1_interface, - 1, 0); - wl_resource_set_implementation(context->resource, - &context_implementation, - context, destroy_input_method_context); - - context->input = input; - context->input_method = input_method; - input_method->context = context; - - - zwp_input_method_v1_send_activate(binding, context->resource); -} - -static void -input_method_context_end_keyboard_grab(struct input_method_context *context) -{ - struct weston_keyboard_grab *grab; - struct weston_keyboard *keyboard; - - keyboard = weston_seat_get_keyboard(context->input_method->seat); - if (!keyboard) - return; - - grab = &keyboard->input_method_grab; - keyboard = grab->keyboard; - if (!keyboard) - return; - - if (keyboard->grab == grab) - weston_keyboard_end_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); - - input_method->input_method_binding = NULL; - input_method->context = 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, - &zwp_input_method_v1_interface, 1, id); - - if (input_method->input_method_binding != NULL) { - wl_resource_post_error(resource, - WL_DISPLAY_ERROR_INVALID_OBJECT, - "interface object already bound"); - return; - } - - if (text_backend->input_method.client != client) { - wl_resource_post_error(resource, - WL_DISPLAY_ERROR_INVALID_OBJECT, - "permission to bind " - "input_method denied"); - return; - } - - wl_resource_set_implementation(resource, NULL, input_method, - unbind_input_method); - input_method->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->input) - deactivate_input_method(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->input) - return; - - if (!surface || input_method->input->surface != surface) - deactivate_input_method(input_method); -} - -static void -input_method_init_seat(struct weston_seat *seat) -{ - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - - if (seat->input_method->focus_listener_initialized) - return; - - if (keyboard) { - seat->input_method->keyboard_focus_listener.notify = - handle_keyboard_focus; - wl_signal_add(&keyboard->focus_signal, - &seat->input_method->keyboard_focus_listener); - keyboard->input_method_grab.interface = - &input_method_context_grab; - } - - seat->input_method->focus_listener_initialized = true; -} - -static void launch_input_method(struct text_backend *text_backend); - -static void -respawn_input_method_process(struct text_backend *text_backend) -{ - struct timespec time; - int64_t tdiff; - - /* if input_method dies more than 5 times in 10 seconds, give up */ - weston_compositor_get_time(&time); - tdiff = timespec_sub_to_msec(&time, - &text_backend->input_method.deathstamp); - if (tdiff > 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 disconnected, giving up.\n"); - return; - } - - weston_log("input_method disconnected, respawning...\n"); - launch_input_method(text_backend); -} - -static void -input_method_client_notifier(struct wl_listener *listener, void *data) -{ - struct text_backend *text_backend; - - text_backend = container_of(listener, struct text_backend, - client_listener); - - text_backend->input_method.client = NULL; - respawn_input_method_process(text_backend); -} - -static void -launch_input_method(struct text_backend *text_backend) -{ - if (!text_backend->input_method.path) - return; - - if (strcmp(text_backend->input_method.path, "") == 0) - return; - - text_backend->input_method.client = - weston_client_start(text_backend->compositor, - text_backend->input_method.path); - - if (!text_backend->input_method.client) { - weston_log("not able to start %s\n", - text_backend->input_method.path); - return; - } - - text_backend->client_listener.notify = input_method_client_notifier; - wl_client_add_destroy_listener(text_backend->input_method.client, - &text_backend->client_listener); -} - -static void -text_backend_seat_created(struct text_backend *text_backend, - struct weston_seat *seat) -{ - struct input_method *input_method; - struct weston_compositor *ec = seat->compositor; - - input_method = zalloc(sizeof *input_method); - if (input_method == NULL) - return; - - input_method->seat = seat; - input_method->input = NULL; - input_method->focus_listener_initialized = false; - input_method->context = NULL; - input_method->text_backend = text_backend; - - input_method->input_method_global = - wl_global_create(ec->wl_display, - &zwp_input_method_v1_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; -} - -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); - - text_backend_seat_created(text_backend, seat); -} - -static void -text_backend_configuration(struct text_backend *text_backend) -{ - struct weston_config *config = wet_get_config(text_backend->compositor); - struct weston_config_section *section; - char *client; - - section = weston_config_get_section(config, - "input-method", NULL, NULL); - client = wet_get_libexec_path("weston-keyboard"); - weston_config_section_get_string(section, "path", - &text_backend->input_method.path, - client); - free(client); -} - -WL_EXPORT void -text_backend_destroy(struct text_backend *text_backend) -{ - wl_list_remove(&text_backend->seat_created_listener.link); - - if (text_backend->input_method.client) { - /* disable respawn */ - wl_list_remove(&text_backend->client_listener.link); - wl_client_destroy(text_backend->input_method.client); - } - - free(text_backend->input_method.path); - free(text_backend); -} - -WL_EXPORT struct text_backend * -text_backend_init(struct weston_compositor *ec) -{ - struct text_backend *text_backend; - struct weston_seat *seat; - - text_backend = zalloc(sizeof(*text_backend)); - if (text_backend == NULL) - return NULL; - - text_backend->compositor = ec; - - text_backend_configuration(text_backend); - - wl_list_for_each(seat, &ec->seat_list, link) - text_backend_seat_created(text_backend, seat); - text_backend->seat_created_listener.notify = handle_seat_created; - wl_signal_add(&ec->seat_created_signal, - &text_backend->seat_created_listener); - - text_input_manager_create(ec); - - launch_input_method(text_backend); - - return text_backend; -} diff --git a/compositor/weston-screenshooter.c b/compositor/weston-screenshooter.c deleted file mode 100755 index fd2234c..0000000 --- a/compositor/weston-screenshooter.c +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include -#include "weston.h" -#include "weston-screenshooter-server-protocol.h" -#include "shared/helpers.h" -#include - -struct screenshooter { - struct weston_compositor *ec; - struct wl_global *global; - struct wl_client *client; - struct weston_process process; - struct wl_listener destroy_listener; - struct weston_recorder *recorder; -}; - -static void -screenshooter_done(void *data, enum weston_screenshooter_outcome outcome) -{ - struct wl_resource *resource = data; - - switch (outcome) { - case WESTON_SCREENSHOOTER_SUCCESS: - weston_screenshooter_send_done(resource); - break; - case WESTON_SCREENSHOOTER_NO_MEMORY: - wl_resource_post_no_memory(resource); - break; - default: - break; - } -} - -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 = - weston_head_from_resource(output_resource)->output; - struct weston_buffer *buffer = - weston_buffer_from_resource(buffer_resource); - - if (buffer == NULL) { - wl_resource_post_no_memory(resource); - return; - } - - weston_screenshooter_shoot(output, buffer, screenshooter_done, resource); -} - -struct weston_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; -// OHOS remove debugger -// bool debug_enabled = -// weston_compositor_is_debug_protocol_enabled(shooter->ec); - bool debug_enabled = false; - - resource = wl_resource_create(client, - &weston_screenshooter_interface, 1, id); - - if (!debug_enabled && !shooter->client) { - wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, - "screenshooter failed: permission denied. "\ - "Debug protocol must be enabled"); - return; - } else if (!debug_enabled && client != shooter->client) { - wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, - "screenshooter failed: permission denied."); - return; - } - - 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_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data) -{ - struct screenshooter *shooter = data; - char *screenshooter_exe; - - - screenshooter_exe = wet_get_bindir_path("weston-screenshooter"); - if (!screenshooter_exe) { - weston_log("Could not construct screenshooter path.\n"); - return; - } - - if (!shooter->client) - shooter->client = weston_client_launch(shooter->ec, - &shooter->process, - screenshooter_exe, screenshooter_sigchld); - free(screenshooter_exe); -} - -static void -recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time, - uint32_t key, void *data) -{ - struct weston_compositor *ec = keyboard->seat->compositor; - struct weston_output *output; - struct screenshooter *shooter = data; - struct weston_recorder *recorder = shooter->recorder;; - static const char filename[] = "capture.wcap"; - - if (recorder) { - weston_recorder_stop(recorder); - shooter->recorder = NULL; - } else { - if (keyboard->focus && keyboard->focus->output) - output = keyboard->focus->output; - else - output = container_of(ec->output_list.next, - struct weston_output, link); - - shooter->recorder = weston_recorder_start(output, filename); - } -} - -static void -screenshooter_destroy(struct wl_listener *listener, void *data) -{ - struct screenshooter *shooter = - container_of(listener, struct screenshooter, destroy_listener); - - wl_list_remove(&shooter->destroy_listener.link); - - wl_global_destroy(shooter->global); - free(shooter); -} - -WL_EXPORT void -screenshooter_create(struct weston_compositor *ec) -{ - struct screenshooter *shooter; - - shooter = zalloc(sizeof *shooter); - if (shooter == NULL) - return; - - shooter->ec = ec; - - shooter->global = wl_global_create(ec->wl_display, - &weston_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/compositor/weston.desktop b/compositor/weston.desktop deleted file mode 100644 index 009b692..0000000 --- a/compositor/weston.desktop +++ /dev/null @@ -1,5 +0,0 @@ -[Desktop Entry] -Name=Weston -Comment=The reference Wayland server -Exec=weston -Type=Application diff --git a/compositor/weston.h b/compositor/weston.h deleted file mode 100644 index e09397f..0000000 --- a/compositor/weston.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright © 2016 Giulio Camuffo - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_H -#define WESTON_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -void -screenshooter_create(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); - -struct wl_client * -weston_client_start(struct weston_compositor *compositor, const char *path); - -void -weston_watch_process(struct weston_process *process); - -struct weston_config * -wet_get_config(struct weston_compositor *compositor); - -void * -wet_load_module_entrypoint(const char *name, const char *entrypoint); - -int -wet_shell_init(struct weston_compositor *ec, - int *argc, char *argv[]); -int -wet_module_init(struct weston_compositor *ec, - int *argc, char *argv[]); -int -wet_load_module(struct weston_compositor *compositor, - const char *name, int *argc, char *argv[]); - -int -module_init(struct weston_compositor *compositor, - int *argc, char *argv[]); - -char * -wet_get_libexec_path(const char *name); - -char * -wet_get_bindir_path(const char *name); - -int -wet_load_xwayland(struct weston_compositor *comp); - -struct text_backend; - -struct text_backend * -text_backend_init(struct weston_compositor *ec); - -void -text_backend_destroy(struct text_backend *text_backend); - -int -wet_main(int argc, char *argv[]); - - -/* test suite utilities */ - -/** Opaque type for a test suite to define. */ -struct wet_testsuite_data; - -void -wet_testsuite_data_set(struct wet_testsuite_data *data); - -struct wet_testsuite_data * -wet_testsuite_data_get(void); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/compositor/xwayland.c b/compositor/xwayland.c deleted file mode 100644 index 8eadbe2..0000000 --- a/compositor/xwayland.c +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright © 2011 Intel Corporation - * Copyright © 2016 Giulio Camuffo - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include - -#include -#include "compositor/weston.h" -#include -#include "shared/helpers.h" - -struct wet_xwayland { - struct weston_compositor *compositor; - const struct weston_xwayland_api *api; - struct weston_xwayland *xwayland; - struct wl_event_source *sigusr1_source; - struct wl_client *client; - int wm_fd; - struct weston_process process; -}; - -static int -handle_sigusr1(int signal_number, void *data) -{ - struct wet_xwayland *wxw = data; - - /* We'd be safer if we actually had the struct - * signalfd_siginfo from the signalfd data and could verify - * this came from Xwayland.*/ - wxw->api->xserver_loaded(wxw->xwayland, wxw->client, wxw->wm_fd); - wl_event_source_remove(wxw->sigusr1_source); - - return 1; -} - -static pid_t -spawn_xserver(void *user_data, const char *display, int abstract_fd, int unix_fd) -{ - struct wet_xwayland *wxw = user_data; - pid_t pid; - char s[12], abstract_fd_str[12], unix_fd_str[12], wm_fd_str[12]; - int sv[2], wm[2], fd; - char *xserver = NULL; - struct weston_config *config = wet_get_config(wxw->compositor); - struct weston_config_section *section; - - if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) { - weston_log("wl connection socketpair failed\n"); - return 1; - } - - if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wm) < 0) { - weston_log("X wm connection socketpair failed\n"); - return 1; - } - - pid = fork(); - switch (pid) { - case 0: - /* SOCK_CLOEXEC closes both ends, so we need to unset - * the flag on the client fd. */ - fd = dup(sv[1]); - if (fd < 0) - goto fail; - snprintf(s, sizeof s, "%d", fd); - setenv("WAYLAND_SOCKET", s, 1); - - fd = dup(abstract_fd); - if (fd < 0) - goto fail; - snprintf(abstract_fd_str, sizeof abstract_fd_str, "%d", fd); - fd = dup(unix_fd); - if (fd < 0) - goto fail; - snprintf(unix_fd_str, sizeof unix_fd_str, "%d", fd); - fd = dup(wm[1]); - if (fd < 0) - goto fail; - snprintf(wm_fd_str, sizeof wm_fd_str, "%d", fd); - - section = weston_config_get_section(config, - "xwayland", NULL, NULL); - weston_config_section_get_string(section, "path", - &xserver, XSERVER_PATH); - - /* Ignore SIGUSR1 in the child, which will make the X - * server send SIGUSR1 to the parent (weston) when - * it's done with initialization. During - * initialization the X server will round trip and - * block on the wayland compositor, so avoid making - * blocking requests (like xcb_connect_to_fd) until - * it's done with that. */ - signal(SIGUSR1, SIG_IGN); - - if (execl(xserver, - xserver, - display, - "-rootless", - "-listen", abstract_fd_str, - "-listen", unix_fd_str, - "-wm", wm_fd_str, - "-terminate", - NULL) < 0) - weston_log("exec of '%s %s -rootless " - "-listen %s -listen %s -wm %s " - "-terminate' failed: %s\n", - xserver, display, - abstract_fd_str, unix_fd_str, wm_fd_str, - strerror(errno)); - fail: - _exit(EXIT_FAILURE); - - default: - close(sv[1]); - wxw->client = wl_client_create(wxw->compositor->wl_display, sv[0]); - - close(wm[1]); - wxw->wm_fd = wm[0]; - - wxw->process.pid = pid; - weston_watch_process(&wxw->process); - break; - - case -1: - weston_log("Failed to fork to spawn xserver process\n"); - break; - } - - return pid; -} - -static void -xserver_cleanup(struct weston_process *process, int status) -{ - struct wet_xwayland *wxw = - container_of(process, struct wet_xwayland, process); - struct wl_event_loop *loop = - wl_display_get_event_loop(wxw->compositor->wl_display); - - wxw->api->xserver_exited(wxw->xwayland, status); - wxw->sigusr1_source = wl_event_loop_add_signal(loop, SIGUSR1, - handle_sigusr1, wxw); - wxw->client = NULL; -} - -int -wet_load_xwayland(struct weston_compositor *comp) -{ - const struct weston_xwayland_api *api; - struct weston_xwayland *xwayland; - struct wet_xwayland *wxw; - struct wl_event_loop *loop; - - if (weston_compositor_load_xwayland(comp) < 0) - return -1; - - api = weston_xwayland_get_api(comp); - if (!api) { - weston_log("Failed to get the xwayland module API.\n"); - return -1; - } - - xwayland = api->get(comp); - if (!xwayland) { - weston_log("Failed to get the xwayland object.\n"); - return -1; - } - - wxw = zalloc(sizeof *wxw); - if (!wxw) - return -1; - - wxw->compositor = comp; - wxw->api = api; - wxw->xwayland = xwayland; - wxw->process.cleanup = xserver_cleanup; - if (api->listen(xwayland, wxw, spawn_xserver) < 0) - return -1; - - loop = wl_display_get_event_loop(comp->wl_display); - wxw->sigusr1_source = wl_event_loop_add_signal(loop, SIGUSR1, - handle_sigusr1, wxw); - - return 0; -} diff --git a/data/COPYING b/data/COPYING deleted file mode 100644 index 0345023..0000000 --- a/data/COPYING +++ /dev/null @@ -1,55 +0,0 @@ -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. - - -(C) 2013 DENSO CORPORATION - -Permission to use, copy, modify, distribute, and sell following listed images -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 images -without specific, written prior permission. The copyright holders make -no representations about the suitability of these images for any -purpose. It is provided "as is" without express or implied warranty. - -background.png -tiling.png -fullscreen.png -panel.png -random.png -sidebyside.png -home.png -icon_ivi_clickdot.png -icon_ivi_flower.png -icon_ivi_simple-egl.png -icon_ivi_simple-shm.png -icon_ivi_smoke.png - - -For the SVG icons: - -© 2016 Samsung Electronics Co., Ltd - -This work is dual-licenced under both the MIT "Expat" License and the -Creative Commons Attribution-Share Alike 3.0 United States License, and -may be redistributed under either (or both) licenses as desired. See -Weston's COPYING for details of the MIT license. To view a copy of the -CC-SA-3.0 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. - -icons.svg -icon_terminal.png -icon_editor.png -icon_flower.png diff --git a/data/background.png b/data/background.png deleted file mode 100644 index 67caee38c48b2f9ea146d1eeb37f3bee0d80052d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 135501 zcmYg%18^o!_w^IoHaE6y+qSc@ZEIsY*=WO!ZQC|C#uM|~7r*!aow`-ieP^bsapz7~ zpVLuF3X%x0xUc{K06|(xOa%Y{|62qDK!g6hu&)N|06A)hF+qG0%dPyN1OzR|O_cJW=Y=ksx- zLovN?!ITLb9x|lQi0Sz*{c9op>!ULl>R;7=dlwG2$|fePm#efJET*#9&(CS^T>e&O z8VqpJ_5}5m(-rYaz&-8)IuJ9rwV6p~eo->z)kMOd?e2{jTLnzba0%5|Ou3$MD&zAF zi%;bVg1WBl$sKY(lG-}%$?mk$JVLy3vMh-arIT&`=z?<{15-u5>i0GSnf)cN`cY2^ zei#_v*_N`WXUuC%#SRN10^~feI*VV6)V#y3A#Elv;2NyX{gghomsA%sZxJ+ zV`{0Sf_j!(i4DCtWegp&D4eEUyUed?qFuC`^upj)Ot-hIgl96Ey2{@I)ZQ~ARMr5_ zI;AYEg&XIJb}(yPFPFQrd&b?5&eyIJa|_LEqZ)>rC3A?KjmB$)sb;VrOK$N?wPnnO0T;3CE0^W1 zmG+xX6yy$p61eYE>Zg{(cHun|e3r(mdXg6nAtDf#5pEaE5&$HPz;PBmF zzTAE<*XfriD!6aHuo$DrOh0Iei4EG!upTPgYO{y9!5zzws3< zx8Ush2Uw)IrJF>v6lNJSZ`SM{bJH;$vuD3Ioe-)hp%BA%tyMOmZM;`dF3)8+Kd5=W z9Cm(6eUPh_B3GpkY*D!xcB%MZVR{39WumprGX0@hay;uK{k&wN-ff3~6EO7)P19*L zOo7FNb2)f~=#7|ZWdC|MCPbcb6_??Hs~&B$ggw%X^k=nKqZgT&T7 zX^k5f<6F>R1k)`JlotV)tUj?Fwd~d3bzP>Zm@tmHhLerKX>&!AMTt#r@PfV364AKq z>vp~ul7g{d=y1k&7tdrz9Yp@`r!zg>glfcfqq&_*&he5jJ&_&Bo2JSA)WftYbn&E) zi0PyRvIicc3Cu3Re2SjDgWH*n_{56iv%u^dw|q=eh&zeaphfC;Mi{nHc;KnOx%?1q zu?9o3;1NdV5YB`Nfaa<{OykzbuAIR-<$vXF4JhZSo z5{B^?r|9qaaV0|MlnXyDS4~#AmKL=#ybF8%Z5K!sk zM!I|daJt1XSSv_ywNAK4@=c_Tdy+8ehm@TNA&k4A3TSvtueMQaN!r_YG1bL&%1j6m z?yH?xd?j*hsp&@;g6qWi8ZQRI(qy0STU+L}NXP(VSPaFL2+_Ra5b+tyOj?{@Vp4)4 zaG`^Ez&;yjdiW=mr}j#ptgVJfaxzv`Wk~c_bf=?ypvv_<6dqnYZ&b<{t(G<>?a^sS z9ki==<4hH**VGfE(;G^`N_=|DW&TV{eEJHc{h6*k2j95-Hnbam;Tq>0YJ(3P4BvRy z2yIf$RZ^_|fwBMg?u#5V?#|WB+b%ufad@zb(e=A~K_z^}Civ+Jio=qyAOqQrEqOPe# z(tN2nJ(%bAlc=&V@$_E1XE;&!A6}?{L}g9C^%OtkY+{$%lnn$9RL+u|I8b{P0=1W$ zm!}OZfY&dp-KMW{KP;c&ZykL_USho;hVVR_j7owLh?p&k*$3%?w+wb05^=C5k&R~t zJCzUauN5vqOP6#93eous8LA2_%Z`2En~aT>D8i8i{w zo6sUPtfoi^i@jz^h_`Sn4uj%7%(h?`*AyA!BU}{GVY|T>F(OIoY3u8aoSh8MB97ff zb|Pu_m%$Y~myfqR$p9Ulo>v%QLYaT9+ouANox{^^$gH?o7cs+LyO4z23AZVaS9J}V zWtzAuY;F8$7&o5w8KZcD_UEQZ7gRxy7jVsKd zn{>50KxLOHw!gXe%84Q@gvE0N4O9@wFf|Y(OBLD#_@(o{hm8Fy$s8D=Wp(wNwHB3A z_ev;_LO)7Ca#&=!^y}Jnv+KcMNbn&I*|p#KlsDZipW5_KgAVbhZW0=uq<$+{pn{gd z=YqN-vxgV7R^Ft=ZWPj865LACJ8d@c2c642+YPF_+^wWG={U3rvcSS@ahXoK@kdoc zaxvZ#+IjxNCu$z!XwvgdZ7v~X7bll%>IJ`LWpl?is+)bwj0SXU# zr#93W7fFU#T@K2rJKMn<5}EJ3P-T=|LJFVaWqp@uW$wm6_Ou%~u?i#fQRMG6mGxBx*m2S%lvcUdJzf-ZG)Ok|%T6m)Wa&`Q1CUB$4@5wWLb5=PW9h&K<6HKg9d4ZBR}>W57X4 zNx!Vadelwx$=#G3VxfO%8Zl?f$i<(QrU9@B9I$-gmoG`hNi#FB|MW0if&zpz)C$jY zm=L?LoE;VlPga}(mW<#ru?u4e`?aXS=aaC3LF0-+D!%Pe*_2{d-3;3iz@%QuqA z%(`2uAGXYpkSsfR!X2NNyZ~HmXcFHvUo{X5UUngzmF_b>Lx3u0W+!f(TJeS&&7|3n zO)vcdTi;#OnS5gy zkNHU^`23!(YY$;$@W19&!39ldVf(oL^o7d`13My&0F>c@4W#^LLn~lFk5)-U#|UiI zv!6S=Z{uPQ^|W3z54ZD64)>QwnlF(IVZ!?6hxu(^aJ0igT35;z z%pDW%IMP~^xz+Jz+ReA=DF@|VnJ4)wZ@<<#;Q5;aqk}Pa_`l6QZ>IKzQYb!@7&`c0 zwt535^zXL)t9gL`_@ION0O*|#$~N0do@s_0I%9Q7;<1-SrpR#TmMva;q%a!p377)Y z+}fF@!2@+lv?B|m?dRZvQDS3K^qgT(JD77^3YK?Bsn zlnM-)$MX%9uNwMOX+r(_pc{h5 zIJ}KGs#=~cpi41SJb^=?5)+heSar@YAUs2w1hqL<=m6zg^vMX=-A0}-F^p=(F9H}^ znK8`Pz7>GrDX!*(9fy+nK0cz}+#X#HyJOe+I9G|L>$g>A)1igUA^;h&|d-Yt|8Yg66l@&j_iLkf}?6ERG{Bw zbJ%~!ANr13=s&?BjI2fo`1hRrorM4p$G-gSCO`0!QF6)+36}hJN^W23DhrKZcm?yK z2KDIY^M{Y8^cJE4YXHCer9w1zRnd{^N^c(<5&p1f18f|hZTj$78aOk*I5D3Dqx|Fg z82gm|92uVfjAC3Tu25kjzGydXS+yP=MNdA#S4S*RX=>pV_oRa@P8<5`6_h1C`)h0G z>j`kvcpRXR`0gA!4cl{;S4Z-JPP90h)n_-5pm>AX$uj>yeinfaqYo8@LBNsLz_C}A zhgpO?^mL?bOMdq0cpy0{XlrO) zEkLV`I0|t9^-9sy6zVnc40v@-pJZKD2@$b082ppNkplUw7w%C%}3OlJB9; z!6{v*4W&9?;~dPS0`*o@JL)Y(N|?gm)HI$5`LtmYUi+EeLj&`T%7yUNYF$%yQO|!X z_Zyp*mA8MiXxy1=Yoi*_A;yufpsUT20jB5shmIXdEBBWj8dW$O0OUu^)XRGcZZgiuEZ{hBuxi~^iY0@C%ib*X3S?erU=!&2X@z71KuaLGk3X?@RS=Zt^zVo{RHJT$91XSpfx67 z0`9?Xkr4wOY5V$#NW>+;1FGb_{QO^{uAdb70(!c=yFcH(cR%s*``7pnuUl))oNQkn zVTQB>9CdrhX22Ryc$X!0xu3XP#n{URrVfBuFHE6uE7;u4;gAC)F(a?C%P0}}^4ahT z38@(n$Tc~#9!!DMDJajMz$jvs}fw(1F+=;_apg$!0egn!U!z z1*8PGklcYRWS($8i?}YeXN~5!SJ~SZ&Hk$O+yjGO__E1=;QlC$1axQocVG8M^$|FN zYcIaXnxgs!kU`jda0bVfo)-ER-SI%AEQf_5wE~Ct604WQkbor8pc|NX9vf#bqAA6v zwT4HQzEPOjYKBPuUPs+i|B4Qd1w?;94ldmP!SEB<19}hD`=yitEyRUH`u@|>c&=x@ zl&FxFNj!ud>+IdfL}36yMIYbktt$iWTU;H`>7aDp{Jbw#&izFwRY$FhSXUYxLBI!_ zx&ky*=U7HCyT%}HJ2}}p6syBKx|q{=-A>kU6Ha54NR2w)iIEb|#-OEt#QKI?Kx9>& zrhoHt+unXwehKH**Twrsd(Dqxfw=c3+=wKE9=6f?Z3p9kr&-L$KPdLXz7Bdj5MG(9 zpyB1`6pFQI<(>$swdo%Zz=s}BmyvTwa2H#KB;cis-#3%)g9g_VJnf;nO|cKI#;(!Siu6yYT&b*N9^iO>tN?paHTAT%5(GhiMwR4fZ2nN*FAsZO zPU5#}h%_U!@w+ngQF(e#TVY;SAD@qbJ`u}MQhgSCWkVV*8c66vd~emI*u0gkJg_V{ z4+4s|nk@C|u#?|L?$YRL;6V6g$w``FnBml>w2M$-VX|`6f3WJ8d%)61YOkXUq=uBM zt5fe>kF~Br3Yar>NZMWeXnWow`}!q zKQh1`F|CN4-7vawXs)22a$cXhC#)sII0N}_@qTLJ2ifl6c+bP@f;jxBSf~a)4udqm zJh2C>L=@)V6ve$AWBj>@j#4X)>3^9#dj(hX1#Z6^#J@E)}NE8ly!3T&d}h{4MA zCXo*qX3t$wx-oHoaq`q0n<{UOy>+P)H6X6oE$4*{^vPv5$g!=QANX0VRhA%S`V0jc z`q5-*6E(1XV{W7I@*;yUYEVmixT8wG53U8}+`R=0L`1{Qh4f9Cu{7!8f7|RfKh10i zB%ErY?ywtr>967HDLl*A`N?tmxg%~By20@V1cDq(2$}$0czSS7;(3+Pd6UUDuOa%) z%2|x3IbZFs-l@GQ`{nI{x<38reroq0ukK$UHwSE=jCB9QGg6>n#8)lm^8smd@L9gp zyw2gJ-bwF+fUdwoq>3B!uNxay2t6l|btmy6LqeL}bA@4-xw3TxwcP5p3e6v$`qo66 zRZiy+tcD*2?O2F=6!&MQBv$JlzdoFgqK-A`8^ZJ!CyJM#yR^9X+I_`;7m7SXtmJR| zJy|EfUc%J~22fEv!kN!HwD4?57~xgePQQHao~;Db5>ISS?i>r1`>Ye)HC{)Xd~NLb zcWymG2FKK*FH#GFVC`gi?swn}TcX;Z3Iy_MUEBr4QWZW<$HV0edA{fudytF?xRm+T z^ydYv&`tJmo&bF_LS8SgEfl>Q8be$%ub~xBhk-2o;VtQ3WgCJm4FUW9A;;W?PYHOk zgy((y{7k3~WDkKlWVpymsiU(B&7dv2F^CAGASlPIq4CbQTpr6?1ccSpq9j9}?1lX@ zQm9w5e=t-uP7=0J5KVp_9y7C;VS%cDBK~CQiZ5qbOrcBpD3NsLay+U6?TqI;xY%@} z;;T-wi5H@r zZ1>@^hTVbpL7NKKsa*Y&870JLm<}gZzy<+ zp!El}&2H351Jxk4;wQkosVSq(z&W1MeXT4HO9gdH$z$<~u{gRczvV<(glgrDLKA@0twzxv~2x=&&`K8O3>pVhFR!B4H`5|-(83tt}b|h?K zG0-{Bigr#g2M=u)e|ehOLl!Uqpp^p-00=BicaZqY z+@!PN{jJD0{4H9!O#NkdcC)+yfE&|mUyp-Ud#Y~Nbu7C#Sn32$mkrxgNT)Zdq)Vjw z_qHIO&}(HryLbi&%=1t?(=bVdf|Ar;x0?P&Ab{X7S-4IvWk7DJAQwPp?~8G z&mdK1!sq+msl!uzEvAFvQw61!!$X^FV;6vMES^9s>-O}h<-F-paJupAH`w$s0X}hq zLr9z7AJA-NR*?6Xj|H0-kbkHoZ1`rzFNb48O|D(xvtZNq^lM(~8ZvHfoC*Y9ZxtwM zs!0X)*a&tnL9K!_H#S>>LiDoc79x;p77=&;nKMdPNqWQ$Ui>CF+!9 zzZ_TzL*)dbWr!m-lmkD3^!UX2=nW@%>1$oC@AbiKOKwLPMV2%i za!{k+Uy!4M+17kOYTp4*4bu@r;@f}b?R;)97KT@nT*A-l;G-8whFdyC-dx9FDb$`&j4_1~WEJ(BGbJ;igz|HH5u z6$F4XvHcJCG*jl;8ATC@_M7{9)+6BcGvZ4?FhXY`j{TCEEi==f2saC* zA@V!tP+?&vNr0#7&d19ekv~7b1AJkzL0vV+{stCIb%nxS(ptLsDUg-{FO_b=GpKD2 zuBEG_-Tj$)P7JWR?33AzJvoOWR#Q>X; zc?c>+SR(u7Y#BUEF5Wy=O&Hapua-v5`O9pmo=efLa5%IHedw3_OOZFw5VG>t2~z=P zInOCXNv^y+SS{?M2-LzHO;B*K>o!CR+>?c17R4PB|_`8 z-rB3ex1bGF)pQlErYYkrjz2ZFe&^!72dSGJ9gBO{qYO+6{|H!dq&nbSgiVBjEr>kI zth0MSpsN44;aJ|h;d#Y!&Yh_)sh4A2?2aaTuQ1F6?oyvP8RiyCIO9bgfxXG!jCD@X)0SzlaX@;Ncxz%Kt=}H+;=KkI z*r}x)3kAVsRKH5E|KxGC;>-7$9y-UAhD92~Hq{lc8RuV$diC!(MFMI~DZ={D6a0jx zMz?f{_EKXfDrt3Hwzwm+m*KhQ``~i*tsNx1kJN6==i>!}4k>V?cPwnMN6!9 zzZ?1k{?N*z@$~o_JnO`eNf&Z|4~1YV1ieNej)gD+ukx*_l#2;5Qh~q%`<6KZuXf)j z*>uQ!|Ni(93u?(pbTkgKPRsYF~?yn3<4$YX)i8C z?-2Cq(hU;c#wzEzQ7uHFxM%YaYbDusfGNuZ+OJi-Iz6p(#s?2c#yM8oJ?9>iueAhdH5&UVeBVx zKf84=kPEI^!r&CUP+MY`*l7PuMC4-)22V)fHK~|gfCaj{xr?0$3fH(g%Y;M6v&6s8 z__vi!@sjs!-u?wmPKh|w&xs^~x9`@51kX}TzZz#&=Pi38Eg?RH@8zO@3)wo6$-w8s zQgeQ{Z&kc%LG7@40*SgW>Rt+i z)K-WEb^C)53^@KOj68IVCobg)>oyhcPe3_D#b%DIRu-p6hkN;g z&>ok(_9bSWvjAKP*G=MN6A69Qt?Qp!N^`PnXY@R^awA`MGoR`h4|-(S#r$+P9f?$I zUk;Ya+$yV1wi24!G!Izr4)(W>`24Szn^T&+(As!|?e3*w=<-fVFbKI4)@LNw))cXI3}7Aj zYRpt&rtgCHXBu)28Fm&u`dA7)?;2`zTMV466N`t_3EGs(K zmKt4_&S?*($TPbc)?c{K!@f_qr}tL_mgz&3{cuXyYtAO2>F`PE8L*a6cBRncbJ?(n z`)(fvvF97=Qh6F>e_CXL^#!={F5;4befZi7M-uaMI8#A&M}ABpFL|zc)kIos;~ z3!C+cFtL(zK#nNle*X^Q*x?|tNw2&zj8Ybx(6Q-Y83U!3HF@+_T!rh|Nr?m8Q&Gv_X=RgpO z!omChx7x5regMe8T^k{Wp}X+5h!(6$Dt|FI4hO_MJ=AZg7hb5DTQfz3|Xh z+ZSD&oL{qYiK^za^RwSi?GWsI9MuZGzxp!IgG^ki=bpi3X?=V;oSxhDNx{qyEhq88 zsLXY9b-<4kG0SSSY8p<2a!ujjoQ|AICZ|44~G@v z#>h_{aKGyAGqwZyYFjRHOo-DWktfg}{(en4#8776Fj$C*M)UhIaYMX3MWh@&iNa2yb!ss;d z4|F=6sPBe|?tYI1y5h{*8S|8@$_I|)ZZ>MRx^OCo-*n}mu&Ub+32US@>N7g+@|AU- z5BuH0TQ^ea0KUeJ32i0dWQ&IK(Pg@l7=oQ!zO|85Y)vpISO zj+ud;FU)tm1egB)L-5FLl)&%6v$XoMlMq`pU*04!8q1vwrHs}6y^o|5vwNLi4iy-> zF2H`t*d1?xNyN9$+THJbSetnf1=%rHz>L?CJAvJ>w?fHtO?J$8+jVMs z#eXa3!?2LOjCLJQtsn-ed`GkFjBc!j{CDYFb{VkI0xQx=mOQHN9P-9H+wHu!q{8V8 z=P?nFGm7_Q+iFD;!EFB{%B?+SW}+~QYUFmSW8?QR{ha~vvf{2jrOT>ev!aU|V`n|& z9k+XnYjKC@qerGX_t7Wr9AfND5C9p1*9*n+k9g}9 zq(Bf%hce+MJ!2U~j-o^DP?7$wIrdvl2xHnX!)@vENi5@vs=jtYDlZzin%M0w|%4M+HGRBLJYb}I*cK8r(`&NiRFwD&25)Urn^tP{F#f^d%Zad zpRN4#wOm_&<|$1pY&#U{!c*$>qW$199W95e1p+&mI$grlPz zWg=V+Y3^v86ePJ(9uz5#UCd$@{qy-YQQi{_VgchZ{oJW^DfdAv}1 z0H~{h{}czf4V8u2($_JaK_+N;y%8qbSdse=1%wzE%@k=Lr5jeE)Lr?i(-ofYWr5a>dlbk-I zW}UhB=tO)+Q1hcC@*M+d|8>>(^KJOvW~W2dKyU8)M71qoE%2jsS9!HeB68$lR zF4PF@2A2l+4!iq#U1T4@LeVOPtdTwpt+_c&e4yFlS)YwF=EJ7UmXN`#1^O#3`TAgZ zka*L`xo@zVqbhZ}8nsVbX0fyNSLav~oO$g9mlzv`^D+j;gd`s)br?rpASQ97Zh~(^ zq5VD}vLsx5pB3daniXeDXl_WA<~IZXI)iN zS=NZSJl5aGULi2^$Q{#!&&%9j_Frpswi1y%_BH*@X?zU|#FNUn#AoP5`0$>34$5MG zWG_Q?nhbu=5pT3X@Q2-tat;%(7uvV`CLoZ8p_SFbVIkIDFG7wKGM_gStM7)??CIv7 z|LvLwKNlzMYP_YsA%2zi1r8=+vZIQ;^utB_P=u;9mtJG{%@q+E_*NzF{CFyYgvy*`;6tXEn-Qf|0Ugmf6z zGvbVB;bXzwE9lLETi6Z~CE5R>mJt9%3gTbLVz>E68vca{2w=OnI9bK=@(WdX&!WyP z<9wAv!eJmdmZ=1@nqBI1Y<3&`$gIhM&yHmKGqto9l@-i}y(M1AZj22M_W1~3AVf3B zj-jkmLx(vYrizAo)2~!KytB+LD)rcnK}c~|?B@a&2hcFy{zEryL6s1y(d#P0 zVjSbRjqU2MVN8O5j5cKGN<~4=v7)c;glkZwgeB4>1O}hyPVpNPYgJrBFD%4 z0VcYFbH!|e3y4!9{x}<1P-Ux0Y|fu$J2d_9)Hlp$Oe^&@4|^Nebh-uaH<6S$R&oS> z&LVuviCXIR29@LCj+HwoeFwQ2Ie-0$HPU6c3R5_3z!c>2g@uBMOW{;#@*&~b%U$69 zVm#!}J~&4mIGsiRT^k{j%=jKrE+K=G#ND2vzzNU>V0WxT>kv^r!!O2AMmSE-v~Y>< zMppez0=eAAzP>oxY#-dr6AJeNPWh*{5_%vjHOg+7gDFVyoJx_{$N_Pk7`vJjO%cp? z87CVNLI3qBB;miz8I;>Yft{RZmcUhaXl1JLpv^#RnTBlhyC0zc!Hko*Ae zy28A%KS3N+Ru;!g7ZKJiekS{lxjxHtj=_yXURvx(qXdMZ zSp)Cs=dX{xUijL{W1?e1{4D4?qR-S{2OKsCBjLRsY227wUb$ckXF&CIR9kv4 zXmz3SUjxrW(v}6+E5Ddxu>ceO3S`wwwZAfld9kI>`MXpZDuyhG&_WwF!cY8T?6wolQ%V&}vCstZ6XrIsReLP&3L1D$}4uev6aA~qxOy-*9-I?3wyof7>s?z7sALN87Ill;v^z=`StTyym;BuAwYe;b& zD8EFHmFiZTN6L9J4Vy!`g%}c$SHAgP2s)H3bDk6$RRC7W2TJ-var<4ZlwXjePYR=h z3nvkz^l|+fMPf*l9$6(1AT4z<<^>OYuKIoszOXT~zFj^Rl$Weq&2A|CB`^)NK=vGA zFCOivbutwE?ROXfJNnBW#zef=D$}K21JbOnf+soxbLLv?F6Z1|S7a!Y;`}<9J8Oe^ zL`0STK^$I2rER3}w0Mt4b9H`!Ni%QEG!6zkTSt5*JxIZDV^kUzWw@DSHT?I9*Kd5c z@1oX!0GA%rx1cw-ha25vqX$nbESniuiZEm|La08Uj})?;EzPer?+Piyv6`Xq*PU&mNX1Cj3epVt&I$2?d_xc8ye<2#L1?_@FhN(9k zNSuD3El{=bfNobDKWe$Es%-E=Gxpdq8JsQfk=-)`!N*p?u{Sjwry!YQAVN0NLe9zk zBh|(J!)8h#bBbBgBa`A+hc?7G+N6E;?w)NM4{f?puEaw2z%n0WY%n;-by{Cybzj~;K0SXVeNVHCtA|Lb+uB=laue6TN290`jPsa>%Eo~ z4Dg7to7{Rhq_DhS6m0#b--)@mWr`nej9 z?h=HcMC^caV`HyFu}{l3=h4Tyzb{5S2=OIXSy%Cp+1Z3 z$k6Qucs+xQvAj5N_-{Xm>qP7iF$pUL83nx?<1!5EuH@6?--(fW$_yv&0(lz*6&%fMINTQ<`|HK>zBvPv@tnWOoZh7u{!P{?FD6#+iRqo;`X%QKSEM z@Fs5uBoJx(XS`5HW)GEdt4hl#ZXS$h#-HS)hp#WQAm01+mv2|4%Kasr55&D-fxM28 zG#DRDStTH$pF)B%aTj6GK4Zlf6GDQZj;mCS)A(^w&+Le|Pbd4DB}DxAA|NPNdAQrM z$Sz7lfeTQxd~B3xA1(I)r>hzga!7^2S{cA7*z-V;i2JDNgXu)SoZ$FyVxiyrF6-M0 zcU7BmPw>8B;AiIrAyUsq1M}ZQ03E9TQeOMF5ek0XojU~V96$H{fsM3Cz~V=kq(sk$ z_)eAI79)|c4sxh9L(>DM;vhKVMRWOq7t2Kf`@j(wFJn#&@IU++x*>BzIoH5+?+{+F zj_xZ+bRE)}eXH+}J^G%WS_0kAB|Vek{y6XigQBTNiS30k<1xQWy8mqfC2^ieZHasG z+wnAX-YZrJvDTHLDbCR zUFm22b~Q-9y{?<}&(~ znyy!SJIF+&A9~eo;xuZ@(D(iQW`8;|8l>FkF7XFPa5ku`MNMUL$DP6WM%qHK*zS2e zx#f#PW-aLgM-nl-&@9YgEE^2*Tul{5T|3%w_B^$r0==Z=xZb&PRgzf5b1DiYk0k4e zYRMLvt%N2suC>LVkEM6zG@P8zO&Am~wn;j?7@AG?`_=0o&suxmwmrvb?{tXUTieGL zKRH`&%+WJh>{rf$W+2u%l~A9m5L&L^4&T38UuEXv`*-yWXc>57re%TQ7~q>m2Dl72 zif@-XHd&DxEr?}FjaAR1tC;G|M4SX_Tiakw>U-*#F}jd1H$yi=tWKvZ5&sb!`XyVVlQR&!YjN8CWkX_cu?mQI!o}u{by&FJI(QcO ze<`|f;TH4GgVJW$A*_|JL^9{J)hmc{i}{wDxPCz|CXJUc)0n6~tI*~aemCou$$_H+ zcWKDUbygL{(d?LWJTu(*b~K%_k8sHf zKFXU&`2a?51w1FzL`7z)p};~vYQCI8DFotWvrls+`0OE>Rc0&B{a7CoV_-?7t&Fyx zvegi}ePd-2TL$2EE>Tfe=(PGxn!MJG?u|GNoqlxct5c7D{Ou)8+RX3=Lije(bFY$WxQG2vK%)!j%?A{scP&A%&&+DHowB+gEvYI~}atj>oO)yL!z6X4+=f_P3 z*+;&Rhvk}jckdu|zYlEV95>G$3bA!P3F_9@b50wWkm1#RI>ZJEg7`-gMn*H$tB46> zyEUv`kd7sS#@8n`wVW-z=G-$XT)v)uy*{vme-%`S+o3h7UL+l+>{_#`+?y^?x;I#y(xRWpDPF<-VqLZ*{bNI zJLnOw?Z#r)Zl1^!FFLtX1BvO5gxcM?JHNV#40pN#393n>Er=`RAwfqjFw<~rB5+QcN7&{_ZEuK$=vU5|z^f zwt!+a8b4^ykN($HelBN?M<)d*bQgTCo-QMPZxP42Fg+vHC#_I|@nYn+p3MMt>Xz;>c_YS zCPT;7M{@3bHOyMB1kTl1;+aMSFY;w2S}Bp4n@&nu&V;QlHs@cbk!2=VV9jZXjnk^8 zHWuF@(Dj@z-fkau1b1%tya#50L%du+S|q!Vk`NjPsDsXl_8xg1@gcOcsDO3At)5$@ zpY~FvO6g&NlqI(>rLm>hiNaEQ*q9&81hp|Gy5UV3y z3yp_|y$&}LDIe+|`)m47g)viXj==MHb??(|;^na{Rv^Q18d}|WZyo$U^ zf+=mOs4}5trcPP4WGr8>@vat$7R!1~MD{rXf z7&U2kS_xQ`s4Z7AZzKsC0k>e>Zv6DkD!|r3X)Z*Xh3gB&Xb7s zz>mWQUJ7YAskTh{sT|>j5cxc17u_P43T(gV$1ns|IC;*YgLvKKM#Oy5P0?7>@9Xv5 zR@BC!JdSdpK`76RaVE=6e8xs%_Ekw^X|lkhtEhA)y`*6Vc{!;LBRyH4-r?P}|Hsoe zMTZtF+s3wS+qP}nwrwXnwzFf~wr$(Clef>g@80>dy2n@_y=qlg*XTLBniiFI1*{c5 z*Y{p1Mxw5X&V+k>aCf@nROjLN{N074XNULyTi~yU7vHmdzzDvkQ^Z`FsC(&;|4I?G@4ID>KDw2_VR{b$LKzKVN;+NG1Yw8g{wXLLNj zZXozm^$UeR(q;J^^qzOP+a-hW5T;kLnmU`BF5)8tyexSI06+9cVO&Y0a!eEBuRKp{ zk`Q=*D*(v{;*Rh}%Z=Sg4mdK4;wuF)xq=55Xi|9T^d_6GbZ5AMeO^NUk_88o1~Z|U zp7KW|tNhggXOYs`n*wuGRr1;z6}x7)m`|8i)Nl?!tH)V_JN!f-Ngt@Rx7$)q4tI@e zGnonCV9VEnW4&h9DEsKz6O>qe8l3t!^mph!-@^a(TKq5Md@1~kS{AO3b1LuFeldTy zLVuuEo&BFA|8t3A+x=T=kom9M$#eeERsXxU7b*`Q;8YNgWW;B~z4s-2IKGZ@A7#1Y z5dB@C!$kX1M?n{$Mn$1m=`ggQ#v^cdugB`htEKn*Q#%gq0_nI;l^UJH>ls;@DL%Qf zmyIol@1tLS@6(n|F{cp2=I<`a8urg@6PGpLu;n?j+Xg92M)J=CifsH^X>d zRsYp?QH_ugXQV5}WHJHP)#v>_thMj34I)ri?n@@D>nf@Mw^{OCqECZnQ;Cd zyqD{mLHE6$%BrhPLC@hJVvlQt(g4@2ZSGT-L*Pv^UV8m8O4G7pvo|xg7|?w!0VH!k z(9)K&z^xyrv&CB`@l$r&P!2I66sY7z!?Q;O}{-Gj#Owe%NpNawJG^sUx2p1^Z(ei4qGcL((CKyc8TZCNGnp$iB^FbD%B!p=^7rW#Q9CDf`rzvr-6c&T0DG!`q*gcxr5l_V>561&2^#O32sy#fyqOxd>p|L2L`o zajZIT!jSc#t{f?4taDKzPO%E`cOvcFgo9imw#I8Dkn7Ly$aK3=*M$xaCY-(R*dl9v zgPYZi92!IePsxm>_lT18H2-!_c!+YX`r$2Ediv-v5M-)4i69;ENyp=c3LP#R@!p&q z7PZsr302nw3^f^IGzo#FGu@M>l)x+r;_QqGu5-7l=hg6>WHmXQM0wDA`_L}fMInh_ z-zj3@2uj(Ni`hwQsNJ?W>7dj2uz6mS2t{>>4gGaSi+YZS;SD@Pq>56u-s;V(w}_<@ z%a?Umrb`*|a?>97pD$rgRlgiTLqr*6k+OEb=_Mg_+-raY;^RkQql1A8P)Dm3LJf~J z8SQQJjMe}L`fcKMHC&R~4~ZfDhM-b}UqPp0i&P~5D54BOt?j|UIlCbM8I{=XBXWip z4_Q4WObyKiy)?aA!+0Z;+XK|KGLv>JO+X}C(`t@3h_9~wMHS|`2a*nKsX@s?m?}+| z9PruB9QFJ{%t&H|i7h@EaJBjs1`~nrdZ;@;>bv5n6B-O zW+&hOX(?iT&`Jr(OPcsE4#A(JYfs&+n5}(H(tx#F&2JhU12IRXuUoPjaNm|ptz7w> zpKk}lRpY~6C{~f06J?5qp#w?)VQ9#iZl<1g!d)3PcL1AW_ zN6Na-oRn9e0TPC=o(SWMdh^b1w{Lk*#7Z)N;~%D-Lig!QV-KzD3^l`xn_ShP8)zUP zR+FUADD%i1YdhWM-Y%ygKq~=;#^wpj6kLxaT@=u9)8F!|6!RtNk5QHhm|VQr@vOPj zJhbN5!Y<-+dQ3~`)E{db8il68m%&e0A@p(a=un0|3q!n@7*%}O83#=|A)QEJnwTn< zY)!E+!H?llL_S*Fv=2|V{(H=9j+*!T`7Z$bX!HzPcePBfy5oqRq;MxE}^YWstC$#^U-)a2Z01_dsmW0&C+hiT6fGPp4ojZ>Gu;qWGm!L+Qz#hNmqaw`D^L zs@&;t$jtOcdxpcp@X6$j4#Js34*s07o!F1aJ%Rk`N?PYu8#XkL^XhTsZ*k|h5YUrD z{{@))kJRA5iUYqgcz`@Sujh;9O7&X3?thKv#i}2Gx&Q4gSFXx8@saj8(Cc%D2}ng} ziF|yXA*hKTR5NKHENNm95F&{aSoXvZ1xp$d#|;y-8O~%-F(gJnsncyIa#NdeBAQ8a zlJX?Vp6<`H$bhZ@6~{6J?2w_{-MsX?9Gp(P8sv^LQ-3C27Oi?xk>6|P;4&>o3&%cE zo{sPo<(+&kO_y|`72y-61eB(7yU%r_c`_+WqlK^z)bqUYu^prEPcCCZ;ab;4W$9t) z4Tg;Ju=0qeNp~m#rO$47#U^KD^D%UneXZPaw0Zulcu%kURA^K7WYhg>&HNENB=4Ku zx%wYFd~gGFdwr|;C!X3edH>fO;!?FWc^N&x3_FZsL}dYdNQqy!x*~MPb3%gg6Yn{4 zxR{)Hh5>JH1bc(Q@&}a@IcUit19{p{MFvhOXl{wS*G}kB=Vi5u081J75*B_4R-w}2 zaO$tbP=r(@EJDe9?u^uCHcq8C&8776d{T~h#RBDh5}h<37&?RG?e6i?LTr!PIM3e7 zhXl!?F~%+^4SNWp{v2bEsV?bJS|fNPhWQ7XF=n1`qK9d?q|lxTgN=|W5b!%QC{J7M zQ)>QsTa#cwH))&am2uKnYWbRo@a=yGrA@bkp@ zjG%~3aU_Aw$j49OikgmU14xJg>6x&23Xw6yf5{OgQq)}3DMnc2LvT;YDp1xTfe&1U z7LD+1`Yr#}F7gz@2R`z25tXk$TPW{b$5JZ>Nfbwdc=1RDIyM6MTTc|zMjBt`oc3gQ93l5{HpY&%p&#N%uZ#}ybFelldK;zw^>X}h z=j{Wq2A|7@K}$k(NI`KUd8XHC61X_1H@5+(1nkEdNd&@Hr))!grl#@*g-#Mod{i7{ z=(f;46Ppj)psBo7(2x5(<}o5Eo$(>)Y8<6W%|n&12Z^WDs>TkTF?|WPlqR$_rfJZ( z3HwnwV#$As`j@<(YAFt++6}61GE`&qN4Rt3%ljC(ZyZ#oGWH!lXLT$ryc^jIs!U^5 zn0vYLFXQswg^4CeM~gSnIC#M9@7rmue~)QTTlgwnUJ2DhM1QgBn>A2OWY*C=%55Se zjR=_C&125zgE|K(r4(uLWa)>Sjq(NP!(URY>sFEb7_Z&)v{I7oT&b>_+D?c$fM2NJ zAL7YIh!vuxf4TApDcJi-|&6s4?Z2AuTg6-3N(B{`tAq76S5wf zg!&R=%5Q8O#>g5sq39JM<%aSa5Fr={;KK!kWH&TKVd5U%pd^-%oCVDy$t(rd@(Jr} zMTYgl?+PMT6esI~jt^&Zp#gUhfYzj=jb{+nI}JE5jS9rQm7|~@A<1?Gv1s0ER*6~* z5O8ISnkvZ*0x^q`K$qqFa%0kG2MSqv07u0-jv&+_q8L;@*0SL>CQ4_b4q`Y10~j1t zq$i|hJ~VYBYC7?Chk6lWWFDdQQ6h+r0fx|eRY%9c8wi1Q-~&Lt>AB6Ixz(_i*Wj8n zE`i~z%4p+>_n@c+19z*FLQqJOMWlc_j1g4M`{-Ur*3FY+eMJ!f9yB7!X-m<4DL#e_ z-9jE^rxF>(8-rBph`FSR)VzM7kH|+~FVq6RhPN;-n`?h4tS)}}>&h=Ft}o#=9vT6U z2rpK0!A%|GgJB>jvX;Bs)~d$#9XOat+T+}b3e9}22Y3iKho zLU+Djw=<07Smp8uX@{Hgb|y~>>C9epO@Uf5-X{6v0=%}_VgO*41yi)&cbOC=D(5@) z1op9Kp!ilspm%3M9*K*TRfXubJTJ|HzbV07{4|5zEPH-4V~ zC0`uaIbuFB%>RdzK>#6!{~s;}M9@JW9^jREiw6uVG2GhwcT(q)2u&A!3WiaSvdQhkB z^4EK19+^G-F4fttpgwwjR+@FdTJ4TssXjh+tHr^2Ki5Iq?=HDTup2)XB_x*7HGYa3 zKNKCsA!~DL%3l36CW8my{x5qZ%maYq_hc^8|F-OJ#D4I_H`3+MSdAVV3b_lbXP+U4 z0CFhzAqx>?5ED*MZ~x2H`*Q<>9}OGmZHzG^A1G7iNzP+8x|E~Yu$T>l&l~+x-?7u; z`Of^x9Ug)3D!b>Z7gtf|fP)tU+dKE$OyUhuEn zU?ST{=E-O#KvOfrcck8Wac21HEkX}Un=>kwFij4;HOdBei>eH*sl47ET{OS$F8yDB zeRvrBQ2bX_rRLoVxAvM1I@Q~a3oWK6feiK52Dmj7LzeX@s3pr_!pA9`zqX>1xffd5 z3{h)Jfa|c}iRNcwWH&}pA2OIEt`{yf<3_liC{lGM4qoS&VF+uH197ApP$VA>S<>ky z=OyIb?X$94XWrE7+h>$}miafwluZPY>=@Dhy$C#ymU_FO)0bkTiVa`QDQYFcA~tIw9Sk1Y0a&pjQUe>Lfd%ftxsgF)h;A1LkyJqD3dQBB8pw>0nj=Sx z&5MEVL>rkyTOREgj}v;@3Zbf-?E!)KYcf)`#BC+A4m^_cn}N0D=d zjoo(=+Czc1$>iYr%aPkVe>_T0@Tn49b06~LZo?lAE-&JzAD$_{v_4@-D;vG*E7TH2 z{dC583NI=i!QJR&C&H{%%R8(J8H>^AKutsIyEo6{s1waHg&whW5?-r4pOWx>gS^pq zI&b;5okgvAn`H9YASyzitO7Qyty%luzdhH%1gklqw}3aF0y+6w849)zIiap)I&W?hi?*p zaP>4!4Z3jD{CWQ=jQfbFF8)BWckZESS`H(m$U4>;cznHJ`I_<1)k9YTs77ljf;+Hb z9bFjrn_M#2u=ZLZou#(L_^Gs~mRmFp_U(9=Mk;nVjk zhf3ZCU+h{k;&UQWri~R|gl=AwC$8)Y6HryD)JMA?nSHu>G&Uy>q8ewHJbz)l8WZ#! zVbWefmcj!NxSM>%?;eLc8-#DgE&m3;Jbuj$&Pdl(x(MhL=bK^qD(;emGe58~dWIYP z7v1)}A^nb{n|>N{dQE5sP#iX>BM~~KIzqdogrC+v+X3#=-$b_FfE5S@ra@y+t|Ax( zy6#!>i;f#L29VS&al%V8PN|NYM;$#qG|m(sQM!KyV?`Rvo^c%DS@g~y$2vA?lXIgE z%-=Nv@G6$CNPW_C!Af*^3RCyMLXDh>-OjLqgd%%7li$x(bIpw3j6m(3_cjz=1wEmR zQT%o3pvP-()guv;wyb^Ksa;wJ0_PcJB8G!!l_3}FQnKMzB~RC5vlH~TiWd~vxL2YA zu39d^Qspk^JT5-8$RBf_)N%^;w;#1GYtFn= zY?}){rMhXiGlYsK*Gyc1r|jkQ+XPIa+ts(uLR)wS3?Kt3vd0h*6%O&`Yt?gqdkANt zHQP$YgK0g>c{}T@&4gJJ{GzM0pd*l$bzF!!HC7F_mq$kc`_fpUrt-kQh@oZkTXzGQ zgvrCMfO;}BNPsq4l7e&Urx(RYPT7pmTUuamU0)N9?|$#Vn+CIZpvLwE&W(x@n&K4* zZBhdx{Ey!(*rLsfKeI{557vklHfSwz<3!pn#mxD+eAPR@b8SS+8^SKG|86YW9yFU) zPsZQiXIQ}cTVW|jwl$sVag0tDkKTj~t6E%Ecw^s$vT@htJ_~q~c~&JSRpOM0V%HcU zv97{;xK#+7(A=;uZmHk!xIUF(G{`+J!rkYVIq9SDGMx_rXjXk3-P_o?qmi~ua}Mi1 zxUBNZNC_i?Z4W-dOP96#b2$ZXON(lJit_?&#s6nDTH{v;+o$1oqK6s$?difD8Jt1< zZ?<*!>_m8&y2s}@Hd_S#ACX9WX%G({$ZK%0D%gKDBS-i7v#{{g5F`DU!T*zY(Xrk0 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

YKLj$Mur0@2vW#2 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 tZi&86Rh82PO5&U=(k%>Nm zAd{qWRbrX@P~rOIr~)-dm0oDPNN%Mlet%^0!eT0ZjT5UMNv0w}Zcl6G2*M+Z;`Z!u z%Isr@2-C~t#W8!$tUh=Y;XfB$Lp@TlQZb=S?jeq3ubE^dbnnynkCO#HpJqa3@;wcB z&?$rlj#C|Ro5dr$(XurYMRUmIQ6-c~rxIqf*{w0=JtFq)wg3>e+wHH_YV~bA9)GVd zHlWveeL&O!^Oo@*hRR% z{{we-zhSvtewa)qKiu>wrCc6-5V5p*&{~)4^?G+=15>I~$RyFD(de_C%;%Sv&%_=> zo)@4Lz-F^~vREvB`a!6w%J&d#4fSj8n5M#leh?xFOek9VyTu zMxCrIOSIAF=jZ(8X%BdyNLpj3(@~b(J{Mp=g6j_=wyn69+Y+H(YDT5HzEYu#gJ_{D zAQfHh-T=5lAqYIQKebF3V{1bu)JxU1RAeqktP@N$oyr{z?%M=H47p9)hDxPV>ZNMt z$YsSM915Avs;Xo(BXHB{;zW9xcHNoKI!J_KNJccwH^s?}7b?){Y`WN*{%uHw`Y`>E z6RP~(Jjh6fGLPcovZ+mBgA~y^~QLOCu$f@S0 zeXuNEmF;^X9_ABK*(~bAr=;m!udBN30a+lQOKy7tA3z-k0gVa&?@M*)b!&iRxs?GE zigmfj(s`aWPIUtP2~trm&AKu>V_mKRi`t&*P}=~#Qn`K3PGqHG>v)Dbxc$$Uhb=FW zovq8ZZDn0ocDGh9!x_qXmlEiik);%!5ke+*taa!(6l1wlsd5@;gGy_&&QlcV6uO;C zyXCi0g5EYK{&0TCqU-RoN7 zzyjHs+GS=c=YE|7JBc}n{ZLbz;BC$L@TJzneW@4{+wRBXk*I!6t1Ot88CwC_DhjmH zPft(pzs9%nyJn}4bNr^c{ncsgT4{Etywj;XS%4JR#*4fWETPij-%yk8m05~6wKi{f zY2xRvo)w)x-O!({^|`Sg?K%;vTkqPtU+&b7-S*>Ve_U>wRLvJ)YrHbv#LLy+0C1KU z6A77sYrM-fB?f?4VrJQ8-^YRNvUrwPxm`a3bR2w!y4_C@i)^>pmh}eMq9ZFc9w0jx zL}sD$-gdw8?%;L<e z%Tjzfn4K~+GX&IREi#ob(T4jb2&L*izJNn;D8U9KOKw#}?-BrNVIoTb^l0KPd#LoC ze?u__+^sFMSd#+=NDrzPYSEa7DTQ0LXkO09!y8k!r$H`_B?}B2LPdVj&AHv<>UE-f z&hzh7wI9a+j>?=5;q8pZ`vu4vzV=x>Tl{Y*zNI_j-A!=2nR7kpN^_k9K)cJ%L8$P~ zIG8s?`j9t5^e}q?^_O0{__-``Joq(2>BGzeXNm$)@d6Xy1kEzc@gUwgHm=Q>mCeJ9 z1GOSdMS^4;-j#D-Ub$xdV8T#4HXNMt3crfXLX(wH{aaV~^~YoW8(0VR;pU;sHg^Dt jLuDp%oT@%^DS!JH*9hwt6#3*K00000NkvXXu0mjfSkwe6 diff --git a/data/fullscreen.png b/data/fullscreen.png deleted file mode 100644 index dd4944b15db6cc32c9a36ecb8003f95bc80fe699..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3152 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 diff --git a/data/icon_editor.png b/data/icon_editor.png deleted file mode 100644 index d1324ad97e9969eec8209930b3b0289b83e0c2b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 737 zcmV<70v`Q|P)C;QK2CS{EZF>{&b520uy=P643PrHnjvXudHIScQ120cc z`1!a)=c<{H*~*oJx(u|GmLeyo1xZPS#KbfpE-p?&Nl6J-uB^k>%`Wh8^}t0%{IK_w z9}>AVkeiFt)D|pQ(2gBD+F)f>2O}dR2^TLGKqj+;T<#CWIqwuvBPXAcugNduEAoLP zz!7S78*bfdLwUJ$X%Z7#@cHK{47|bdv7!NIVzv%}7vJCmqbDT+sv zzsNV_6C&%@4D#}t@Z*nplt01LsU>i9bcCs?Dc-()i-QLbCQva_6n`%+i}*%q2qY&b zgYOGOxY@Vx7l7h!8_3x9uq+_-TgLKJ@{5o-$6^L;5TEfpv( zE=ER122#WC!^i$S?%jI{Wpmn;ueRaL^zh{sgc-GFdQ0|NsExCBT^P7;Jfm{E0gwICMvr{@8V-KV<& z8-4>C(AU?$&$Vq!jf`yL*dut8jk^rI8?bKW^z`(!c6WETB^3kp@!>O@=9NAGTir1c T023aD00000NkvXXu0mjfQ1?nu diff --git a/data/icon_flower.png b/data/icon_flower.png deleted file mode 100644 index 1e80f2c8cde36d8ceeb69bc2d9ba62ab808d5258..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1160 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�ki~KAiaLq$=5q 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 diff --git a/data/icon_terminal.png b/data/icon_terminal.png deleted file mode 100644 index 4c4c3b69468f6083a659092d7522991ec2632eed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 525 zcmV+o0`mQdP)w&|)nVC7~PT zKLR+>0O#}BSZgtx&0t?!JOmGDqz$kt>nt*x!G;#@yMsMjT=L7$c)5W4NaoHtj7B4j z$72XIK)c-rD~X~Acnfq)2VpZr@+N`v9dehW`O<{jJGfU4(U%D3lfmh9B6SiL#OnFW zZViMm4DtB&5l_7*H1Y=G&oPe2BgiyOZ(}nBMOigZpx-J89!lV%zzjYh%kc8m3Tje5P# zZ(bwp);~oV%cR<1v)L4E9&SpK1iRf1{MLz7c?C5u6EEvfHv&PXWN ze5=(0PbL_>2fbeJdB5Lxt`!)K%ey45f%5gDc|sT7%WAdy!1v&vzi2-I`FxD7^n85b P00000NkvXXu0mjfqD=Mf diff --git a/data/icon_window.png b/data/icon_window.png deleted file mode 100644 index 2a07cc1619242a84bfbbbf6303fd55872c84a105..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`vYsxEAr*|t3)p`6bIR$lP2ddR jl`u`X!q8=~lz|~=NBGfW3(v^|6*73b`njxgN@xNA^5PXE diff --git a/data/icons.svg b/data/icons.svg deleted file mode 100644 index f57a02b..0000000 --- a/data/icons.svg +++ /dev/null @@ -1,1012 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - $ weston - - - - - - - - - - - W - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/meson.build b/data/meson.build deleted file mode 100644 index 16f172f..0000000 --- a/data/meson.build +++ /dev/null @@ -1,29 +0,0 @@ -install_data( - [ - 'background.png', - 'border.png', - 'fullscreen.png', - 'home.png', - 'icon_editor.png', - 'icon_flower.png', - 'icon_ivi_clickdot.png', - 'icon_ivi_flower.png', - 'icon_ivi_simple-egl.png', - 'icon_ivi_simple-shm.png', - 'icon_ivi_smoke.png', - 'icon_terminal.png', - 'icon_window.png', - 'panel.png', - 'pattern.png', - 'random.png', - 'sidebyside.png', - 'sign_close.png', - 'sign_maximize.png', - 'sign_minimize.png', - 'terminal.png', - 'tiling.png', - 'wayland.png', - 'wayland.svg', - ], - install_dir: join_paths(dir_data, 'weston') -) diff --git a/data/panel.png b/data/panel.png deleted file mode 100644 index f0f46b2b9db87df497f5ae65d3b6ea685a6e8567..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27761 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 z z9B_(+L!_{lsqjUD;}W_^R7;S)06~nRXqQHmp2gX{an}(h5o%6pRRD1sXjB%N*q{Z; z3Wto`32{!0D$-LQjUDn|bEm#t5=JWrs0Su)6(dcfR6y^eVgM&EQ)GlkMHS9b4Q|iZ z`(wGEy2AM9q;UV-KQCB{DZ2uYp)hdF0xH~ot@4p3XfKYOyIFnLld!2X|pZm{IP+=XgMeSz&CbG=8ft*vB9af-9= zvdlK6of5JF#NqKS60X_Uf`_4FL(3(cB5O_J^>9BoXyO1ZrCf2%yGcNc#NtvhZ=4bi zp#RTXcN~6HPGV4ph@*^LSujp1XDY5KyfHR$I#A65cy0;qa>A*M^V%#1btAgO{e64> zyzpTf&T>voieC1--|xeS_UoA4UqG8|v&E>4e=Y~`zT#}D@r;+?l3gE`WG}T_>i{_7 zFNXr}&lUIizHroTs3lkIc8iqUmU*|%%W@xY#nGET4Ik5JNu;lv{yo>9E(KjvN^2d` z&l}(fzN|5GjVYxwR_rQ2pZDR7(xzSc0yD<4u47kT@cW+5$ZJ1e`n_^DR~C%gufWM0 zw}0@40=M_%s|};~`p1yd20VzEG14<;nq&vnR&aFyM3E>hR6!tH00000NkvXXu0mjf DB&LQ6 diff --git a/data/random.png b/data/random.png deleted file mode 100644 index 2ed2a1c56e96e802bdd82c6a1c87a320669375b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4553 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 diff --git a/data/sidebyside.png b/data/sidebyside.png deleted file mode 100644 index f5b6f5363fe77c5f9d856871b035838d6b2c0583..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3630 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 C`XO2X diff --git a/data/sign_maximize.png b/data/sign_maximize.png deleted file mode 100644 index ae9e86abea358a359c42eb6a0406f1b74ab17bbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 85 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`GM+AuAr*|t5^)dgSI%H#o4~-f imT@uLgr0VG1_p0+lQa8Vf;EBa7(8A5T-G@yGywqJc@y&h diff --git a/data/sign_minimize.png b/data/sign_minimize.png deleted file mode 100644 index fd3a64aaffa6afd4117a5b13e5c7416cc6555253..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`VxBIJAr*|t68jQFdYb;X7(8X; b(Pv=z!zTEC!o8cKKotz0u6{1-oD!MR4#oxbw!>MU7Gc)tb*)!)yuz)xq!}D^A0IgJ0nk8uzj6YSjZ9Gjq zm$8TO%!X!~OK3c|?KQM*yD!tXBhPnPR^%7yj5zpD!ZghlGz_EUoF7K-!}Gj6ecvy+ zc3l_y5B?tooHu|{3iC@#*bQ92az!*Fh(Q>J$o1mO$B)QC(=;)jo|#5{T|H8%6dpc$ zh<9(_p{%?N(^J#1Z5y#z49R3tXnUUMk*gcj)z{(QfBuyZPo6qO1E8&~on{J3*ATtc z*xZEiu`$%t)j~H!0!q_J1^er_5(AB7It2rl5ga~x1iws7;Nry#fJRYnDH?wI^cksi z8XEi1)ZBtJ&r@02b{a`$NQf3SOEbu!p|O#vPhpC@b^A8jJKAAdmPGD_zC_>G+Xt)6 z5+R@824R38!y~WLwgY1A5_!IluiwApw?F^jxrgSaCd3m7$?WU5Z_=0Q>S`odZ$`~5 zZ0Sw52e}MN3`$DW(9nRlZ{MP#vJzFS`M}T+ni?8eq7*)V{X)ZL7?ugX&Z7l02v8Q5 zBghKcG+eDjg&r_!g9Xu5L>>KoeWJkLd@uu1(n zy+Vi2Y{!;zgd~TF^t0lm=WV=-YHDj?Mj0?tqv-db>6$b+_kI|NL0&i@J)VDf^M3tR z_kIG~aNW7i`C%XQovm#P$UrC6PPIvoJWhUA vSy78D6Y+ zLt;ch6afGG9NXq9n_xXx$>nl@+$8pTY@_vJ+p2B5x?p)_m9`q=gRNP!&Lj{3AiWrr zPJs`Z|NWp4$eG~_IOY}%uOA7K1Xo$R$;TsMn9cs6+h#R14Nv%&f&fuM6o!d%$_8Db zs0gw;JKE;DiMza^aHx`sT)Zsaa$G z2)Ec&(yW{O!9X~i$>+n-kk4*mLI}i4ufvZKbU((Aj~Xuv(zHsgQRxj-Rb5KO;^~Zv5*WG~ zbvaJjIkD614-`c{b@Y??_<|l%e=evynhXhbTg^d-J>&DJ$LyAYYKA}zgZrJn{{wHo zm*Fv7XIvE~1DHVsyh=)&Hn+woUQ2)P9hjyz(cp5fb$@OIcT}*LS)#3AeIOLC-rtsG z9-&&)dsh$u1W@IHk)`i{y<*F(97VPeFffj~`qKOCO{iG}HpSyN$Wc~3GJ&y>&Qxq3 zS$2G~U$DfK3G8DP$7sg)p8>JQjMxfG(%MgZg-gr9)udBTqUIW>INP~4FU>sxM>T5s z$45a}4dzWYLqm*r?l!+R7%s0_F&I(?CkTrg9IF zLUevp#v+CLqp=&r`8^G-uQXu!EGcL^B%U3_*AB{+RqD6(9Psv-LnX-klVgvQIFycz z5mTJ87fDpE7h>1|80ulHW} z{^z&%e#+}Dah%AKn+heic>oAasM#?N9o>GhJUQA!@W&4W5Cn%G?LaQs1QyY(jLy&V`$PO4WLw!XBTH@!}AL% z+X1#3T4@3~ij)3ep#Nubd66xb6?H$0h}?;wETLx!Q%evBkTjC0b31XIY{?oxcrcO( z-EMDtZx7V!b`FgIg<&|KPXGNUKl=Co{7=FW(q9z7;r$0M|K!hf^?(2APtH!?lvp9V z90U{%1Wp*8zOh5H#n`hW8bH!u3BUW?NP^tZc!++L`2DBiQmT=?GVG+T#;_HU+Xn#)=;xMcb(bf;?hGxeaZuR@mKlp@7`Wbr%4=R#}U#M z;lI}jZ1Ds2x~oB`Y;@Jl8dxC>BO*{MNW*Y=IWq*+AfT7PN=UkO)hLRFZ?C@n^{+>9 zd^VY+S!T5no6`Xx%a*grBs@N@8=(pMT;KWn*TXm-H3&B@7%gEwnM9gk5n&Wvdo)Od zd__ml1H_dBvmlVnAJmIP@8xz%u<=Y!qdzxbQKefspN{#{n3jHH(& zVRU-(TK9HWndN;}RoP~Q!fM$0P=XFYxrqi#s1W47GPm zU?=IUD55y7s?rh4QX;#|;TAzNt%#t`lL|7x_jv%~-bfmx%t~4Pd0=y>_xrSfNG(i_R z>u+vBQ7CJW30#A6t(cPrCN=~qOQvZ7s6eRz++M0e>Q=8IZK*3@lK_@r>(DMY4v0_} zM`)xPz)FBXX$Y7qER6+?o3z!SxCU2?6Rdn>1Z68&f^><=$rjK>)&I~g;FG_iw~=l) zvM4K8hZ?q4$pyx?3^>45H-xoZ9Z08u8#I6;5P%RgA^|L+u5AC2Cuz^iNIp7&r)ZrN zCx`|lC$YLx(wd2pvUA7|Wg1YpAV7RkrW(3%tqwFd#j}}IgK>GGdH}qpe2AaGhA2sI z4Ri#c+7>Gw%e7LN=%=`nCc#8S_YZ|YXxmM4S$D|dI00FmMw`V`c}`CRL4y~9jRAss zQ=Zx$sMHetqR5s65SYLS?G8tO``dElbr4N)JofDhZt7hDmdgJH7I}7HbI=FJ{x`-K z;vYe`E_9iS!?b~OpV-pzgpInGI?|`JiY_hSC6L5VtMPYgc&uyC#U_B>I3kjQKv(FjI6j;9s zNUPZ&m@2@##1bsT5exzvHaj5OZB1ZfK++(;TT=lMBn?JjjR9S3j)+zaxFW1t0p2+X zR`CXaFbbnM4x=d0n{#{$2WtVQj!l)(866>95n@d+MQR=qB1$^05hAC75uiZ;O$ehP zumn)V%t%%b_sv zr^`i(y)He)EYkGN>(_Zvo{Yv#M7(s(65^s?s4pymzmx@OXEi(PXV*h53f+3%aVx z#WGuz)!xx@5Cq4&JGxgWj_g^>F!Y0 z-Esczt@Z~YV*8Ir>cgYNcysg-%h@bjF6{?-ts!9k)aFT*#wnP}U!G z^!evss+~0zRup6TjZzrtHKAE?^m-5WcJiXM-kbyD?s>Wqf!sa6V8Z>q-K;3%FwjRK zb9bOE&L8yhv#Isw$X~$V`!76;s!EKz2l{cGsZw>Ix7C0C+)E&F>P7<7-~iy&phSY8 z!B2>SQh^fvK}a&PqIi3CJ(;JVi>wY*pfvjA@@hPr_2Z-{D|GdM6J!H~6&J-P7gyuy zyq`1(x6$zC=6?FR)O3-sd3ii`Tq__^du#^*TA~hMsM{Ou(RY&T-A3& z=MN&nT;2SECqPXge~@Vw5!z}9tcK~#5b*XII00FOkPfW-^WEv&+M6??rx@_qxglR> z+#htR-92AhBi!_57&vdvYjZ2N zM3XZ`SS=J(i?~6+_pL}88pH*-*$ja+lsVNgBXHy_h>>k*xgwp$X`E_h6bV5P!xl`y}$UIzxVosIDs_SKC&AN z5E^~XAN1ThYqFxS5YPM{Wn~$~iM=VJ-m0qf5Lo{^LQ%wVf+eoIdmfUz2RhJ-fRzDs zh+s9OX_gmaGJyzj9A`yo2o-*@V#s2Ox>#q;$#^`OF8~w(X+wUKj-uD2vG#{W$p(G780XTs)gq&bCm{z3e>(TinO&bJY zzVE4%V%t&+oAOI;R%{{sRP&;dgOce~6=S8zE|5nZm$qLJ*p6dhVQIj%H|!#aUS znd*=-nE*=C0JPMs;kPG9I;jj0^2l$6|>w~2=fxV+705T~;e+i|?%I`h-a%=OJ^5WMZBG3=z1S-I)ct;TYT$>BO#)c4bxe6mg zxHNemw(oyGVY5Ipk)Bq7T;H!pk%a^{nk6>2J#9>YpAR9tDTn~H$sG@p(GrB4RAT^9 zeX0NgmbNzbAf^F39vlO)DzeR0tv44we4g0+J_xryly?iz&kgPdevcw;&4WO`2ACRt zBn=Qg22qm7`v`?y^cn;Bfkfv5M1pV~2;+wZh|pkd8$si*FsvCDNS#`Fcsjt{?~YJ! zK*j+I6;eJxZy*zbz#^Om(LqHTfC!q8YZ{c(Ab+(+1Z4?cr@hOAfPyfJqBxFB$u*jb zgQmMD44Q9|41oMWhM<2?K7dx?q`cE<%43yRu zC9wvCc~lbsNx$rJVm4`(QFIS9>3nY7J!PqL2$M8bqWuY0BEsP8)OmAgl%=&ls48E% zRD&W#28BGUJtTtL3x;rFHE059pxcaV18E4)PEQx<;ynR0p>9Q`3D-4YMJN3-ffY*H zQ=7Fn=bK;qO499}PiJYCE0C5k)LLs*x*VTte~^thC}(Ha%jJtd_@gJEe)g~?lp1g=!nPs&;U7JD@$#WZ zLrcQ#HEi|${-7v6IKEq!<&pCTVH+~}gO+)*EUVpvqx~Q_+8Okd*pk~IIe#J?hv7!1{&r$;&f5_gLujn45}>Wi{)uign^RQuCfo#eT<8!xrL5>4w?{SB5Fd307b1~oj^ry1=y9rXfn&oG7RFTKggXN5sRXj7CA7W2Tk&9 z8|tvkibAyJApZ%(VT9Zenq3dT4wJoWmS7PxBD4-ry>Lmhtg28oo%Vd~3kn#6NZyZ* zsGpT(d7}#+0%gT{B>eA3PiuQ4yv1!N1Lgt{~rA4Q!jZ;B%~0 zQ^r8sVS&(sKnSlYKa*epoED;46rrLgc=IpF5Nd}g1zYzJ-Vh+*WP!=fizLUVN{}zK zkiK8Cs}6FQRYlRMFfh-)NtDE4Q78Zq3Gyj<;5-E>KtgFX>!GKjPnBkZ!xms$RLeC`Zu_9&2u$Q?{8(by_F^mZL(zAr#1)&)M$xODWc5ZH_C zAQ+vE)K-5e-Yb21m z!3BsqihlXDA#kTPebfV6eRXv2@cHxJ&Q7RDQ2*#|c6qKpxhf{pFbH;gU5A7=H&Z}y zs_?fLZU_*10|dTtMlA*>%6mO?G);LG7Ns6LIy(0rJ)Gpl|DB(QWwz{uQT+JXy-!~J z?t^=W+x@aCE5sI40(kfOUrDr$Xln>;_&SNkP#DL(!H$m3e+@^M^EAY=G_%uScW`I# z>Bk>E+TYE~vZ|`~E5&v?puN_1QQo|!e5kT(pZ^=3>*%~Xzx;n)F=^MBTRPK?-qj;2%-Jt8U-MI6Zz3r!M9W7G2Q;*F?Zz4YtlsY zl*%%hD!}4$jF^KBP6u}u@u=sT4^sM&zT)D2sy3t4BF)tLx5v(%W27O1Dq-EOP*MYyuqTCzd{YX%=2;3Ikk zFzy*=Cb#a7Jc@{8-VR;?rapQn@n+n0nIH82y9TEK zy*9=%4+X0LAv;X#%qHSZ-8ArvI(YHE7>vIWDhEPEUzqx2a0)PBPaJbea0>9N?TlxY z4BDTBGUD`(9ptHx!<={j{N8RW25S{C?06h=STG9ki*1f)3j_x3M?#tV33w(sjJXuY zY?0~$>@F+Cx!7M0&&CS^y*4DFG?js0l=`jsp6h2{0VdvsW0p#F0q&6{#N1dOo_#O; z!_SA7S`x~r3!vXlNa7Q&&z3O$23YtrR1VSoEwZlw3x6bL8W)02A_2R|#+g|K_;!8N zKTtWCgfsU;nE48fIzPTa>$?rqP5<{?!-U%)yhQd*{GRJUYYQ<`KMSTlR!(!p3qW4S z7r?VU+C7~+=K~T>@68e$xOtyJ?F2O8v-`NhCN^^)&A%wlF5)tS}#zSBSMopfeWNxG{}RlS}1*=o+CO+u0eF=!Ky{-(@# z%QkF`puWBB%dtBdew2ZCaSuclJyPSWuT<}TtQAr}o1i@Rxq(qmoJT_yelT1}Q;fT; z>+SuM%P}`rvH~EAZR;2bI@PTKcyyiO7ec7l6jhU$2knJNV&UV=qtER81~B>@R>tsS z(jBLSleBzDQXmFp{!Qz!N9F3P^6yy;1&?%g1HfIaf|`1zf~NG=eoxQynXzaL=S^Jq<( zZD4O&B?|NE2V$0iOVb8$?zDHpA!8c5NG_)yJDL80&at1ASp)d=ka>peRJOu`eCdmY zgwkp6B3@B9i+S$vh$A>O5HY9OgllznKnoc)Tn z#Fj7wyuafnL@BBmc9`z*Z?$^@pfU#=5;7|r)Xx)i=iZ(vTMY^!m%D_5hud>aJjYx37Bx7`Utb4_!5(eeOIe z$FMR=Uo)L&c52pf3NfUmM}6Cc0SrG@;0aL1%9!zSnn&rPcw`qmjMtKc14$il7Mag? zwB8y3_4tCE8vt~I@ocjI>=Jse*77)rzc(yEkjf)_PPm3O06~g*Nt$n#X3zg%g=ANQz0M80d2+OwzvVF7{ zS--IbaHopv{#L*)LkERV%2gsu(#qKdAs+oDCzjZdMKJPYO~c!X1%Ts->i*7RZ9w}k z!WQ)iF|;BQKG>S$F2!nE0+Vjln|u0uEC7U!MI(QovleQnNhhORnfVnEl`Zus5Xx~! zBE{Ta1TMte8v44`vGubDj?-&RnIB&-@(0ZLNcuJOY-R(%r1lrR8P@lcx&GGuC~!ynO*^JYaOiQ zCFaHo6jpX=002B`>F%fCO!rCK`y!AKJwX@i>(? z0G-U3Nw=97R)CKlf0hkFgMJ3k{5;l1-E5Z)tJX`e2ujdPTEz@&3UT+xWdOA^TCc|a z#ZXT`1#^B%cbAWHv%FOB`I4eX!tEUeJdGw5y+6EUtE&~9D9^69oaiPX% z4^4NT*cK`wz*9uEW}!Y%-_RphyMPtb?*e?fPk2K3v*my-TZ(^OIsdS7OyLV4*`B_mS_6-}M z;89lwFgKec=yc%;QQs%$2$*}XtDs`OP5pQSw$I*ksG&@>Sf&6cWHW$$geQbXP6FXdug0Qj6ouJ~T& zCjhE{*9cFFkZSILh@X75PV8{nJ`P;R`f!z@3?}IQ?6Po=hKo-;KfyUeV-?=VI=O_6 z|6)Zz=+R0Gxq(@Eobo~VygLIpO(i?{Q}KxlpW++=6})gBvL#yqIKz_5w)=4OBGr8ox+o%V6rS+rd1&!RR=RZfsf(8MW5qs~xI{7`o=NwK$DV#v3&B$OkflS2{}GZ6gQ*d(zN(Z#qC zG$CFw{3sXgYM<804o=8Uj!d1HfxFS{e6e=O4X_B6u8^9blXObN+%0SXaK*-rF&CJg zFV+sZLh%dIcYl3q5QsqxZPKSbg--wqmpIW>i-=`5Z(YG70@+1kz>`wC*_BT{^eTiT zY4Oz3@M5ej<=9ZxD{~#=vOaK7rF%*gJ;OOc-7FU^_3t&Q6ZpP-X!yhSu=>X$tMm<2 z>kuh#Bk;7EbSpyA&^<~rV4pV9uM_wW9n_)y0mBZLFK*!uXfWlG3-&at6pFY&fo#Rh zWw63^ji4S587ukp`te39k*6R}pDwrQ7x0+<2`mc73jP~jGuNmbEAt+$924JrAM!eB zR=2hKXAnb1{zF^Xr;9JrJgH*%(Q!CbkcwUUI&<%J+J6iKF$>7kv2&5)*AhL0dRw2! z>~C=);AIo2o~0oE{BHy1hH~a%N0^=W;C(n2RF02+BM{L=87Al``?hf&9U)Q`bIf^y zP8J14En*NTv1aNJ@)v~^-7QcF1iz0Y`$~2Vv#A>l<;?W=6(raKD11WjM@05hET}G! zUi_UP*EA}hPQ-yNjX<)coI+DY0b+aR8Wkh(l$1N~A+mh~u_YWS*Sh6OcZ)yO)ihMV z@Cr(nn^#7Fry>ep$kL}X`l=qr5GdjQE_{PC&6j|{}1 zMV~ymf{E2E4JdO+nh`|eNO6YeM zMelW7_VH8*|B)$;p;U>%n5ZYj zR_wj5+~R6MQv8C;yM_al(3d^0h3wO2AxV=MjET0g{VY!&p->5+t(s#lg6bWnnEiwN zsy}qng(L%Fuwqnj$fJ2yUt-L;ECBQ;^2jx@a@Pm8y^yF&@)e8MI0gY8A2P^ck6~c& zzHt};+Qyjx#k3^Z*W|sQLJT7G=t=8G>&(yX-mS>bbI;_m`iY;7Rnmo@XL25G5|S*4 zL5y~bLS*GG>?a>dlc)YVp!h|}`R9k+R^Z8vL5z|A*`x36b_MIepRbFVpT%ncbAHrQ z{GtK21Zxysz404BO2?62))hr`>j{iJC0+xV`VcFl=xMnMUs&;z5HW}oZRbZ>w^y0| zfqLy<`Le1KE$N^pO0FQK?Y}-G2@-=7=FxHj-N&HKubt+8b**0^9W|1(Ve~m7%HEJC zha_QQa73h;`}BqN5728(Xb983U)PV{092Yxl=YPG_m@QpIw8pcF*qfZM9)ffAw}_p zK&6X_dyc<4W3gv}g@r5i!I)t&M^ zA3p6;J9gxl7}_QCXiZMg!}5iya>9Ypg_nNM3lKxQ=HIjqqe@9p2fOI?A%BaCGb&v8 zZUaza)3>}V9^Wy}a${u~(4ve9J6VxZ<{VAo`I|?y$ zV8W@CPdjnc_C7(suty7=Z{MJGc!hmBg_E>$Na~=((CL^*8?Z-*hrEt`dXYW)nQ_r( zDQ+{XLKZOU>wOgR!H{Qjf_910;zCj - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c deleted file mode 100644 index 7e9a324..0000000 --- a/desktop-shell/exposay.c +++ /dev/null @@ -1,737 +0,0 @@ -/* - * Copyright © 2010-2012 Intel Corporation - * Copyright © 2011-2012 Collabora, Ltd. - * Copyright © 2013 Raspberry Pi Foundation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include "shell.h" -#include "shared/helpers.h" - -struct exposay_surface { - struct desktop_shell *shell; - struct exposay_output *eoutput; - struct weston_surface *surface; - struct weston_view *view; - struct wl_listener view_destroy_listener; - struct wl_list link; - - int x; - int y; - int width; - int height; - double scale; - - int row; - int column; - - /* The animations only apply a transformation for their own lifetime, - * and don't have an option to indefinitely maintain the - * transformation in a steady state - so, we apply our own once the - * animation has finished. */ - struct weston_transform transform; -}; - -static void exposay_set_state(struct desktop_shell *shell, - enum exposay_target_state state, - struct weston_seat *seat); -static void exposay_check_state(struct desktop_shell *shell); - -static void -exposay_surface_destroy(struct exposay_surface *esurface) -{ - wl_list_remove(&esurface->link); - wl_list_remove(&esurface->view_destroy_listener.link); - - if (esurface->shell->exposay.focus_current == esurface->view) - esurface->shell->exposay.focus_current = NULL; - if (esurface->shell->exposay.focus_prev == esurface->view) - esurface->shell->exposay.focus_prev = NULL; - - free(esurface); -} - -static void -exposay_in_flight_inc(struct desktop_shell *shell) -{ - shell->exposay.in_flight++; -} - -static void -exposay_in_flight_dec(struct desktop_shell *shell) -{ - if (--shell->exposay.in_flight > 0) - return; - - exposay_check_state(shell); -} - -static void -exposay_animate_in_done(struct weston_view_animation *animation, void *data) -{ - struct exposay_surface *esurface = data; - - wl_list_insert(&esurface->view->geometry.transformation_list, - &esurface->transform.link); - weston_matrix_init(&esurface->transform.matrix); - weston_matrix_scale(&esurface->transform.matrix, - esurface->scale, esurface->scale, 1.0f); - weston_matrix_translate(&esurface->transform.matrix, - esurface->x - esurface->view->geometry.x, - esurface->y - esurface->view->geometry.y, - 0); - - weston_view_geometry_dirty(esurface->view); - weston_compositor_schedule_repaint(esurface->view->surface->compositor); - - exposay_in_flight_dec(esurface->shell); -} - -static void -exposay_animate_in(struct exposay_surface *esurface) -{ - exposay_in_flight_inc(esurface->shell); - - weston_move_scale_run(esurface->view, - esurface->x - esurface->view->geometry.x, - esurface->y - esurface->view->geometry.y, - 1.0, esurface->scale, 0, - exposay_animate_in_done, esurface); -} - -static void -exposay_animate_out_done(struct weston_view_animation *animation, void *data) -{ - struct exposay_surface *esurface = data; - struct desktop_shell *shell = esurface->shell; - - exposay_surface_destroy(esurface); - - exposay_in_flight_dec(shell); -} - -static void -exposay_animate_out(struct exposay_surface *esurface) -{ - exposay_in_flight_inc(esurface->shell); - - /* Remove the static transformation set up by - * exposay_transform_in_done(). */ - wl_list_remove(&esurface->transform.link); - weston_view_geometry_dirty(esurface->view); - - weston_move_scale_run(esurface->view, - esurface->x - esurface->view->geometry.x, - esurface->y - esurface->view->geometry.y, - 1.0, esurface->scale, 1, - exposay_animate_out_done, esurface); -} - -static void -exposay_highlight_surface(struct desktop_shell *shell, - struct exposay_surface *esurface) -{ - struct weston_view *view = esurface->view; - - if (shell->exposay.focus_current == view) - return; - - shell->exposay.row_current = esurface->row; - shell->exposay.column_current = esurface->column; - shell->exposay.cur_output = esurface->eoutput; - - activate(shell, view, shell->exposay.seat, - WESTON_ACTIVATE_FLAG_NONE); - shell->exposay.focus_current = view; -} - -static int -exposay_is_animating(struct desktop_shell *shell) -{ - if (shell->exposay.state_cur == EXPOSAY_LAYOUT_INACTIVE || - shell->exposay.state_cur == EXPOSAY_LAYOUT_OVERVIEW) - return 0; - - return (shell->exposay.in_flight > 0); -} - -static void -exposay_pick(struct desktop_shell *shell, int x, int y) -{ - struct exposay_surface *esurface; - - if (exposay_is_animating(shell)) - return; - - wl_list_for_each(esurface, &shell->exposay.surface_list, link) { - if (x < esurface->x || x > esurface->x + esurface->width) - continue; - if (y < esurface->y || y > esurface->y + esurface->height) - continue; - - exposay_highlight_surface(shell, esurface); - return; - } -} - -static void -handle_view_destroy(struct wl_listener *listener, void *data) -{ - struct exposay_surface *esurface = container_of(listener, - struct exposay_surface, - view_destroy_listener); - - exposay_surface_destroy(esurface); -} - -/* Compute each surface size and then inner pad (10% of surface size). - * After that, it's necessary to recompute surface size (90% of its - * original size). Also, each surface can't be bigger than half the - * exposay area width and height. - */ -static void -exposay_surface_and_inner_pad_size(pixman_rectangle32_t exposay_area, struct exposay_output *eoutput) -{ - if (exposay_area.height < exposay_area.width) - eoutput->surface_size = exposay_area.height / eoutput->grid_size; - else - eoutput->surface_size = exposay_area.width / eoutput->grid_size; - - eoutput->padding_inner = eoutput->surface_size / 10; - eoutput->surface_size -= eoutput->padding_inner; - - if ((uint32_t)eoutput->surface_size > (exposay_area.width / 2)) - eoutput->surface_size = exposay_area.width / 2; - if ((uint32_t)eoutput->surface_size > (exposay_area.height / 2)) - eoutput->surface_size = exposay_area.height / 2; -} - -/* Compute the exposay top/left margin in order to centralize it */ -static void -exposay_margin_size(struct desktop_shell *shell, pixman_rectangle32_t exposay_area, - int row_size, int column_size, int *left_margin, int *top_margin) -{ - (*left_margin) = exposay_area.x + (exposay_area.width - row_size) / 2; - (*top_margin) = exposay_area.y + (exposay_area.height - column_size) / 2; -} - -/* Pretty lame layout for now; just tries to make a square. Should take - * aspect ratio into account really. Also needs to be notified of surface - * addition and removal and adjust layout/animate accordingly. - * - * Lay the grid out as square as possible, losing surfaces from the - * bottom row if required. Start with fixed padding of a 10% margin - * around the outside, and maximise the area made available to surfaces - * after this. Also, add an inner padding between surfaces that varies - * with the surface size (10% of its size). - * - * If we can't make a square grid, add one extra row at the bottom which - * will have a smaller number of columns. - */ -static enum exposay_layout_state -exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) -{ - struct workspace *workspace = shell->exposay.workspace; - struct weston_output *output = shell_output->output; - struct exposay_output *eoutput = &shell_output->eoutput; - struct weston_view *view; - struct exposay_surface *esurface, *highlight = NULL; - pixman_rectangle32_t exposay_area; - int pad, row_size, column_size, left_margin, top_margin; - int last_row_size, last_row_margin_increase; - int populated_rows; - int i; - - eoutput->num_surfaces = 0; - wl_list_for_each(view, &workspace->layer.view_list.link, layer_link.link) { - if (!get_shell_surface(view->surface)) - continue; - if (view->output != output) - continue; - eoutput->num_surfaces++; - } - - if (eoutput->num_surfaces == 0) { - eoutput->grid_size = 0; - eoutput->padding_inner = 0; - eoutput->surface_size = 0; - return EXPOSAY_LAYOUT_OVERVIEW; - } - - /* Get exposay area and position, taking into account - * the shell panel position and size */ - get_output_work_area(shell, output, &exposay_area); - - /* Compute grid size */ - eoutput->grid_size = floor(sqrtf(eoutput->num_surfaces)); - if (pow(eoutput->grid_size, 2) != eoutput->num_surfaces) - eoutput->grid_size++; - - /* Compute each surface size and the inner padding between them */ - exposay_surface_and_inner_pad_size(exposay_area, eoutput); - - /* Compute each row/column size */ - pad = eoutput->surface_size + eoutput->padding_inner; - row_size = (pad * eoutput->grid_size) - eoutput->padding_inner; - /* We may have empty rows that should be desconsidered to compute - * column size */ - populated_rows = ceil(eoutput->num_surfaces / (float) eoutput->grid_size); - column_size = (pad * populated_rows) - eoutput->padding_inner; - - /* The last row size can be different, since it may have less surfaces - * than the grid size. Also, its margin may be increased to centralize - * its surfaces, in the case where we don't have a perfect grid. */ - last_row_size = ((eoutput->num_surfaces % eoutput->grid_size) * pad) - eoutput->padding_inner; - if (eoutput->num_surfaces % eoutput->grid_size) - last_row_margin_increase = (row_size - last_row_size) / 2; - else - last_row_margin_increase = 0; - - /* Compute a top/left margin to centralize the exposay */ - exposay_margin_size(shell, exposay_area, row_size, column_size, &left_margin, &top_margin); - - i = 0; - wl_list_for_each(view, &workspace->layer.view_list.link, layer_link.link) { - - if (!get_shell_surface(view->surface)) - continue; - if (view->output != output) - continue; - - esurface = malloc(sizeof(*esurface)); - if (!esurface) { - exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, - shell->exposay.seat); - break; - } - - wl_list_insert(&shell->exposay.surface_list, &esurface->link); - esurface->shell = shell; - esurface->eoutput = eoutput; - esurface->view = view; - - esurface->row = i / eoutput->grid_size; - esurface->column = i % eoutput->grid_size; - - esurface->x = left_margin + (pad * esurface->column); - esurface->y = top_margin + (pad * esurface->row); - - /* If this is the last row, increase left margin (it sums 0 if - * we have a perfect square) to centralize the surfaces */ - if (eoutput->num_surfaces / eoutput->grid_size == esurface->row) - esurface->x += last_row_margin_increase; - - if (view->surface->width > view->surface->height) - esurface->scale = eoutput->surface_size / (float) view->surface->width; - else - esurface->scale = eoutput->surface_size / (float) view->surface->height; - esurface->width = view->surface->width * esurface->scale; - esurface->height = view->surface->height * esurface->scale; - - /* Surfaces are usually rectangular, but their exposay surfaces - * are square. centralize them in their own square */ - if (esurface->width > esurface->height) - esurface->y += (esurface->width - esurface->height) / 2; - else - esurface->x += (esurface->height - esurface->width) / 2; - - if (shell->exposay.focus_current == esurface->view) - highlight = esurface; - - exposay_animate_in(esurface); - - /* We want our destroy handler to be after the animation - * destroy handler in the list, this way when the view is - * destroyed, the animation can safely call the animation - * completion callback before we free the esurface in our - * destroy handler. - */ - esurface->view_destroy_listener.notify = handle_view_destroy; - wl_signal_add(&view->destroy_signal, &esurface->view_destroy_listener); - - i++; - } - - if (highlight) { - shell->exposay.focus_current = NULL; - exposay_highlight_surface(shell, highlight); - } - - weston_compositor_schedule_repaint(shell->compositor); - - return EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW; -} - -static void -exposay_focus(struct weston_pointer_grab *grab) -{ -} - -static void -exposay_motion(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_motion_event *event) -{ - struct desktop_shell *shell = - container_of(grab, struct desktop_shell, exposay.grab_ptr); - - weston_pointer_move(grab->pointer, event); - - exposay_pick(shell, - wl_fixed_to_int(grab->pointer->x), - wl_fixed_to_int(grab->pointer->y)); -} - -static void -exposay_button(struct weston_pointer_grab *grab, const struct timespec *time, - uint32_t button, uint32_t state_w) -{ - struct desktop_shell *shell = - container_of(grab, struct desktop_shell, exposay.grab_ptr); - struct weston_seat *seat = grab->pointer->seat; - enum wl_pointer_button_state state = state_w; - - if (button != BTN_LEFT) - return; - - /* Store the surface we clicked on, and don't do anything if we end up - * releasing on a different surface. */ - if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - shell->exposay.clicked = shell->exposay.focus_current; - return; - } - - if (shell->exposay.focus_current == shell->exposay.clicked) - exposay_set_state(shell, EXPOSAY_TARGET_SWITCH, seat); - else - shell->exposay.clicked = NULL; -} - -static void -exposay_axis(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_axis_event *event) -{ -} - -static void -exposay_axis_source(struct weston_pointer_grab *grab, uint32_t source) -{ -} - -static void -exposay_frame(struct weston_pointer_grab *grab) -{ -} - -static void -exposay_pointer_grab_cancel(struct weston_pointer_grab *grab) -{ - struct desktop_shell *shell = - container_of(grab, struct desktop_shell, exposay.grab_ptr); - - exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, shell->exposay.seat); -} - -static const struct weston_pointer_grab_interface exposay_ptr_grab = { - exposay_focus, - exposay_motion, - exposay_button, - exposay_axis, - exposay_axis_source, - exposay_frame, - exposay_pointer_grab_cancel, -}; - -static int -exposay_maybe_move(struct desktop_shell *shell, int row, int column) -{ - struct exposay_surface *esurface; - - wl_list_for_each(esurface, &shell->exposay.surface_list, link) { - if (esurface->eoutput != shell->exposay.cur_output || - esurface->row != row || esurface->column != column) - continue; - - exposay_highlight_surface(shell, esurface); - return 1; - } - - return 0; -} - -static void -exposay_key(struct weston_keyboard_grab *grab, const struct timespec *time, - uint32_t key, uint32_t state_w) -{ - struct weston_seat *seat = grab->keyboard->seat; - struct desktop_shell *shell = - container_of(grab, struct desktop_shell, exposay.grab_kbd); - enum wl_keyboard_key_state state = state_w; - - if (state != WL_KEYBOARD_KEY_STATE_RELEASED) - return; - - switch (key) { - case KEY_ESC: - exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, seat); - break; - case KEY_ENTER: - exposay_set_state(shell, EXPOSAY_TARGET_SWITCH, seat); - break; - case KEY_UP: - exposay_maybe_move(shell, shell->exposay.row_current - 1, - shell->exposay.column_current); - break; - case KEY_DOWN: - /* Special case for trying to move to the bottom row when it - * has fewer items than all the others. */ - if (!exposay_maybe_move(shell, shell->exposay.row_current + 1, - shell->exposay.column_current) && - shell->exposay.row_current < (shell->exposay.cur_output->grid_size - 1)) { - exposay_maybe_move(shell, shell->exposay.row_current + 1, - (shell->exposay.cur_output->num_surfaces % - shell->exposay.cur_output->grid_size) - 1); - } - break; - case KEY_LEFT: - exposay_maybe_move(shell, shell->exposay.row_current, - shell->exposay.column_current - 1); - break; - case KEY_RIGHT: - exposay_maybe_move(shell, shell->exposay.row_current, - shell->exposay.column_current + 1); - break; - case KEY_TAB: - /* Try to move right, then down (and to the leftmost column), - * then if all else fails, to the top left. */ - if (!exposay_maybe_move(shell, shell->exposay.row_current, - shell->exposay.column_current + 1) && - !exposay_maybe_move(shell, shell->exposay.row_current + 1, 0)) - exposay_maybe_move(shell, 0, 0); - break; - default: - break; - } -} - -static void -exposay_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 desktop_shell *shell = - container_of(grab, struct desktop_shell, exposay.grab_kbd); - struct weston_seat *seat = (struct weston_seat *) grab->keyboard->seat; - - /* We want to know when mod has been pressed and released. - * FIXME: There is a problem here: if mod is pressed, then a key - * is pressed and released, then mod is released, we will treat that - * as if only mod had been pressed and released. */ - if (seat->modifier_state) { - if (seat->modifier_state == shell->binding_modifier) { - shell->exposay.mod_pressed = true; - } else { - shell->exposay.mod_invalid = true; - } - } else { - if (shell->exposay.mod_pressed && !shell->exposay.mod_invalid) - exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, seat); - - shell->exposay.mod_invalid = false; - shell->exposay.mod_pressed = false; - } - - return; -} - -static void -exposay_cancel(struct weston_keyboard_grab *grab) -{ - struct desktop_shell *shell = - container_of(grab, struct desktop_shell, exposay.grab_kbd); - - exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, shell->exposay.seat); -} - -static const struct weston_keyboard_grab_interface exposay_kbd_grab = { - exposay_key, - exposay_modifier, - exposay_cancel, -}; - -/** - * Called when the transition from overview -> inactive has completed. - */ -static enum exposay_layout_state -exposay_set_inactive(struct desktop_shell *shell) -{ - struct weston_seat *seat = shell->exposay.seat; - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - if (pointer) - weston_pointer_end_grab(pointer); - - if (keyboard) { - weston_keyboard_end_grab(keyboard); - if (keyboard->input_method_resource) - keyboard->grab = &keyboard->input_method_grab; - } - - return EXPOSAY_LAYOUT_INACTIVE; -} - -/** - * Begins the transition from overview to inactive. */ -static enum exposay_layout_state -exposay_transition_inactive(struct desktop_shell *shell, int switch_focus) -{ - struct exposay_surface *esurface; - - /* Call activate() before we start the animations to avoid - * animating back the old state and then immediately transitioning - * to the new. */ - if (switch_focus && shell->exposay.focus_current) - activate(shell, shell->exposay.focus_current, - shell->exposay.seat, - WESTON_ACTIVATE_FLAG_CONFIGURE); - else if (shell->exposay.focus_prev) - activate(shell, shell->exposay.focus_prev, - shell->exposay.seat, - WESTON_ACTIVATE_FLAG_CONFIGURE); - - wl_list_for_each(esurface, &shell->exposay.surface_list, link) - exposay_animate_out(esurface); - weston_compositor_schedule_repaint(shell->compositor); - - return EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE; -} - -static enum exposay_layout_state -exposay_transition_active(struct desktop_shell *shell) -{ - struct weston_seat *seat = shell->exposay.seat; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct shell_output *shell_output; - bool animate = false; - - shell->exposay.workspace = get_current_workspace(shell); - shell->exposay.focus_prev = get_default_view(keyboard->focus); - shell->exposay.focus_current = get_default_view(keyboard->focus); - shell->exposay.clicked = NULL; - wl_list_init(&shell->exposay.surface_list); - - lower_fullscreen_layer(shell, NULL); - shell->exposay.grab_kbd.interface = &exposay_kbd_grab; - weston_keyboard_start_grab(keyboard, - &shell->exposay.grab_kbd); - weston_keyboard_set_focus(keyboard, NULL); - - shell->exposay.grab_ptr.interface = &exposay_ptr_grab; - if (pointer) { - weston_pointer_start_grab(pointer, - &shell->exposay.grab_ptr); - weston_pointer_clear_focus(pointer); - } - wl_list_for_each(shell_output, &shell->output_list, link) { - enum exposay_layout_state state; - - state = exposay_layout(shell, shell_output); - - if (state == EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW) - animate = true; - } - - return animate ? EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW - : EXPOSAY_LAYOUT_OVERVIEW; -} - -static void -exposay_check_state(struct desktop_shell *shell) -{ - enum exposay_layout_state state_new = shell->exposay.state_cur; - int do_switch = 0; - - /* Don't do anything whilst animations are running, just store up - * target state changes and only act on them when the animations have - * completed. */ - if (exposay_is_animating(shell)) - return; - - switch (shell->exposay.state_target) { - case EXPOSAY_TARGET_OVERVIEW: - switch (shell->exposay.state_cur) { - case EXPOSAY_LAYOUT_OVERVIEW: - goto out; - case EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW: - state_new = EXPOSAY_LAYOUT_OVERVIEW; - break; - default: - state_new = exposay_transition_active(shell); - break; - } - break; - - case EXPOSAY_TARGET_SWITCH: - do_switch = 1; /* fallthrough */ - case EXPOSAY_TARGET_CANCEL: - switch (shell->exposay.state_cur) { - case EXPOSAY_LAYOUT_INACTIVE: - goto out; - case EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE: - state_new = exposay_set_inactive(shell); - break; - default: - state_new = exposay_transition_inactive(shell, do_switch); - break; - } - - break; - } - -out: - shell->exposay.state_cur = state_new; -} - -static void -exposay_set_state(struct desktop_shell *shell, enum exposay_target_state state, - struct weston_seat *seat) -{ - shell->exposay.state_target = state; - shell->exposay.seat = seat; - exposay_check_state(shell); -} - -void -exposay_binding(struct weston_keyboard *keyboard, enum weston_keyboard_modifier modifier, - void *data) -{ - struct desktop_shell *shell = data; - - exposay_set_state(shell, EXPOSAY_TARGET_OVERVIEW, keyboard->seat); -} diff --git a/desktop-shell/input-panel.c b/desktop-shell/input-panel.c deleted file mode 100644 index 8292f20..0000000 --- a/desktop-shell/input-panel.c +++ /dev/null @@ -1,412 +0,0 @@ -/* - * Copyright © 2010-2012 Intel Corporation - * Copyright © 2011-2012 Collabora, Ltd. - * Copyright © 2013 Raspberry Pi Foundation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include - -#include "shell.h" -#include "input-method-unstable-v1-server-protocol.h" -#include "shared/helpers.h" - -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 weston_view *view; - struct wl_listener surface_destroy_listener; - - struct weston_view_animation *anim; - - struct weston_output *output; - uint32_t panel; -}; - -static void -input_panel_slide_done(struct weston_view_animation *animation, void *data) -{ - struct input_panel_surface *ipsurf = data; - - ipsurf->anim = NULL; -} - -static void -show_input_panel_surface(struct input_panel_surface *ipsurf) -{ - struct desktop_shell *shell = ipsurf->shell; - struct weston_seat *seat; - struct weston_surface *focus; - float x, y; - - wl_list_for_each(seat, &shell->compositor->seat_list, link) { - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(seat); - - if (!keyboard || !keyboard->focus) - continue; - focus = weston_surface_get_main_surface(keyboard->focus); - if (!focus) - continue; - ipsurf->output = focus->output; - x = ipsurf->output->x + (ipsurf->output->width - ipsurf->surface->width) / 2; - y = ipsurf->output->y + ipsurf->output->height - ipsurf->surface->height; - weston_view_set_position(ipsurf->view, x, y); - } - - weston_layer_entry_insert(&shell->input_panel_layer.view_list, - &ipsurf->view->layer_link); - weston_view_geometry_dirty(ipsurf->view); - weston_view_update_transform(ipsurf->view); - ipsurf->surface->is_mapped = true; - ipsurf->view->is_mapped = true; - weston_surface_damage(ipsurf->surface); - - if (ipsurf->anim) - weston_view_animation_destroy(ipsurf->anim); - - ipsurf->anim = - weston_slide_run(ipsurf->view, - ipsurf->surface->height * 0.9, 0, - input_panel_slide_done, ipsurf); -} - -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 *ipsurf, *next; - - shell->text_input.surface = (struct weston_surface*)data; - - if (shell->showing_input_panels) - return; - - shell->showing_input_panels = true; - - if (!shell->locked) - weston_layer_set_position(&shell->input_panel_layer, - WESTON_LAYER_POSITION_TOP_UI); - - wl_list_for_each_safe(ipsurf, next, - &shell->input_panel.surfaces, link) { - if (ipsurf->surface->width == 0) - continue; - - show_input_panel_surface(ipsurf); - } -} - -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_view *view, *next; - - if (!shell->showing_input_panels) - return; - - shell->showing_input_panels = false; - - if (!shell->locked) - weston_layer_unset_position(&shell->input_panel_layer); - - wl_list_for_each_safe(view, next, - &shell->input_panel_layer.view_list.link, - layer_link.link) - weston_view_unmap(view); -} - -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 int -input_panel_get_label(struct weston_surface *surface, char *buf, size_t len) -{ - return snprintf(buf, len, "input panel"); -} - -static void -input_panel_committed(struct weston_surface *surface, int32_t sx, int32_t sy) -{ - struct input_panel_surface *ip_surface = surface->committed_private; - struct desktop_shell *shell = ip_surface->shell; - struct weston_view *view; - float x, y; - - if (surface->width == 0) - return; - - if (ip_surface->panel) { - view = get_default_view(shell->text_input.surface); - if (view == NULL) - return; - x = view->geometry.x + shell->text_input.cursor_rectangle.x2; - y = view->geometry.y + shell->text_input.cursor_rectangle.y2; - } else { - x = ip_surface->output->x + (ip_surface->output->width - surface->width) / 2; - y = ip_surface->output->y + ip_surface->output->height - surface->height; - } - - weston_view_set_position(ip_surface->view, x, y); - - if (!weston_surface_is_mapped(surface) && shell->showing_input_panels) - show_input_panel_surface(ip_surface); -} - -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->committed = NULL; - weston_surface_set_label_func(input_panel_surface->surface, NULL); - weston_view_destroy(input_panel_surface->view); - - free(input_panel_surface); -} - -static struct input_panel_surface * -get_input_panel_surface(struct weston_surface *surface) -{ - if (surface->committed == input_panel_committed) { - return surface->committed_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->committed = input_panel_committed; - surface->committed_private = input_panel_surface; - weston_surface_set_label_func(surface, input_panel_get_label); - - input_panel_surface->shell = shell; - - input_panel_surface->surface = surface; - input_panel_surface->view = weston_view_create(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; - struct weston_head *head; - - wl_list_insert(&shell->input_panel.surfaces, - &input_panel_surface->link); - - head = weston_head_from_resource(output_resource); - input_panel_surface->output = head->output; - 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 zwp_input_panel_surface_v1_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->committed already set"); - return; - } - - ipsurf->resource = - wl_resource_create(client, - &zwp_input_panel_surface_v1_interface, - 1, - id); - wl_resource_set_implementation(ipsurf->resource, - &input_panel_surface_implementation, - ipsurf, - destroy_input_panel_surface_resource); -} - -static const struct zwp_input_panel_v1_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, - &zwp_input_panel_v1_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"); -} - -void -input_panel_destroy(struct desktop_shell *shell) -{ - wl_list_remove(&shell->show_input_panel_listener.link); - wl_list_remove(&shell->hide_input_panel_listener.link); -} - -int -input_panel_setup(struct desktop_shell *shell) -{ - struct weston_compositor *ec = shell->compositor; - - 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); - - wl_list_init(&shell->input_panel.surfaces); - - if (wl_global_create(shell->compositor->wl_display, - &zwp_input_panel_v1_interface, 1, - shell, bind_input_panel) == NULL) - return -1; - - return 0; -} diff --git a/desktop-shell/meson.build b/desktop-shell/meson.build deleted file mode 100644 index c67bfd6..0000000 --- a/desktop-shell/meson.build +++ /dev/null @@ -1,31 +0,0 @@ -if get_option('shell-desktop') - config_h.set_quoted('WESTON_SHELL_CLIENT', get_option('desktop-shell-client-default')) - - srcs_shell_desktop = [ - 'shell.c', - 'exposay.c', - 'input-panel.c', - weston_desktop_shell_server_protocol_h, - weston_desktop_shell_protocol_c, - input_method_unstable_v1_server_protocol_h, - input_method_unstable_v1_protocol_c, - ] - deps_shell_desktop = [ - dep_libm, - dep_libexec_weston, - dep_libshared, - dep_lib_desktop, - dep_libweston_public, - ] - plugin_shell_desktop = shared_library( - 'desktop-shell', - srcs_shell_desktop, - include_directories: common_inc, - dependencies: deps_shell_desktop, - name_prefix: '', - install: true, - install_dir: dir_module_weston, - install_rpath: '$ORIGIN' - ) - env_modmap += 'desktop-shell.so=@0@;'.format(plugin_shell_desktop.full_path()) -endif diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c deleted file mode 100755 index b35aab2..0000000 --- a/desktop-shell/shell.c +++ /dev/null @@ -1,5253 +0,0 @@ -/* - * Copyright © 2010-2012 Intel Corporation - * Copyright © 2011-2012 Collabora, Ltd. - * Copyright © 2013 Raspberry Pi Foundation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "shell.h" -#include "compositor/weston.h" -#include "weston-desktop-shell-server-protocol.h" -#include -#include "shared/helpers.h" -#include "shared/timespec-util.h" -#include - -#define DEFAULT_NUM_WORKSPACES 1 -#define DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH 200 - -struct focus_state { - struct desktop_shell *shell; - 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; -}; - -/* - * Surface stacking and ordering. - * - * This is handled using several linked lists of surfaces, organised into - * ‘layers’. The layers are ordered, and each of the surfaces in one layer are - * above all of the surfaces in the layer below. The set of layers is static and - * in the following order (top-most first): - * • Lock layer (only ever displayed on its own) - * • Cursor layer - * • Input panel layer - * • Fullscreen layer - * • Panel layer - * • Workspace layers - * • Background layer - * - * The list of layers may be manipulated to remove whole layers of surfaces from - * display. For example, when locking the screen, all layers except the lock - * layer are removed. - * - * A surface’s layer is modified on configuring the surface, in - * set_surface_type() (which is only called when the surface’s type change is - * _committed_). If a surface’s type changes (e.g. when making a window - * fullscreen) its layer changes too. - * - * In order to allow popup and transient surfaces to be correctly stacked above - * their parent surfaces, each surface tracks both its parent surface, and a - * linked list of its children. When a surface’s layer is updated, so are the - * layers of its children. Note that child surfaces are *not* the same as - * subsurfaces — child/parent surfaces are purely for maintaining stacking - * order. - * - * The children_link list of siblings of a surface (i.e. those surfaces which - * have the same parent) only contains weston_surfaces which have a - * shell_surface. Stacking is not implemented for non-shell_surface - * weston_surfaces. This means that the following implication does *not* hold: - * (shsurf->parent != NULL) ⇒ !wl_list_is_empty(shsurf->children_link) - */ - -struct shell_surface { - struct wl_signal destroy_signal; - - struct weston_desktop_surface *desktop_surface; - struct weston_view *view; - int32_t last_width, last_height; - - struct desktop_shell *shell; - - struct wl_list children_list; - struct wl_list children_link; - - int32_t saved_x, saved_y; - bool saved_position_valid; - bool saved_rotation_valid; - int unresponsive, grabbed; - uint32_t resize_edges; - - struct { - struct weston_transform transform; - struct weston_matrix rotation; - } rotation; - - struct { - struct weston_transform transform; /* matrix from x, y */ - struct weston_view *black_view; - } fullscreen; - - struct weston_transform workspace_transform; - - struct weston_output *fullscreen_output; - struct weston_output *output; - struct wl_listener output_destroy_listener; - - struct surface_state { - bool fullscreen; - bool maximized; - bool lowered; - } state; - - struct { - bool is_set; - int32_t x; - int32_t y; - } xwayland; - - int focus_count; - - bool destroying; -}; - -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; - bool client_initiated; -}; - -struct weston_touch_move_grab { - struct shell_touch_grab base; - int active; - 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 weston_surface *focused_surface; - - struct wl_listener caps_changed_listener; - struct wl_listener pointer_focus_listener; - struct wl_listener keyboard_focus_listener; -}; - - -static struct desktop_shell * -shell_surface_get_shell(struct shell_surface *shsurf); - -static void -set_busy_cursor(struct shell_surface *shsurf, struct weston_pointer *pointer); - -static void -surface_rotate(struct shell_surface *surface, struct weston_pointer *pointer); - -static void -shell_fade_startup(struct desktop_shell *shell); - -static void -shell_fade(struct desktop_shell *shell, enum fade_type type); - -static struct shell_seat * -get_shell_seat(struct weston_seat *seat); - -static void -get_output_panel_size(struct desktop_shell *shell, - struct weston_output *output, - int *width, int *height); - -static void -shell_surface_update_child_surface_layers(struct shell_surface *shsurf); - -static int -shell_surface_get_label(struct weston_surface *surface, char *buf, size_t len) -{ - const char *t, *c; - struct weston_desktop_surface *desktop_surface = - weston_surface_get_desktop_surface(surface); - - t = weston_desktop_surface_get_title(desktop_surface); - c = weston_desktop_surface_get_app_id(desktop_surface); - - return snprintf(buf, len, "%s window%s%s%s%s%s", - "top-level", - t ? " '" : "", t ?: "", t ? "'" : "", - c ? " of " : "", c ?: ""); -} - -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; -} - -struct weston_view * -get_default_view(struct weston_surface *surface) -{ - struct shell_surface *shsurf; - struct weston_view *view; - - if (!surface || wl_list_empty(&surface->views)) - return NULL; - - shsurf = get_shell_surface(surface); - if (shsurf) - return shsurf->view; - - wl_list_for_each(view, &surface->views, surface_link) - if (weston_view_is_mapped(view)) - return view; - - return container_of(surface->views.next, struct weston_view, surface_link); -} - -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 weston_desktop_shell_cursor cursor) -{ - struct desktop_shell *shell = shsurf->shell; - - weston_seat_break_desktop_grabs(pointer->seat); - - 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); - - shsurf->grabbed = 1; - weston_pointer_start_grab(pointer, &grab->grab); - if (shell->child.desktop_shell) { - weston_desktop_shell_send_grab_cursor(shell->child.desktop_shell, - cursor); - weston_pointer_set_focus(pointer, - get_default_view(shell->grab_surface), - wl_fixed_from_int(0), - wl_fixed_from_int(0)); - } -} - -static void -get_panel_size(struct desktop_shell *shell, - struct weston_view *view, - int *width, - int *height) -{ - float x1, y1; - float x2, y2; - weston_view_to_global_float(view, 0, 0, &x1, &y1); - weston_view_to_global_float(view, - view->surface->width, - view->surface->height, - &x2, &y2); - *width = (int)(x2 - x1); - *height = (int)(y2 - y1); -} - -static void -get_output_panel_size(struct desktop_shell *shell, - struct weston_output *output, - int *width, - int *height) -{ - struct weston_view *view; - - *width = 0; - *height = 0; - - if (!output) - return; - - wl_list_for_each(view, &shell->panel_layer.view_list.link, layer_link.link) { - if (view->surface->output == output) { - get_panel_size(shell, view, width, height); - return; - } - } - - /* the correct view wasn't found */ -} - -void -get_output_work_area(struct desktop_shell *shell, - struct weston_output *output, - pixman_rectangle32_t *area) -{ - int32_t panel_width = 0, panel_height = 0; - - if (!output) { - area->x = 0; - area->y = 0; - area->width = 0; - area->height = 0; - - return; - } - - area->x = output->x; - area->y = output->y; - - get_output_panel_size(shell, output, &panel_width, &panel_height); - switch (shell->panel_position) { - case WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP: - default: - area->y += panel_height; - /* fallthrough */ - case WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM: - area->width = output->width; - area->height = output->height - panel_height; - break; - case WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT: - area->x += panel_width; - /* fallthrough */ - case WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT: - area->width = output->width - panel_width; - area->height = output->height; - break; - } -} - -static void -shell_grab_end(struct shell_grab *grab) -{ - if (grab->shsurf) { - wl_list_remove(&grab->shsurf_destroy_listener.link); - grab->shsurf->grabbed = 0; - - if (grab->shsurf->resize_edges) { - grab->shsurf->resize_edges = 0; - } - } - - 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; - - weston_seat_break_desktop_grabs(touch->seat); - - 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; - shsurf->grabbed = 1; - - weston_touch_start_grab(touch, &grab->grab); - if (shell->child.desktop_shell) - weston_touch_set_focus(touch, - get_default_view(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); - grab->shsurf->grabbed = 0; - } - - weston_touch_end_grab(grab->touch); -} - -static void -center_on_output(struct weston_view *view, - 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 if (!strcmp("none", modifier)) - return 0; - else - return MODIFIER_SUPER; -} - -static enum animation_type -get_animation_type(char *animation) -{ - if (!animation) - return ANIMATION_NONE; - - if (!strcmp("zoom", animation)) - return ANIMATION_ZOOM; - else if (!strcmp("fade", animation)) - return ANIMATION_FADE; - else if (!strcmp("dim-layer", animation)) - return ANIMATION_DIM_LAYER; - else - return ANIMATION_NONE; -} - -static void -shell_configuration(struct desktop_shell *shell) -{ - struct weston_config_section *section; - char *s, *client; - bool allow_zap; - - section = weston_config_get_section(wet_get_config(shell->compositor), - "shell", NULL, NULL); - client = wet_get_libexec_path(WESTON_SHELL_CLIENT); - weston_config_section_get_string(section, "client", &s, client); - free(client); - shell->client = s; - - weston_config_section_get_bool(section, - "allow-zap", &allow_zap, true); - shell->allow_zap = allow_zap; - - weston_config_section_get_string(section, - "binding-modifier", &s, "super"); - shell->binding_modifier = get_modifier(s); - free(s); - - weston_config_section_get_string(section, - "exposay-modifier", &s, "none"); - shell->exposay_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, "close-animation", &s, "fade"); - shell->win_close_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_string(section, "focus-animation", &s, "none"); - shell->focus_animation_type = get_animation_type(s); - free(s); - weston_config_section_get_uint(section, "num-workspaces", - &shell->workspaces.num, - DEFAULT_NUM_WORKSPACES); -} - -struct weston_output * -get_default_output(struct weston_compositor *compositor) -{ - if (wl_list_empty(&compositor->output_list)) - return NULL; - - return container_of(compositor->output_list.next, - struct weston_output, link); -} - -static int -focus_surface_get_label(struct weston_surface *surface, char *buf, size_t len) -{ - return snprintf(buf, len, "focus highlight effect for output %s", - (surface->output ? surface->output->name : "NULL")); -} - -/* no-op func for checking focus surface */ -static void -focus_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) -{ -} - -static struct focus_surface * -get_focus_surface(struct weston_surface *surface) -{ - if (surface->committed == focus_surface_committed) - return surface->committed_private; - else - return NULL; -} - -static bool -is_focus_surface (struct weston_surface *es) -{ - return (es->committed == focus_surface_committed); -} - -static bool -is_focus_view (struct weston_view *view) -{ - return is_focus_surface (view->surface); -} - -static struct focus_surface * -create_focus_surface(struct weston_compositor *ec, - struct weston_output *output) -{ - struct focus_surface *fsurf = NULL; - struct weston_surface *surface = NULL; - - fsurf = malloc(sizeof *fsurf); - if (!fsurf) - return NULL; - - fsurf->surface = weston_surface_create(ec); - surface = fsurf->surface; - if (surface == NULL) { - free(fsurf); - return NULL; - } - - surface->committed = focus_surface_committed; - surface->output = output; - surface->is_mapped = true; - surface->committed_private = fsurf; - weston_surface_set_label_func(surface, focus_surface_get_label); - - fsurf->view = weston_view_create(surface); - if (fsurf->view == NULL) { - weston_surface_destroy(surface); - free(fsurf); - return NULL; - } - weston_view_set_output(fsurf->view, output); - fsurf->view->is_mapped = true; - - weston_surface_set_size(surface, output->width, output->height); - weston_view_set_position(fsurf->view, output->x, output->y); - weston_surface_set_color(surface, 0.0, 0.0, 0.0, 1.0); - pixman_region32_fini(&surface->opaque); - pixman_region32_init_rect(&surface->opaque, output->x, output->y, - output->width, output->height); - pixman_region32_fini(&surface->input); - pixman_region32_init(&surface->input); - - wl_list_init(&fsurf->workspace_transform.link); - - return fsurf; -} - -static void -focus_surface_destroy(struct focus_surface *fsurf) -{ - weston_surface_destroy(fsurf->surface); - free(fsurf); -} - -static void -focus_animation_done(struct weston_view_animation *animation, void *data) -{ - struct workspace *ws = data; - - ws->focus_animation = NULL; -} - -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 weston_surface *main_surface; - struct weston_view *next; - struct weston_view *view; - - main_surface = weston_surface_get_main_surface(state->keyboard_focus); - - next = NULL; - wl_list_for_each(view, - &state->ws->layer.view_list.link, layer_link.link) { - if (view->surface == main_surface) - continue; - if (is_focus_view(view)) - continue; - if (!get_shell_surface(view->surface)) - continue; - - next = view; - break; - } - - /* if the focus was a sub-surface, activate its main surface */ - if (main_surface != state->keyboard_focus) - next = get_default_view(main_surface); - - if (next) { - if (state->keyboard_focus) { - wl_list_remove(&state->surface_destroy_listener.link); - wl_list_init(&state->surface_destroy_listener.link); - } - state->keyboard_focus = NULL; - activate(state->shell, next, state->seat, - WESTON_ACTIVATE_FLAG_CONFIGURE); - } else { - if (state->shell->focus_animation_type == ANIMATION_DIM_LAYER) { - if (state->ws->focus_animation) - weston_view_animation_destroy(state->ws->focus_animation); - - state->ws->focus_animation = weston_fade_run( - state->ws->fsurf_front->view, - state->ws->fsurf_front->view->alpha, 0.0, 300, - focus_animation_done, state->ws); - } - - wl_list_remove(&state->link); - focus_state_destroy(state); - } -} - -static struct focus_state * -focus_state_create(struct desktop_shell *shell, struct weston_seat *seat, - struct workspace *ws) -{ - struct focus_state *state; - - state = malloc(sizeof *state); - if (state == NULL) - return NULL; - - state->shell = shell; - state->keyboard_focus = 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(shell, seat, ws); - - return state; -} - -static void -focus_state_set_focus(struct focus_state *state, - struct weston_surface *surface) -{ - if (state->keyboard_focus) { - wl_list_remove(&state->surface_destroy_listener.link); - wl_list_init(&state->surface_destroy_listener.link); - } - - state->keyboard_focus = surface; - if (surface) - wl_signal_add(&surface->destroy_signal, - &state->surface_destroy_listener); -} - -static void -restore_focus_state(struct desktop_shell *shell, struct workspace *ws) -{ - struct focus_state *state, *next; - struct weston_surface *surface; - struct wl_list pending_seat_list; - struct weston_seat *seat, *next_seat; - - /* Temporarily steal the list of seats so that we can keep - * track of the seats we've already processed */ - wl_list_init(&pending_seat_list); - wl_list_insert_list(&pending_seat_list, &shell->compositor->seat_list); - wl_list_init(&shell->compositor->seat_list); - - wl_list_for_each_safe(state, next, &ws->focus_list, link) { - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(state->seat); - - wl_list_remove(&state->seat->link); - wl_list_insert(&shell->compositor->seat_list, - &state->seat->link); - - if (!keyboard) - continue; - - surface = state->keyboard_focus; - - weston_keyboard_set_focus(keyboard, surface); - } - - /* For any remaining seats that we don't have a focus state - * for we'll reset the keyboard focus to NULL */ - wl_list_for_each_safe(seat, next_seat, &pending_seat_list, link) { - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(seat); - - wl_list_insert(&shell->compositor->seat_list, &seat->link); - - if (!keyboard) - continue; - - weston_keyboard_set_focus(keyboard, NULL); - } -} - -static void -replace_focus_state(struct desktop_shell *shell, struct workspace *ws, - struct weston_seat *seat) -{ - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct focus_state *state; - - wl_list_for_each(state, &ws->focus_list, link) { - if (state->seat == seat) { - focus_state_set_focus(state, keyboard->focus); - 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) - focus_state_set_focus(state, NULL); -} - -static void -animate_focus_change(struct desktop_shell *shell, struct workspace *ws, - struct weston_view *from, struct weston_view *to) -{ - struct weston_output *output; - bool focus_surface_created = false; - - /* FIXME: Only support dim animation using two layers */ - if (from == to || shell->focus_animation_type != ANIMATION_DIM_LAYER) - return; - - output = get_default_output(shell->compositor); - if (ws->fsurf_front == NULL && (from || to)) { - ws->fsurf_front = create_focus_surface(shell->compositor, output); - if (ws->fsurf_front == NULL) - return; - ws->fsurf_front->view->alpha = 0.0; - - ws->fsurf_back = create_focus_surface(shell->compositor, output); - if (ws->fsurf_back == NULL) { - focus_surface_destroy(ws->fsurf_front); - return; - } - ws->fsurf_back->view->alpha = 0.0; - - focus_surface_created = true; - } else { - weston_layer_entry_remove(&ws->fsurf_front->view->layer_link); - weston_layer_entry_remove(&ws->fsurf_back->view->layer_link); - } - - if (ws->focus_animation) { - weston_view_animation_destroy(ws->focus_animation); - ws->focus_animation = NULL; - } - - if (to) - weston_layer_entry_insert(&to->layer_link, - &ws->fsurf_front->view->layer_link); - else if (from) - weston_layer_entry_insert(&ws->layer.view_list, - &ws->fsurf_front->view->layer_link); - - if (focus_surface_created) { - ws->focus_animation = weston_fade_run( - ws->fsurf_front->view, - ws->fsurf_front->view->alpha, 0.4, 300, - focus_animation_done, ws); - } else if (from) { - weston_layer_entry_insert(&from->layer_link, - &ws->fsurf_back->view->layer_link); - ws->focus_animation = weston_stable_fade_run( - ws->fsurf_front->view, 0.0, - ws->fsurf_back->view, 0.4, - focus_animation_done, ws); - } else if (to) { - weston_layer_entry_insert(&ws->layer.view_list, - &ws->fsurf_back->view->layer_link); - ws->focus_animation = weston_stable_fade_run( - ws->fsurf_front->view, 0.0, - ws->fsurf_back->view, 0.4, - focus_animation_done, ws); - } -} - -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); - - if (ws->fsurf_front) - focus_surface_destroy(ws->fsurf_front); - if (ws->fsurf_back) - focus_surface_destroy(ws->fsurf_back); - - 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(struct desktop_shell *shell) -{ - struct workspace *ws = malloc(sizeof *ws); - if (ws == NULL) - return NULL; - - weston_layer_init(&ws->layer, shell->compositor); - - wl_list_init(&ws->focus_list); - wl_list_init(&ws->seat_destroyed_listener.link); - ws->seat_destroyed_listener.notify = seat_destroyed; - ws->fsurf_front = NULL; - ws->fsurf_back = NULL; - ws->focus_animation = NULL; - - return ws; -} - -static int -workspace_is_empty(struct workspace *ws) -{ - return wl_list_empty(&ws->layer.view_list.link); -} - -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; -} - -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); - weston_layer_set_position(&ws->layer, WESTON_LAYER_POSITION_NORMAL); - - 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 struct weston_transform * -view_get_transform(struct weston_view *view) -{ - struct focus_surface *fsurf = NULL; - struct shell_surface *shsurf = NULL; - - if (is_focus_view(view)) { - fsurf = get_focus_surface(view->surface); - return &fsurf->workspace_transform; - } - - shsurf = get_shell_surface(view->surface); - if (shsurf) - return &shsurf->workspace_transform; - - return NULL; -} - -static void -view_translate(struct workspace *ws, struct weston_view *view, double d) -{ - struct weston_transform *transform = view_get_transform(view); - - if (!transform) - return; - - if (wl_list_empty(&transform->link)) - wl_list_insert(view->geometry.transformation_list.prev, - &transform->link); - - weston_matrix_init(&transform->matrix); - weston_matrix_translate(&transform->matrix, - 0.0, d, 0.0); - weston_view_geometry_dirty(view); -} - -static void -workspace_translate_out(struct workspace *ws, double fraction) -{ - struct weston_view *view; - unsigned int height; - double d; - - wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { - height = get_output_height(view->surface->output); - d = height * fraction; - - view_translate(ws, view, d); - } -} - -static void -workspace_translate_in(struct workspace *ws, double fraction) -{ - struct weston_view *view; - unsigned int height; - double d; - - wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { - height = get_output_height(view->surface->output); - - if (fraction > 0) - d = -(height - height * fraction); - else - d = height + height * fraction; - - view_translate(ws, view, d); - } -} - -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 = (struct timespec) { 0 }; - - weston_layer_set_position(&to->layer, WESTON_LAYER_POSITION_NORMAL); - weston_layer_set_position(&from->layer, WESTON_LAYER_POSITION_NORMAL - 1); - - weston_compositor_schedule_repaint(shell->compositor); -} - -static void -workspace_deactivate_transforms(struct workspace *ws) -{ - struct weston_view *view; - struct weston_transform *transform; - - wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { - transform = view_get_transform(view); - if (!transform) - continue; - - if (!wl_list_empty(&transform->link)) { - wl_list_remove(&transform->link); - wl_list_init(&transform->link); - } - weston_view_geometry_dirty(view); - } -} - -static void -finish_workspace_change_animation(struct desktop_shell *shell, - struct workspace *from, - struct workspace *to) -{ - struct weston_view *view; - - weston_compositor_schedule_repaint(shell->compositor); - - /* Views that extend past the bottom of the output are still - * visible after the workspace animation ends but before its layer - * is hidden. In that case, we need to damage below those views so - * that the screen is properly repainted. */ - wl_list_for_each(view, &from->layer.view_list.link, layer_link.link) - weston_view_damage_below(view); - - wl_list_remove(&shell->workspaces.animation.link); - workspace_deactivate_transforms(from); - workspace_deactivate_transforms(to); - shell->workspaces.anim_to = NULL; - - weston_layer_unset_position(&shell->workspaces.anim_from->layer); -} - -static void -animate_workspace_change_frame(struct weston_animation *animation, - struct weston_output *output, - const struct timespec *time) -{ - 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; - int64_t t; - double x, y; - - if (workspace_is_empty(from) && workspace_is_empty(to)) { - finish_workspace_change_animation(shell, from, to); - return; - } - - if (timespec_is_zero(&shell->workspaces.anim_timestamp)) { - if (shell->workspaces.anim_current == 0.0) - shell->workspaces.anim_timestamp = *time; - else - timespec_add_msec(&shell->workspaces.anim_timestamp, - time, - /* Inverse of movement function 'y' below. */ - -(asin(1.0 - shell->workspaces.anim_current) * - DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH * - M_2_PI)); - } - - t = timespec_sub_to_msec(time, &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 = (struct timespec) { 0 }; - - output = container_of(shell->compositor->output_list.next, - struct weston_output, link); - wl_list_insert(&output->animation_list, - &shell->workspaces.animation.link); - - weston_layer_set_position(&to->layer, WESTON_LAYER_POSITION_NORMAL); - weston_layer_set_position(&from->layer, WESTON_LAYER_POSITION_NORMAL - 1); - - 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; - weston_layer_set_position(&to->layer, WESTON_LAYER_POSITION_NORMAL); - weston_layer_unset_position(&from->layer); -} - -static void -change_workspace(struct desktop_shell *shell, unsigned int index) -{ - struct workspace *from; - struct workspace *to; - struct focus_state *state; - - if (index == shell->workspaces.current) - return; - - /* Don't change workspace when there is any fullscreen surfaces. */ - if (!wl_list_empty(&shell->fullscreen_layer.view_list.link)) - 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); - 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 (shell->focus_animation_type != ANIMATION_NONE) { - wl_list_for_each(state, &from->focus_list, link) - if (state->keyboard_focus) - animate_focus_change(shell, from, - get_default_view(state->keyboard_focus), NULL); - - wl_list_for_each(state, &to->focus_list, link) - if (state->keyboard_focus) - animate_focus_change(shell, to, - NULL, get_default_view(state->keyboard_focus)); - } - - if (workspace_is_empty(to) && workspace_is_empty(from)) - update_workspace(shell, index, from, to); - else - animate_workspace_change(shell, index, from, to); -} - -static bool -workspace_has_only(struct workspace *ws, struct weston_surface *surface) -{ - struct wl_list *list = &ws->layer.view_list.link; - 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_view, layer_link.link)->surface == surface; -} - -static void -surface_keyboard_focus_lost(struct weston_surface *surface) -{ - struct weston_compositor *compositor = surface->compositor; - struct weston_seat *seat; - struct weston_surface *focus; - - wl_list_for_each(seat, &compositor->seat_list, link) { - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(seat); - - if (!keyboard) - continue; - - focus = weston_surface_get_main_surface(keyboard->focus); - if (focus == surface) - weston_keyboard_set_focus(keyboard, NULL); - } -} - -static void -take_surface_to_workspace_by_seat(struct desktop_shell *shell, - struct weston_seat *seat, - unsigned int index) -{ - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct weston_surface *surface; - struct weston_view *view; - struct shell_surface *shsurf; - struct workspace *from; - struct workspace *to; - struct focus_state *state; - - surface = weston_surface_get_main_surface(keyboard->focus); - view = get_default_view(surface); - if (view == NULL || - index == shell->workspaces.current || - is_focus_view(view)) - return; - - from = get_current_workspace(shell); - to = get_workspace(shell, index); - - weston_layer_entry_remove(&view->layer_link); - weston_layer_entry_insert(&to->layer.view_list, &view->layer_link); - - shsurf = get_shell_surface(surface); - if (shsurf != NULL) - shell_surface_update_child_surface_layers(shsurf); - - replace_focus_state(shell, to, seat); - drop_focus_state(shell, from, surface); - - if (shell->workspaces.anim_from == to && - shell->workspaces.anim_to == from) { - reverse_workspace_change_animation(shell, index, from, to); - - 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 { - if (shsurf != NULL && - 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); - } - - state = ensure_focus_state(shell, seat); - if (state != NULL) - focus_state_set_focus(state, surface); -} - -static void -touch_move_grab_down(struct weston_touch_grab *grab, - const struct timespec *time, - int touch_id, wl_fixed_t x, wl_fixed_t y) -{ -} - -static void -touch_move_grab_up(struct weston_touch_grab *grab, const struct timespec *time, - int touch_id) -{ - struct weston_touch_move_grab *move = - (struct weston_touch_move_grab *) container_of( - grab, struct shell_touch_grab, grab); - - if (touch_id == 0) - move->active = 0; - - if (grab->touch->num_tp == 0) { - shell_touch_grab_end(&move->base); - free(move); - } -} - -static void -touch_move_grab_motion(struct weston_touch_grab *grab, - const struct timespec *time, int touch_id, - wl_fixed_t x, wl_fixed_t y) -{ - 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 || !move->active) - return; - - es = weston_desktop_surface_get_surface(shsurf->desktop_surface); - - weston_view_set_position(shsurf->view, dx, dy); - - weston_compositor_schedule_repaint(es->compositor); -} - -static void -touch_move_grab_frame(struct weston_touch_grab *grab) -{ -} - -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_frame, - touch_move_grab_cancel, -}; - -static int -surface_touch_move(struct shell_surface *shsurf, struct weston_touch *touch) -{ - struct weston_touch_move_grab *move; - - if (!shsurf) - return -1; - - if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || - weston_desktop_surface_get_maximized(shsurf->desktop_surface)) - return 0; - - move = malloc(sizeof *move); - if (!move) - return -1; - - move->active = 1; - move->dx = wl_fixed_from_double(shsurf->view->geometry.x) - - touch->grab_x; - move->dy = wl_fixed_from_double(shsurf->view->geometry.y) - - touch->grab_y; - - shell_touch_grab_start(&move->base, &touch_move_grab_interface, shsurf, - touch); - - return 0; -} - -static void -noop_grab_focus(struct weston_pointer_grab *grab) -{ -} - -static void -noop_grab_axis(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_axis_event *event) -{ -} - -static void -noop_grab_axis_source(struct weston_pointer_grab *grab, - uint32_t source) -{ -} - -static void -noop_grab_frame(struct weston_pointer_grab *grab) -{ -} - -static void -constrain_position(struct weston_move_grab *move, int *cx, int *cy) -{ - struct shell_surface *shsurf = move->base.shsurf; - struct weston_surface *surface = - weston_desktop_surface_get_surface(shsurf->desktop_surface); - struct weston_pointer *pointer = move->base.grab.pointer; - int x, y, bottom; - const int safety = 50; - pixman_rectangle32_t area; - struct weston_geometry geometry; - - x = wl_fixed_to_int(pointer->x + move->dx); - y = wl_fixed_to_int(pointer->y + move->dy); - - if (shsurf->shell->panel_position == - WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP) { - get_output_work_area(shsurf->shell, surface->output, &area); - geometry = - weston_desktop_surface_get_geometry(shsurf->desktop_surface); - - bottom = y + geometry.height + geometry.y; - if (bottom - safety < area.y) - y = area.y + safety - geometry.height - - geometry.y; - - if (move->client_initiated && - y + geometry.y < area.y) - y = area.y - geometry.y; - } - - *cx = x; - *cy = y; -} - -static void -move_grab_motion(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_motion_event *event) -{ - 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 *surface; - int cx, cy; - - weston_pointer_move(pointer, event); - if (!shsurf) - return; - - surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); - - constrain_position(move, &cx, &cy); - - weston_view_set_position(shsurf->view, cx, cy); - - weston_compositor_schedule_repaint(surface->compositor); -} - -static void -move_grab_button(struct weston_pointer_grab *grab, - const struct timespec *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, - noop_grab_axis, - noop_grab_axis_source, - noop_grab_frame, - move_grab_cancel, -}; - -static int -surface_move(struct shell_surface *shsurf, struct weston_pointer *pointer, - bool client_initiated) -{ - struct weston_move_grab *move; - - if (!shsurf) - return -1; - - if (shsurf->grabbed || - weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || - weston_desktop_surface_get_maximized(shsurf->desktop_surface)) - return 0; - - move = malloc(sizeof *move); - if (!move) - return -1; - - move->dx = wl_fixed_from_double(shsurf->view->geometry.x) - - pointer->grab_x; - move->dy = wl_fixed_from_double(shsurf->view->geometry.y) - - pointer->grab_y; - move->client_initiated = client_initiated; - - shell_grab_start(&move->base, &move_grab_interface, shsurf, - pointer, WESTON_DESKTOP_SHELL_CURSOR_MOVE); - - return 0; -} - -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, - const struct timespec *time, - struct weston_pointer_motion_event *event) -{ - 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; - struct weston_size min_size, max_size; - wl_fixed_t from_x, from_y; - wl_fixed_t to_x, to_y; - - weston_pointer_move(pointer, event); - - if (!shsurf) - return; - - weston_view_from_global_fixed(shsurf->view, - pointer->grab_x, pointer->grab_y, - &from_x, &from_y); - weston_view_from_global_fixed(shsurf->view, - 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); - } - - max_size = weston_desktop_surface_get_max_size(shsurf->desktop_surface); - min_size = weston_desktop_surface_get_min_size(shsurf->desktop_surface); - - min_size.width = MAX(1, min_size.width); - min_size.height = MAX(1, min_size.height); - - if (width < min_size.width) - width = min_size.width; - else if (max_size.width > 0 && width > max_size.width) - width = max_size.width; - if (height < min_size.height) - height = min_size.height; - else if (max_size.width > 0 && width > max_size.width) - width = max_size.width; - weston_desktop_surface_set_size(shsurf->desktop_surface, width, height); -} - -static void -resize_grab_button(struct weston_pointer_grab *grab, - const struct timespec *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) { - if (resize->base.shsurf != NULL) { - struct weston_desktop_surface *desktop_surface = - resize->base.shsurf->desktop_surface; - weston_desktop_surface_set_resizing(desktop_surface, - false); - } - - 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; - - if (resize->base.shsurf != NULL) { - struct weston_desktop_surface *desktop_surface = - resize->base.shsurf->desktop_surface; - weston_desktop_surface_set_resizing(desktop_surface, false); - } - - 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, - noop_grab_axis, - noop_grab_axis_source, - noop_grab_frame, - resize_grab_cancel, -}; - -/* - * Returns the bounding box of a surface and all its sub-surfaces, - * in surface-local coordinates. */ -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->width, - surface->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->width, - subsurface->surface->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_pointer *pointer, uint32_t edges) -{ - struct weston_resize_grab *resize; - const unsigned resize_topbottom = - WL_SHELL_SURFACE_RESIZE_TOP | WL_SHELL_SURFACE_RESIZE_BOTTOM; - const unsigned resize_leftright = - WL_SHELL_SURFACE_RESIZE_LEFT | WL_SHELL_SURFACE_RESIZE_RIGHT; - const unsigned resize_any = resize_topbottom | resize_leftright; - struct weston_geometry geometry; - - if (shsurf->grabbed || - weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || - weston_desktop_surface_get_maximized(shsurf->desktop_surface)) - return 0; - - /* Check for invalid edge combinations. */ - if (edges == WL_SHELL_SURFACE_RESIZE_NONE || edges > resize_any || - (edges & resize_topbottom) == resize_topbottom || - (edges & resize_leftright) == resize_leftright) - return 0; - - resize = malloc(sizeof *resize); - if (!resize) - return -1; - - resize->edges = edges; - - geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); - resize->width = geometry.width; - resize->height = geometry.height; - - shsurf->resize_edges = edges; - weston_desktop_surface_set_resizing(shsurf->desktop_surface, true); - shell_grab_start(&resize->base, &resize_grab_interface, shsurf, - pointer, edges); - - return 0; -} - -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_desktop_surface *desktop_surface; - struct weston_view *view; - wl_fixed_t sx, sy; - - view = weston_compositor_pick_view(pointer->seat->compositor, - pointer->x, pointer->y, - &sx, &sy); - desktop_surface = weston_surface_get_desktop_surface(view->surface); - - if (!grab->shsurf || grab->shsurf->desktop_surface != desktop_surface) { - shell_grab_end(grab); - free(grab); - } -} - -static void -busy_cursor_grab_motion(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_motion_event *event) -{ - weston_pointer_move(grab->pointer, event); -} - -static void -busy_cursor_grab_button(struct weston_pointer_grab *base, - const struct timespec *time, - uint32_t button, uint32_t state) -{ - struct shell_grab *grab = (struct shell_grab *) base; - struct shell_surface *shsurf = grab->shsurf; - struct weston_pointer *pointer = grab->grab.pointer; - struct weston_seat *seat = pointer->seat; - - if (shsurf && button == BTN_LEFT && state) { - activate(shsurf->shell, shsurf->view, seat, - WESTON_ACTIVATE_FLAG_CONFIGURE); - surface_move(shsurf, pointer, false); - } else if (shsurf && button == BTN_RIGHT && state) { - activate(shsurf->shell, shsurf->view, seat, - WESTON_ACTIVATE_FLAG_CONFIGURE); - surface_rotate(shsurf, pointer); - } -} - -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, - noop_grab_axis, - noop_grab_axis_source, - noop_grab_frame, - busy_cursor_grab_cancel, -}; - -static void -handle_pointer_focus(struct wl_listener *listener, void *data) -{ - struct weston_pointer *pointer = data; - struct weston_view *view = pointer->focus; - struct shell_surface *shsurf; - struct weston_desktop_client *client; - - if (!view) - return; - - shsurf = get_shell_surface(view->surface); - if (!shsurf) - return; - - client = weston_desktop_surface_get_client(shsurf->desktop_surface); - - if (shsurf->unresponsive) - set_busy_cursor(shsurf, pointer); - else - weston_desktop_client_ping(client); -} - -static void -shell_surface_lose_keyboard_focus(struct shell_surface *shsurf) -{ - if (--shsurf->focus_count == 0) - weston_desktop_surface_set_activated(shsurf->desktop_surface, false); -} - -static void -shell_surface_gain_keyboard_focus(struct shell_surface *shsurf) -{ - if (shsurf->focus_count++ == 0) - weston_desktop_surface_set_activated(shsurf->desktop_surface, true); -} - -static void -handle_keyboard_focus(struct wl_listener *listener, void *data) -{ - struct weston_keyboard *keyboard = data; - struct shell_seat *seat = get_shell_seat(keyboard->seat); - - if (seat->focused_surface) { - struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); - if (shsurf) - shell_surface_lose_keyboard_focus(shsurf); - } - - seat->focused_surface = weston_surface_get_main_surface(keyboard->focus); - - if (seat->focused_surface) { - struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); - if (shsurf) - shell_surface_gain_keyboard_focus(shsurf); - } -} - -/* The surface will be inserted into the list immediately after the link - * returned by this function (i.e. will be stacked immediately above the - * returned link). */ -static struct weston_layer_entry * -shell_surface_calculate_layer_link (struct shell_surface *shsurf) -{ - struct workspace *ws; - - if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) && - !shsurf->state.lowered) { - return &shsurf->shell->fullscreen_layer.view_list; - } - - /* Move the surface to a normal workspace layer so that surfaces - * which were previously fullscreen or transient are no longer - * rendered on top. */ - ws = get_current_workspace(shsurf->shell); - return &ws->layer.view_list; -} - -static void -shell_surface_update_child_surface_layers (struct shell_surface *shsurf) -{ - weston_desktop_surface_propagate_layer(shsurf->desktop_surface); -} - -/* Update the surface’s layer. Mark both the old and new views as having dirty - * geometry to ensure the changes are redrawn. - * - * If any child surfaces exist and are mapped, ensure they’re in the same layer - * as this surface. */ -static void -shell_surface_update_layer(struct shell_surface *shsurf) -{ - struct weston_surface *surface = - weston_desktop_surface_get_surface(shsurf->desktop_surface); - struct weston_layer_entry *new_layer_link; - - new_layer_link = shell_surface_calculate_layer_link(shsurf); - - if (new_layer_link == NULL) - return; - if (new_layer_link == &shsurf->view->layer_link) - return; - - weston_view_geometry_dirty(shsurf->view); - weston_layer_entry_remove(&shsurf->view->layer_link); - weston_layer_entry_insert(new_layer_link, &shsurf->view->layer_link); - weston_view_geometry_dirty(shsurf->view); - weston_surface_damage(surface); - - shell_surface_update_child_surface_layers(shsurf); -} - -static void -notify_output_destroy(struct wl_listener *listener, void *data) -{ - struct shell_surface *shsurf = - container_of(listener, - struct shell_surface, output_destroy_listener); - - shsurf->output = NULL; - shsurf->output_destroy_listener.notify = NULL; -} - -static void -shell_surface_set_output(struct shell_surface *shsurf, - struct weston_output *output) -{ - struct weston_surface *es = - weston_desktop_surface_get_surface(shsurf->desktop_surface); - - /* get the default output, if the client set it as NULL - check whether the output is available */ - if (output) - shsurf->output = output; - else if (es->output) - shsurf->output = es->output; - else - shsurf->output = get_default_output(es->compositor); - - if (shsurf->output_destroy_listener.notify) { - wl_list_remove(&shsurf->output_destroy_listener.link); - shsurf->output_destroy_listener.notify = NULL; - } - - if (!shsurf->output) - return; - - shsurf->output_destroy_listener.notify = notify_output_destroy; - wl_signal_add(&shsurf->output->destroy_signal, - &shsurf->output_destroy_listener); -} - -static void -weston_view_set_initial_position(struct weston_view *view, - struct desktop_shell *shell); - -static void -unset_fullscreen(struct shell_surface *shsurf) -{ - /* Unset the fullscreen output, driver configuration and transforms. */ - wl_list_remove(&shsurf->fullscreen.transform.link); - wl_list_init(&shsurf->fullscreen.transform.link); - - if (shsurf->fullscreen.black_view) - weston_surface_destroy(shsurf->fullscreen.black_view->surface); - shsurf->fullscreen.black_view = NULL; - - if (shsurf->saved_position_valid) - weston_view_set_position(shsurf->view, - shsurf->saved_x, shsurf->saved_y); - else - weston_view_set_initial_position(shsurf->view, shsurf->shell); - shsurf->saved_position_valid = false; - - if (shsurf->saved_rotation_valid) { - wl_list_insert(&shsurf->view->geometry.transformation_list, - &shsurf->rotation.transform.link); - shsurf->saved_rotation_valid = false; - } -} - -static void -unset_maximized(struct shell_surface *shsurf) -{ - struct weston_surface *surface = - weston_desktop_surface_get_surface(shsurf->desktop_surface); - - /* undo all maximized things here */ - shell_surface_set_output(shsurf, get_default_output(surface->compositor)); - - if (shsurf->saved_position_valid) - weston_view_set_position(shsurf->view, - shsurf->saved_x, shsurf->saved_y); - else - weston_view_set_initial_position(shsurf->view, shsurf->shell); - shsurf->saved_position_valid = false; - - if (shsurf->saved_rotation_valid) { - wl_list_insert(&shsurf->view->geometry.transformation_list, - &shsurf->rotation.transform.link); - shsurf->saved_rotation_valid = false; - } -} - -static void -set_minimized(struct weston_surface *surface) -{ - struct shell_surface *shsurf; - struct workspace *current_ws; - struct weston_view *view; - - view = get_default_view(surface); - if (!view) - return; - - assert(weston_surface_get_main_surface(view->surface) == view->surface); - - shsurf = get_shell_surface(surface); - current_ws = get_current_workspace(shsurf->shell); - - weston_layer_entry_remove(&view->layer_link); - weston_layer_entry_insert(&shsurf->shell->minimized_layer.view_list, &view->layer_link); - - drop_focus_state(shsurf->shell, current_ws, view->surface); - surface_keyboard_focus_lost(surface); - - shell_surface_update_child_surface_layers(shsurf); - weston_view_damage_below(view); -} - - -static struct desktop_shell * -shell_surface_get_shell(struct shell_surface *shsurf) -{ - return shsurf->shell; -} - -static int -black_surface_get_label(struct weston_surface *surface, char *buf, size_t len) -{ - struct weston_view *fs_view = surface->committed_private; - struct weston_surface *fs_surface = fs_view->surface; - int n; - int rem; - int ret; - - n = snprintf(buf, len, "black background surface for "); - if (n < 0) - return n; - - rem = (int)len - n; - if (rem < 0) - rem = 0; - - if (fs_surface->get_label) - ret = fs_surface->get_label(fs_surface, buf + n, rem); - else - ret = snprintf(buf + n, rem, ""); - - if (ret < 0) - return n; - - return n + ret; -} - -static void -black_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy); - -static struct weston_view * -create_black_surface(struct weston_compositor *ec, - struct weston_view *fs_view, - float x, float y, int w, int h) -{ - struct weston_surface *surface = NULL; - struct weston_view *view; - - surface = weston_surface_create(ec); - if (surface == NULL) { - weston_log("no memory\n"); - return NULL; - } - view = weston_view_create(surface); - if (surface == NULL) { - weston_log("no memory\n"); - weston_surface_destroy(surface); - return NULL; - } - - surface->committed = black_surface_committed; - surface->committed_private = fs_view; - weston_surface_set_label_func(surface, black_surface_get_label); - 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); - - weston_surface_set_size(surface, w, h); - weston_view_set_position(view, x, y); - - return view; -} - -static void -shell_ensure_fullscreen_black_view(struct shell_surface *shsurf) -{ - struct weston_surface *surface = - weston_desktop_surface_get_surface(shsurf->desktop_surface); - struct weston_output *output = shsurf->fullscreen_output; - - assert(weston_desktop_surface_get_fullscreen(shsurf->desktop_surface)); - - if (!shsurf->fullscreen.black_view) - shsurf->fullscreen.black_view = - create_black_surface(surface->compositor, - shsurf->view, - output->x, output->y, - output->width, - output->height); - - weston_view_geometry_dirty(shsurf->fullscreen.black_view); - weston_layer_entry_remove(&shsurf->fullscreen.black_view->layer_link); - weston_layer_entry_insert(&shsurf->view->layer_link, - &shsurf->fullscreen.black_view->layer_link); - weston_view_geometry_dirty(shsurf->fullscreen.black_view); - weston_surface_damage(surface); - - shsurf->fullscreen.black_view->is_mapped = true; - shsurf->state.lowered = false; -} - -/* 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_surface *surface = - weston_desktop_surface_get_surface(shsurf->desktop_surface); - int32_t surf_x, surf_y, surf_width, surf_height; - - /* Reverse the effect of lower_fullscreen_layer() */ - weston_layer_entry_remove(&shsurf->view->layer_link); - weston_layer_entry_insert(&shsurf->shell->fullscreen_layer.view_list, - &shsurf->view->layer_link); - - if (!shsurf->fullscreen_output) { - /* If there is no output, there's not much we can do. - * Position the window somewhere, whatever. */ - weston_view_set_position(shsurf->view, 0, 0); - return; - } - - shell_ensure_fullscreen_black_view(shsurf); - - surface_subsurfaces_boundingbox(surface, &surf_x, &surf_y, - &surf_width, &surf_height); - - if (surface->buffer_ref.buffer) - center_on_output(shsurf->view, shsurf->fullscreen_output); -} - -static void -shell_map_fullscreen(struct shell_surface *shsurf) -{ - shell_configure_fullscreen(shsurf); -} - -static struct weston_output * -get_focused_output(struct weston_compositor *compositor) -{ - struct weston_seat *seat; - struct weston_output *output = NULL; - - wl_list_for_each(seat, &compositor->seat_list, link) { - struct weston_touch *touch = weston_seat_get_touch(seat); - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(seat); - - /* Priority has touch focus, then pointer and - * then keyboard focus. We should probably have - * three for loops and check first for touch, - * then for pointer, etc. but unless somebody has some - * objections, I think this is sufficient. */ - if (touch && touch->focus) - output = touch->focus->output; - else if (pointer && pointer->focus) - output = pointer->focus->output; - else if (keyboard && keyboard->focus) - output = keyboard->focus->output; - - if (output) - break; - } - - return output; -} - -static void -destroy_shell_seat(struct wl_listener *listener, void *data) -{ - struct shell_seat *shseat = - container_of(listener, - struct shell_seat, seat_destroy_listener); - - wl_list_remove(&shseat->seat_destroy_listener.link); - free(shseat); -} - -static void -shell_seat_caps_changed(struct wl_listener *listener, void *data) -{ - struct weston_keyboard *keyboard; - struct weston_pointer *pointer; - struct shell_seat *seat; - - seat = container_of(listener, struct shell_seat, caps_changed_listener); - keyboard = weston_seat_get_keyboard(seat->seat); - pointer = weston_seat_get_pointer(seat->seat); - - if (keyboard && - wl_list_empty(&seat->keyboard_focus_listener.link)) { - wl_signal_add(&keyboard->focus_signal, - &seat->keyboard_focus_listener); - } else if (!keyboard) { - wl_list_remove(&seat->keyboard_focus_listener.link); - wl_list_init(&seat->keyboard_focus_listener.link); - } - - if (pointer && - wl_list_empty(&seat->pointer_focus_listener.link)) { - wl_signal_add(&pointer->focus_signal, - &seat->pointer_focus_listener); - } else if (!pointer) { - wl_list_remove(&seat->pointer_focus_listener.link); - wl_list_init(&seat->pointer_focus_listener.link); - } -} - -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; - - shseat->seat_destroy_listener.notify = destroy_shell_seat; - wl_signal_add(&seat->destroy_signal, - &shseat->seat_destroy_listener); - - shseat->keyboard_focus_listener.notify = handle_keyboard_focus; - wl_list_init(&shseat->keyboard_focus_listener.link); - - shseat->pointer_focus_listener.notify = handle_pointer_focus; - wl_list_init(&shseat->pointer_focus_listener.link); - - shseat->caps_changed_listener.notify = shell_seat_caps_changed; - wl_signal_add(&seat->updated_caps_signal, - &shseat->caps_changed_listener); - shell_seat_caps_changed(&shseat->caps_changed_listener, NULL); - - 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); - assert(listener != NULL); - - return container_of(listener, - struct shell_seat, seat_destroy_listener); -} - -static void -fade_out_done_idle_cb(void *data) -{ - struct shell_surface *shsurf = data; - - weston_surface_destroy(shsurf->view->surface); - - if (shsurf->output_destroy_listener.notify) { - wl_list_remove(&shsurf->output_destroy_listener.link); - shsurf->output_destroy_listener.notify = NULL; - } - - free(shsurf); -} - -static void -fade_out_done(struct weston_view_animation *animation, void *data) -{ - struct shell_surface *shsurf = data; - struct wl_event_loop *loop; - - loop = wl_display_get_event_loop(shsurf->shell->compositor->wl_display); - - if (weston_view_is_mapped(shsurf->view)) { - weston_view_unmap(shsurf->view); - wl_event_loop_add_idle(loop, fade_out_done_idle_cb, shsurf); - } -} - -struct shell_surface * -get_shell_surface(struct weston_surface *surface) -{ - if (weston_surface_is_desktop_surface(surface)) { - struct weston_desktop_surface *desktop_surface = - weston_surface_get_desktop_surface(surface); - return weston_desktop_surface_get_user_data(desktop_surface); - } - return NULL; -} - -/* - * libweston-desktop - */ - -static void -desktop_surface_added(struct weston_desktop_surface *desktop_surface, - void *shell) -{ - struct weston_desktop_client *client = - weston_desktop_surface_get_client(desktop_surface); - struct wl_client *wl_client = - weston_desktop_client_get_client(client); - struct weston_view *view; - struct shell_surface *shsurf; - struct weston_surface *surface = - weston_desktop_surface_get_surface(desktop_surface); - - view = weston_desktop_surface_create_view(desktop_surface); - if (!view) - return; - - shsurf = calloc(1, sizeof *shsurf); - if (!shsurf) { - if (wl_client) - wl_client_post_no_memory(wl_client); - else - weston_log("no memory to allocate shell surface\n"); - return; - } - - weston_surface_set_label_func(surface, shell_surface_get_label); - - shsurf->shell = (struct desktop_shell *) shell; - shsurf->unresponsive = 0; - shsurf->saved_position_valid = false; - shsurf->saved_rotation_valid = false; - shsurf->desktop_surface = desktop_surface; - shsurf->view = view; - shsurf->fullscreen.black_view = NULL; - wl_list_init(&shsurf->fullscreen.transform.link); - - shell_surface_set_output( - shsurf, get_default_output(shsurf->shell->compositor)); - - wl_signal_init(&shsurf->destroy_signal); - - /* 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); - - /* - * initialize list as well as link. The latter allows to use - * wl_list_remove() even when this surface is not in another list. - */ - wl_list_init(&shsurf->children_list); - wl_list_init(&shsurf->children_link); - - weston_desktop_surface_set_user_data(desktop_surface, shsurf); - weston_desktop_surface_set_activated(desktop_surface, - shsurf->focus_count > 0); -} - -static void -desktop_surface_removed(struct weston_desktop_surface *desktop_surface, - void *shell) -{ - struct shell_surface *shsurf = - weston_desktop_surface_get_user_data(desktop_surface); - struct shell_surface *shsurf_child, *tmp; - struct weston_surface *surface = - weston_desktop_surface_get_surface(desktop_surface); - - if (!shsurf) - return; - - wl_list_for_each_safe(shsurf_child, tmp, &shsurf->children_list, children_link) { - wl_list_remove(&shsurf_child->children_link); - wl_list_init(&shsurf_child->children_link); - } - wl_list_remove(&shsurf->children_link); - - wl_signal_emit(&shsurf->destroy_signal, shsurf); - - if (shsurf->fullscreen.black_view) - weston_surface_destroy(shsurf->fullscreen.black_view->surface); - - weston_surface_set_label_func(surface, NULL); - weston_desktop_surface_set_user_data(shsurf->desktop_surface, NULL); - shsurf->desktop_surface = NULL; - - weston_desktop_surface_unlink_view(shsurf->view); - if (weston_surface_is_mapped(surface) && - shsurf->shell->win_close_animation_type == ANIMATION_FADE) { - pixman_region32_fini(&surface->pending.input); - pixman_region32_init(&surface->pending.input); - pixman_region32_fini(&surface->input); - pixman_region32_init(&surface->input); - weston_fade_run(shsurf->view, 1.0, 0.0, 300.0, - fade_out_done, shsurf); - } else { - weston_view_destroy(shsurf->view); - - if (shsurf->output_destroy_listener.notify) { - wl_list_remove(&shsurf->output_destroy_listener.link); - shsurf->output_destroy_listener.notify = NULL; - } - - free(shsurf); - } -} - -static void -set_maximized_position(struct desktop_shell *shell, - struct shell_surface *shsurf) -{ - pixman_rectangle32_t area; - struct weston_geometry geometry; - - get_output_work_area(shell, shsurf->output, &area); - geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); - - weston_view_set_position(shsurf->view, - area.x - geometry.x, - area.y - geometry.y); -} - -static void -set_position_from_xwayland(struct shell_surface *shsurf) -{ - struct weston_geometry geometry; - float x; - float y; - - assert(shsurf->xwayland.is_set); - - geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); - x = shsurf->xwayland.x - geometry.x; - y = shsurf->xwayland.y - geometry.y; - - weston_view_set_position(shsurf->view, x, y); - -#ifdef WM_DEBUG - weston_log("%s: XWM %d, %d; geometry %d, %d; view %f, %f\n", - __func__, shsurf->xwayland.x, shsurf->xwayland.y, - geometry.x, geometry.y, x, y); -#endif -} - -static void -map(struct desktop_shell *shell, struct shell_surface *shsurf, - int32_t sx, int32_t sy) -{ - struct weston_surface *surface = - weston_desktop_surface_get_surface(shsurf->desktop_surface); - struct weston_compositor *compositor = shell->compositor; - struct weston_seat *seat; - - /* initial positioning, see also configure() */ - if (shsurf->state.fullscreen) { - center_on_output(shsurf->view, shsurf->fullscreen_output); - shell_map_fullscreen(shsurf); - } else if (shsurf->state.maximized) { - set_maximized_position(shell, shsurf); - } else if (shsurf->xwayland.is_set) { - set_position_from_xwayland(shsurf); - } else { - weston_view_set_initial_position(shsurf->view, shell); - } - - /* Surface stacking order, see also activate(). */ - shell_surface_update_layer(shsurf); - - weston_view_update_transform(shsurf->view); - shsurf->view->is_mapped = true; - if (shsurf->state.maximized) { - surface->output = shsurf->output; - weston_view_set_output(shsurf->view, shsurf->output); - } - - if (!shell->locked) { - wl_list_for_each(seat, &compositor->seat_list, link) - activate(shell, shsurf->view, seat, - WESTON_ACTIVATE_FLAG_CONFIGURE); - } - - if (!shsurf->state.fullscreen && !shsurf->state.maximized) { - switch (shell->win_animation_type) { - case ANIMATION_FADE: - weston_fade_run(shsurf->view, 0.0, 1.0, 300.0, NULL, NULL); - break; - case ANIMATION_ZOOM: - weston_zoom_run(shsurf->view, 0.5, 1.0, NULL, NULL); - break; - case ANIMATION_NONE: - default: - break; - } - } -} - -static void -desktop_surface_committed(struct weston_desktop_surface *desktop_surface, - int32_t sx, int32_t sy, void *data) -{ - struct shell_surface *shsurf = - weston_desktop_surface_get_user_data(desktop_surface); - struct weston_surface *surface = - weston_desktop_surface_get_surface(desktop_surface); - struct weston_view *view = shsurf->view; - struct desktop_shell *shell = data; - bool was_fullscreen; - bool was_maximized; - - if (surface->width == 0) - return; - - was_fullscreen = shsurf->state.fullscreen; - was_maximized = shsurf->state.maximized; - - shsurf->state.fullscreen = - weston_desktop_surface_get_fullscreen(desktop_surface); - shsurf->state.maximized = - weston_desktop_surface_get_maximized(desktop_surface); - - if (!weston_surface_is_mapped(surface)) { - map(shell, shsurf, sx, sy); - surface->is_mapped = true; - if (shsurf->shell->win_close_animation_type == ANIMATION_FADE) - ++surface->ref_count; - return; - } - - if (sx == 0 && sy == 0 && - shsurf->last_width == surface->width && - shsurf->last_height == surface->height && - was_fullscreen == shsurf->state.fullscreen && - was_maximized == shsurf->state.maximized) - return; - - if (was_fullscreen) - unset_fullscreen(shsurf); - if (was_maximized) - unset_maximized(shsurf); - - if ((shsurf->state.fullscreen || shsurf->state.maximized) && - !shsurf->saved_position_valid) { - shsurf->saved_x = shsurf->view->geometry.x; - shsurf->saved_y = shsurf->view->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_view_geometry_dirty(shsurf->view); - shsurf->saved_rotation_valid = true; - } - } - - if (shsurf->state.fullscreen) { - shell_configure_fullscreen(shsurf); - } else if (shsurf->state.maximized) { - set_maximized_position(shell, shsurf); - surface->output = shsurf->output; - } else { - float from_x, from_y; - float to_x, to_y; - float x, y; - - if (shsurf->resize_edges) { - sx = 0; - sy = 0; - } - - if (shsurf->resize_edges & WL_SHELL_SURFACE_RESIZE_LEFT) - sx = shsurf->last_width - surface->width; - if (shsurf->resize_edges & WL_SHELL_SURFACE_RESIZE_TOP) - sy = shsurf->last_height - surface->height; - - weston_view_to_global_float(shsurf->view, 0, 0, &from_x, &from_y); - weston_view_to_global_float(shsurf->view, sx, sy, &to_x, &to_y); - x = shsurf->view->geometry.x + to_x - from_x; - y = shsurf->view->geometry.y + to_y - from_y; - - weston_view_set_position(shsurf->view, x, y); - } - - shsurf->last_width = surface->width; - shsurf->last_height = surface->height; - - /* XXX: would a fullscreen surface need the same handling? */ - if (surface->output) { - wl_list_for_each(view, &surface->views, surface_link) - weston_view_update_transform(view); - } -} - -static void -get_maximized_size(struct shell_surface *shsurf, int32_t *width, int32_t *height) -{ - struct desktop_shell *shell; - pixman_rectangle32_t area; - - shell = shell_surface_get_shell(shsurf); - get_output_work_area(shell, shsurf->output, &area); - - *width = area.width; - *height = area.height; -} - -static void -set_fullscreen(struct shell_surface *shsurf, bool fullscreen, - struct weston_output *output) -{ - struct weston_desktop_surface *desktop_surface = shsurf->desktop_surface; - struct weston_surface *surface = - weston_desktop_surface_get_surface(shsurf->desktop_surface); - int32_t width = 0, height = 0; - - if (fullscreen) { - /* handle clients launching in fullscreen */ - if (output == NULL && !weston_surface_is_mapped(surface)) { - /* Set the output to the one that has focus currently. */ - output = get_focused_output(surface->compositor); - } - - shell_surface_set_output(shsurf, output); - shsurf->fullscreen_output = shsurf->output; - - if (shsurf->output) { - width = shsurf->output->width; - height = shsurf->output->height; - } - } else if (weston_desktop_surface_get_maximized(desktop_surface)) { - get_maximized_size(shsurf, &width, &height); - } - weston_desktop_surface_set_fullscreen(desktop_surface, fullscreen); - weston_desktop_surface_set_size(desktop_surface, width, height); -} - -static void -desktop_surface_move(struct weston_desktop_surface *desktop_surface, - struct weston_seat *seat, uint32_t serial, void *shell) -{ - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_touch *touch = weston_seat_get_touch(seat); - struct shell_surface *shsurf = - weston_desktop_surface_get_user_data(desktop_surface); - struct weston_surface *surface = - weston_desktop_surface_get_surface(shsurf->desktop_surface); - struct wl_resource *resource = surface->resource; - struct weston_surface *focus; - - if (pointer && - pointer->focus && - pointer->button_count > 0 && - pointer->grab_serial == serial) { - focus = weston_surface_get_main_surface(pointer->focus->surface); - if ((focus == surface) && - (surface_move(shsurf, pointer, true) < 0)) - wl_resource_post_no_memory(resource); - } else if (touch && - touch->focus && - touch->grab_serial == serial) { - focus = weston_surface_get_main_surface(touch->focus->surface); - if ((focus == surface) && - (surface_touch_move(shsurf, touch) < 0)) - wl_resource_post_no_memory(resource); - } -} - -static void -desktop_surface_resize(struct weston_desktop_surface *desktop_surface, - struct weston_seat *seat, uint32_t serial, - enum weston_desktop_surface_edge edges, void *shell) -{ - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct shell_surface *shsurf = - weston_desktop_surface_get_user_data(desktop_surface); - struct weston_surface *surface = - weston_desktop_surface_get_surface(shsurf->desktop_surface); - struct wl_resource *resource = surface->resource; - struct weston_surface *focus; - - if (!pointer || - pointer->button_count == 0 || - pointer->grab_serial != serial || - pointer->focus == NULL) - return; - - focus = weston_surface_get_main_surface(pointer->focus->surface); - if (focus != surface) - return; - - if (surface_resize(shsurf, pointer, edges) < 0) - wl_resource_post_no_memory(resource); -} - -static void -desktop_surface_set_parent(struct weston_desktop_surface *desktop_surface, - struct weston_desktop_surface *parent, - void *shell) -{ - struct shell_surface *shsurf_parent; - struct shell_surface *shsurf = - weston_desktop_surface_get_user_data(desktop_surface); - - /* unlink any potential child */ - wl_list_remove(&shsurf->children_link); - - if (parent) { - shsurf_parent = weston_desktop_surface_get_user_data(parent); - wl_list_insert(shsurf_parent->children_list.prev, - &shsurf->children_link); - } else { - wl_list_init(&shsurf->children_link); - } -} - -static void -desktop_surface_fullscreen_requested(struct weston_desktop_surface *desktop_surface, - bool fullscreen, - struct weston_output *output, void *shell) -{ - struct shell_surface *shsurf = - weston_desktop_surface_get_user_data(desktop_surface); - - set_fullscreen(shsurf, fullscreen, output); -} - -static void -set_maximized(struct shell_surface *shsurf, bool maximized) -{ - struct weston_desktop_surface *desktop_surface = shsurf->desktop_surface; - struct weston_surface *surface = - weston_desktop_surface_get_surface(shsurf->desktop_surface); - int32_t width = 0, height = 0; - - if (maximized) { - struct weston_output *output; - - if (!weston_surface_is_mapped(surface)) - output = get_focused_output(surface->compositor); - else - output = surface->output; - - shell_surface_set_output(shsurf, output); - - get_maximized_size(shsurf, &width, &height); - } - weston_desktop_surface_set_maximized(desktop_surface, maximized); - weston_desktop_surface_set_size(desktop_surface, width, height); -} - -static void -desktop_surface_maximized_requested(struct weston_desktop_surface *desktop_surface, - bool maximized, void *shell) -{ - struct shell_surface *shsurf = - weston_desktop_surface_get_user_data(desktop_surface); - - set_maximized(shsurf, maximized); -} - -static void -desktop_surface_minimized_requested(struct weston_desktop_surface *desktop_surface, - void *shell) -{ - struct weston_surface *surface = - weston_desktop_surface_get_surface(desktop_surface); - - /* apply compositor's own minimization logic (hide) */ - set_minimized(surface); -} - -static void -set_busy_cursor(struct shell_surface *shsurf, struct weston_pointer *pointer) -{ - struct shell_grab *grab; - - if (pointer->grab->interface == &busy_cursor_grab_interface) - return; - - grab = malloc(sizeof *grab); - if (!grab) - return; - - shell_grab_start(grab, &busy_cursor_grab_interface, shsurf, pointer, - WESTON_DESKTOP_SHELL_CURSOR_BUSY); - /* Mark the shsurf as ungrabbed so that button binding is able - * to move it. */ - shsurf->grabbed = 0; -} - -static void -end_busy_cursor(struct weston_compositor *compositor, - struct weston_desktop_client *desktop_client) -{ - struct shell_surface *shsurf; - struct shell_grab *grab; - struct weston_seat *seat; - - wl_list_for_each(seat, &compositor->seat_list, link) { - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_desktop_client *grab_client; - - if (!pointer) - continue; - - if (pointer->grab->interface != &busy_cursor_grab_interface) - continue; - - grab = (struct shell_grab *) pointer->grab; - shsurf = grab->shsurf; - if (!shsurf) - continue; - - grab_client = - weston_desktop_surface_get_client(shsurf->desktop_surface); - if (grab_client == desktop_client) { - shell_grab_end(grab); - free(grab); - } - } -} - -static void -desktop_surface_set_unresponsive(struct weston_desktop_surface *desktop_surface, - void *user_data) -{ - struct shell_surface *shsurf = - weston_desktop_surface_get_user_data(desktop_surface); - bool *unresponsive = user_data; - - shsurf->unresponsive = *unresponsive; -} - -static void -desktop_surface_ping_timeout(struct weston_desktop_client *desktop_client, - void *shell_) -{ - struct desktop_shell *shell = shell_; - struct shell_surface *shsurf; - struct weston_seat *seat; - bool unresponsive = true; - - weston_desktop_client_for_each_surface(desktop_client, - desktop_surface_set_unresponsive, - &unresponsive); - - - wl_list_for_each(seat, &shell->compositor->seat_list, link) { - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_desktop_client *grab_client; - - if (!pointer || !pointer->focus) - continue; - - shsurf = get_shell_surface(pointer->focus->surface); - if (!shsurf) - continue; - - grab_client = - weston_desktop_surface_get_client(shsurf->desktop_surface); - if (grab_client == desktop_client) - set_busy_cursor(shsurf, pointer); - } -} - -static void -desktop_surface_pong(struct weston_desktop_client *desktop_client, - void *shell_) -{ - struct desktop_shell *shell = shell_; - bool unresponsive = false; - - weston_desktop_client_for_each_surface(desktop_client, - desktop_surface_set_unresponsive, - &unresponsive); - end_busy_cursor(shell->compositor, desktop_client); -} - -static void -desktop_surface_set_xwayland_position(struct weston_desktop_surface *surface, - int32_t x, int32_t y, void *shell_) -{ - struct shell_surface *shsurf = - weston_desktop_surface_get_user_data(surface); - - shsurf->xwayland.x = x; - shsurf->xwayland.y = y; - shsurf->xwayland.is_set = true; -} - -static const struct weston_desktop_api shell_desktop_api = { - .struct_size = sizeof(struct weston_desktop_api), - .surface_added = desktop_surface_added, - .surface_removed = desktop_surface_removed, - .committed = desktop_surface_committed, - .move = desktop_surface_move, - .resize = desktop_surface_resize, - .set_parent = desktop_surface_set_parent, - .fullscreen_requested = desktop_surface_fullscreen_requested, - .maximized_requested = desktop_surface_maximized_requested, - .minimized_requested = desktop_surface_minimized_requested, - .ping_timeout = desktop_surface_ping_timeout, - .pong = desktop_surface_pong, - .set_xwayland_position = desktop_surface_set_xwayland_position, -}; - -/* ************************ * - * end of libweston-desktop * - * ************************ */ -static void -configure_static_view(struct weston_view *ev, struct weston_layer *layer, int x, int y) -{ - struct weston_view *v, *next; - - if (!ev->output) - return; - - wl_list_for_each_safe(v, next, &layer->view_list.link, layer_link.link) { - if (v->output == ev->output && v != ev) { - weston_view_unmap(v); - v->surface->committed = NULL; - weston_surface_set_label_func(v->surface, NULL); - } - } - - weston_view_set_position(ev, ev->output->x + x, ev->output->y + y); - ev->surface->is_mapped = true; - ev->is_mapped = true; - - if (wl_list_empty(&ev->layer_link.link)) { - weston_layer_entry_insert(&layer->view_list, &ev->layer_link); - weston_compositor_schedule_repaint(ev->surface->compositor); - } -} - - -static struct shell_output * -find_shell_output_from_weston_output(struct desktop_shell *shell, - struct weston_output *output) -{ - struct shell_output *shell_output; - - wl_list_for_each(shell_output, &shell->output_list, link) { - if (shell_output->output == output) - return shell_output; - } - - return NULL; -} - -static int -background_get_label(struct weston_surface *surface, char *buf, size_t len) -{ - return snprintf(buf, len, "background for output %s", - (surface->output ? surface->output->name : "NULL")); -} - -static void -background_committed(struct weston_surface *es, int32_t sx, int32_t sy) -{ - struct desktop_shell *shell = es->committed_private; - struct weston_view *view; - - view = container_of(es->views.next, struct weston_view, surface_link); - - configure_static_view(view, &shell->background_layer, 0, 0); -} - -static void -handle_background_surface_destroy(struct wl_listener *listener, void *data) -{ - struct shell_output *output = - container_of(listener, struct shell_output, background_surface_listener); - - weston_log("background surface gone\n"); - wl_list_remove(&output->background_surface_listener.link); - output->background_surface = NULL; -} - -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); - struct shell_output *sh_output; - struct weston_view *view, *next; - - if (surface->committed) { - wl_resource_post_error(surface_resource, - WL_DISPLAY_ERROR_INVALID_OBJECT, - "surface role already assigned"); - return; - } - - wl_list_for_each_safe(view, next, &surface->views, surface_link) - weston_view_destroy(view); - view = weston_view_create(surface); - - surface->committed = background_committed; - surface->committed_private = shell; - weston_surface_set_label_func(surface, background_get_label); - surface->output = weston_head_from_resource(output_resource)->output; - weston_view_set_output(view, surface->output); - - sh_output = find_shell_output_from_weston_output(shell, surface->output); - if (sh_output->background_surface) { - /* The output already has a background, tell our helper - * there is no need for another one. */ - weston_desktop_shell_send_configure(resource, 0, - surface_resource, - 0, 0); - } else { - weston_desktop_shell_send_configure(resource, 0, - surface_resource, - surface->output->width, - surface->output->height); - - sh_output->background_surface = surface; - - sh_output->background_surface_listener.notify = - handle_background_surface_destroy; - wl_signal_add(&surface->destroy_signal, - &sh_output->background_surface_listener); - } -} - -static int -panel_get_label(struct weston_surface *surface, char *buf, size_t len) -{ - return snprintf(buf, len, "panel for output %s", - (surface->output ? surface->output->name : "NULL")); -} - -static void -panel_committed(struct weston_surface *es, int32_t sx, int32_t sy) -{ - struct desktop_shell *shell = es->committed_private; - struct weston_view *view; - int width, height; - int x = 0, y = 0; - - view = container_of(es->views.next, struct weston_view, surface_link); - - get_panel_size(shell, view, &width, &height); - switch (shell->panel_position) { - case WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP: - break; - case WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM: - y = view->output->height - height; - break; - case WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT: - break; - case WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT: - x = view->output->width - width; - break; - } - - configure_static_view(view, &shell->panel_layer, x, y); -} - -static void -handle_panel_surface_destroy(struct wl_listener *listener, void *data) -{ - struct shell_output *output = - container_of(listener, struct shell_output, panel_surface_listener); - - weston_log("panel surface gone\n"); - wl_list_remove(&output->panel_surface_listener.link); - output->panel_surface = NULL; -} - - -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); - struct weston_view *view, *next; - struct shell_output *sh_output; - - if (surface->committed) { - wl_resource_post_error(surface_resource, - WL_DISPLAY_ERROR_INVALID_OBJECT, - "surface role already assigned"); - return; - } - - wl_list_for_each_safe(view, next, &surface->views, surface_link) - weston_view_destroy(view); - view = weston_view_create(surface); - - surface->committed = panel_committed; - surface->committed_private = shell; - weston_surface_set_label_func(surface, panel_get_label); - surface->output = weston_head_from_resource(output_resource)->output; - weston_view_set_output(view, surface->output); - - sh_output = find_shell_output_from_weston_output(shell, surface->output); - if (sh_output->panel_surface) { - /* The output already has a panel, tell our helper - * there is no need for another one. */ - weston_desktop_shell_send_configure(resource, 0, - surface_resource, - 0, 0); - } else { - weston_desktop_shell_send_configure(resource, 0, - surface_resource, - surface->output->width, - surface->output->height); - - sh_output->panel_surface = surface; - - sh_output->panel_surface_listener.notify = handle_panel_surface_destroy; - wl_signal_add(&surface->destroy_signal, &sh_output->panel_surface_listener); - } -} - -static int -lock_surface_get_label(struct weston_surface *surface, char *buf, size_t len) -{ - return snprintf(buf, len, "lock window"); -} - -static void -lock_surface_committed(struct weston_surface *surface, int32_t sx, int32_t sy) -{ - struct desktop_shell *shell = surface->committed_private; - struct weston_view *view; - - view = container_of(surface->views.next, struct weston_view, surface_link); - - if (surface->width == 0) - return; - - center_on_output(view, get_default_output(shell->compositor)); - - if (!weston_surface_is_mapped(surface)) { - weston_layer_entry_insert(&shell->lock_layer.view_list, - &view->layer_link); - weston_view_update_transform(view); - surface->is_mapped = true; - view->is_mapped = true; - 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); - - weston_view_create(surface); - surface->committed = lock_surface_committed; - surface->committed_private = shell; - weston_surface_set_label_func(surface, lock_surface_get_label); -} - -static void -resume_desktop(struct desktop_shell *shell) -{ - struct workspace *ws = get_current_workspace(shell); - - weston_layer_unset_position(&shell->lock_layer); - - if (shell->showing_input_panels) - weston_layer_set_position(&shell->input_panel_layer, - WESTON_LAYER_POSITION_TOP_UI); - weston_layer_set_position(&shell->fullscreen_layer, - WESTON_LAYER_POSITION_FULLSCREEN); - weston_layer_set_position(&shell->panel_layer, - WESTON_LAYER_POSITION_UI); - weston_layer_set_position(&ws->layer, WESTON_LAYER_POSITION_NORMAL); - - 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); - weston_view_create(shell->grab_surface); -} - -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 void -desktop_shell_set_panel_position(struct wl_client *client, - struct wl_resource *resource, - uint32_t position) -{ - struct desktop_shell *shell = wl_resource_get_user_data(resource); - - if (position != WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP && - position != WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM && - position != WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT && - position != WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT) { - wl_resource_post_error(resource, - WESTON_DESKTOP_SHELL_ERROR_INVALID_ARGUMENT, - "bad position argument"); - return; - } - - shell->panel_position = position; -} - -static const struct weston_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, - desktop_shell_set_panel_position -}; - -static void -move_binding(struct weston_pointer *pointer, const struct timespec *time, - uint32_t button, void *data) -{ - struct weston_surface *focus; - struct weston_surface *surface; - struct shell_surface *shsurf; - - if (pointer->focus == NULL) - return; - - focus = pointer->focus->surface; - - surface = weston_surface_get_main_surface(focus); - if (surface == NULL) - return; - - shsurf = get_shell_surface(surface); - if (shsurf == NULL || - weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || - weston_desktop_surface_get_maximized(shsurf->desktop_surface)) - return; - - surface_move(shsurf, pointer, false); -} - -static void -maximize_binding(struct weston_keyboard *keyboard, const struct timespec *time, - uint32_t button, void *data) -{ - struct weston_surface *focus = keyboard->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) - return; - - set_maximized(shsurf, !weston_desktop_surface_get_maximized(shsurf->desktop_surface)); -} - -static void -fullscreen_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t button, void *data) -{ - struct weston_surface *focus = keyboard->focus; - struct weston_surface *surface; - struct shell_surface *shsurf; - bool fullscreen; - - surface = weston_surface_get_main_surface(focus); - if (surface == NULL) - return; - - shsurf = get_shell_surface(surface); - if (shsurf == NULL) - return; - - fullscreen = - weston_desktop_surface_get_fullscreen(shsurf->desktop_surface); - - set_fullscreen(shsurf, !fullscreen, NULL); -} - -static void -touch_move_binding(struct weston_touch *touch, const struct timespec *time, void *data) -{ - struct weston_surface *focus; - struct weston_surface *surface; - struct shell_surface *shsurf; - - if (touch->focus == NULL) - return; - - focus = touch->focus->surface; - surface = weston_surface_get_main_surface(focus); - if (surface == NULL) - return; - - shsurf = get_shell_surface(surface); - if (shsurf == NULL || - weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || - weston_desktop_surface_get_maximized(shsurf->desktop_surface)) - return; - - surface_touch_move(shsurf, touch); -} - -static void -resize_binding(struct weston_pointer *pointer, const struct timespec *time, - uint32_t button, void *data) -{ - struct weston_surface *focus; - struct weston_surface *surface; - uint32_t edges = 0; - int32_t x, y; - struct shell_surface *shsurf; - - if (pointer->focus == NULL) - return; - - focus = pointer->focus->surface; - - surface = weston_surface_get_main_surface(focus); - if (surface == NULL) - return; - - shsurf = get_shell_surface(surface); - if (shsurf == NULL || - weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || - weston_desktop_surface_get_maximized(shsurf->desktop_surface)) - return; - - weston_view_from_global(shsurf->view, - wl_fixed_to_int(pointer->grab_x), - wl_fixed_to_int(pointer->grab_y), - &x, &y); - - if (x < surface->width / 3) - edges |= WL_SHELL_SURFACE_RESIZE_LEFT; - else if (x < 2 * surface->width / 3) - edges |= 0; - else - edges |= WL_SHELL_SURFACE_RESIZE_RIGHT; - - if (y < surface->height / 3) - edges |= WL_SHELL_SURFACE_RESIZE_TOP; - else if (y < 2 * surface->height / 3) - edges |= 0; - else - edges |= WL_SHELL_SURFACE_RESIZE_BOTTOM; - - surface_resize(shsurf, pointer, edges); -} - -static void -surface_opacity_binding(struct weston_pointer *pointer, - const struct timespec *time, - struct weston_pointer_axis_event *event, - void *data) -{ - float step = 0.005; - struct shell_surface *shsurf; - struct weston_surface *focus = pointer->focus->surface; - 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; - - shsurf->view->alpha -= event->value * step; - - if (shsurf->view->alpha > 1.0) - shsurf->view->alpha = 1.0; - if (shsurf->view->alpha < step) - shsurf->view->alpha = step; - - weston_view_geometry_dirty(shsurf->view); - weston_surface_damage(surface); -} - -static void -do_zoom(struct weston_seat *seat, const struct timespec *time, uint32_t key, - uint32_t axis, double value) -{ - struct weston_compositor *compositor = seat->compositor; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_output *output; - float increment; - - if (!pointer) { - weston_log("Zoom hotkey pressed but seat '%s' contains no pointer.\n", seat->seat_name); - return; - } - - wl_list_for_each(output, &compositor->output_list, link) { - if (pixman_region32_contains_point(&output->region, - wl_fixed_to_double(pointer->x), - wl_fixed_to_double(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 * - -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; - - if (!output->zoom.active) { - if (output->zoom.level <= 0.0) - continue; - weston_output_activate_zoom(output, seat); - } - - output->zoom.spring_z.target = output->zoom.level; - - weston_output_update_zoom(output); - } - } -} - -static void -zoom_axis_binding(struct weston_pointer *pointer, const struct timespec *time, - struct weston_pointer_axis_event *event, - void *data) -{ - do_zoom(pointer->seat, time, 0, event->axis, event->value); -} - -static void -zoom_key_binding(struct weston_keyboard *keyboard, const struct timespec *time, - uint32_t key, void *data) -{ - do_zoom(keyboard->seat, time, key, 0, 0); -} - -static void -terminate_binding(struct weston_keyboard *keyboard, const struct timespec *time, - uint32_t key, void *data) -{ - struct weston_compositor *compositor = data; - - weston_compositor_exit(compositor); -} - -static void -rotate_grab_motion(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_motion_event *event) -{ - 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; - - weston_pointer_move(pointer, event); - - if (!shsurf) - return; - - surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); - - cx = 0.5f * surface->width; - cy = 0.5f * surface->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_view_geometry_dirty(shsurf->view); - - 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->view->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 = shsurf->view->geometry.x + cx; - cposy = shsurf->view->geometry.y + cy; - dposx = rotate->center.x - cposx; - dposy = rotate->center.y - cposy; - if (dposx != 0.0f || dposy != 0.0f) { - weston_view_set_position(shsurf->view, - shsurf->view->geometry.x + dposx, - shsurf->view->geometry.y + dposy); - } - - /* Repaint implies weston_view_update_transform(), which - * lazily applies the damage due to rotation update. - */ - weston_compositor_schedule_repaint(surface->compositor); -} - -static void -rotate_grab_button(struct weston_pointer_grab *grab, - const struct timespec *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, - noop_grab_axis, - noop_grab_axis_source, - noop_grab_frame, - rotate_grab_cancel, -}; - -static void -surface_rotate(struct shell_surface *shsurf, struct weston_pointer *pointer) -{ - struct weston_surface *surface = - weston_desktop_surface_get_surface(shsurf->desktop_surface); - struct rotate_grab *rotate; - float dx, dy; - float r; - - rotate = malloc(sizeof *rotate); - if (!rotate) - return; - - weston_view_to_global_float(shsurf->view, - surface->width * 0.5f, - surface->height * 0.5f, - &rotate->center.x, &rotate->center.y); - - 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); - if (r > 20.0f) { - struct weston_matrix inverse; - - weston_matrix_init(&inverse); - weston_matrix_rotate_xy(&inverse, dx / r, -dy / r); - weston_matrix_multiply(&shsurf->rotation.rotation, &inverse); - - weston_matrix_init(&rotate->rotation); - weston_matrix_rotate_xy(&rotate->rotation, dx / r, dy / r); - } else { - weston_matrix_init(&shsurf->rotation.rotation); - weston_matrix_init(&rotate->rotation); - } - - shell_grab_start(&rotate->base, &rotate_grab_interface, shsurf, - pointer, WESTON_DESKTOP_SHELL_CURSOR_ARROW); -} - -static void -rotate_binding(struct weston_pointer *pointer, const struct timespec *time, - uint32_t button, void *data) -{ - struct weston_surface *focus; - struct weston_surface *base_surface; - struct shell_surface *surface; - - if (pointer->focus == NULL) - return; - - focus = pointer->focus->surface; - - base_surface = weston_surface_get_main_surface(focus); - if (base_surface == NULL) - return; - - surface = get_shell_surface(base_surface); - if (surface == NULL || - weston_desktop_surface_get_fullscreen(surface->desktop_surface) || - weston_desktop_surface_get_maximized(surface->desktop_surface)) - return; - - surface_rotate(surface, pointer); -} - -/* Move all fullscreen layers down to the current workspace and hide their - * black views. The surfaces' state is set to both fullscreen and lowered, - * and this is reversed when such a surface is re-configured, see - * shell_configure_fullscreen() and shell_ensure_fullscreen_black_view(). - * - * lowering_output = NULL - Lower on all outputs, else only lower on the - * specified output. - * - * This should be used when implementing shell-wide overlays, such as - * the alt-tab switcher, which need to de-promote fullscreen layers. */ -void -lower_fullscreen_layer(struct desktop_shell *shell, - struct weston_output *lowering_output) -{ - struct workspace *ws; - struct weston_view *view, *prev; - - ws = get_current_workspace(shell); - wl_list_for_each_reverse_safe(view, prev, - &shell->fullscreen_layer.view_list.link, - layer_link.link) { - struct shell_surface *shsurf = get_shell_surface(view->surface); - - if (!shsurf) - continue; - - /* Only lower surfaces which have lowering_output as their fullscreen - * output, unless a NULL output asks for lowering on all outputs. - */ - if (lowering_output && (shsurf->fullscreen_output != lowering_output)) - continue; - - /* We can have a non-fullscreen popup for a fullscreen surface - * in the fullscreen layer. */ - if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface)) { - /* Hide the black view */ - weston_layer_entry_remove(&shsurf->fullscreen.black_view->layer_link); - wl_list_init(&shsurf->fullscreen.black_view->layer_link.link); - weston_view_damage_below(shsurf->fullscreen.black_view); - - } - - /* Lower the view to the workspace layer */ - weston_layer_entry_remove(&view->layer_link); - weston_layer_entry_insert(&ws->layer.view_list, &view->layer_link); - weston_view_damage_below(view); - weston_surface_damage(view->surface); - - shsurf->state.lowered = true; - } -} - -static struct shell_surface *get_last_child(struct shell_surface *shsurf) -{ - struct shell_surface *shsurf_child; - - wl_list_for_each_reverse(shsurf_child, &shsurf->children_list, children_link) { - if (weston_view_is_mapped(shsurf_child->view)) - return shsurf_child; - } - - return NULL; -} - -void -activate(struct desktop_shell *shell, struct weston_view *view, - struct weston_seat *seat, uint32_t flags) -{ - struct weston_surface *es = view->surface; - struct weston_surface *main_surface; - struct focus_state *state; - struct workspace *ws; - struct weston_surface *old_es; - struct shell_surface *shsurf, *shsurf_child; - - main_surface = weston_surface_get_main_surface(es); - shsurf = get_shell_surface(main_surface); - assert(shsurf); - - shsurf_child = get_last_child(shsurf); - if (shsurf_child) { - /* Activate last xdg child instead of parent. */ - activate(shell, shsurf_child->view, seat, flags); - return; - } - - /* Only demote fullscreen surfaces on the output of activated shsurf. - * Leave fullscreen surfaces on unrelated outputs alone. */ - if (shsurf->output) - lower_fullscreen_layer(shell, shsurf->output); - - weston_view_activate(view, seat, flags); - - state = ensure_focus_state(shell, seat); - if (state == NULL) - return; - - old_es = state->keyboard_focus; - focus_state_set_focus(state, es); - - if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) && - flags & WESTON_ACTIVATE_FLAG_CONFIGURE) - shell_configure_fullscreen(shsurf); - - /* Update the surface’s layer. This brings it to the top of the stacking - * order as appropriate. */ - shell_surface_update_layer(shsurf); - - if (shell->focus_animation_type != ANIMATION_NONE) { - ws = get_current_workspace(shell); - animate_focus_change(shell, ws, get_default_view(old_es), get_default_view(es)); - } -} - -/* no-op func for checking black surface */ -static void -black_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) -{ -} - -static bool -is_black_surface_view(struct weston_view *view, struct weston_view **fs_view) -{ - struct weston_surface *surface = view->surface; - - if (surface->committed == black_surface_committed) { - if (fs_view) - *fs_view = surface->committed_private; - return true; - } - return false; -} - -static void -activate_binding(struct weston_seat *seat, - struct desktop_shell *shell, - struct weston_view *focus_view, - uint32_t flags) -{ - struct weston_view *main_view; - struct weston_surface *main_surface; - - if (!focus_view) - return; - - if (is_black_surface_view(focus_view, &main_view)) - focus_view = main_view; - - main_surface = weston_surface_get_main_surface(focus_view->surface); - if (!get_shell_surface(main_surface)) - return; - - activate(shell, focus_view, seat, flags); -} - -static void -click_to_activate_binding(struct weston_pointer *pointer, - const struct timespec *time, - uint32_t button, void *data) -{ - if (pointer->grab != &pointer->default_grab) - return; - if (pointer->focus == NULL) - return; - - activate_binding(pointer->seat, data, pointer->focus, - WESTON_ACTIVATE_FLAG_CLICKED | - WESTON_ACTIVATE_FLAG_CONFIGURE); -} - -static void -touch_to_activate_binding(struct weston_touch *touch, - const struct timespec *time, - void *data) -{ - if (touch->grab != &touch->default_grab) - return; - if (touch->focus == NULL) - return; - - activate_binding(touch->seat, data, touch->focus, - WESTON_ACTIVATE_FLAG_CONFIGURE); -} - -static void -unfocus_all_seats(struct desktop_shell *shell) -{ - struct weston_seat *seat, *next; - - wl_list_for_each_safe(seat, next, &shell->compositor->seat_list, link) { - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(seat); - - if (!keyboard) - continue; - - weston_keyboard_set_focus(keyboard, NULL); - } -} - -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. */ - - weston_layer_unset_position(&shell->panel_layer); - weston_layer_unset_position(&shell->fullscreen_layer); - if (shell->showing_input_panels) - weston_layer_unset_position(&shell->input_panel_layer); - weston_layer_unset_position(&ws->layer); - - weston_layer_set_position(&shell->lock_layer, - WESTON_LAYER_POSITION_LOCK); - - weston_compositor_sleep(shell->compositor); - - /* Remove the keyboard focus on all seats. This will be - * restored to the workspace's saved state via - * restore_focus_state when the compositor is unlocked */ - unfocus_all_seats(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) -{ - struct wl_resource *shell_resource; - - 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; - - shell_resource = shell->child.desktop_shell; - weston_desktop_shell_send_prepare_lock_surface(shell_resource); - shell->prepare_event_sent = true; -} - -static void -shell_fade_done_for_output(struct weston_view_animation *animation, void *data) -{ - struct shell_output *shell_output = data; - struct desktop_shell *shell = shell_output->shell; - - shell_output->fade.animation = NULL; - switch (shell_output->fade.type) { - case FADE_IN: - weston_surface_destroy(shell_output->fade.view->surface); - shell_output->fade.view = NULL; - break; - case FADE_OUT: - lock(shell); - break; - default: - break; - } -} - -static struct weston_view * -shell_fade_create_surface_for_output(struct desktop_shell *shell, struct shell_output *shell_output) -{ - struct weston_compositor *compositor = shell->compositor; - struct weston_surface *surface; - struct weston_view *view; - - surface = weston_surface_create(compositor); - if (!surface) - return NULL; - - view = weston_view_create(surface); - if (!view) { - weston_surface_destroy(surface); - return NULL; - } - - weston_surface_set_size(surface, shell_output->output->width, shell_output->output->height); - weston_view_set_position(view, shell_output->output->x, shell_output->output->y); - weston_surface_set_color(surface, 0.0, 0.0, 0.0, 1.0); - weston_layer_entry_insert(&compositor->fade_layer.view_list, - &view->layer_link); - pixman_region32_init(&surface->input); - surface->is_mapped = true; - view->is_mapped = true; - - return view; -} - -static void -shell_fade(struct desktop_shell *shell, enum fade_type type) -{ - float tint; - struct shell_output *shell_output; - - 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; - } - - /* Create a separate fade surface for each output */ - wl_list_for_each(shell_output, &shell->output_list, link) { - shell_output->fade.type = type; - - if (shell_output->fade.view == NULL) { - shell_output->fade.view = shell_fade_create_surface_for_output(shell, shell_output); - if (!shell_output->fade.view) - continue; - - shell_output->fade.view->alpha = 1.0 - tint; - weston_view_update_transform(shell_output->fade.view); - } - - if (shell_output->fade.view->output == NULL) { - /* If the black view gets a NULL output, we lost the - * last output and we'll just cancel the fade. This - * happens when you close the last window under the - * X11 or Wayland backends. */ - shell->locked = false; - weston_surface_destroy(shell_output->fade.view->surface); - shell_output->fade.view = NULL; - } else if (shell_output->fade.animation) { - weston_fade_update(shell_output->fade.animation, tint); - } else { - shell_output->fade.animation = - weston_fade_run(shell_output->fade.view, - 1.0 - tint, tint, 300.0, - shell_fade_done_for_output, shell_output); - } - } -} - -static void -do_shell_fade_startup(void *data) -{ - struct desktop_shell *shell = data; - struct shell_output *shell_output; - - if (shell->startup_animation_type == ANIMATION_FADE) { - shell_fade(shell, FADE_IN); - } else { - weston_log("desktop shell: " - "unexpected fade-in animation type %d\n", - shell->startup_animation_type); - wl_list_for_each(shell_output, &shell->output_list, link) { - weston_surface_destroy(shell_output->fade.view->surface); - shell_output->fade.view = NULL; - } - } -} - -static void -shell_fade_startup(struct desktop_shell *shell) -{ - struct wl_event_loop *loop; - struct shell_output *shell_output; - bool has_fade = false; - - wl_list_for_each(shell_output, &shell->output_list, link) { - if (!shell_output->fade.startup_timer) - continue; - - wl_event_source_remove(shell_output->fade.startup_timer); - shell_output->fade.startup_timer = NULL; - has_fade = true; - } - - if (has_fade) { - 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; - struct shell_output *shell_output; - - if (shell->startup_animation_type == ANIMATION_NONE) - return; - - wl_list_for_each(shell_output, &shell->output_list, link) { - if (shell_output->fade.view != NULL) { - weston_log("%s: warning: fade surface already exists\n", - __func__); - continue; - } - - shell_output->fade.view = shell_fade_create_surface_for_output(shell, shell_output); - if (!shell_output->fade.view) - continue; - - weston_view_update_transform(shell_output->fade.view); - weston_surface_damage(shell_output->fade.view->surface); - - loop = wl_display_get_event_loop(shell->compositor->wl_display); - shell_output->fade.startup_timer = - wl_event_loop_add_timer(loop, fade_startup_timeout, shell); - wl_event_source_timer_update(shell_output->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); - - struct weston_seat *seat; - - wl_list_for_each(seat, &shell->compositor->seat_list, link) - weston_seat_break_desktop_grabs(seat); - - shell_fade(shell, FADE_OUT); - /* lock() is called from shell_fade_done_for_output() */ -} - -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 -transform_handler(struct wl_listener *listener, void *data) -{ - struct weston_surface *surface = data; - struct shell_surface *shsurf = get_shell_surface(surface); - const struct weston_xwayland_surface_api *api; - int x, y; - - if (!shsurf) - return; - - api = shsurf->shell->xwayland_surface_api; - if (!api) { - api = weston_xwayland_surface_get_api(shsurf->shell->compositor); - shsurf->shell->xwayland_surface_api = api; - } - - if (!api || !api->is_xwayland_surface(surface)) - return; - - if (!weston_view_is_mapped(shsurf->view)) - return; - - x = shsurf->view->geometry.x; - y = shsurf->view->geometry.y; - - api->send_position(surface, x, y); -} - -static void -center_on_output(struct weston_view *view, struct weston_output *output) -{ - int32_t surf_x, surf_y, width, height; - float x, y; - - if (!output) { - weston_view_set_position(view, 0, 0); - return; - } - - surface_subsurfaces_boundingbox(view->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_view_set_position(view, x, y); -} - -static void -weston_view_set_initial_position(struct weston_view *view, - struct desktop_shell *shell) -{ - struct weston_compositor *compositor = shell->compositor; - int ix = 0, iy = 0; - int32_t range_x, range_y; - int32_t x, y; - struct weston_output *output, *target_output = NULL; - struct weston_seat *seat; - pixman_rectangle32_t area; - - /* 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) { - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - if (pointer) { - ix = wl_fixed_to_int(pointer->x); - iy = wl_fixed_to_int(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_view_set_position(view, 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. - */ - get_output_work_area(shell, target_output, &area); - - x = area.x; - y = area.y; - range_x = area.width - view->surface->width; - range_y = area.height - view->surface->height; - - if (range_x > 0) - x += random() % range_x; - - if (range_y > 0) - y += random() % range_y; - - weston_view_set_position(view, x, y); -} - -static bool -check_desktop_shell_crash_too_early(struct desktop_shell *shell) -{ - struct timespec now; - - if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) - return false; - - /* - * If the shell helper client dies before the session has been - * up for roughly 30 seconds, better just make Weston shut down, - * because the user likely has no way to interact with the desktop - * anyway. - */ - if (now.tv_sec - shell->startup_time.tv_sec < 30) { - weston_log("Error: %s apparently cannot run at all.\n", - shell->client); - weston_log_continue(STAMP_SPACE "Quitting..."); - weston_compositor_exit_with_code(shell->compositor, - EXIT_FAILURE); - - return true; - } - - return false; -} - -static void launch_desktop_shell_process(void *data); - -static void -respawn_desktop_shell_process(struct desktop_shell *shell) -{ - struct timespec time; - - /* if desktop-shell dies more than 5 times in 30 seconds, give up */ - weston_compositor_get_time(&time); - if (timespec_sub_to_msec(&time, &shell->child.deathstamp) > 30000) { - shell->child.deathstamp = time; - shell->child.deathcount = 0; - } - - shell->child.deathcount++; - if (shell->child.deathcount > 5) { - weston_log("%s disconnected, giving up.\n", shell->client); - return; - } - - weston_log("%s disconnected, respawning...\n", shell->client); - launch_desktop_shell_process(shell); -} - -static void -desktop_shell_client_destroy(struct wl_listener *listener, void *data) -{ - struct desktop_shell *shell; - - shell = container_of(listener, struct desktop_shell, - child.client_destroy_listener); - - wl_list_remove(&shell->child.client_destroy_listener.link); - shell->child.client = NULL; - /* - * unbind_desktop_shell() will reset shell->child.desktop_shell - * before the respawned process has a chance to create a new - * desktop_shell object, because we are being called from the - * wl_client destructor which destroys all wl_resources before - * returning. - */ - - if (!check_desktop_shell_crash_too_early(shell)) - respawn_desktop_shell_process(shell); - - shell_fade_startup(shell); -} - -static void -launch_desktop_shell_process(void *data) -{ - struct desktop_shell *shell = data; - - shell->child.client = weston_client_start(shell->compositor, - shell->client); - - if (!shell->child.client) { - weston_log("not able to start %s\n", shell->client); - return; - } - - shell->child.client_destroy_listener.notify = - desktop_shell_client_destroy; - wl_client_add_destroy_listener(shell->child.client, - &shell->child.client_destroy_listener); -} - -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, &weston_desktop_shell_interface, - 1, id); - - if (client == shell->child.client) { - wl_resource_set_implementation(resource, - &desktop_shell_implementation, - shell, unbind_desktop_shell); - shell->child.desktop_shell = resource; - return; - } - - wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, - "permission to bind desktop_shell denied"); -} - -struct switcher { - struct desktop_shell *shell; - struct weston_view *current; - struct wl_listener listener; - struct weston_keyboard_grab grab; - struct wl_array minimized_array; -}; - -static void -switcher_next(struct switcher *switcher) -{ - struct weston_view *view; - struct weston_view *first = NULL, *prev = NULL, *next = NULL; - struct shell_surface *shsurf; - struct workspace *ws = get_current_workspace(switcher->shell); - - /* temporary re-display minimized surfaces */ - struct weston_view *tmp; - struct weston_view **minimized; - wl_list_for_each_safe(view, tmp, &switcher->shell->minimized_layer.view_list.link, layer_link.link) { - weston_layer_entry_remove(&view->layer_link); - weston_layer_entry_insert(&ws->layer.view_list, &view->layer_link); - minimized = wl_array_add(&switcher->minimized_array, sizeof *minimized); - *minimized = view; - } - - wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { - shsurf = get_shell_surface(view->surface); - if (shsurf) { - if (first == NULL) - first = view; - if (prev == switcher->current) - next = view; - prev = view; - view->alpha = 0.25; - weston_view_geometry_dirty(view); - weston_surface_damage(view->surface); - } - - if (is_black_surface_view(view, NULL)) { - view->alpha = 0.25; - weston_view_geometry_dirty(view); - weston_surface_damage(view->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; - wl_list_for_each(view, &next->surface->views, surface_link) - view->alpha = 1.0; - - shsurf = get_shell_surface(switcher->current->surface); - if (shsurf && weston_desktop_surface_get_fullscreen(shsurf->desktop_surface)) - shsurf->fullscreen.black_view->alpha = 1.0; -} - -static void -switcher_handle_view_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_view *view; - struct weston_keyboard *keyboard = switcher->grab.keyboard; - struct workspace *ws = get_current_workspace(switcher->shell); - - wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { - if (is_focus_view(view)) - continue; - - view->alpha = 1.0; - weston_surface_damage(view->surface); - } - - if (switcher->current) { - activate(switcher->shell, switcher->current, - keyboard->seat, - WESTON_ACTIVATE_FLAG_CONFIGURE); - } - - wl_list_remove(&switcher->listener.link); - weston_keyboard_end_grab(keyboard); - if (keyboard->input_method_resource) - keyboard->grab = &keyboard->input_method_grab; - - /* re-hide surfaces that were temporary shown during the switch */ - struct weston_view **minimized; - wl_array_for_each(minimized, &switcher->minimized_array) { - /* with the exception of the current selected */ - if ((*minimized)->surface != switcher->current->surface) { - weston_layer_entry_remove(&(*minimized)->layer_link); - weston_layer_entry_insert(&switcher->shell->minimized_layer.view_list, &(*minimized)->layer_link); - weston_view_damage_below(*minimized); - } - } - wl_array_release(&switcher->minimized_array); - - free(switcher); -} - -static void -switcher_key(struct weston_keyboard_grab *grab, - const struct timespec *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 = 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_keyboard *keyboard, const struct timespec *time, - uint32_t key, void *data) -{ - struct desktop_shell *shell = data; - struct switcher *switcher; - - switcher = malloc(sizeof *switcher); - if (!switcher) - return; - - switcher->shell = shell; - switcher->current = NULL; - switcher->listener.notify = switcher_handle_view_destroy; - wl_list_init(&switcher->listener.link); - wl_array_init(&switcher->minimized_array); - - lower_fullscreen_layer(switcher->shell, NULL); - switcher->grab.interface = &switcher_grab; - weston_keyboard_start_grab(keyboard, &switcher->grab); - weston_keyboard_set_focus(keyboard, NULL); - switcher_next(switcher); -} - -static void -backlight_binding(struct weston_keyboard *keyboard, const struct timespec *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); -} - -static void -force_kill_binding(struct weston_keyboard *keyboard, - const struct timespec *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 = 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_keyboard *keyboard, - const struct timespec *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_keyboard *keyboard, - const struct timespec *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_keyboard *keyboard, - const struct timespec *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_keyboard *keyboard, - const struct timespec *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, keyboard->seat, new_index); -} - -static void -workspace_move_surface_down_binding(struct weston_keyboard *keyboard, - const struct timespec *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, keyboard->seat, new_index); -} - -static void -shell_reposition_view_on_output_change(struct weston_view *view) -{ - struct weston_output *output, *first_output; - struct weston_compositor *ec = view->surface->compositor; - struct shell_surface *shsurf; - float x, y; - int visible; - - if (wl_list_empty(&ec->output_list)) - return; - - x = view->geometry.x; - y = view->geometry.y; - - /* At this point the destroyed output is not in the list anymore. - * If the view is still visible somewhere, we leave where it is, - * otherwise, move it to the first output. */ - visible = 0; - wl_list_for_each(output, &ec->output_list, link) { - if (pixman_region32_contains_point(&output->region, - x, y, NULL)) { - visible = 1; - break; - } - } - - if (!visible) { - first_output = container_of(ec->output_list.next, - struct weston_output, link); - - x = first_output->x + first_output->width / 4; - y = first_output->y + first_output->height / 4; - - weston_view_set_position(view, x, y); - } else { - weston_view_geometry_dirty(view); - } - - - shsurf = get_shell_surface(view->surface); - if (!shsurf) - return; - - shsurf->saved_position_valid = false; - set_maximized(shsurf, false); - set_fullscreen(shsurf, false, NULL); -} - -void -shell_for_each_layer(struct desktop_shell *shell, - shell_for_each_layer_func_t func, void *data) -{ - struct workspace **ws; - - func(shell, &shell->fullscreen_layer, data); - func(shell, &shell->panel_layer, data); - func(shell, &shell->background_layer, data); - func(shell, &shell->lock_layer, data); - func(shell, &shell->input_panel_layer, data); - - wl_array_for_each(ws, &shell->workspaces.array) - func(shell, &(*ws)->layer, data); -} - -static void -shell_output_changed_move_layer(struct desktop_shell *shell, - struct weston_layer *layer, - void *data) -{ - struct weston_view *view; - - wl_list_for_each(view, &layer->view_list.link, layer_link.link) - shell_reposition_view_on_output_change(view); - -} - -static void -handle_output_destroy(struct wl_listener *listener, void *data) -{ - struct shell_output *output_listener = - container_of(listener, struct shell_output, destroy_listener); - struct desktop_shell *shell = output_listener->shell; - - shell_for_each_layer(shell, shell_output_changed_move_layer, NULL); - - if (output_listener->panel_surface) - wl_list_remove(&output_listener->panel_surface_listener.link); - if (output_listener->background_surface) - wl_list_remove(&output_listener->background_surface_listener.link); - wl_list_remove(&output_listener->destroy_listener.link); - wl_list_remove(&output_listener->link); - free(output_listener); -} - -static void -shell_resize_surface_to_output(struct desktop_shell *shell, - struct weston_surface *surface, - const struct weston_output *output) -{ - if (!surface) - return; - - weston_desktop_shell_send_configure(shell->child.desktop_shell, 0, - surface->resource, - output->width, - output->height); -} - - -static void -handle_output_resized(struct wl_listener *listener, void *data) -{ - struct desktop_shell *shell = - container_of(listener, struct desktop_shell, resized_listener); - struct weston_output *output = (struct weston_output *)data; - struct shell_output *sh_output = find_shell_output_from_weston_output(shell, output); - - shell_resize_surface_to_output(shell, sh_output->background_surface, output); - shell_resize_surface_to_output(shell, sh_output->panel_surface, output); -} - -static void -create_shell_output(struct desktop_shell *shell, - struct weston_output *output) -{ - struct shell_output *shell_output; - - shell_output = zalloc(sizeof *shell_output); - if (shell_output == NULL) - return; - - shell_output->output = output; - shell_output->shell = shell; - shell_output->destroy_listener.notify = handle_output_destroy; - wl_signal_add(&output->destroy_signal, - &shell_output->destroy_listener); - wl_list_insert(shell->output_list.prev, &shell_output->link); - - if (wl_list_length(&shell->output_list) == 1) - shell_for_each_layer(shell, - shell_output_changed_move_layer, NULL); -} - -static void -handle_output_create(struct wl_listener *listener, void *data) -{ - struct desktop_shell *shell = - container_of(listener, struct desktop_shell, output_create_listener); - struct weston_output *output = (struct weston_output *)data; - - create_shell_output(shell, output); -} - -static void -handle_output_move_layer(struct desktop_shell *shell, - struct weston_layer *layer, void *data) -{ - struct weston_output *output = data; - struct weston_view *view; - float x, y; - - wl_list_for_each(view, &layer->view_list.link, layer_link.link) { - if (view->output != output) - continue; - - x = view->geometry.x + output->move_x; - y = view->geometry.y + output->move_y; - weston_view_set_position(view, x, y); - } -} - -static void -handle_output_move(struct wl_listener *listener, void *data) -{ - struct desktop_shell *shell; - - shell = container_of(listener, struct desktop_shell, - output_move_listener); - - shell_for_each_layer(shell, handle_output_move_layer, data); -} - -static void -setup_output_destroy_handler(struct weston_compositor *ec, - struct desktop_shell *shell) -{ - struct weston_output *output; - - wl_list_init(&shell->output_list); - wl_list_for_each(output, &ec->output_list, link) - create_shell_output(shell, output); - - shell->output_create_listener.notify = handle_output_create; - wl_signal_add(&ec->output_created_signal, - &shell->output_create_listener); - - shell->output_move_listener.notify = handle_output_move; - wl_signal_add(&ec->output_moved_signal, &shell->output_move_listener); -} - -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; - struct shell_output *shell_output, *tmp; - - /* Force state to unlocked so we don't try to fade */ - shell->locked = false; - - if (shell->child.client) { - /* disable respawn */ - wl_list_remove(&shell->child.client_destroy_listener.link); - wl_client_destroy(shell->child.client); - } - - wl_list_remove(&shell->destroy_listener.link); - wl_list_remove(&shell->idle_listener.link); - wl_list_remove(&shell->wake_listener.link); - wl_list_remove(&shell->transform_listener.link); - -// OHOS remove text_backend -// text_backend_destroy(shell->text_backend); - input_panel_destroy(shell); - - wl_list_for_each_safe(shell_output, tmp, &shell->output_list, link) { - wl_list_remove(&shell_output->destroy_listener.link); - wl_list_remove(&shell_output->link); - free(shell_output); - } - - wl_list_remove(&shell->output_create_listener.link); - wl_list_remove(&shell->output_move_listener.link); - wl_list_remove(&shell->resized_listener.link); - - wl_array_for_each(ws, &shell->workspaces.array) - workspace_destroy(*ws); - wl_array_release(&shell->workspaces.array); - - free(shell->client); - free(shell); -} - -static void -shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) -{ - uint32_t mod; - int i, num_workspace_bindings; - - if (shell->allow_zap) - weston_compositor_add_key_binding(ec, KEY_BACKSPACE, - MODIFIER_CTRL | MODIFIER_ALT, - terminate_binding, ec); - - /* fixed bindings */ - weston_compositor_add_button_binding(ec, BTN_LEFT, 0, - click_to_activate_binding, - shell); - weston_compositor_add_button_binding(ec, BTN_RIGHT, 0, - click_to_activate_binding, - shell); - weston_compositor_add_touch_binding(ec, 0, - touch_to_activate_binding, - shell); - weston_compositor_add_key_binding(ec, KEY_BRIGHTNESSDOWN, 0, - backlight_binding, ec); - weston_compositor_add_key_binding(ec, KEY_BRIGHTNESSUP, 0, - backlight_binding, ec); - - /* configurable bindings */ - if (shell->exposay_modifier) - weston_compositor_add_modifier_binding(ec, shell->exposay_modifier, - exposay_binding, shell); - - mod = shell->binding_modifier; - if (!mod) - return; - - /* This binding is not configurable, but is only enabled if there is a - * valid binding modifier. */ - 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, - mod, zoom_axis_binding, - NULL); - - 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_key_binding(ec, KEY_M, mod | MODIFIER_SHIFT, - maximize_binding, NULL); - weston_compositor_add_key_binding(ec, KEY_F, mod | MODIFIER_SHIFT, - fullscreen_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_RIGHT, mod, - resize_binding, shell); - weston_compositor_add_button_binding(ec, BTN_LEFT, - mod | MODIFIER_SHIFT, - resize_binding, shell); - - if (ec->capabilities & WESTON_CAP_ROTATION_ANY) - weston_compositor_add_button_binding(ec, BTN_MIDDLE, 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_F10, mod, 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); - } - - weston_install_debug_key_binding(ec, mod); -} - -static void -handle_seat_created(struct wl_listener *listener, void *data) -{ - struct weston_seat *seat = data; - - create_shell_seat(seat); -} - -WL_EXPORT int -wet_shell_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; - - if (!weston_compositor_add_destroy_listener_once(ec, - &shell->destroy_listener, - shell_destroy)) { - free(shell); - return 0; - } - - 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->transform_listener.notify = transform_handler; - wl_signal_add(&ec->transform_signal, &shell->transform_listener); - - weston_layer_init(&shell->fullscreen_layer, ec); - weston_layer_init(&shell->panel_layer, ec); - weston_layer_init(&shell->background_layer, ec); - weston_layer_init(&shell->lock_layer, ec); - weston_layer_init(&shell->input_panel_layer, ec); - - weston_layer_set_position(&shell->fullscreen_layer, - WESTON_LAYER_POSITION_FULLSCREEN); - weston_layer_set_position(&shell->panel_layer, - WESTON_LAYER_POSITION_UI); - weston_layer_set_position(&shell->background_layer, - WESTON_LAYER_POSITION_BACKGROUND); - - wl_array_init(&shell->workspaces.array); - wl_list_init(&shell->workspaces.client_list); - - if (input_panel_setup(shell) < 0) - return -1; - -// OHOS remove text_backend -// shell->text_backend = text_backend_init(ec); -// if (!shell->text_backend) -// return -1; - - shell_configuration(shell); - - shell->exposay.state_cur = EXPOSAY_LAYOUT_INACTIVE; - shell->exposay.state_target = EXPOSAY_TARGET_CANCEL; - - 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(shell); - if (*pws == NULL) - return -1; - } - activate_workspace(shell, 0); - - weston_layer_init(&shell->minimized_layer, ec); - - wl_list_init(&shell->workspaces.anim_sticky_list); - wl_list_init(&shell->workspaces.animation.link); - shell->workspaces.animation.frame = animate_workspace_change_frame; - - shell->desktop = weston_desktop_create(ec, &shell_desktop_api, shell); - if (!shell->desktop) - return -1; - - if (wl_global_create(ec->wl_display, - &weston_desktop_shell_interface, 1, - shell, bind_desktop_shell) == NULL) - return -1; - - weston_compositor_get_time(&shell->child.deathstamp); - - shell->panel_position = WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP; - - setup_output_destroy_handler(ec, shell); - - loop = wl_display_get_event_loop(ec->wl_display); -// OHOS -// wl_event_loop_add_idle(loop, launch_desktop_shell_process, shell); - - wl_list_for_each(seat, &ec->seat_list, link) - handle_seat_created(NULL, seat); - shell->seat_create_listener.notify = handle_seat_created; - wl_signal_add(&ec->seat_created_signal, &shell->seat_create_listener); - - shell->resized_listener.notify = handle_output_resized; - wl_signal_add(&ec->output_resized_signal, &shell->resized_listener); - - screenshooter_create(ec); - - shell_add_bindings(ec, shell); - - shell_fade_init(shell); - - clock_gettime(CLOCK_MONOTONIC, &shell->startup_time); - - return 0; -} diff --git a/desktop-shell/shell.h b/desktop-shell/shell.h deleted file mode 100644 index 9f0b6ab..0000000 --- a/desktop-shell/shell.h +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright © 2010-2012 Intel Corporation - * Copyright © 2011-2012 Collabora, Ltd. - * Copyright © 2013 Raspberry Pi Foundation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include - -#include -#include - -#include "weston-desktop-shell-server-protocol.h" - -enum animation_type { - ANIMATION_NONE, - - ANIMATION_ZOOM, - ANIMATION_FADE, - ANIMATION_DIM_LAYER, -}; - -enum fade_type { - FADE_IN, - FADE_OUT -}; - -enum exposay_target_state { - EXPOSAY_TARGET_OVERVIEW, /* show all windows */ - EXPOSAY_TARGET_CANCEL, /* return to normal, same focus */ - EXPOSAY_TARGET_SWITCH, /* return to normal, switch focus */ -}; - -enum exposay_layout_state { - EXPOSAY_LAYOUT_INACTIVE = 0, /* normal desktop */ - EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE, /* in transition to normal */ - EXPOSAY_LAYOUT_OVERVIEW, /* show all windows */ - EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW, /* in transition to all windows */ -}; - -struct exposay_output { - int num_surfaces; - int grid_size; - int surface_size; - int padding_inner; -}; - -struct exposay { - /* XXX: Make these exposay_surfaces. */ - struct weston_view *focus_prev; - struct weston_view *focus_current; - struct weston_view *clicked; - struct workspace *workspace; - struct weston_seat *seat; - - struct wl_list surface_list; - - struct weston_keyboard_grab grab_kbd; - struct weston_pointer_grab grab_ptr; - - enum exposay_target_state state_target; - enum exposay_layout_state state_cur; - int in_flight; /* number of animations still running */ - - int row_current; - int column_current; - struct exposay_output *cur_output; - - bool mod_pressed; - bool mod_invalid; -}; - -struct focus_surface { - struct weston_surface *surface; - struct weston_view *view; - struct weston_transform workspace_transform; -}; - -struct workspace { - struct weston_layer layer; - - struct wl_list focus_list; - struct wl_listener seat_destroyed_listener; - - struct focus_surface *fsurf_front; - struct focus_surface *fsurf_back; - struct weston_view_animation *focus_animation; -}; - -struct shell_output { - struct desktop_shell *shell; - struct weston_output *output; - struct exposay_output eoutput; - struct wl_listener destroy_listener; - struct wl_list link; - - struct weston_surface *panel_surface; - struct wl_listener panel_surface_listener; - - struct weston_surface *background_surface; - struct wl_listener background_surface_listener; - - struct { - struct weston_view *view; - struct weston_view_animation *animation; - enum fade_type type; - struct wl_event_source *startup_timer; - } fade; -}; - -struct weston_desktop; -struct desktop_shell { - struct weston_compositor *compositor; - struct weston_desktop *desktop; - const struct weston_xwayland_surface_api *xwayland_surface_api; - - struct wl_listener idle_listener; - struct wl_listener wake_listener; - struct wl_listener transform_listener; - struct wl_listener resized_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 wl_client *client; - struct wl_resource *desktop_shell; - struct wl_listener client_destroy_listener; - - unsigned deathcount; - struct timespec deathstamp; - } child; - - bool locked; - bool showing_input_panels; - bool prepare_event_sent; - - struct text_backend *text_backend; - - 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; - struct timespec anim_timestamp; - double anim_current; - struct workspace *anim_from; - struct workspace *anim_to; - } workspaces; - - struct { - struct wl_resource *binding; - struct wl_list surfaces; - } input_panel; - - struct exposay exposay; - - bool allow_zap; - uint32_t binding_modifier; - uint32_t exposay_modifier; - enum animation_type win_animation_type; - enum animation_type win_close_animation_type; - enum animation_type startup_animation_type; - enum animation_type focus_animation_type; - - struct weston_layer minimized_layer; - - struct wl_listener seat_create_listener; - struct wl_listener output_create_listener; - struct wl_listener output_move_listener; - struct wl_list output_list; - - enum weston_desktop_shell_panel_position panel_position; - - char *client; - - struct timespec startup_time; -}; - -struct weston_output * -get_default_output(struct weston_compositor *compositor); - -struct weston_view * -get_default_view(struct weston_surface *surface); - -struct shell_surface * -get_shell_surface(struct weston_surface *surface); - -struct workspace * -get_current_workspace(struct desktop_shell *shell); - -void -get_output_work_area(struct desktop_shell *shell, - struct weston_output *output, - pixman_rectangle32_t *area); - -void -lower_fullscreen_layer(struct desktop_shell *shell, - struct weston_output *lowering_output); - -void -activate(struct desktop_shell *shell, struct weston_view *view, - struct weston_seat *seat, uint32_t flags); - -void -exposay_binding(struct weston_keyboard *keyboard, - enum weston_keyboard_modifier modifier, - void *data); -int -input_panel_setup(struct desktop_shell *shell); -void -input_panel_destroy(struct desktop_shell *shell); - -typedef void (*shell_for_each_layer_func_t)(struct desktop_shell *, - struct weston_layer *, void *); - -void -shell_for_each_layer(struct desktop_shell *shell, - shell_for_each_layer_func_t func, - void *data); diff --git a/doc/doxygen/devtools.dox b/doc/doxygen/devtools.dox deleted file mode 100644 index 2d6672f..0000000 --- a/doc/doxygen/devtools.dox +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/** -@mainpage - -- @ref zunitc - Simple test framework - -@section tools_overview Overview - -The tools area currently consists of one sub-project (@ref zunitc) that is -refined from the prior single weston/tests source folder. - -@subsection tools_overview_old Old Code Organization - -The original 'tests' folder contained basic weston testing with an -integrated test runner framework. Over time things progressed to the -stage where splitting apart into discrete layers was warranted. - -@dotfile tools_arch_old.gv "Original test code organization" - -@subsection tools_overview_new New Code Organization - -The test code that is not weston-specific gets split out to a separate -folder and/or folders. - -@dotfile tools_arch_new.gv "Refactored test code organization" - -*/ diff --git a/doc/doxygen/tooldev.doxygen.in b/doc/doxygen/tooldev.doxygen.in deleted file mode 100644 index b3d86f5..0000000 --- a/doc/doxygen/tooldev.doxygen.in +++ /dev/null @@ -1,12 +0,0 @@ -PROJECT_NAME = "Tool Internals" -OUTPUT_DIRECTORY = @top_builddir@/docs/developer -JAVADOC_AUTOBRIEF = YES -OPTIMIZE_OUTPUT_FOR_C = YES -EXTRACT_ALL = YES -INPUT = \ - @top_srcdir@/doc/doxygen/devtools.dox \ - @top_srcdir@/tools/zunitc -RECURSIVE = YES -GENERATE_LATEX = NO -DOTFILE_DIRS = @top_srcdir@/doc/doxygen -STRIP_FROM_PATH = @top_srcdir@ diff --git a/doc/doxygen/tools.dox b/doc/doxygen/tools.dox deleted file mode 100644 index 9bbc11d..0000000 --- a/doc/doxygen/tools.dox +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/** -@mainpage - -- @ref zunitc - Simple test framework - -*/ diff --git a/doc/doxygen/tools.doxygen.in b/doc/doxygen/tools.doxygen.in deleted file mode 100644 index 613edd4..0000000 --- a/doc/doxygen/tools.doxygen.in +++ /dev/null @@ -1,11 +0,0 @@ -PROJECT_NAME = "Tools" -OUTPUT_DIRECTORY = @top_builddir@/docs/tools -JAVADOC_AUTOBRIEF = YES -OPTIMIZE_OUTPUT_FOR_C = YES -INPUT = \ - @top_srcdir@/doc/doxygen/tools.dox \ - @top_srcdir@/tools/zunitc/doc/zunitc.dox \ - @top_srcdir@/tools/zunitc/inc/zunitc/zunitc.h -GENERATE_LATEX = NO -DOTFILE_DIRS = @top_srcdir@/doc/doxygen -STRIP_FROM_PATH = @top_srcdir@ diff --git a/doc/doxygen/tools_arch_new.gv b/doc/doxygen/tools_arch_new.gv deleted file mode 100644 index e4626f3..0000000 --- a/doc/doxygen/tools_arch_new.gv +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -digraph toolarch_new { - rankdir = "TB"; - - node[shape=record] - - subgraph cluster_0 { - label = "./tests"; - - keyboard_test_c [label = "{keyboard-test.c|tests\l}"] - text_test_c [label = "{text-test.c|tests\l}"] - vertex_clip_test_c [label = "{vertex-clip-test.c|tests\l}"] - - spacer [shape = point, style = invis] - - weston_test_client_helper [label = "{weston-test-client-helper.h/.c|Weston test protocol\l}"] - - weston_test_c [label = "{weston-test.c|Extension protocol\nimplementation}"] - } - - subgraph cluster_1 { - label = "./tools/waycheck"; - - waycheck [label = "{waycheck.c| \n \n }"] - } - - subgraph cluster_2 { - label = "./tools/wayland_fixtures"; - - wtst_fixtures [label = "{wtst_fixtures.h/c|Wayland tracking structs\lWayland callbacks\l}"] - } - - subgraph cluster_3 { - label = "./tools/zunitc"; - - zunitc [label = "{zunitc|Test definition macros\lTest running functions\lTest reporting functions\lTest run lifecycle\l}"] - } - - keyboard_test_c -> weston_test_client_helper - keyboard_test_c -> wtst_fixtures - keyboard_test_c -> zunitc - vertex_clip_test_c -> zunitc - text_test_c -> weston_test_client_helper - text_test_c -> wtst_fixtures - text_test_c -> zunitc - - waycheck -> wtst_fixtures - waycheck -> zunitc - - wtst_fixtures -> zunitc - - edge [style = dashed, arrowhead = open] - weston_test_client_helper -> weston_test_c - - edge [style = invis] - weston_test_client_helper -> zunitc - - text_test_c -> spacer - keyboard_test_c -> spacer - spacer -> weston_test_client_helper -} diff --git a/doc/doxygen/tools_arch_old.gv b/doc/doxygen/tools_arch_old.gv deleted file mode 100644 index 1b38123..0000000 --- a/doc/doxygen/tools_arch_old.gv +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -digraph toolarch_old { - rankdir = "TB"; - - node[shape = record] - - subgraph cluster_0 { - label = "./tests"; - - keyboard_test_c [label = "{keyboard-test.c|tests\l}"] - text_test_c [label = "{text-test.c|tests\l}"] - vertex_clip_test_c [label = "{vertex-clip-test.c|tests\l}"] - - weston_test_client_helper [label = "{weston-test-client-helper.h/.c|Wayland tracking structs\lWeston test protocol\lWayland callbacks\lTest run lifecycle\l}"] - - weston_test_c [label = "{weston-test.c|Extension protocol\nimplementation}"] - weston_test_runner [label = "{weston-test-runner.h/.c|Test definition macros\lTest running functions\lTest reporting functions\lTest run lifecycle\l}"] - } - - weston_test_client_helper -> weston_test_runner - keyboard_test_c -> weston_test_client_helper - keyboard_test_c -> weston_test_runner - vertex_clip_test_c -> weston_test_runner - text_test_c -> weston_test_client_helper - text_test_c -> weston_test_runner - - edge [style = dashed, arrowhead = open] - weston_test_client_helper -> weston_test_c -} diff --git a/doc/scripts/calibration-helper.bash b/doc/scripts/calibration-helper.bash deleted file mode 100755 index 72effe3..0000000 --- a/doc/scripts/calibration-helper.bash +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash - -# Copyright 2018 Collabora, Ltd. -# Copyright 2018 General Electric Company -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice (including the -# next paragraph) shall be included in all copies or substantial -# portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -# This is an example script working as Weston's calibration helper. -# Its purpose is to permanently store the calibration matrix for the given -# touchscreen input device into a udev property. Since this script naturally -# runs as the user that runs Weston, it presumably cannot write directly into -# /etc. It is left for the administrator to set up appropriate files and -# permissions. - -# To use this script, one needs to edit weston.ini, in section [libinput], add: -# calibration_helper=/path/to/bin/calibration-helper.bash - -# exit immediately if any command fails -set -e - -# The arguments Weston gives us: -SYSPATH="$1" -MATRIX="$2 $3 $4 $5 $6 $7" - -# Pick something to recognize the right touch device with. -# Usually one would use something like a serial. -SERIAL=$(udevadm info "$SYSPATH" --query=property | \ - awk -- 'BEGIN { FS="=" } { if ($1 == "ID_SERIAL") { print $2; exit } }') - -# If cannot find a serial, tell the server to not use the new calibration. -[ -z "$SERIAL" ] && exit 1 - -# You'd have this write a file instead. -echo "ACTION==\"add|change\",SUBSYSTEM==\"input\",ENV{ID_SERIAL}==\"$SERIAL\",ENV{LIBINPUT_CALIBRATION_MATRIX}=\"$MATRIX\"" - -# Then you'd tell udev to reload the rules: -#udevadm control --reload -# This lets Weston get the new calibration if you unplug and replug the input -# device. Instead of writing a udev rule directly, you could have a udev rule -# with IMPORT{file}="/path/to/calibration", write -# "LIBINPUT_CALIBRATION_MATRIX=\"$MATRIX\"" into /path/to/calibration instead, -# and skip this reload step. - -# Make udev process the new rule by triggering a "change" event: -#udevadm trigger "$SYSPATH" -# If you were to restart Weston without rebooting, this lets it pick up the new -# calibration. diff --git a/doc/scripts/gdb/flight_rec.py b/doc/scripts/gdb/flight_rec.py deleted file mode 100755 index 9872f17..0000000 --- a/doc/scripts/gdb/flight_rec.py +++ /dev/null @@ -1,103 +0,0 @@ -# -# Copyright © 2019 Collabora Ltd. -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice (including the -# next paragraph) shall be included in all copies or substantial -# portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# Usage: source this script then 'display_flight_rec' -# - -import gdb - -class DisplayFlightRecorder(gdb.Command): - def __init__(self): - - self.rb = '' - symbol_found = False - - ring_buff = gdb.lookup_global_symbol("weston_primary_flight_recorder_ring_buffer") - if ring_buff == None: - print("'weston_ring_buffer' symbol not found!") - print("Either weston is too old or weston hasn't been loaded in memory") - else: - self.rb = ring_buff - self.display_rb_data(self.rb) - symbol_found = True - - if symbol_found: - super(DisplayFlightRecorder, self).__init__("display_flight_rec", - gdb.COMMAND_DATA) - - def display_rb_data(self, rb): - print("Flight recorder data found. Use 'display_flight_rec' " - "to display its contents") - # display this data (only) if symbol is not empty (happens if the program is not ran at all) - if rb.value(): - print("Data at byte {append}, Size: {size}B, " - "Overlaped: {overlap}".format(append=rb.value()['append_pos'], - size=rb.value()['size'], - overlap=rb.value()['overlap'])) - - # poor's man fwrite() - def gen_contents(self, start, stop): - _str = '' - for j in range(start, stop): - _str += chr(self.rb.value()['buf'][j]) - return _str - - # mirrors C version, as to make sure we're not reading other parts... - def display_flight_rec_contents(self): - - # symbol is there but not loaded, we're not far enough - if self.rb.value() == 0x0: - print("Flight recorder found, but not loaded yet!") - return - else: - print("Displaying flight recorder contents:") - - append_pos = self.rb.value()['append_pos'] - size = self.rb.value()['size'] - overlap = self.rb.value()['overlap'] - - # if we haven't overflown and we're still at 0 means - # we still aren't far enough to be populated - if append_pos == 0 and not overlap: - print("Flight recorder doesn't have anything to display right now") - return - - # now we can print stuff - rb_data = '' - if not overlap: - if append_pos: - rb_data = self.gen_contents(0, append_pos) - else: - rb_data = self.gen_contents(0, size) - else: - rb_data = self.gen_contents(append_pos, size) - rb_data += self.gen_contents(0, append_pos) - - print("{data}".format(data=rb_data)) - - # called when invoking 'display_flight_rec' - def invoke(self, arg, from_tty): - self.display_flight_rec_contents() - -DisplayFlightRecorder() diff --git a/doc/scripts/remoting-client-receive.bash b/doc/scripts/remoting-client-receive.bash deleted file mode 100755 index b518689..0000000 --- a/doc/scripts/remoting-client-receive.bash +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -# Copyright © 2018 Renesas Electronics Corp. -# -# 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: IGEL Co., Ltd. - -# By using this script, client can receive remoted output via gstreamer. -# Usage: -# remoting-client-receive.bash - -gst-launch-1.0 rtpbin name=rtpbin \ - udpsrc caps="application/x-rtp,media=(string)video,clock-rate=(int)90000,encoding-name=JPEG,payload=26" port=$1 ! \ - rtpbin.recv_rtp_sink_0 \ - rtpbin. ! rtpjpegdepay ! jpegdec ! autovideosink \ - udpsrc port=$(($1 + 1)) ! rtpbin.recv_rtcp_sink_0 \ - rtpbin.send_rtcp_src_0 ! \ - udpsink port=$(($1 + 2)) sync=false async=false diff --git a/doc/sphinx/conf.py.in b/doc/sphinx/conf.py.in deleted file mode 100644 index ab8f9bf..0000000 --- a/doc/sphinx/conf.py.in +++ /dev/null @@ -1,204 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Configuration file for the Sphinx documentation builder. -# -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys -import sphinx - -sys.path.append(os.path.abspath('sphinxext')) - -# -- Project information ----------------------------------------------------- - -project = u'weston' -copyright = u'2019, Weston community' -author = u'Weston community ' - - -# The short X.Y version -version = u'' -# The full version, including alpha/beta/rc tags -release = u'@VERSION@' - - -# -- General configuration --------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -needs_sphinx = '2.1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.autosectionlabel', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.ifconfig', - 'sphinx.ext.viewcode', - 'breathe', -] - -breathe_projects = { "weston": "@BUILD_ROOT@/xml/" } -breathe_default_members = ('members', 'undoc-members') -breathe_default_project = "weston" - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -source_suffix = ['.rst' ] - -# The master toctree document. -master_doc = 'index' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = None - -# default domain -primary_domain = 'cpp' - -# To automatically number figures, tables, etc. and be able to reference them. -numfig = True - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'weston' - - -# -- Options for LaTeX output ------------------------------------------------ - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'weston.tex', u'Weston Documentation', - u'Weston community', 'manual'), -] - - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'weston', u'Weston Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'weston', u'Wweston Documentation', - author, 'Weston community', 'Weston Documentation' - 'Miscellaneous'), -] - - -# -- Options for Epub output ------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] - - -# -- Extension configuration ------------------------------------------------- - -# -- Options for intersphinx extension --------------------------------------- - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/3': None} - -# -- Options for todo extension ---------------------------------------------- - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True diff --git a/doc/sphinx/doxygen.ini.in b/doc/sphinx/doxygen.ini.in deleted file mode 100644 index cfeba09..0000000 --- a/doc/sphinx/doxygen.ini.in +++ /dev/null @@ -1,2480 +0,0 @@ -# Doxyfile 1.8.13 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. -# -# All text after a double hash (##) is considered a comment and is placed in -# front of the TAG it is preceding. -# -# All text after a single hash (#) is considered a comment and will be ignored. -# The format is: -# TAG = value [value, ...] -# For lists, items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (\" \"). - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all text -# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv -# for the list of possible encodings. -# The default value is: UTF-8. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by -# double-quotes, unless you are using Doxywizard) that should identify the -# project for which the documentation is generated. This name is used in the -# title of most generated pages and in a few other places. -# The default value is: My Project. - -PROJECT_NAME = "libweston" - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. This -# could be handy for archiving the generated documentation or if some version -# control system is used. - -PROJECT_NUMBER = - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a -# quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = - -# With the PROJECT_LOGO tag one can specify a logo or an icon that is included -# in the documentation. The maximum height of the logo should not exceed 55 -# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy -# the logo to the output directory. - -PROJECT_LOGO = - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path -# into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If -# left blank the current directory will be used. - -OUTPUT_DIRECTORY = @OUTPUT_DIR@ - -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. -# The default value is: NO. - -CREATE_SUBDIRS = NO - -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII -# characters to appear in the names of generated files. If set to NO, non-ASCII -# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode -# U+3044. -# The default value is: NO. - -ALLOW_UNICODE_NAMES = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. -# The default value is: English. - -OUTPUT_LANGUAGE = English - -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member -# descriptions after the members that are listed in the file and class -# documentation (similar to Javadoc). Set to NO to disable this. -# The default value is: YES. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief -# description of a member or function before the detailed description -# -# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. -# The default value is: YES. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator that is -# used to form the text in various listings. Each string in this list, if found -# as the leading text of the brief description, will be stripped from the text -# and the result, after processing the whole list, is used as the annotated -# text. Otherwise, the brief description is used as-is. If left blank, the -# following values are used ($name is automatically replaced with the name of -# the entity):The $name class, The $name widget, The $name file, is, provides, -# specifies, contains, represents, a, an and the. - -ABBREVIATE_BRIEF = "The $name class" \ - "The $name widget" \ - "The $name file" \ - is \ - provides \ - specifies \ - contains \ - represents \ - a \ - an \ - the - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief -# description. -# The default value is: NO. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. -# The default value is: NO. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path -# before files name in the file list and in the header files. If set to NO the -# shortest path that makes the file name unique will be used -# The default value is: YES. - -FULL_PATH_NAMES = YES - -# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. -# Stripping is only done if one of the specified strings matches the left-hand -# part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to -# strip. -# -# Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. -# This tag requires that the tag FULL_PATH_NAMES is set to YES. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the -# path mentioned in the documentation of a class, which tells the reader which -# header file to include in order to use a class. If left blank only the name of -# the header file containing the class definition is used. Otherwise one should -# specify the list of include paths that are normally passed to the compiler -# using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't -# support long names like on DOS, Mac, or CD-ROM. -# The default value is: NO. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) -# The default value is: NO. - -JAVADOC_AUTOBRIEF = YES - -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) -# The default value is: NO. - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a -# multi-line C++ special comment block (i.e. a block of //! or /// comments) as -# a brief description. This used to be the default behavior. The new default is -# to treat a multi-line C++ comment block as a detailed description. Set this -# tag to YES if you prefer the old behavior instead. -# -# Note that setting this tag to YES also means that rational rose comments are -# not recognized any more. -# The default value is: NO. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the -# documentation from any documented member that it re-implements. -# The default value is: YES. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new -# page for each member. If set to NO, the documentation of a member will be part -# of the file/class/namespace that contains it. -# The default value is: NO. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen -# uses this value to replace tabs by spaces in code fragments. -# Minimum value: 1, maximum value: 16, default value: 4. - -TAB_SIZE = 8 - -# This tag can be used to specify a number of aliases that act as commands in -# the documentation. An alias has the form: -# name=value -# For example adding -# "sideeffect=@par Side Effects:\n" -# will allow you to put the command \sideeffect (or @sideeffect) in the -# documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines. - -ALIASES = "rst=\verbatim embed:rst" -ALIASES += "endrst=\endverbatim" -ALIASES += "rststar=\verbatim embed:rst:leading-asterisk" -ALIASES += "endrststar=\endverbatim" - -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources -# only. Doxygen will then generate output that is more tailored for C. For -# instance, some of the names that are used will be different. The list of all -# members will be omitted, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_FOR_C = YES - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or -# Python sources only. Doxygen will then generate output that is more tailored -# for that language. For instance, namespaces will be presented as packages, -# qualified scopes will look different, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources. Doxygen will then generate output that is tailored for Fortran. -# The default value is: NO. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for VHDL. -# The default value is: NO. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given -# extension. Doxygen has a built-in mapping, but you can override or extend it -# using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: -# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: -# Fortran. In the later case the parser tries to guess whether the code is fixed -# or free formatted code, this is the default for Fortran type files), VHDL. For -# instance to make doxygen treat .inc files as Fortran files (default is PHP), -# and .f files as C (default is Fortran), use: inc=Fortran f=C. -# -# Note: For files without extension you can use no_extension as a placeholder. -# -# Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. - -EXTENSION_MAPPING = - -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments -# according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in -# case of backward compatibilities issues. -# The default value is: YES. - -MARKDOWN_SUPPORT = YES - -# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up -# to that level are automatically included in the table of contents, even if -# they do not have an id attribute. -# Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 0. -# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. - -TOC_INCLUDE_HEADINGS = 0 - -# When enabled doxygen tries to link words that correspond to documented -# classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by putting a % sign in front of the word or -# globally by setting AUTOLINK_SUPPORT to NO. -# The default value is: YES. - -AUTOLINK_SUPPORT = YES - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. -# The default value is: NO. - -BUILTIN_STL_SUPPORT = NO - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. -# The default value is: NO. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. -# The default value is: NO. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate -# getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. -# This will only work if the methods are indeed getting or setting a simple -# type. If this is not the case, or you want to show the methods anyway, you -# should set this option to NO. -# The default value is: YES. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. -# The default value is: NO. - -DISTRIBUTE_GROUP_DOC = NO - -# If one adds a struct or class to a group and this option is enabled, then also -# any nested class or struct is added to the same group. By default this option -# is disabled and one has to add nested compounds explicitly via \ingroup. -# The default value is: NO. - -GROUP_NESTED_COMPOUNDS = NO - -# Set the SUBGROUPING tag to YES to allow class member groups of the same type -# (for instance a group of public functions) to be put as a subgroup of that -# type (e.g. under the Public Functions section). Set it to NO to prevent -# subgrouping. Alternatively, this can be done per class using the -# \nosubgrouping command. -# The default value is: YES. - -SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions -# are shown inside the group in which they are included (e.g. using \ingroup) -# instead of on a separate page (for HTML and Man pages) or section (for LaTeX -# and RTF). -# -# Note that this feature does not work in combination with -# SEPARATE_MEMBER_PAGES. -# The default value is: NO. - -INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions -# with only public data fields or simple typedef fields will be shown inline in -# the documentation of the scope in which they are defined (i.e. file, -# namespace, or group documentation), provided this scope is documented. If set -# to NO, structs, classes, and unions are shown on a separate page (for HTML and -# Man pages) or section (for LaTeX and RTF). -# The default value is: NO. - -INLINE_SIMPLE_STRUCTS = NO - -# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or -# enum is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically be -# useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. -# The default value is: NO. - -TYPEDEF_HIDES_STRUCT = NO - -# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This -# cache is used to resolve symbols given their name and scope. Since this can be -# an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The -# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range -# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest -# the optimal cache size from a speed point of view. -# Minimum value: 0, maximum value: 9, default value: 0. - -LOOKUP_CACHE_SIZE = 0 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in -# documentation are documented, even if no documentation was available. Private -# class members and static file members will be hidden unless the -# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. -# Note: This will also disable the warnings about undocumented members that are -# normally produced when WARNINGS is set to YES. -# The default value is: NO. - -EXTRACT_ALL = YES - -# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will -# be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal -# scope will be included in the documentation. -# The default value is: NO. - -EXTRACT_PACKAGE = NO - -# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be -# included in the documentation. -# The default value is: NO. - -EXTRACT_STATIC = YES - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO, -# only classes defined in header files are included. Does not have any effect -# for Java sources. -# The default value is: YES. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. If set to YES, local methods, -# which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO, only methods in the interface are -# included. -# The default value is: NO. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base name of -# the file that contains the anonymous namespace. By default anonymous namespace -# are hidden. -# The default value is: NO. - -EXTRACT_ANON_NSPACES = NO - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all -# undocumented members inside documented classes or files. If set to NO these -# members will be included in the various overviews, but no documentation -# section is generated. This option has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. If set -# to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO, these declarations will be -# included in the documentation. -# The default value is: NO. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO, these -# blocks will be appended to the function's detailed documentation block. -# The default value is: NO. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation that is typed after a -# \internal command is included. If the tag is set to NO then the documentation -# will be excluded. Set it to YES to include the internal documentation. -# The default value is: NO. - -INTERNAL_DOCS = NO - -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. -# The default value is: system dependent. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES, the -# scope will be hidden. -# The default value is: NO. - -HIDE_SCOPE_NAMES = NO - -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will -# append additional text to a page's title, such as Class Reference. If set to -# YES the compound reference will be hidden. -# The default value is: NO. - -HIDE_COMPOUND_REFERENCE= NO - -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of -# the files that are included by a file in the documentation of that file. -# The default value is: YES. - -SHOW_INCLUDE_FILES = YES - -# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each -# grouped member an include statement to the documentation, telling the reader -# which file to include in order to use the member. -# The default value is: NO. - -SHOW_GROUPED_MEMB_INC = NO - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include -# files with double quotes in the documentation rather than with sharp brackets. -# The default value is: NO. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the -# documentation for inline members. -# The default value is: YES. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the -# (detailed) documentation of file and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. -# The default value is: YES. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief -# descriptions of file, namespace and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. Note that -# this will also influence the order of the classes in the class list. -# The default value is: NO. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the -# (brief and detailed) documentation of class members so that constructors and -# destructors are listed first. If set to NO the constructors will appear in the -# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. -# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief -# member documentation. -# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting -# detailed member documentation. -# The default value is: NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy -# of group names into alphabetical order. If set to NO the group names will -# appear in their defined order. -# The default value is: NO. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by -# fully-qualified names, including namespaces. If set to NO, the class list will -# be sorted only by class name, not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the alphabetical -# list. -# The default value is: NO. - -SORT_BY_SCOPE_NAME = NO - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper -# type resolution of all parameters of a function it will reject a match between -# the prototype and the implementation of a member function even if there is -# only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still -# accept a match between prototype and implementation in such cases. -# The default value is: NO. - -STRICT_PROTO_MATCHING = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo -# list. This list is created by putting \todo commands in the documentation. -# The default value is: YES. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test -# list. This list is created by putting \test commands in the documentation. -# The default value is: YES. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug -# list. This list is created by putting \bug commands in the documentation. -# The default value is: YES. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) -# the deprecated list. This list is created by putting \deprecated commands in -# the documentation. -# The default value is: YES. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional documentation -# sections, marked by \if ... \endif and \cond -# ... \endcond blocks. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the -# initial value of a variable or macro / define can have for it to appear in the -# documentation. If the initializer consists of more lines than specified here -# it will be hidden. Use a value of 0 to hide initializers completely. The -# appearance of the value of individual variables and macros / defines can be -# controlled using \showinitializer or \hideinitializer command in the -# documentation regardless of this setting. -# Minimum value: 0, maximum value: 10000, default value: 30. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES, the -# list will mention the files that were used to generate the documentation. -# The default value is: YES. - -SHOW_USED_FILES = YES - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This -# will remove the Files entry from the Quick Index and from the Folder Tree View -# (if specified). -# The default value is: YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces -# page. This will remove the Namespaces entry from the Quick Index and from the -# Folder Tree View (if specified). -# The default value is: YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command command input-file, where command is the value of the -# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file -# version. For an example see the documentation. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can -# optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. -# -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE -# tag is left empty. - -LAYOUT_FILE = - -# The CITE_BIB_FILES tag can be used to specify one or more bib files containing -# the reference definitions. This must be a list of .bib files. The .bib -# extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. -# For LaTeX the style of the bibliography can be controlled using -# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. See also \cite for info how to create references. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the -# messages are off. -# The default value is: NO. - -QUIET = YES - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES -# this implies that the warnings are on. -# -# Tip: Turn warnings on while writing the documentation. -# The default value is: YES. - -WARNINGS = YES - -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate -# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag -# will automatically be disabled. -# The default value is: YES. - -WARN_IF_UNDOCUMENTED = YES - -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. -# The default value is: YES. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that -# are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. -# The default value is: NO. - -WARN_NO_PARAMDOC = NO - -# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. -# The default value is: NO. - -WARN_AS_ERROR = YES - -# The WARN_FORMAT tag determines the format of the warning messages that doxygen -# can produce. The string should contain the $file, $line, and $text tags, which -# will be replaced by the file and line number from which the warning originated -# and the warning text. Optionally the format may contain $version, which will -# be replaced by the version of the file (if it could be obtained via -# FILE_VERSION_FILTER) -# The default value is: $file:$line: $text. - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning and error -# messages should be written. If left blank the output is written to standard -# error (stderr). - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag is used to specify the files and/or directories that contain -# documented source files. You may enter file names like myfile.cpp or -# directories like /usr/src/myproject. Separate the files or directories with -# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING -# Note: If this tag is empty the current directory is searched. - -INPUT = @SRC_ROOT@/libweston \ - @SRC_ROOT@/include/libweston \ - @SRC_ROOT@/tests - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses -# libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: http://www.gnu.org/software/libiconv) for the list of -# possible encodings. -# The default value is: UTF-8. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. -# -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, -# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. - -FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cpp \ - *.c++ \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.idl \ - *.ddl \ - *.odl \ - *.h \ - *.hh \ - *.hxx \ - *.hpp \ - *.h++ \ - *.cs \ - *.d \ - *.php \ - *.php4 \ - *.php5 \ - *.phtml \ - *.inc \ - *.m \ - *.markdown \ - *.md \ - *.mm \ - *.dox \ - *.py \ - *.pyw \ - *.f90 \ - *.f95 \ - *.f03 \ - *.f08 \ - *.f \ - *.for \ - *.tcl \ - *.vhd \ - *.vhdl \ - *.ucf \ - *.qsf - -# The RECURSIVE tag can be used to specify whether or not subdirectories should -# be searched for input files as well. -# The default value is: NO. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# -# Note that relative paths are relative to the directory from which doxygen is -# run. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system feature) are excluded -# from the input. -# The default value is: NO. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories for example use the pattern */test/* - -EXCLUDE_PATTERNS = - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or directories -# that contain example code fragments that are included (see the \include -# command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank all -# files are included. - -EXAMPLE_PATTERNS = * - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude commands -# irrespective of the value of the RECURSIVE tag. -# The default value is: NO. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or directories -# that contain images that are to be included in the documentation (see the -# \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command: -# -# -# -# where is the value of the INPUT_FILTER tag, and is the -# name of an input file. Doxygen will then use the output that the filter -# program writes to standard output. If FILTER_PATTERNS is specified, this tag -# will be ignored. -# -# Note that the filter must not add or remove lines; it is applied before the -# code is scanned, but not when the output code is generated. If lines are added -# or removed, the anchors will not be placed correctly. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: pattern=filter -# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how -# filters are used. If the FILTER_PATTERNS tag is empty or if none of the -# patterns match the file name, INPUT_FILTER is applied. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will also be used to filter the input files that are used for -# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). -# The default value is: NO. - -FILTER_SOURCE_FILES = NO - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and -# it is also possible to disable source filtering for a specific pattern using -# *.ext= (so without naming a filter). -# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. - -FILTER_SOURCE_PATTERNS = - -# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that -# is part of the input, its contents will be placed on the main page -# (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. - -USE_MDFILE_AS_MAINPAGE = - -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will be -# generated. Documented entities will be cross-referenced with these sources. -# -# Note: To get rid of all source code in the generated output, make sure that -# also VERBATIM_HEADERS is set to NO. -# The default value is: NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. -# The default value is: NO. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any -# special comment blocks from generated source code fragments. Normal C, C++ and -# Fortran comments will always remain visible. -# The default value is: YES. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# function all documented functions referencing it will be listed. -# The default value is: NO. - -REFERENCED_BY_RELATION = NO - -# If the REFERENCES_RELATION tag is set to YES then for each documented function -# all documented entities called/used by that function will be listed. -# The default value is: NO. - -REFERENCES_RELATION = NO - -# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES then the hyperlinks from functions in REFERENCES_RELATION and -# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will -# link to the documentation. -# The default value is: YES. - -REFERENCES_LINK_SOURCE = YES - -# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the -# source code will show a tooltip with additional information such as prototype, -# brief description and links to the definition and documentation. Since this -# will make the HTML file larger and loading of large files a bit slower, you -# can opt to disable this feature. -# The default value is: YES. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -SOURCE_TOOLTIPS = YES - -# If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in -# source browser. The htags tool is part of GNU's global source tagging system -# (see http://www.gnu.org/software/global/global.html). You will need version -# 4.8.6 or higher. -# -# To use it do the following: -# - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the config file -# - Make sure the INPUT points to the root of the source tree -# - Run doxygen as normal -# -# Doxygen will invoke htags (and that will in turn invoke gtags), so these -# tools must be available from the command line (i.e. in the search path). -# -# The result: instead of the source browser generated by doxygen, the links to -# source code will now point to the output of htags. -# The default value is: NO. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a -# verbatim copy of the header file for each class for which an include is -# specified. Set to NO to disable this. -# See also: Section \class. -# The default value is: YES. - -VERBATIM_HEADERS = YES - -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all -# compounds will be generated. Enable this if the project contains a lot of -# classes, structs, unions or interfaces. -# The default value is: YES. - -ALPHABETICAL_INDEX = YES - -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output -# The default value is: YES. - -GENERATE_HTML = NO - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each -# generated HTML page (for example: .htm, .php, .asp). -# The default value is: .html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a -# standard header. -# -# To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. -# the setting GENERATE_TREEVIEW). It is highly recommended to start with a -# default header using -# doxygen -w html new_header.html new_footer.html new_stylesheet.css -# YourConfigFile -# and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally -# uses. -# Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description -# of the possible markers and block names see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard -# footer. See HTML_HEADER for more information on how to generate a default -# footer and what special commands can be used inside the footer. See also -# section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style -# sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. -# See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. -# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as -# it is more robust and this tag (HTML_STYLESHEET) will in the future become -# obsolete. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_STYLESHEET = - -# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. -# This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefore more robust against future updates. -# Doxygen will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_STYLESHEET = - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that the -# files will be copied as-is; there are no commands or markers available. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value -# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 -# purple, and 360 is red again. -# Minimum value: 0, maximum value: 359, default value: 220. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A -# value of 255 will produce the most vivid colors. -# Minimum value: 0, maximum value: 255, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the -# luminance component of the colors in the HTML output. Values below 100 -# gradually make the output lighter, whereas values above 100 make the output -# darker. The value divided by 100 is the actual gamma applied, so 80 represents -# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not -# change the gamma. -# Minimum value: 40, maximum value: 240, default value: 80. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = NO - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_SECTIONS = NO - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries -# shown in the various tree structured indices initially; the user can expand -# and collapse entries dynamically later on. Doxygen will expand the tree to -# such a level that at most the specified number of entries are visible (unless -# a fully collapsed tree already exceeds this amount). So setting the number of -# entries 1 will produce a full collapsed tree by default. 0 is a special value -# representing an infinite number of entries and will result in a full expanded -# tree by default. -# Minimum value: 0, maximum value: 9999, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_INDEX_NUM_ENTRIES = 100 - -# If the GENERATE_DOCSET tag is set to YES, additional index files will be -# generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: http://developer.apple.com/tools/xcode/), introduced with -# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_DOCSET = NO - -# This tag determines the name of the docset feed. A documentation feed provides -# an umbrella under which multiple documentation sets from a single provider -# (such as a company or product suite) can be grouped. -# The default value is: Doxygen generated docs. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# This tag specifies a string that should uniquely identify the documentation -# set bundle. This should be a reverse domain-name style string, e.g. -# com.mycompany.MyDocSet. Doxygen will append .docset to the name. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. -# The default value is: org.doxygen.Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. -# The default value is: Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three -# additional HTML index files: index.hhp, index.hhc, and index.hhk. The -# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. -# -# The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML -# files are now used as the Windows 98 help format, and will replace the old -# Windows help format (.hlp) on all Windows platforms in the future. Compressed -# HTML files also contain an index, a table of contents, and you can search for -# words in the documentation. The HTML workshop also contains a viewer for -# compressed HTML files. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_HTMLHELP = NO - -# The CHM_FILE tag can be used to specify the file name of the resulting .chm -# file. You can add a path in front of the file if the result should not be -# written to the html output directory. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_FILE = - -# The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler (hhc.exe). If non-empty, -# doxygen will try to run the HTML help compiler on the generated index.hhp. -# The file has to be specified with full path. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -HHC_LOCATION = - -# The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -GENERATE_CHI = NO - -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) -# and project file content. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_INDEX_ENCODING = - -# The BINARY_TOC flag controls whether a binary table of contents is generated -# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it -# enables the Previous and Next buttons. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members to -# the table of contents of the HTML help documentation and to the tree view. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that -# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help -# (.qch) of the generated HTML documentation. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify -# the file name of the resulting .qch file. The path specified is relative to -# the HTML output folder. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help -# Project output. For more information please see Qt Help Project / Namespace -# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt -# Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- -# folders). -# The default value is: doc. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_VIRTUAL_FOLDER = doc - -# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom -# filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_SECT_FILTER_ATTRS = - -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be -# generated, together with the HTML files, they form an Eclipse help plugin. To -# install this plugin and make it available under the help contents menu in -# Eclipse, the contents of the directory containing the HTML and XML files needs -# to be copied into the plugins directory of eclipse. The name of the directory -# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. -# After copying Eclipse needs to be restarted before the help appears. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the Eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have this -# name. Each documentation set should have its own identifier. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# If you want full control over the layout of the generated HTML pages it might -# be necessary to disable the index and replace it with your own. The -# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top -# of each HTML page. A value of NO enables the index and the value YES disables -# it. Since the tabs in the index contain the same information as the navigation -# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -DISABLE_INDEX = NO - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. If the tag -# value is set to YES, a side panel will be generated containing a tree-like -# index structure (just like the one that is generated for HTML Help). For this -# to work a browser that supports JavaScript, DHTML, CSS and frames is required -# (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_TREEVIEW = NO - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. -# -# Note that a value of 0 will completely suppress the enum values from appearing -# in the overview section. -# Minimum value: 0, maximum value: 20, default value: 4. -# This tag requires that the tag GENERATE_HTML is set to YES. - -ENUM_VALUES_PER_LINE = 4 - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used -# to set the initial width (in pixels) of the frame in which the tree is shown. -# Minimum value: 0, maximum value: 1500, default value: 250. -# This tag requires that the tag GENERATE_HTML is set to YES. - -TREEVIEW_WIDTH = 250 - -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to -# external symbols imported via tag files in a separate window. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -EXT_LINKS_IN_WINDOW = NO - -# Use this tag to change the font size of LaTeX formulas included as images in -# the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML -# output directory to force them to be regenerated. -# Minimum value: 8, maximum value: 50, default value: 10. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_FONTSIZE = 10 - -# Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering -# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX -# installed or if you want to formulas look prettier in the HTML output. When -# enabled you may also need to install MathJax separately and configure the path -# to it using the MATHJAX_RELPATH option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -USE_MATHJAX = NO - -# When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. -# Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. -# The default value is: HTML-CSS. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_FORMAT = HTML-CSS - -# When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from http://www.mathjax.org before deployment. -# The default value is: http://cdn.mathjax.org/mathjax/latest. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest - -# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax -# extension names that should be enabled during MathJax rendering. For example -# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_EXTENSIONS = - -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an -# example see the documentation. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_CODEFILE = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and -# should work on any modern browser. Note that when using HTML help -# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) -# there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then -# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to -# search using the keyboard; to jump to the search box use + S -# (what the is depends on the OS and browser, but it is typically -# , /E{TXW$$ohJ-O8AO0NrnXK@BTO9Ky zM3*@0U3piM2A9!0=)SVDpO4SP)Ko}NP{ZUJFyKo|OVQDw)ZfIMU0O1Hzys1D6H{7Y zVPSIe?bJ?RU*8IzIZ1dO@4x$f@n_32hF+?$Gb%AyqiRrpH2 z-_P_#jI7qxYqsI#Wy8D@xMqHSegT2#B~#wD*jUN}walJ*M$wSa*w_yW>4g?UP3G|M z@YSaINY#yAwLikUipo_;&o3{J9p=z+yd3uOC0%Hv!_wfRE%#dXWt~ra4*o947Y>5o zb*#2w^F8767P=-WK7GxN@MHjvtyQH<=Gsv9a|?#Zx;A z3JZZtwr;)LspPF2siN&37QY(3lC%ZO{eHR<6w|CItx~%?8C~!|PMZI!U$rsUZ<(9pCpPkLT*SwVcMz^zcl3^_eL%5yOqxkWjB7`&rqG zI6=wha}CNxc#u##<&o5Zs|IA_s_pMM?CjYOOur4GSr~-Zj!rGR-FYNL?g7xnf$i+W zeF$Y!Uf-aQmp)nvG`#QV=xAp4AtIs&#a`vMK6Te(?-yK+9(6mtwe*v{753mYISBz8~=1XyoYCzrUY>PbC0SRm+94Kl42Q zx+aF;vAe1IgMig&^~=i=kZg!ZNT4(m=k++;nFj6>xFhkT>!A2dx5NQRaRSK>bf!#Y zQ@C-OnsI8G5W#rqCOYLrA#LBibEkd&2mDofdb+*>XVcN@Wn=?L8>S{E;GBRXTeB=Y zBH~NfSCD2UB>~Du7M$~>4NF%c1?Rhg_H*RlH`|FMI#h8~-aZll8g<&Uua6Qm-Ao30dOJr)S7+w~a6M6ylMC3-ePj1n4FtRwSJ@8| z(~B3?7g7Wd=e3W{o2-Ik32rl*^tr*wX#?4FKD#O-RsoD%WntkW4_>a=(lRq+t+FyVzg8r5w09SyF)bm1rj7&ce>gTh z817tkC46$cls-lLck7W}wR4{(G(@%ZeY}k56G( z43|5@YJ+E1_MD|+Z>bEfN7N|Io7T9{Ld`eokL+;#;D&GBhALCE8 z_0^Dz?p4X=VFZw=I<7*^2UMxmoQG!iljxOFK zbJDuzKqa^$9WLZ)PISo)h#z_R7xj7%3eEaJnFbbcU}y*kmV*UC z*x1X}(CRdf!ZfvGK3DCvl@c~Nw zxOQd>AUZI=v>OAo2MD+WG8H9;^Jw*BaMKKy*fN6u=-Rbli+qY+@F+kwHYreJVP=Lj z|IOqF_`|U15Q5; zbcmuFG%<#*h-km)a{}!J+Wzpc7O2y)v2LT)C&CRtGYft7N;Nz7`}=*`lF-LC^-eM1(?OodV{2POI#enCMoV&WI&CMq@*OR4;YV%i}|wLfN*eh z+yZX{7Z+F3+qYV-ivV1Xoc&bKmbSA!4^UCvsT39F?_ZdzU%|9(n~xInm2%Yg{n!pN z2Ncek!hf}>sWODFZY5{5Xs0A7S93<0nVAKt!^TiAcgk5WJ5_uJ9U!uL1_!|gv$;IN z?m(LtJj^2%+CDlu>gDAHD`3GDK-8 z;SE^m*p#>dhYS|XgSm|CEbS`U1P3sFWrHCD8wbbpXm11@HOmB^+jZa&3z&a(&m$X) z4pQxY0@dQe0{AX%?%YJ!29WZ;f5I|GdomzU6hm}(=JI}2moYb}rFJF2u@Wk1YqwUo z0P6;3xv{ZxV36D2wk~alHGAuplatf-?k} zKt#Y{eTJ0;=V+!E58Sq#OiTf~q<^@}I$Yt(-MJ5qggEBe@#J7Xu1RW6&F zk(OrF)>fO3FzOTX9(ii-x}Bqqux{t?g*9QvL(D}S-v@!^jw1Kk% zYhm$3&yQ&$G+9O|TI7ERlQzqDGvItRrBAC^5kpKV$nQcJZZa*L7}^Ae8>(OB%OGnV z?6i7%0DAlO?c1|w&u#~j2;1IYzfegC;+T@HD<2m&EgQNFz)$eDI2L2#FM?i3cn6@gTk2(N+aML!FIt)PRzOaNffhiHJVql!bF`vjHKZ2(@Tx@+ga~c-LXrVOyD=61A%N(wT zH9`15MX=+X>HOC;Ob8D9Y)T=g7nQr8DJ;RO0nnci2^#B_^NpG6McZYa*M7Rg7=~;> zBMzqEb#Zp~_4n5 z5AX~SNHmlj9*%yILH7XIi7^FT#b^>!NBVDnuMdO}KRi4PGS?$>^D92RW;}^Exq5O& zr_kE1FF&7l^TB070fG1OO!;c&P(C#3OG!)PKnePw`=403GKvS`SA0}iNxW;o;_eKt zhP*xv1%+IjNvYB}l7L^`?cQ|YRQCDS)dhKRakxT|p5DixZ-3t{Q&}1Z*cdg2P$>AJ zWFMY6!CelTg}Ry=Xs_=S9=W?$$g@L~`j=(^6~@Ig zs9eCgf+Ua8*q`~4>UsW8pO2|5~oJ+{SL>_M&r zpYEcCepPF-oq#5~EV6}vM9ILB!a8J8pte(Tk%sQw?~+rSByiG7%v6+=3yX{3|A)d1 z1D{go0S{Cc;H$R51JplgqM<=4N`EYCF@3}F(*=Mt^BEKp@Hll{eI2wmu(q{T<*-Pu z32g+OvvwmMdztdMp>tk4ivFp zyVh({07$Rs{rhJFzZ&7MCkB7>8Nf^Kpm~P@mi8!}1(JbM`wXpYUh-fl3!jCA@WeC& zW``ybIyzk&-9y_0NVS}t3TqV$3kyJJeW@755ZyW31kz`jdHXA((gnV=Mwq^0r-4ws zR_3qDuvc?x#_qya&=cdoZXaj^3X>{X*M-vWvk+Wjm3mG}ik~iN8Aac++`?@MiS_Jr z34GR1R$7AA!p~3FR z{uHmz3)uP4)pQkUYm(K<9oaE>WQ)a7rHkL9Vtdr^&-_}7+zutb4OkX|*{26%l!U~< zP=iMTMORmsvx^IuG=Nbo$j`R}iVf|smfb7~ZT~MH*2uNw(kYoZo-;z9qe&=MVMOayfko@`9>6fAH6%ddmLa)xo{49G>Gl+K`w2M&zu zGMMVjVL(%KQk?Zcua$+uD|P`uVNBRK-@DfiHoDeW{+sM0z}EmPB`d}$VA*^i8i81+ z0HtG+M56FlyimwPihJ(dIgsDLk_5ZZ-Q68YQ8JkJ{=HO27c=oQI47`+5+Z>)>YJ7o ze-IA1M#DuyPEOW?VaTHSms)ypQxDhxknm`a4?)2gS6S_BwCi0?t`!;p04pb=ZB;Y| z$F{2E)Z0{_sw4hDJxEcb)CvIykY=oQK>fgFf%XpM!1Cdi=MgVE zyCo3IdKhPL&KwHLLuu~21gs~pRB#`7V(voI&Xp_eMv(}^nvp>K){*D3Is{Y<) z_w<&{eD^hE!@n|$YTLh>I5ujF{tbC3>-bud9ymvk7ZjMopn3(DW;|yDFy_6zsz7%} zXnX8H8{VKp+2b3$yp{unchi+zTwN`P%S&={UjCzk*({b6D#-M0#wP>^+6DfD175|f zbX=VMrJ(5ce>EMZcCBLlkW2F~lVK;GKoe~MY&tHlf#6~_xmajFR_hDm7&QI0yC3ZD z>*?u1nxG82R(L@9RIRqTa zKRIAfxey6$O7@{o0+?)J_g(Hf4xbkw93+bS3K$o)V^KM_o%`Qe-B&G;Pg-e>i@2+tbN07n*#iyGu3GG9eFu8P`JzO6Lvf`VTeJU~ol8hN2wnadBUyE(b6iH$hfL zBA?Xg(E5<#f=C42Y<*@Sfq~;qjK{<=xC@1V>i|j!2#&o`Zvt^~abP`ecoYDVhHg{H zc<(Zxhy^WM+w+KROJ%0K(o*dlQtGfL)GQwIWh6!lB+~j~7^QLK74P>MWcStAXnV zIn1y?4YGjx{rk6ZQ2L@y#o<{3l}6tzFnr15=vw(L99W{(avT|-e?XsWEJq7BHHJB8 zd*ClWWP@THJjo7}JO~Tv5KWf)3LIHO4Ypli@px7Xp+-oN_C*){&?#q)1WlgC)( znX8p8(I1RYeRcKu0#}_srUXr9mP6wU$n@9Gj7WLAwfc6nAuXLU#giR?usVPs#TJx{@LyRMga=wl z$F?FM@`1_3_PxZw@L8WTj^EagPey!JQ7TjrVg)oLJ|9_~Ug+U^9wU8LJ8hh3E(>+G<9Uxd87x+k71HI^VbKzKwB_gJ!QwVi3$8`-W;mwR;#Q-%OHX`{jdqE= z6(i-OcCbRUPr`mhV!BJlLfg_S<%}OIE10;rLLU2kL&kqs0l^xi8V(YOe7e$xbrh7M zA3l5lA`A{d!>|Cd9QTcmiI=zT<`@6)0$87!v6K_Q{|5@jJ@M}p48~&r;v4y-{SeWc z65H%|9S4hjjY#(&*~e-hy{?hz)w+K_8KMSuJuFjza+q`=<^`J6CUZ%-UVJ za3Q45;o7lp36;wgy-+I?dMqd2n%1$k<=Pr;IUSieDlW3Ozy*Cp3HuX_c0Zt<0qqh7 zzK&7p5IWg_Lxm~_QV+e4+s7AneWTz&kBl)v+SgxNCVsy-1b#zGR#{9QX0Sr93aI( zX)$?^f6D6cF-7&A8%1cM_IoWrfc7!lw#>0dF5vO4{|;{N)lr>p@<>=hANZiWY`=@EBrG{U<*c6|FG(!MpWO~ z<{aBx2|m`)e{a+Iv#;@w{>J~o*Q4Kgn7_lYr?Vke}Iv(1j?-Cz59S9CoDV2~Cd z0$|esh~)wz1BaXF%_|6^V_)`Bp3P(vRF64HR^d47hV6p!F){Pw<6K)YAV~hwO&Iaz zEY1rUy#PnNZ?mC#zVXZ6d&Qskma9a!Vq8I&dGH_#LSzOoo51V~gM|V}8|ftPGZ<)^ zXRwzgyWD1FRIGIWpGX)?Ixqo_6k8|3z6VAUDly}+Un!tYoDsBNJ?Qy!8X+gE`ezr= z24K6Hw(V`cpXIP3YdYJ*8LVUA5blt@rR@$;qmNMN4g$ z*((_LRC0>9eKflH@vY%G~}FA=}r+z$C=@onlUnD#&ejecOaF^2*bP6J;6 z+V%7~qqb;wUZAi`RLJAO$$H{ekco_-&^{0&@FwF zAmnU6l1ICheWZUd5TTLCgVfM=sGduq<*Av5u#8Hh#AXs&^Hjs3Y5k`c40&($)XGoN zXsGwtu(mW(W$7rBf;$j3#Rt{^Xh`zNCL~Y(iNX%G0EKB7vR?WNDMj9V}m+1|O z8CQl$8PoIg(8D+H^cqaD+XKg=DxmJiKN(5&wpGjt%v;0=ofH_%P`r6DV4?rt5=M{F z=HyG4FP8|O%=_I>{clw&SMEuho%~Dr>RL=6p~Vf`8_lb_ZtEm#onrp0+d zz=Czm|IiqN0TN)1f~sYST0YzW(l4o>t}lCMPghs_$%S{JCZC>iC25>Ee0sC&F&;j? zRIs06roP+@w+Yz&#>oFtXlIL;`#j}PZRb`@JGhSi^PoV0A6{A_Sh$p0gvGdeZ$Z)FpE}}A<#hcuZ6j} zu;-yr9S5lU8|~qLpTJ-oZjh|-$k~~{B0eTGGznml%fSRjq{geDempaMeK5hp3=!au zfb@Z2hQ!o_R$|ck&?TPj6mEqQSc$HEJ4T@biQ^9K4YQ#7-M9hpVh~eMQk;vE6HW)* zFH{os+4R5o)3AaYUwbdh*n;!Gb#ozSjgK_>^=o1e2UM;Zh}2+_l}F^A{Y!B9T}->j z_S+-wXdu8!HPqCkFcY7PD*nPd;hnvC>z0qdzkhbNC9vXPOGM>DB4iGGCK3&kXu`w7 zE)b%R7jbdIJUpL&^jrZftphk0ydJo9z^}nF0I_&C0Qc`caslHWbcrzOILJos{FmOp zkAk#B0;<~Da+pB>#&a@zT;cwm(n5C%LdL;AeBF#yp}82+;fr(8PJpV)BTiQ3-n}te zufE4nvoh)~R$QzJmZawkKKsqeD_sk}LGAus*nCp>wQzc~}sH0uahu0W8Po2g;lu+COF(ZX8i*_)>Mq(YXwt zdBP)WgbW_)6(2y#)>UV3*c#fdnDY$4qXVWB4v4XjK+zcf#AU^IRE;>P__2oOiwu3I$UCc>5p_UR37MEDWnt!2#VKh`UZ zky2x_Nq?(vqNCBIWMm-Ft=naw7pQeHy-qdTJA5FKYB5*Fp47lNdz|B0cc6yfB?G1y zy}7fDOfjg_A769gTdDIVOHw@}+;ThO>zR&0#`}BK_+yM@iwele#!tt#^80tXt?dVl zVnDag>uCU)2*P6|X$c0NxHUl#ylD|d6jDD?WY3Km=Pqg%VjbBrTlu+T6nS)vA|70+7(mtTaSl~%fwfwgyZT)vAa{bruRWIN{J>s-dU1i^-p9@n;j=I6YaxJuRUo~+= zP$Ur3h*v&d;$Wjw4m~jnWbx&Zs-s#Q_I>ycS0>(QJ6bRI>Qk?DbC#2P-g|Gm>G|xq z+sroD!C-srGTne~jmph_Qz%_w8jiK0p&@j~SLvNv(eyg*#}i_(DB4R;jXy7lUF8hZ zDQG5F&=L?Z+Zh>w6V@Ys2sfCefI!HF1C{1{ba-I47tA~SqEUau z^GKLZUq|ORhEPqIphO6WHVhgHPMGMlFJ4uo*_^fT;io#r*tyC{7(7JV@=# z0kiT`xGd52uUs&e!U-&oj0_CDk70@w_;JA@K_1K?Ag~Mmy*Qo*An-vze)HxH7;Fx~ zhB(nwaTd00A&?@$;zX2`XtookXDNFe?(YDt23jN#ijH=6gvddQqTiAf1si&1-i5U0 zBU^DML$g{uH~Wmxps}vD7VQjxduuC*?tX#gg9^Xq3LTC@7=>kJd+X5865+WMkzI8d zO}GX|k&EED9634|IpY^-3*!=!+KbKlI5#<2SxeA@=s%C4Optd z8B_?P!^j8;t-t{wH4BIY%;MYIPPVpr3h5ux(u^%FVR#61XaH4hQ3S%^MD^oS;CwPE zFoa^Bo?c!Giw5Ov3a%Y%SP z$SEZBwTDR?qk=~ueVpsPRY%UkC;Fnqo_S~sotvMBi-|%zCBaD=2&WAF4KOHw-UxE?Qun<=WMTuDpNcGLlE0bpSoY=mDwuGSqnWmYy9|nkhEwQq2%pBRKGuSq z3s`R#RSrq{1`ki}qMzT{0=4EsEII%qdIM|`rv2O@gfK^`rVv0+$+=DPUU2vToV?fz zE2HlaM1lAy<$L$e(S4eU79~=CD`57?r z0C+3aWQD#(hsR@ULZFW*D=WKvc7vXV@FwZNiEdh%C+Lwma^bN4!8He7RX6}TLuUzK zJftMZx6sszK7G<)af>7PHmqvs-2yxWFuMwDCr};IP*I_ckHQ%ThkMH~L<9yK^rGNR z;Cg91wjaAT^1v(-b}_IzeZ&JH^nwc>-1-^x@I08z0a*Dq+Mb~cvQeLRI$ z?AHn!(AT%y;rmG9YsSQce&NekuWXiwD_|ub#i@x13GG9_SKWFV@crOv2g8ml^pL`4 z_+ZuFunf*yBBJpJu;$SE%1(zMLKJN};7|eQ?7^Nb@(``>gA@)!vK$usA*1`(wSz^n ztE)bjw`d^l^=o=pCh!%b2LRfDpbbJ3bm^82Wp&PmQ1V}D^U+{qq~0=inR;EhQYxyg z1AU8MVd_bV29$4USy_gH7^nBYV`*+GY{%crrkXJQcCj)D_CaLN!#)I?T7HA+&bN?{ zX|b}5DxBsM;Ln6*7k>r*QB*%2X^M73?`LObLiY=(?&=G_l}pRXO}}afDGEHkK*LpN zRq54ZBLlRui&R+$FXV%U2TpQe3ZYL|BB_0$FK@cm7YBAWVAptiupB|FM@;i)Ef^*i zCUt>?Wa`VuinE(At`Guew@E=I$V^a2#0!FKXvGd2CP0Vtcx~;T&s+(BX?%HjWkXBO z-(VJ9ueh`{sv}vtgs4%Yq{{@n>>xnJOSnTE^v9=q|NcF=1COAo?h1!Syad=GU@Rgk zZvrgNuoNmc@T(K3TZa%FjvP-B-=j;9 z0!)DDhg$~h9=HIYYcEadJ?zO%z807RM2Rl}@((O!DJkq^CAJ6^Wo4MWs2~O22Rnhd zdcCM!MCkNl*e8Ei@zi03#*4i(Gik^~CLos=7c*g)!Tb*&!oo~>Ch)dd3>M#0R)#@1 zjZHh`lk;zS#2M|~#4jjF6{wkui~H4$!OR@suRo2)-}A14KBlkKIs$In94amGxEb(H zCIy2s)~BF69dTQ~D>V!K-Ne+?P$kh5@;fRyIPmaF$cTy6WNDV2PBHX|PRPJ@^WO;- zVuhh4&RI2`sdCbjf>m1C+7&K02C~x9Xnj6PWJW>qgKDa}8d}BB6p3;#Od^B=2@Z1d z=&-P^krBD8Fun&ShUh;32D}E?V_~Di0rG2}QEB=SM1*RXr9^z;0uYceg%BFE+l}?* z#y-Z~XTEP^lUp}N>jRC?(r+}6*Jj>0S7w`D&dd*&WQC|#uWH#o*a}0t5)>Oi1OTC8 zaH^pRCYb>#0&FX|5g74Ur3*@Euq9<=CeBM`zqGA$g|rgdI6?NYQ6JX8`I+{8Zc7!i zZAtN~Kc=55J1yvQ%3Sh_>?JodH64#Hi8{?fOnuwYu}C&GCa~%)Br?;2eh51-=rWve z4UCMb5Px7aBos%>=FL~#@TI7xsAQ?3XvD*mGpbkgC3S?a@I+#9_{r>%{@1haU*nJfILrq@&r1+qZ z3Vyw=;LC%MhDOf$SpGb5ydyuuD4?syzNC{@Wr-7Gzz148 zYkJ44Eao6jOc9G^T_X$)MPaxcU*oj{GTcYRhkOR-#{pK5>P&5tpzqd{QU;e!M z$qfz;7*cxbsQYSYV`*90fw~dgUm1q=hrYJ$tNfE>D;hy7`N1MhA}2k-jR=U~@!s-> z`NzI^Xbs~@f!od+7$$R)R>EjHC4lDL=Ntv2G_0YG>%6;6dzehDtZs`1J&?_z0}N<) zBTWKUvlDtC4I_TTr27+-t^O{q(x7WE*G)4Om5X%ryu4c_)A52X8^$nUve&w%{Menp z-T;WG-1Eqz@Bp|ynE$d|bres?$-zMz+6cB>DNLw)o1$-+!bd`z7aPo;aHw=%H-y3h=powgqeJAUw41|#zg!KD4rGQwFPQeDP8Hf>#tILM_{I) z{SMVMu!wJA?kS8CjERA-&l3?rG2k}omg8*R!=+Qz+Mn-9?bb<8NGdpTRh~cD4nQk7 zrZhRlC>cn!NnA8cl{S^V<%Ot-^&Nxml!Nur7>2o93E2{o zDPx9Yh^Hu1Dn%4ZrjX%$eoH;Oo&7xTxvukE=Y7w0{Il6=wT9pCd*AnGx*y#;3s&wt ztE8l4Sq*T5{H`$8XRls;YK^rHKj7diww{e4R0p}WLrC2@W@Uv=?B4Wqn^Dy5yjbXS z__{DE(+qeGtTjn!96PHq34G`%%vM%b`uh6Rrg6Xx zvXpAx8bn)u{wxpx2k%Y9ZYU~tIOIZ$50HmJiZ7}*xJvOVQ=qvzn^#A?cn0)&Fzx3V z--V?bPPMF9S{k1(4y5Ut+eB*|{&I6o$ zxU8J5xL{+j%{0i11yTj!D!B6e0!E=kAj%johD-+S?d&Y%{~?rI6~tntUKGbWys8C? zLx}9PJ<(R>%tCXoAl_JlE;Qf*%FXaxcRu-ySrY*5d-{qGE_g`Dk^d3RvfN~wi5a@SZSuLeGiZbA?>%JqtJb@@6# zYE9C|Wv;F1mPfeO_qK`C8CWY`I80{?#(<>ZsoG)4(a@-Z{`;WtAu}^R@1QOuS1&!A zke|N|6%j@b9pwGtyVlE%An~DF&ooFy6wqVE+ z6cR$_wYs4pSAp2e_V(I;j$0I*O7I;axC>EZd-)qdLKseHE&4q@MqDO+z>B`$@P>({ zeVZ~%vGvvESM*XrTRO^e1Nfsq*khY6^E?k4BKWgf39fhpNf8WK0PRd zfhgwvu)fxwYY`G0TPx%Isf{Iq-y6iA(c0q z`z~u9gOR22LswGV2^-vXh&s$$iB9a$q057zbxUy@X`?q9@KUPNmE_`QQ_-?p0&U*L z4I4Z<7Kkj5(*kaS))Jyk2*&ye2i>Jh&zhTa5n52P6~gtD^6!)A)VHudXiGQ!)DQ{z zXfau|IJfbXD)bm%$vv~&8ns-UZE3cu&iV)jeaz=kj; zLYdBB!jQOB*$Yba|CjJ7KX+UKD0Zy!zgMYIgI>a`UiAT|iq?AXoi8BvK0l-vYm5o|$A8FLoT@n1iGMy265iigj}#Ei-uz zjtcrb{q;3xrfY8dbd&DB3G$4`+k7<4&=DYSi-8ZGRh*|Ks7<8Ik5yDGl;6)Eo}B|d$^+yksA*^r29!p6 zJ1*XSQwfblPorURlzED=&88T4leQ~f9RlEdh&KsG`btvoaSuDAF2IrZTl<=JS3 z!lWr=%0XXG#HA9UjOV*koo$G?RFS6QkxAV2m>~%ckIoBj&;L!3E8p*LK(hKFt#{BR z$2>i5oMXpDgdKecs>;5Q>gef$%Qw&j*^>XefU3xd2+*6zSb|Fr#1ry2I&07@>FVlw zdwXMI1R7LVhaCjMf`SO~OU*&4IoI9)YU=G2$n-A^{f*Hn&^^u<0^ky6qzL(DKgarma{2w7$ z_qF~7f>k@rnvT4}zcfqBZpZ9Y6+(+jt&CX=hC4QK!Nh0!4d|$2l~-DLdUAvS*9r!t zp&zB`xD^4Z7l>mg`y=2Vrc)qf_O|r+(UR$l{bM2Dt(!Mb+oL!+`~FV0@=W@&pBUCb zdVhU^JI`=N?sZ+NG((o(_d)wft~BmwpoUiwafviH%22gHzgmiRvWU2Nas;Gk@KreU zAd3_3NFngk={hOU1_qQ-$`)q=Hp0y8ayWghr46U8Us0>B`KrVrW4O@}3gxm(bEC0%3E6ha#1L~lYE;+)WN8cyMp z9yW-yc%AQjHr@ETQ^Uir+-r&r;|?gy^;}+dGKWn~uZtJ?)V*(C6%bvfh>_Gev9OTR zX43PeK%eYciM}iC1@Y*&Zpm_=M}z_*y8u$D2DJ}pYieFnjEIka4XA)&+sU_TSbyN< z^`OH6QYu7gvK?0yi&_@e&V|sJu*}RIEuBhwO6w_AUXcyAlW8tzX?zZZJZ6i4KvlOB z5eWc1O{%D~`?X5zh8yZ*!{mbxhiVNcs~PIS{Itu#I7QA8%GtEpwwj)dt<5ZKgj zKL9Dpf_?3}%43sNICH(n?mV|S^|iwtH)y_75&1P>(jq$>n{@$s9S+4#YpxAQJqv2OGARy&R_zFF|=IOQvV<36$k5%k_F zD!{51wG=>D4-pn0iI&1N#*P)Oa; zcHsb>obgeU%d-5!ni7kYe^0C8fBiC`fei6&)Y1j6Qs6jJ2fZn)eC9mu(PT0{s?V(S=Y* zNg&FaOA2wS8hVan2jpDpe#fK4ou#g6tvf0Gf@+&Sy}FY3;19dBZk>lu1U{Q zX!Q8rRLKEiM@MtN$Y+XZk|i}$OzJaE8~g+~?b)?!@Byt}?@I)by!hagA%C};co;%f@a6!(*K6X-=q}tvCIlGftacl`dKPY336?Df z3`m2=REMbQt*}{7Ptuiz@nXm$)PN|;bGinJWWHUJX{{NMlYUmpy34YsatFN@lGbwI8Tz=*r`Ys-rvBQN zWR%To4}|C8LQL4Num&j>Ak+{ok1l>lV&hPu#}jw9R@*?`CxUTEn}LiSNp)b@z|o;I zVydd%DoLTN*<8HKLekLI7R23Q=~a7OTt?6w*3>A^P7Q+($%vgraGdwjH=*iC@0{{G zU17%Fvm&=0QYR;jS_Io=1U1Prc6Px;=w6J{azGPcz{2;NH(%7zWB^x)K@NyCQbt08 zgUv4ER+$Il0Fp`AFG%bcfiWC$-a3goY^seB0H-JMh zN`wCez5&>u09ObL|E^QRDe(I7;FnVsg=o$upBXCah1yBJQ&a`$)7p(u$`3XrScJ-ZI_Ed$&r}g2Z7L*VrvhW0mHY1@A%TlDO}p~-7Ru3(oR`?*#);-FjYs$m zY%!lW1y25Zr+lDlp&(*qpYpU6-v(qQt$-H(F8kSXGpSYi zQ83@aDx;l@?5bE13;C(x_}P(-!s#o#IvT%v0AQRMNmd5U0h`xb!ZmH1Ui9cycSvL3 z<3T!w1h#h}0U#{RSFbwvoBjRVTyHlk05@fdry?#cBKcW^&>qZMCZG+#Ix#u9eZVbN z+r8^Co15OHf|3W7zuU%W}yk^YlV{8c8Jte(fX^+?M_yYR>T5j96NTi?C z=HW`;?D*}Qk~w*OXS?sS>z+A7g6mmGq^vU*%S+=CO#Q81{xg=T0BJSObG!lM9h5b- zHbUN)vkyRwnp(xy*OYSo;5nvfG=$0&L8T4>Pu?k<@Ie}y_>Ajh`rQh`R!qJUN8U5b83!U;##O!+Hr zpp|dcguDAp`~ih|a7C8K_}i}4TuQ}ZD@`_pkM)p&Xd*IQH(A>i>coQw?;vcA6%L*> z!%zkNfyFaw98479hg1H_tLNtGPDE4HLtV2_7!Gj! zP1wzav`;FgHB!Mij&vn3IG8c2b_E;Tm)=95M9}@c+;`JFuznGFi?-I2GCu;bfD*@p zeHx*MLwoo?K8F!BUu!vUn6+<6O<(kZ%mjLpDg)ycA86GF_j-Cx;P;j1Rog7xpK?cU z^m8F(BZIUJeM9?B=NB}vTW>54CnopO1orwmj;`U&ySNsVnh@bjb)sGNBofUaX&HWr zO)_w_lhz&@mtI(1zR!ph6q2^x-&_5!e9c<4EjnJ4Bi0DOCS6!bR8OCyGS`F>sXl!9 z@2`=SbB2x|TB84?`lfVh9hI#))oO-dOlV-jM8rr}mJw`|sn0efO@e8~@8DZN=QQ`b zaci;a^3SOYQ1$n#L3IM#6KDwJk(-yTnrjdICIS%(6JCu_OLVe-JUtFCit_Y`ZUc{n zBo`k5#mpu9kY%j3XHVmCDah$}@7dGW-yd^u3tvF0|29-$q+6j6hw}`Z9~Lqpqxmmb z1at0V{iVdl0tEK>!l!Kix)@n5u;K8@%YTE=<^3yjfpQGG{IM&b_VBN^Fe>tL#RY+4 zhS&(ee2yXzMVnw0LsbyR!WSv2`^RcIipvnjnvS>>%dA!``Z@RLHEOR3fXdWl$`umCpSei7ndvzRao{W4;&z>pb=H*3PN9@5ZiT6Q)Tq}UM z04n610T(ohjSwspa^P`6pWlUEDIz+$y7{KchBq2NIwuKV)a^{Hk$U%z52Q9=9N4vp zq*EVfC46rVyrii_VE+% z6m@X>I1nna9zZvT1`;oS0m8L9tdMwOkC8+*$ap4%MJ+w>4z`)>9)nug2SBe%NU<8t zU;q(l{nKz|zG!Fzu?R~3b0KuxWBskpFzBHSz(n$?1Br||X1VRH>KLKWnqY(iB|uaJ zyc0ok|6STL+{A^1ot2G{FUe52Ct2(9dIkqO#D44 z=nWHaO)RJ}7V|~dd_=4n`0`?UTJJrn&xr+Hpksh@fg9>tT8&U){0rDm05AZf4jlhD zri(a$eIViHXw~`3ii(zjG01Ci2|f}_J@4ORV1Ptrw>0!vFnH3_)5D)^dF&V@t65HeL0v~;Vo`2@o?O<Qpq49 z=6#Adc7_!8qMp+b2iN#HWD-SOePT&Hg%)1BgS7a8qxw{6f?Yl)8Ex_w zN=gq^SV9J~5RCzIFigo7#E)BY+eO_l50EbTTP%OW$gwpSElTYLLs-8~KQVId*RpkR^1uA#B$OJpdL(zb|-?oD`#4OJUNMbomf z!l#BF`R#80ikq?+dTI#tP|gtP)Hwt}7n0=p1qHDXf-o^h4whwO_WN;iJT zz`6~G2}N0eyc+_Hb8=o}?bNge(Y`ed&j`=UuKj}xdU6~KUoNly`Mmp(y|Y;2%~P`_ z#K^e+qeX62ENCQp7f~z8o-{p>ieaIl5JIx4qQ6YlOFbeMrJle?ESk(lKDa~f zR%vO7%5^lcGYknqv+1*w>31%r7f;$C>J|E1B&H#JR?ki~%zD^AwdGTg z`yp->YZSM@kujclLj%-db$RWldD1Jayz}_fK-Mm@8o?@1eClZb-3lNrVGBq*uyo2+BZ8Ks+Kn}d~u5|&dfZJsB=IQd0|~n z6W}Ke@}F29r7b|taYOA0vIKegy5NJ0lV)LIfyL;8w;`gFK5igjMLlFNKYS7a0+7a0 zE*75;>Nuqv#UtBblW{km8ahGp;Vx!cULHLzzq}0kM6uF6K1}Sn`1H_fWhPws1O2-* zfF=k8*9G)<6aZ_9VudIl$=NQjk%GEJJ}h=tu~Ne@G0PzT4TmJ&`4&4OIzh6#?lx7i zd7-u&g3t%G-Ez+FLkSlyKx~}Z>3Mk`&^SOv`EmvuX0h(f5=9u58gvIB9v2rW6VL72 z{afv@Ajtqm*bax+*nos2K7`vhaZHpEZ&@^zU~Sl~07G?>huh=wEeVjaLCKHi9bb4@ z{AhO;%WZH4QJJSOPdoyZC z6@k)#a(eWKTF$lOz=7+P>|VN@woXVvVZmB4V(g{WVt$B9Hz7%c9{5cxHsRlJqOGS} zDA8_>OmoX4nVjk3bF&vghT(p}@s7aYI?JrmWqYoeqdrhKPocMlHi=LU8<#lNY`>GCX6=!B+Qcd%Dfc49 zRa?%2{nL3GhlK%Wv1Y}BDgxK(aVy{yHNw)ps@xrT&!HxO?7PxD_}n=~kHNJf0)&Ad zY!-w_h!zf1y%-%0-RvO)It%C%j)O*XqK@n?j1czG=uxoS(HR%lTI zUjH(a+=r!W8v@xlxlf>^ZWDwJN|U__uyx~!G;|K#X&h!9`bSeP zf{As{nsW>X+S?sH95Fy=*ZF!&&1Ak`F1jWLg;8ZJ^ERih`;h%LJ8E z_JjI?!C-6jw&J6TwfkaZpEfr~A^S78-Bqpwp~aT^+q_47{;=)?HD#r(eZIE4E%T@b z44gfODXwD*Qln}3Y)o#hm_9*jZ}Ev35a@DtTyS_C(|DaP2yfgSAalAk5l5euLw>KAP?;%KGS5?tP;HtkBapHdc_8CDNU(|Gcl5%OPimco|F3!Jl9S zBR+cc6fKA67t1>_<8BSdiDu5u4zZwEnC`yGBo=ZIyfi4WAj?IO1``$DRx_sq-ZwkW z-m-z*_w~*&Faxkiz`HCY0e3;QKc+H$OHPp`y5diX?^_qSD4lcR821)qvOd_F!LXRl zuZh4mEJhgfIL*wCeys`Op(~{{b#y>rnW4h)8x4mPak~+F(L6SgeLaDQQvzUo=ePsr z57S%_Pi3Ls4U(TH^AGAGP#R+#Q4fhM9kdDmX8+mXbCi05J2?M#^BmJqkzNG(K^P(F z#M#@HHPgCNTI1Wdn*{~!B_$S8oZIW0ZfXC$&uHBvdQ!$4AVwylCdEj~*XS4I4WCo9 zMvP}Sqc5SOqr+Y5ld{*CdB~QrIRy*Eo5|kx$kfD?h%7Z^)h^#ELGlY+GApC##|dR> z@$0@q_s?W6FE32fuWfy%UOIf$-KU5&BA-Wcu9y$8mFAwWLOAQCYuCQLbZ+2L^pcX5 zHF!BlB5?@Ea2D2+@S#Ibak35bn~pcHWd!Ll|IQ zPk(X;@Dm&oQs6cE5hN+Lv86pdK7$zQ@UxtU2~!6acSAvdyz0X*Gbygx147Ll?jK%6 zkqM!?wDc&-?|PH10*AT{>yO+TD>SI%H276lt1Vwh`Q{z_bw2rWZwSF0Rh5-=ix#Du zloTfpRZNyU2g{e?&oRd;4D~Xz9Y|DQM|Viwg;1Llt0aNb*4> z;tnmly!zi2$g+CNqF-!CtPEGuljCAXGn=9RnE})aJbqB?7x2?e@g}#{R=*cPW^;>x&TE6RYd96Cn)0V~%@FIW=#!af%p72N2;^0C#g@ppiju?J4 zgru)MK~Ck3h&hPO!IuiLZTn6r-EpLjs*y92_E{SX!HURC5XE!VD$F=NX%Bu|6?Hx~1M z3U>TK5q&l1;_ivnSe+B6D2#OHA8!RZ6_k@Wvv>%3_XHME*REhjTRJS@Sr!6kTq5oT z`~j?39!Hj2ORx^?vShNh5S$rUHuJI`fFdM|IuCGiKw)_C;z=l6urc#4-`Iy4R5LbJ zaAOABYUx@RZR5Lz@p_AlzeU6`_I{V;8hJx0p4Y5)+_@m{h8#>*r=EHqf4QQu2(LXH zfwZvAT`k?D#j~W-B0^lEp)*#Bh9U~}?1@)Gi6|0lPK_LZ7Tm&OL%RtqBxC1KEy;84 zIbYiA+3oruP+nSkWzFzbj#aBZwkBW{JxSY_okbIi(9MyT@j5nHtzi4<9{l!Wy7|O) za&FT1{u13Jvr_|jhCh^EJ{CfnoK@LVsEFi|LFAlAgX5!V# z9dDsOL6}HtGQxZSX&g!Nji_lg+)PAh%~_!c&%E;2P{PAnqwzUv-KYM!LhkVrt1b=0FVtV-&4;AP#Kun z94zdXvB#hyu_bI&VN>0L&fWc=KS$Meqxc#CyOG5)!K*^!ySu zjXAyf#NEilgUv|j5>bJ1XyX20Ez4QKv~&0FYLN7x1zJ`NgYWzE&a7*CYAL>Y_3F!- z-AFKpG6b1(7%FxX+FTA3>XG%NzW%*sq>!K>#8#ZmF|BrSa`k0Ai!h^OY=&<7nj724 zQLnL-eL`u3$4W|C`s8QWixFmx-764cVVCW_vcG{Guw&E#adO@^Rv72S#l@k!$Ct9Q zFXDWeE!C?9PhGhVTN-A<(UFmHgM1Z9#CRaJd^I~e`hZ%VTK(kaIIkS(5b`kiJ)=z= zMdb8@!jQ?s_}46y?mr>Im8dZ61B-Kuxw2xg)t8&@10*vw4LP`RE(iIsy|vf5%OwADfq!l{!_HY2=$EYz$G2lvZ| zIJmh+$6t%=x~TQr?kQ!+IOJlNCVW{q3H1wbc_M>mYzTfqSQp19dg84PJV&E~;R{Yb zY_i|OFfrMej^_-;BZoLyi+6^+smdX@s;cVfcOa2i&$KJ$$IkJ;&h=44vg;fL(jLdXOWkY+)<%p+OPH9vKuKZW=$^Y%*!zo6Ax0)9NGl2`6OtnE z-~E?Hr^n?@10es9xCV0*(lqf6R#KKdH9pTg|~S_Tun~juKzrwpE2Wk0*eE#KD(_zK}U=3j*0s z3hsKT8ml&6-g?5pK~MNH(#0T=LnH5$Ux>66?DS02L(7FwrfW|k!^5$?dZ!Q$JyaXm zk2`U!XvZm}=%g_4!N&7YxJ5Guh34+(3XTyRP<=TF58Haj|f7F0OpFF43-ua-12UASbVmY zJyKR%@!Mn08_m1;2llN=I)xE8t|K2o*~BwUQ3*?0LVbq4P~m#J6CF|6xo1tQ#*HjS ziHP!mfI@63CBHKyaeQ30@B-6!6xPtwGyj&afyRyfTB0y-Bh2sK*`eX#Z{NN#^39_S z7J)Y4ttH=+-!vZ+880*aR@+VH2x(?0QZLJ$z16NrbRN3?yc6shDt0smhe9|+-==QA zzEkxva2%+t@YUYdMzLd?yFED(7M+8qi6~*sl$Ol#=s7`7a|%_EGx@Kr*i>^R-mcGu z!{^l#Aa8C!Gz<7DkmnOdi-E1AJ_oi^1av4gD2PBh3qRFy2CKMVy>=}>3QPR*sMqD2 z=tqyV8`)K1nv4`X0!_fU4J+?WP#M@ogqndIe(iSfn+hk@WLGo$NV~Elyb_0JhXdzE zUSB>IaqdPF@mN4-{CqFREgj(PoL$IftELOwhalZP%z)aZUtL9H9#!`WSP zusQ1guyFtiB2v1PP8n5rOa+Q_B}UAv|MTdH)-$i{tS)O_j*01al+8Pc@&#GnW(f%i zNESpyQcFrqWO*a)u>5ka{xhgO<_vBItA-w6ev!tejo;=B zFM<)))aB;l7{MTSr2jhV67L#H^aX|8%Hk{;5^sEOk=7vt5%A5^z*0MzcXhXK?bXtP zF)t{HA(Pg>wUkqp w [label = "weston_head_is_connected()"]; - c << w [label = "bool"]; - c => w [label = "weston_head_is_enabled()"]; - c << w [label = "bool"]; - c => w [label = "weston_head_is_device_changed()"]; - c << w [label = "bool"]; - c abox c [label = "If the head needs enabling, create an output."]; - c abox c [label = "If the head needs disabling, disable the output or destroy the output."]; - c >> w; -} diff --git a/doc/sphinx/toc/libweston/images/react-to-heads-changed.png b/doc/sphinx/toc/libweston/images/react-to-heads-changed.png deleted file mode 100644 index ba1b8132b82bc609283cbe8a8c35bdbfc090fe03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27241 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

B{ID)0=_E(+a7O8Pc?P@(CePraNrRS8~36&9qs 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

!+x^QFxqC=C?Ap9 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 E0q%_0$^ZZW diff --git a/fullscreen-shell/fullscreen-shell.c b/fullscreen-shell/fullscreen-shell.c deleted file mode 100644 index 17b25a3..0000000 --- a/fullscreen-shell/fullscreen-shell.c +++ /dev/null @@ -1,941 +0,0 @@ -/* - * Copyright © 2013 Jason Ekstrand - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include "compositor/weston.h" -#include "fullscreen-shell-unstable-v1-server-protocol.h" -#include "shared/helpers.h" - -struct fullscreen_shell { - struct wl_client *client; - struct wl_listener client_destroyed; - struct weston_compositor *compositor; - /* XXX: missing compositor destroy listener - * https://gitlab.freedesktop.org/wayland/weston/issues/299 - */ - - struct weston_layer layer; - struct wl_list output_list; - struct wl_listener output_created_listener; - - struct wl_listener seat_created_listener; - - /* List of one surface per client, presented for the NULL output - * - * This is implemented as a list in case someone fixes the shell - * implementation to support more than one client. - */ - struct wl_list default_surface_list; /* struct fs_client_surface::link */ -}; - -struct fs_output { - struct fullscreen_shell *shell; - struct wl_list link; - - struct weston_output *output; - struct wl_listener output_destroyed; - - struct { - struct weston_surface *surface; - struct wl_listener surface_destroyed; - struct wl_resource *mode_feedback; - - int presented_for_mode; - enum zwp_fullscreen_shell_v1_present_method method; - int32_t framerate; - } pending; - - struct weston_surface *surface; - struct wl_listener surface_destroyed; - struct weston_view *view; - struct weston_view *black_view; - struct weston_transform transform; /* matrix from x, y */ - - int presented_for_mode; - enum zwp_fullscreen_shell_v1_present_method method; - uint32_t framerate; -}; - -struct pointer_focus_listener { - struct fullscreen_shell *shell; - struct wl_listener pointer_focus; - struct wl_listener seat_caps; - struct wl_listener seat_destroyed; -}; - -struct fs_client_surface { - struct weston_surface *surface; - enum zwp_fullscreen_shell_v1_present_method method; - struct wl_list link; /* struct fullscreen_shell::default_surface_list */ - struct wl_listener surface_destroyed; -}; - -static void -remove_default_surface(struct fs_client_surface *surf) -{ - wl_list_remove(&surf->surface_destroyed.link); - wl_list_remove(&surf->link); - free(surf); -} - -static void -default_surface_destroy_listener(struct wl_listener *listener, void *data) -{ - struct fs_client_surface *surf; - - surf = container_of(listener, struct fs_client_surface, surface_destroyed); - - remove_default_surface(surf); -} - -static void -replace_default_surface(struct fullscreen_shell *shell, struct weston_surface *surface, - enum zwp_fullscreen_shell_v1_present_method method) -{ - struct fs_client_surface *surf, *prev = NULL; - - if (!wl_list_empty(&shell->default_surface_list)) - prev = container_of(shell->default_surface_list.prev, - struct fs_client_surface, link); - - surf = zalloc(sizeof *surf); - if (!surf) - return; - - surf->surface = surface; - surf->method = method; - - if (prev) - remove_default_surface(prev); - - wl_list_insert(shell->default_surface_list.prev, &surf->link); - - surf->surface_destroyed.notify = default_surface_destroy_listener; - wl_signal_add(&surface->destroy_signal, &surf->surface_destroyed); -} - -static void -pointer_focus_changed(struct wl_listener *listener, void *data) -{ - struct weston_pointer *pointer = data; - - if (pointer->focus && pointer->focus->surface->resource) - weston_seat_set_keyboard_focus(pointer->seat, pointer->focus->surface); -} - -static void -seat_caps_changed(struct wl_listener *l, void *data) -{ - struct weston_seat *seat = data; - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct pointer_focus_listener *listener; - struct fs_output *fsout; - - listener = container_of(l, struct pointer_focus_listener, seat_caps); - - /* no pointer */ - if (pointer) { - if (!listener->pointer_focus.link.prev) { - wl_signal_add(&pointer->focus_signal, - &listener->pointer_focus); - } - } else { - if (listener->pointer_focus.link.prev) { - wl_list_remove(&listener->pointer_focus.link); - } - } - - if (keyboard && keyboard->focus != NULL) { - wl_list_for_each(fsout, &listener->shell->output_list, link) { - if (fsout->surface) { - weston_seat_set_keyboard_focus(seat, fsout->surface); - return; - } - } - } -} - -static void -seat_destroyed(struct wl_listener *l, void *data) -{ - struct pointer_focus_listener *listener; - - listener = container_of(l, struct pointer_focus_listener, - seat_destroyed); - - free(listener); -} - -static void -seat_created(struct wl_listener *l, void *data) -{ - struct weston_seat *seat = data; - struct pointer_focus_listener *listener; - - listener = zalloc(sizeof *listener); - if (!listener) - return; - - listener->shell = container_of(l, struct fullscreen_shell, - seat_created_listener); - listener->pointer_focus.notify = pointer_focus_changed; - listener->seat_caps.notify = seat_caps_changed; - listener->seat_destroyed.notify = seat_destroyed; - - wl_signal_add(&seat->destroy_signal, &listener->seat_destroyed); - wl_signal_add(&seat->updated_caps_signal, &listener->seat_caps); - - seat_caps_changed(&listener->seat_caps, seat); -} - -static void -black_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) -{ -} - -static struct weston_view * -create_black_surface(struct weston_compositor *ec, struct fs_output *fsout, - float x, float y, int w, int h) -{ - struct weston_surface *surface = NULL; - struct weston_view *view; - - surface = weston_surface_create(ec); - if (surface == NULL) { - weston_log("no memory\n"); - return NULL; - } - view = weston_view_create(surface); - if (!view) { - weston_surface_destroy(surface); - weston_log("no memory\n"); - return NULL; - } - - surface->committed = black_surface_committed; - surface->committed_private = fsout; - weston_surface_set_color(surface, 0.0f, 0.0f, 0.0f, 1.0f); - 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); - - weston_surface_set_size(surface, w, h); - weston_view_set_position(view, x, y); - - return view; -} - -static void -fs_output_set_surface(struct fs_output *fsout, struct weston_surface *surface, - enum zwp_fullscreen_shell_v1_present_method method, - int32_t framerate, int presented_for_mode); -static void -fs_output_apply_pending(struct fs_output *fsout); -static void -fs_output_clear_pending(struct fs_output *fsout); - -static void -fs_output_destroy(struct fs_output *fsout) -{ - fs_output_set_surface(fsout, NULL, 0, 0, 0); - fs_output_clear_pending(fsout); - - wl_list_remove(&fsout->link); - - if (fsout->output) - wl_list_remove(&fsout->output_destroyed.link); -} - -static void -output_destroyed(struct wl_listener *listener, void *data) -{ - struct fs_output *output = container_of(listener, - struct fs_output, - output_destroyed); - fs_output_destroy(output); -} - -static void -surface_destroyed(struct wl_listener *listener, void *data) -{ - struct fs_output *fsout = container_of(listener, - struct fs_output, - surface_destroyed); - fsout->surface = NULL; - fsout->view = NULL; - wl_list_remove(&fsout->transform.link); - wl_list_init(&fsout->transform.link); -} - -static void -pending_surface_destroyed(struct wl_listener *listener, void *data) -{ - struct fs_output *fsout = container_of(listener, - struct fs_output, - pending.surface_destroyed); - fsout->pending.surface = NULL; -} - -static void -configure_presented_surface(struct weston_surface *surface, int32_t sx, - int32_t sy); - -static struct fs_output * -fs_output_create(struct fullscreen_shell *shell, struct weston_output *output) -{ - struct fs_output *fsout; - struct fs_client_surface *surf; - - fsout = zalloc(sizeof *fsout); - if (!fsout) - return NULL; - - fsout->shell = shell; - wl_list_insert(&shell->output_list, &fsout->link); - - fsout->output = output; - fsout->output_destroyed.notify = output_destroyed; - wl_signal_add(&output->destroy_signal, &fsout->output_destroyed); - - fsout->surface_destroyed.notify = surface_destroyed; - fsout->pending.surface_destroyed.notify = pending_surface_destroyed; - fsout->black_view = create_black_surface(shell->compositor, fsout, - output->x, output->y, - output->width, output->height); - fsout->black_view->surface->is_mapped = true; - fsout->black_view->is_mapped = true; - weston_layer_entry_insert(&shell->layer.view_list, - &fsout->black_view->layer_link); - wl_list_init(&fsout->transform.link); - - if (!wl_list_empty(&shell->default_surface_list)) { - surf = container_of(shell->default_surface_list.prev, - struct fs_client_surface, link); - - fs_output_set_surface(fsout, surf->surface, surf->method, 0, 0); - configure_presented_surface(surf->surface, 0, 0); - } - - return fsout; -} - -static struct fs_output * -fs_output_for_output(struct weston_output *output) -{ - struct wl_listener *listener; - - if (!output) - return NULL; - - listener = wl_signal_get(&output->destroy_signal, output_destroyed); - - return container_of(listener, struct fs_output, output_destroyed); -} - -static void -restore_output_mode(struct weston_output *output) -{ - if (output && output->original_mode) - weston_output_mode_switch_to_native(output); -} - -/* - * Returns the bounding box of a surface and all its sub-surfaces, - * in surface-local coordinates. */ -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->width, - surface->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->width, - subsurface->surface->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 void -fs_output_center_view(struct fs_output *fsout) -{ - int32_t surf_x, surf_y, surf_width, surf_height; - float x, y; - struct weston_output *output = fsout->output; - - surface_subsurfaces_boundingbox(fsout->view->surface, &surf_x, &surf_y, - &surf_width, &surf_height); - - x = output->x + (output->width - surf_width) / 2 - surf_x / 2; - y = output->y + (output->height - surf_height) / 2 - surf_y / 2; - - weston_view_set_position(fsout->view, x, y); -} - -static void -fs_output_scale_view(struct fs_output *fsout, float width, float height) -{ - float x, y; - int32_t surf_x, surf_y, surf_width, surf_height; - struct weston_matrix *matrix; - struct weston_view *view = fsout->view; - struct weston_output *output = fsout->output; - - surface_subsurfaces_boundingbox(view->surface, &surf_x, &surf_y, - &surf_width, &surf_height); - - if (output->width == surf_width && output->height == surf_height) { - weston_view_set_position(view, - fsout->output->x - surf_x, - fsout->output->y - surf_y); - } else { - matrix = &fsout->transform.matrix; - weston_matrix_init(matrix); - - weston_matrix_scale(matrix, width / surf_width, - height / surf_height, 1); - wl_list_remove(&fsout->transform.link); - wl_list_insert(&fsout->view->geometry.transformation_list, - &fsout->transform.link); - - x = output->x + (output->width - width) / 2 - surf_x; - y = output->y + (output->height - height) / 2 - surf_y; - - weston_view_set_position(view, x, y); - } -} - -static void -fs_output_configure(struct fs_output *fsout, struct weston_surface *surface); - -static void -fs_output_configure_simple(struct fs_output *fsout, - struct weston_surface *configured_surface) -{ - struct weston_output *output = fsout->output; - float output_aspect, surface_aspect; - int32_t surf_x, surf_y, surf_width, surf_height; - - if (fsout->pending.surface == configured_surface) - fs_output_apply_pending(fsout); - - assert(fsout->view); - - restore_output_mode(fsout->output); - - wl_list_remove(&fsout->transform.link); - wl_list_init(&fsout->transform.link); - - surface_subsurfaces_boundingbox(fsout->view->surface, - &surf_x, &surf_y, - &surf_width, &surf_height); - - output_aspect = (float) output->width / (float) output->height; - surface_aspect = (float) surf_width / (float) surf_height; - - switch (fsout->method) { - case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT: - case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_CENTER: - fs_output_center_view(fsout); - break; - - case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_ZOOM: - if (output_aspect < surface_aspect) - fs_output_scale_view(fsout, - output->width, - output->width / surface_aspect); - else - fs_output_scale_view(fsout, - output->height * surface_aspect, - output->height); - break; - - case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_ZOOM_CROP: - if (output_aspect < surface_aspect) - fs_output_scale_view(fsout, - output->height * surface_aspect, - output->height); - else - fs_output_scale_view(fsout, - output->width, - output->width / surface_aspect); - break; - - case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_STRETCH: - fs_output_scale_view(fsout, output->width, output->height); - break; - default: - break; - } - - weston_view_set_position(fsout->black_view, - fsout->output->x - surf_x, - fsout->output->y - surf_y); - weston_surface_set_size(fsout->black_view->surface, - fsout->output->width, - fsout->output->height); -} - -static void -fs_output_configure_for_mode(struct fs_output *fsout, - struct weston_surface *configured_surface) -{ - int32_t surf_x, surf_y, surf_width, surf_height; - struct weston_mode mode; - int ret; - - if (fsout->pending.surface != configured_surface) { - /* Nothing to really reconfigure. We'll just recenter the - * view in case they played with subsurfaces */ - fs_output_center_view(fsout); - return; - } - - /* We have a pending surface */ - surface_subsurfaces_boundingbox(fsout->pending.surface, - &surf_x, &surf_y, - &surf_width, &surf_height); - - /* The actual output mode is in physical units. We need to - * transform the surface size to physical unit size by flipping and - * possibly scaling it. - */ - switch (fsout->output->transform) { - case WL_OUTPUT_TRANSFORM_90: - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - case WL_OUTPUT_TRANSFORM_270: - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - mode.width = surf_height * fsout->output->native_scale; - mode.height = surf_width * fsout->output->native_scale; - break; - - case WL_OUTPUT_TRANSFORM_NORMAL: - case WL_OUTPUT_TRANSFORM_FLIPPED: - case WL_OUTPUT_TRANSFORM_180: - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - default: - mode.width = surf_width * fsout->output->native_scale; - mode.height = surf_height * fsout->output->native_scale; - } - mode.flags = 0; - mode.refresh = fsout->pending.framerate; - - ret = weston_output_mode_switch_to_temporary(fsout->output, &mode, - fsout->output->native_scale); - - if (ret != 0) { - /* The mode switch failed. Clear the pending and - * reconfigure as per normal */ - if (fsout->pending.mode_feedback) { - zwp_fullscreen_shell_mode_feedback_v1_send_mode_failed( - fsout->pending.mode_feedback); - wl_resource_destroy(fsout->pending.mode_feedback); - fsout->pending.mode_feedback = NULL; - } - - fs_output_clear_pending(fsout); - return; - } - - if (fsout->pending.mode_feedback) { - zwp_fullscreen_shell_mode_feedback_v1_send_mode_successful( - fsout->pending.mode_feedback); - wl_resource_destroy(fsout->pending.mode_feedback); - fsout->pending.mode_feedback = NULL; - } - - fs_output_apply_pending(fsout); - - weston_view_set_position(fsout->view, - fsout->output->x - surf_x, - fsout->output->y - surf_y); -} - -static void -fs_output_configure(struct fs_output *fsout, - struct weston_surface *surface) -{ - if (fsout->pending.surface == surface) { - if (fsout->pending.presented_for_mode) - fs_output_configure_for_mode(fsout, surface); - else - fs_output_configure_simple(fsout, surface); - } else { - if (fsout->presented_for_mode) - fs_output_configure_for_mode(fsout, surface); - else - fs_output_configure_simple(fsout, surface); - } - - weston_output_schedule_repaint(fsout->output); -} - -static void -configure_presented_surface(struct weston_surface *surface, int32_t sx, - int32_t sy) -{ - struct fullscreen_shell *shell = surface->committed_private; - struct fs_output *fsout; - - if (surface->committed != configure_presented_surface) - return; - - wl_list_for_each(fsout, &shell->output_list, link) - if (fsout->surface == surface || - fsout->pending.surface == surface) - fs_output_configure(fsout, surface); -} - -static void -fs_output_apply_pending(struct fs_output *fsout) -{ - assert(fsout->pending.surface); - - if (fsout->surface && fsout->surface != fsout->pending.surface) { - wl_list_remove(&fsout->surface_destroyed.link); - - weston_view_destroy(fsout->view); - fsout->view = NULL; - - if (wl_list_empty(&fsout->surface->views)) { - fsout->surface->committed = NULL; - fsout->surface->committed_private = NULL; - } - - fsout->surface = NULL; - } - - fsout->method = fsout->pending.method; - fsout->framerate = fsout->pending.framerate; - fsout->presented_for_mode = fsout->pending.presented_for_mode; - - if (fsout->surface != fsout->pending.surface) { - fsout->surface = fsout->pending.surface; - - fsout->view = weston_view_create(fsout->surface); - if (!fsout->view) { - weston_log("no memory\n"); - return; - } - fsout->view->is_mapped = true; - - wl_signal_add(&fsout->surface->destroy_signal, - &fsout->surface_destroyed); - weston_layer_entry_insert(&fsout->shell->layer.view_list, - &fsout->view->layer_link); - } - - fs_output_clear_pending(fsout); -} - -static void -fs_output_clear_pending(struct fs_output *fsout) -{ - if (!fsout->pending.surface) - return; - - if (fsout->pending.mode_feedback) { - zwp_fullscreen_shell_mode_feedback_v1_send_present_cancelled( - fsout->pending.mode_feedback); - wl_resource_destroy(fsout->pending.mode_feedback); - fsout->pending.mode_feedback = NULL; - } - - wl_list_remove(&fsout->pending.surface_destroyed.link); - fsout->pending.surface = NULL; -} - -static void -fs_output_set_surface(struct fs_output *fsout, struct weston_surface *surface, - enum zwp_fullscreen_shell_v1_present_method method, - int32_t framerate, int presented_for_mode) -{ - fs_output_clear_pending(fsout); - - if (surface) { - if (!surface->committed) { - surface->committed = configure_presented_surface; - surface->committed_private = fsout->shell; - } - - fsout->pending.surface = surface; - wl_signal_add(&fsout->pending.surface->destroy_signal, - &fsout->pending.surface_destroyed); - - fsout->pending.method = method; - fsout->pending.framerate = framerate; - fsout->pending.presented_for_mode = presented_for_mode; - } else if (fsout->surface) { - /* we clear immediately */ - wl_list_remove(&fsout->surface_destroyed.link); - - weston_view_destroy(fsout->view); - fsout->view = NULL; - - if (wl_list_empty(&fsout->surface->views)) { - fsout->surface->committed = NULL; - fsout->surface->committed_private = NULL; - } - - fsout->surface = NULL; - - weston_output_schedule_repaint(fsout->output); - } -} - -static void -fullscreen_shell_release(struct wl_client *client, - struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -fullscreen_shell_present_surface(struct wl_client *client, - struct wl_resource *resource, - struct wl_resource *surface_res, - uint32_t method, - struct wl_resource *output_res) -{ - struct fullscreen_shell *shell = - wl_resource_get_user_data(resource); - struct weston_output *output; - struct weston_surface *surface; - struct weston_seat *seat; - struct fs_output *fsout; - - surface = surface_res ? wl_resource_get_user_data(surface_res) : NULL; - - switch(method) { - case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT: - case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_CENTER: - case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_ZOOM: - case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_ZOOM_CROP: - case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_STRETCH: - break; - default: - wl_resource_post_error(resource, - ZWP_FULLSCREEN_SHELL_V1_ERROR_INVALID_METHOD, - "Invalid presentation method"); - } - - if (output_res) { - output = weston_head_from_resource(output_res)->output; - fsout = fs_output_for_output(output); - fs_output_set_surface(fsout, surface, method, 0, 0); - } else { - replace_default_surface(shell, surface, method); - - wl_list_for_each(fsout, &shell->output_list, link) - fs_output_set_surface(fsout, surface, method, 0, 0); - } - - if (surface) { - wl_list_for_each(seat, &shell->compositor->seat_list, link) { - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(seat); - - if (keyboard && !keyboard->focus) - weston_seat_set_keyboard_focus(seat, surface); - } - } -} - -static void -mode_feedback_destroyed(struct wl_resource *resource) -{ - struct fs_output *fsout = wl_resource_get_user_data(resource); - - fsout->pending.mode_feedback = NULL; -} - -static void -fullscreen_shell_present_surface_for_mode(struct wl_client *client, - struct wl_resource *resource, - struct wl_resource *surface_res, - struct wl_resource *output_res, - int32_t framerate, - uint32_t feedback_id) -{ - struct fullscreen_shell *shell = - wl_resource_get_user_data(resource); - struct weston_output *output; - struct weston_surface *surface; - struct weston_seat *seat; - struct fs_output *fsout; - - output = weston_head_from_resource(output_res)->output; - fsout = fs_output_for_output(output); - - if (surface_res == NULL) { - fs_output_set_surface(fsout, NULL, 0, 0, 0); - return; - } - - surface = wl_resource_get_user_data(surface_res); - fs_output_set_surface(fsout, surface, 0, framerate, 1); - - fsout->pending.mode_feedback = - wl_resource_create(client, - &zwp_fullscreen_shell_mode_feedback_v1_interface, - 1, feedback_id); - wl_resource_set_implementation(fsout->pending.mode_feedback, NULL, - fsout, mode_feedback_destroyed); - - wl_list_for_each(seat, &shell->compositor->seat_list, link) { - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(seat); - - if (keyboard && !keyboard->focus) - weston_seat_set_keyboard_focus(seat, surface); - } -} - -struct zwp_fullscreen_shell_v1_interface fullscreen_shell_implementation = { - fullscreen_shell_release, - fullscreen_shell_present_surface, - fullscreen_shell_present_surface_for_mode, -}; - -static void -output_created(struct wl_listener *listener, void *data) -{ - struct fullscreen_shell *shell; - - shell = container_of(listener, struct fullscreen_shell, - output_created_listener); - - fs_output_create(shell, data); -} - -static void -client_destroyed(struct wl_listener *listener, void *data) -{ - struct fullscreen_shell *shell = container_of(listener, - struct fullscreen_shell, - client_destroyed); - shell->client = NULL; -} - -static void -bind_fullscreen_shell(struct wl_client *client, void *data, uint32_t version, - uint32_t id) -{ - struct fullscreen_shell *shell = data; - struct wl_resource *resource; - - if (shell->client != NULL && shell->client != client) - return; - else if (shell->client == NULL) { - shell->client = client; - wl_client_add_destroy_listener(client, &shell->client_destroyed); - } - - resource = wl_resource_create(client, - &zwp_fullscreen_shell_v1_interface, - 1, id); - wl_resource_set_implementation(resource, - &fullscreen_shell_implementation, - shell, NULL); - - if (shell->compositor->capabilities & WESTON_CAP_CURSOR_PLANE) - zwp_fullscreen_shell_v1_send_capability(resource, - ZWP_FULLSCREEN_SHELL_V1_CAPABILITY_CURSOR_PLANE); - - if (shell->compositor->capabilities & WESTON_CAP_ARBITRARY_MODES) - zwp_fullscreen_shell_v1_send_capability(resource, - ZWP_FULLSCREEN_SHELL_V1_CAPABILITY_ARBITRARY_MODES); -} - -WL_EXPORT int -wet_shell_init(struct weston_compositor *compositor, - int *argc, char *argv[]) -{ - struct fullscreen_shell *shell; - struct weston_seat *seat; - struct weston_output *output; - - shell = zalloc(sizeof *shell); - if (shell == NULL) - return -1; - - shell->compositor = compositor; - wl_list_init(&shell->default_surface_list); - - shell->client_destroyed.notify = client_destroyed; - - weston_layer_init(&shell->layer, compositor); - weston_layer_set_position(&shell->layer, - WESTON_LAYER_POSITION_FULLSCREEN); - - wl_list_init(&shell->output_list); - shell->output_created_listener.notify = output_created; - wl_signal_add(&compositor->output_created_signal, - &shell->output_created_listener); - wl_list_for_each(output, &compositor->output_list, link) - fs_output_create(shell, output); - - shell->seat_created_listener.notify = seat_created; - wl_signal_add(&compositor->seat_created_signal, - &shell->seat_created_listener); - wl_list_for_each(seat, &compositor->seat_list, link) - seat_created(&shell->seat_created_listener, seat); - - wl_global_create(compositor->wl_display, - &zwp_fullscreen_shell_v1_interface, 1, shell, - bind_fullscreen_shell); - - return 0; -} diff --git a/fullscreen-shell/meson.build b/fullscreen-shell/meson.build deleted file mode 100644 index bde06db..0000000 --- a/fullscreen-shell/meson.build +++ /dev/null @@ -1,16 +0,0 @@ -if get_option('shell-fullscreen') - srcs_shell_fullscreen = [ - 'fullscreen-shell.c', - fullscreen_shell_unstable_v1_server_protocol_h, - fullscreen_shell_unstable_v1_protocol_c, - ] - shared_library( - 'fullscreen-shell', - srcs_shell_fullscreen, - include_directories: common_inc, - dependencies: dep_libweston_public, - name_prefix: '', - install: true, - install_dir: dir_module_weston - ) -endif diff --git a/include/config.h b/include/config.h deleted file mode 100644 index b270e04..0000000 --- a/include/config.h +++ /dev/null @@ -1,260 +0,0 @@ -/* config.h. Generated from config.h.in by configure. */ -/* config.h.in. Generated from configure.ac by autoheader. */ - -/* Build the Wayland clients */ -/* #undef BUILD_CLIENTS */ - -/* Build the DRM compositor */ -/* #undef BUILD_DRM_COMPOSITOR */ - -/* Build the fbdev compositor */ -#define BUILD_FBDEV_COMPOSITOR 1 - -/* Build the headless compositor */ -/* #undef BUILD_HEADLESS_COMPOSITOR */ - -/* Build the RDP compositor */ -/* #undef BUILD_RDP_COMPOSITOR */ - -/* Build the vaapi recorder */ -/* #undef BUILD_VAAPI_RECORDER */ - -/* Build the Wayland (nested) compositor */ -/* #undef BUILD_WAYLAND_COMPOSITOR */ - -/* Build the wcap tools */ -/* #undef BUILD_WCAP_TOOLS */ - -/* Build the X11 compositor */ -/* #undef BUILD_X11_COMPOSITOR */ - -/* Build the X server launcher */ -/* #undef BUILD_XWAYLAND */ - -/* Build Weston with EGL support */ -/* #define ENABLE_EGL */ - -/* Build Weston with JUnit output support */ -/* #undef ENABLE_JUNIT_XML */ - -/* Have cairoegl */ -/* #undef HAVE_CAIRO_EGL */ - -/* Build with dbus support */ -/* #undef HAVE_DBUS */ - -/* Define to 1 if you have the header file. */ -#define HAVE_DLFCN_H 1 - -/* libdrm supports modifiers */ -/* #undef HAVE_DRM_ADDFB2_MODIFIERS */ - -/* libdrm supports atomic API */ -/* #undef HAVE_DRM_ATOMIC */ - - -/* libdrm supports modifier advertisement */ -/* #undef HAVE_DRM_FORMATS_BLOB */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_EXECINFO_H */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_FREERDP_VERSION_H */ - -/* gbm supports import with modifiers */ -/* #undef HAVE_GBM_FD_IMPORT */ - -/* GBM supports modifiers */ -/* #undef HAVE_GBM_MODIFIERS */ - -/* Define to 1 if you have the `initgroups' function. */ -#define HAVE_INITGROUPS 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_INTTYPES_H 1 - -/* Have jpeglib */ -/* #undef HAVE_JPEG */ - -/* Have lcms support */ -/* #undef HAVE_LCMS */ - -/* Build etnaviv dmabuf client */ -/* #undef HAVE_LIBDRM_ETNAVIV */ - -/* Build freedreno dmabuf client */ -/* #undef HAVE_LIBDRM_FREEDRENO */ - -/* Build intel dmabuf client */ -/* #undef HAVE_LIBDRM_INTEL */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_LINUX_SYNC_FILE_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_MEMORY_H 1 - -/* Define to 1 if you have the `mkostemp' function. */ -#define HAVE_MKOSTEMP 1 - -/* Have pango */ -/* #undef HAVE_PANGO */ - -/* Define to 1 if you have the `posix_fallocate' function. */ -/* #undef HAVE_POSIX_FALLOCATE */ - -/* Define to 1 if you have the header file. */ -#define HAVE_STDINT_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STDLIB_H 1 - -/* Define to 1 if you have the `strchrnul' function. */ -#define HAVE_STRCHRNUL 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STRINGS_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STRING_H 1 - -/* SURFACE_BITS_CMD has bmp field */ -/* #undef HAVE_SURFACE_BITS_BMP */ - -/* Have systemdlogin */ -/* #undef HAVE_SYSTEMD_LOGIN */ - -/* Have systemdlogin >= 209 */ -/* #undef HAVE_SYSTEMD_LOGIN_209 */ - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_STAT_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_TYPES_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_UNISTD_H 1 - -/* Have webp */ -/* #undef HAVE_WEBP */ - -/* libxcb supports XKB protocol */ -/* #undef HAVE_XCB_XKB */ - -/* Define if xkbcommon is 0.5.0 or newer */ -/* #undef HAVE_XKBCOMMON_COMPOSE */ - -/* Define to the subdirectory where libtool stores uninstalled libraries. */ -#define LT_OBJDIR ".libs/" - -/* Define to 1 if `major', `minor', and `makedev' are declared in . - */ -/* #undef MAJOR_IN_MKDEV */ - -/* Define to 1 if `major', `minor', and `makedev' are declared in - . */ -/* #undef MAJOR_IN_SYSMACROS */ - -/* Name of package */ -#define PACKAGE "weston" - -/* Define to the address where bug reports for this package should be sent. */ -#define PACKAGE_BUGREPORT "https://gitlab.freedesktop.org/wayland/weston/issues/" - -/* Define to the full name of this package. */ -#define PACKAGE_NAME "weston" - -/* Define to the full name and version of this package. */ -#define PACKAGE_STRING "weston 5.0.0" - -/* Define to the one symbol short name of this package. */ -#define PACKAGE_TARNAME "weston" - -/* Define to the home page for this package. */ -#define PACKAGE_URL "https://wayland.freedesktop.org" - -/* Define to the version of this package. */ -#define PACKAGE_VERSION "5.0.0" - -/* Define to 1 if you have the ANSI C header files. */ -#define STDC_HEADERS 1 - -/* Build the systemd sd_notify support */ -/* #undef SYSTEMD_NOTIFY_SUPPORT */ - -/* Use the GLESv2 GL cairo backend */ -/* #undef USE_CAIRO_GLESV2 */ - -/* Use resize memory pool as a performance optimization */ -#define USE_RESIZE_POOL 1 - -#define MAJOR_IN_SYSMACROS 1 - -/* Enable extensions on AIX 3, Interix. */ -#ifndef _ALL_SOURCE -# define _ALL_SOURCE 1 -#endif -/* Enable GNU extensions on systems that have them. */ -#ifndef _GNU_SOURCE -# define _GNU_SOURCE 1 -#endif -/* Enable threading extensions on Solaris. */ -#ifndef _POSIX_PTHREAD_SEMANTICS -# define _POSIX_PTHREAD_SEMANTICS 1 -#endif -/* Enable extensions on HP NonStop. */ -#ifndef _TANDEM_SOURCE -# define _TANDEM_SOURCE 1 -#endif -/* Enable general extensions on Solaris. */ -#ifndef __EXTENSIONS__ -# define __EXTENSIONS__ 1 -#endif - - -/* Version number of package */ -#define VERSION "9.0.0" - -/* The default backend to load, if not wayland nor x11. */ -#define WESTON_NATIVE_BACKEND "drm-backend.so" - -/* The default desktop shell client to load. */ -#define WESTON_SHELL_CLIENT "weston-desktop-shell" - -#define BINDIR "/system/bin" - -#define LIBEXECDIR "/system/bin" - -#define DATADIR "data" -#ifdef __aarch64__ -#define LIBWESTON_MODULEDIR "/system/lib64" -#define MODULEDIR "/system/lib64" -#else -#define LIBWESTON_MODULEDIR "/system/lib" -#define MODULEDIR "/system/lib" -#endif - -/* Enable large inode numbers on Mac OS X 10.5. */ -#ifndef _DARWIN_USE_64_BIT_INODE -# define _DARWIN_USE_64_BIT_INODE 1 -#endif - -/* Number of bits in a file offset, on hosts where this is settable. */ -/* #undef _FILE_OFFSET_BITS */ - -/* Define for large files, on AIXstyle hosts. */ -/* #undef _LARGE_FILES */ - -/* Define to 1 if on MINIX. */ -/* #undef _MINIX */ - -/* Define to 2 if the system does not provide POSIX.1 features except with - this defined. */ -/* #undef _POSIX_1_SOURCE */ - -/* Define to 1 if you need to in order for `stat' and other things to work. */ -/* #undef _POSIX_SOURCE */ - -// #define BUILD_DRM_GBM 1 diff --git a/include/libweston-desktop/libweston-desktop.h b/include/libweston-desktop/libweston-desktop.h deleted file mode 100644 index 3e7ac73..0000000 --- a/include/libweston-desktop/libweston-desktop.h +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright © 2016 Quentin "Sardem FF7" Glidic - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#ifndef WESTON_DESKTOP_H -#define WESTON_DESKTOP_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -enum weston_desktop_surface_edge { - WESTON_DESKTOP_SURFACE_EDGE_NONE = 0, - WESTON_DESKTOP_SURFACE_EDGE_TOP = 1, - WESTON_DESKTOP_SURFACE_EDGE_BOTTOM = 2, - WESTON_DESKTOP_SURFACE_EDGE_LEFT = 4, - WESTON_DESKTOP_SURFACE_EDGE_TOP_LEFT = 5, - WESTON_DESKTOP_SURFACE_EDGE_BOTTOM_LEFT = 6, - WESTON_DESKTOP_SURFACE_EDGE_RIGHT = 8, - WESTON_DESKTOP_SURFACE_EDGE_TOP_RIGHT = 9, - WESTON_DESKTOP_SURFACE_EDGE_BOTTOM_RIGHT = 10, -}; - -struct weston_desktop; -struct weston_desktop_client; -struct weston_desktop_surface; - -struct weston_desktop_api { - size_t struct_size; - void (*ping_timeout)(struct weston_desktop_client *client, - void *user_data); - void (*pong)(struct weston_desktop_client *client, - void *user_data); - - void (*surface_added)(struct weston_desktop_surface *surface, - void *user_data); - void (*surface_removed)(struct weston_desktop_surface *surface, - void *user_data); - void (*committed)(struct weston_desktop_surface *surface, - int32_t sx, int32_t sy, void *user_data); - void (*show_window_menu)(struct weston_desktop_surface *surface, - struct weston_seat *seat, int32_t x, int32_t y, - void *user_data); - void (*set_parent)(struct weston_desktop_surface *surface, - struct weston_desktop_surface *parent, - void *user_data); - void (*move)(struct weston_desktop_surface *surface, - struct weston_seat *seat, uint32_t serial, void *user_data); - void (*resize)(struct weston_desktop_surface *surface, - struct weston_seat *seat, uint32_t serial, - enum weston_desktop_surface_edge edges, void *user_data); - void (*fullscreen_requested)(struct weston_desktop_surface *surface, - bool fullscreen, - struct weston_output *output, - void *user_data); - void (*maximized_requested)(struct weston_desktop_surface *surface, - bool maximized, void *user_data); - void (*minimized_requested)(struct weston_desktop_surface *surface, - void *user_data); - - /** Position suggestion for an Xwayland window - * - * X11 applications assume they can position their windows as necessary, - * which is not possible in Wayland where positioning is driven by the - * shell alone. This function is used to relay absolute position wishes - * from Xwayland clients to the shell. - * - * This is particularly used for mapping windows at specified locations, - * e.g. via the commonly used '-geometry' command line option. In such - * case, a call to surface_added() is immediately followed by - * xwayland_position() if the X11 application specified a position. - * The committed() call that will map the window occurs later, so it - * is recommended to usually store and honour the given position for - * windows that are not yet mapped. - * - * Calls to this function may happen also at other times. - * - * The given coordinates are in the X11 window system coordinate frame - * relative to the X11 root window. Care should be taken to ensure the - * window gets mapped to coordinates that correspond to the proposed - * position from the X11 client perspective. - * - * \param surface The surface in question. - * \param x The absolute X11 coordinate for x. - * \param y The absolute X11 coordinate for y. - * \param user_data The user_data argument passed in to - * weston_desktop_create(). - * - * This callback can be NULL. - */ - void (*set_xwayland_position)(struct weston_desktop_surface *surface, - int32_t x, int32_t y, void *user_data); -}; - -void -weston_seat_break_desktop_grabs(struct weston_seat *seat); - -struct weston_desktop * -weston_desktop_create(struct weston_compositor *compositor, - const struct weston_desktop_api *api, void *user_data); -void -weston_desktop_destroy(struct weston_desktop *desktop); - -struct wl_client * -weston_desktop_client_get_client(struct weston_desktop_client *client); -void -weston_desktop_client_for_each_surface(struct weston_desktop_client *client, - void (*callback)(struct weston_desktop_surface *surface, void *user_data), - void *user_data); -int -weston_desktop_client_ping(struct weston_desktop_client *client); - -bool -weston_surface_is_desktop_surface(struct weston_surface *surface); -struct weston_desktop_surface * -weston_surface_get_desktop_surface(struct weston_surface *surface); - -void -weston_desktop_surface_set_user_data(struct weston_desktop_surface *self, - void *user_data); -struct weston_view * -weston_desktop_surface_create_view(struct weston_desktop_surface *surface); -void -weston_desktop_surface_unlink_view(struct weston_view *view); -void -weston_desktop_surface_propagate_layer(struct weston_desktop_surface *surface); -void -weston_desktop_surface_set_activated(struct weston_desktop_surface *surface, - bool activated); -void -weston_desktop_surface_set_fullscreen(struct weston_desktop_surface *surface, - bool fullscreen); -void -weston_desktop_surface_set_maximized(struct weston_desktop_surface *surface, - bool maximized); -void -weston_desktop_surface_set_resizing(struct weston_desktop_surface *surface, - bool resized); -void -weston_desktop_surface_set_size(struct weston_desktop_surface *surface, - int32_t width, int32_t height); -void -weston_desktop_surface_close(struct weston_desktop_surface *surface); -void -weston_desktop_surface_add_metadata_listener(struct weston_desktop_surface *surface, - struct wl_listener *listener); - -void * -weston_desktop_surface_get_user_data(struct weston_desktop_surface *surface); -struct weston_desktop_client * -weston_desktop_surface_get_client(struct weston_desktop_surface *surface); -struct weston_surface * -weston_desktop_surface_get_surface(struct weston_desktop_surface *surface); -const char * -weston_desktop_surface_get_title(struct weston_desktop_surface *surface); -const char * -weston_desktop_surface_get_app_id(struct weston_desktop_surface *surface); -pid_t -weston_desktop_surface_get_pid(struct weston_desktop_surface *surface); -bool -weston_desktop_surface_get_activated(struct weston_desktop_surface *surface); -bool -weston_desktop_surface_get_maximized(struct weston_desktop_surface *surface); -bool -weston_desktop_surface_get_fullscreen(struct weston_desktop_surface *surface); -bool -weston_desktop_surface_get_resizing(struct weston_desktop_surface *surface); -struct weston_geometry -weston_desktop_surface_get_geometry(struct weston_desktop_surface *surface); -struct weston_size -weston_desktop_surface_get_max_size(struct weston_desktop_surface *surface); -struct weston_size -weston_desktop_surface_get_min_size(struct weston_desktop_surface *surface); - -#ifdef __cplusplus -} -#endif - -#endif /* WESTON_DESKTOP_H */ diff --git a/include/libweston/backend-drm.h b/include/libweston/backend-drm.h deleted file mode 100644 index 350eeb0..0000000 --- a/include/libweston/backend-drm.h +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2011 Intel Corporation - * Copyright © 2015 Giulio Camuffo - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_COMPOSITOR_DRM_H -#define WESTON_COMPOSITOR_DRM_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define WESTON_DRM_BACKEND_CONFIG_VERSION 3 - -struct libinput_device; - -enum weston_drm_backend_output_mode { - /** The output is disabled */ - WESTON_DRM_BACKEND_OUTPUT_OFF, - /** The output will use the current active mode */ - WESTON_DRM_BACKEND_OUTPUT_CURRENT, - /** The output will use the preferred mode. A modeline can be provided - * by setting weston_backend_output_config::modeline in the form of - * "WIDTHxHEIGHT" or in the form of an explicit modeline calculated - * using e.g. the cvt tool. If a valid modeline is supplied it will be - * used, if invalid or NULL the preferred available mode will be used. */ - WESTON_DRM_BACKEND_OUTPUT_PREFERRED, -}; - -#define WESTON_DRM_OUTPUT_API_NAME "weston_drm_output_api_v1" - -struct weston_drm_output_api { - /** The mode to be used by the output. Refer to the documentation - * of WESTON_DRM_BACKEND_OUTPUT_PREFERRED for details. - * - * Returns 0 on success, -1 on failure. - */ - int (*set_mode)(struct weston_output *output, - enum weston_drm_backend_output_mode mode, - const char *modeline); - - /** The pixel format to be used by the output. Valid values are: - * - NULL - The format set at backend creation time will be used; - * - "xrgb8888"; - * - "rgb565" - * - "xrgb2101010" - */ - void (*set_gbm_format)(struct weston_output *output, - const char *gbm_format); - - /** The seat to be used by the output. Set to NULL to use the - * default seat. - */ - void (*set_seat)(struct weston_output *output, - const char *seat); -}; - -static inline const struct weston_drm_output_api * -weston_drm_output_get_api(struct weston_compositor *compositor) -{ - const void *api; - api = weston_plugin_api_get(compositor, WESTON_DRM_OUTPUT_API_NAME, - sizeof(struct weston_drm_output_api)); - - return (const struct weston_drm_output_api *)api; -} - -#define WESTON_DRM_VIRTUAL_OUTPUT_API_NAME "weston_drm_virtual_output_api_v1" - -struct drm_fb; -typedef int (*submit_frame_cb)(struct weston_output *output, int fd, - int stride, struct drm_fb *buffer); - -struct weston_drm_virtual_output_api { - /** Create virtual output. - * This is a low-level function, where the caller is expected to wrap - * the weston_output function pointers as necessary to make the virtual - * output useful. The caller must set up output make, model, serial, - * physical size, the mode list and current mode. - * - * Returns output on success, NULL on failure. - */ - struct weston_output* (*create_output)(struct weston_compositor *c, - char *name); - - /** Set pixel format same as drm_output set_gbm_format(). - * - * Returns the set format. - */ - uint32_t (*set_gbm_format)(struct weston_output *output, - const char *gbm_format); - - /** Set a callback to be called when the DRM-backend has drawn a new - * frame and submits it for display. - * The callback will deliver a buffer to the virtual output's the - * owner and assumes the buffer is now reserved for the owner. The - * callback is called in virtual output repaint function. - * The caller must call buffer_released() and finish_frame(). - * - * The callback parameters are output, FD and stride (bytes) of dmabuf, - * and buffer (drm_fb) pointer. - * The callback returns 0 on success, -1 on failure. - * - * The submit_frame_cb callback hook is responsible for closing the fd - * if it returns success. One needs to call the buffer release and - * finish frame functions if and only if this hook returns success. - */ - void (*set_submit_frame_cb)(struct weston_output *output, - submit_frame_cb cb); - - /** Get fd for renderer fence. - * The returned fence signals when the renderer job has completed and - * the buffer is fully drawn. - * - * Returns fd on success, -1 on failure. - */ - int (*get_fence_sync_fd)(struct weston_output *output); - - /** Notify that the caller has finished using buffer */ - void (*buffer_released)(struct drm_fb *fb); - - /** Notify finish frame - * This function allows the output repainting mechanism to advance to - * the next frame. - */ - void (*finish_frame)(struct weston_output *output, - struct timespec *stamp, - uint32_t presented_flags); -}; - -static inline const struct weston_drm_virtual_output_api * -weston_drm_virtual_output_get_api(struct weston_compositor *compositor) -{ - const void *api; - api = weston_plugin_api_get(compositor, - WESTON_DRM_VIRTUAL_OUTPUT_API_NAME, - sizeof(struct weston_drm_virtual_output_api)); - return (const struct weston_drm_virtual_output_api *)api; -} - -/** The backend configuration struct. - * - * weston_drm_backend_config contains the configuration used by a DRM - * backend. - */ -struct weston_drm_backend_config { - struct weston_backend_config base; - - /** The tty to be used. Set to 0 to use the current tty. */ - int tty; - - /** Whether to use the pixman renderer instead of the OpenGL ES renderer. */ - bool use_pixman; - - /** The seat to be used for input and output. - * - * If seat_id is NULL, the seat is taken from XDG_SEAT environment - * variable. If neither is set, "seat0" is used. The backend will - * take ownership of the seat_id pointer and will free it on - * backend destruction. - */ - char *seat_id; - - /** The pixel format of the framebuffer to be used. - * - * Valid values are: - * - NULL - The default format ("xrgb8888") will be used; - * - "xrgb8888"; - * - "rgb565" - * - "xrgb2101010" - * The backend will take ownership of the format pointer and will free - * it on backend destruction. - */ - char *gbm_format; - - /** Callback used to configure input devices. - * - * This function will be called by the backend when a new input device - * needs to be configured. - * If NULL the device will use the default configuration. - */ - void (*configure_device)(struct weston_compositor *compositor, - struct libinput_device *device); - - /** Maximum duration for a pageflip event to arrive, after which the - * compositor will consider the DRM driver crashed and will try to exit - * cleanly. - * - * It is exprimed in milliseconds, 0 means disabled. */ - uint32_t pageflip_timeout; - - /** Specific DRM device to open - * - * A DRM device name, like "card0", to open. If NULL, use heuristics - * based on seat names and boot_vga to find the right device. - */ - char *specific_device; - - /** Use shadow buffer if using Pixman-renderer. */ - bool use_pixman_shadow; - - /** Allow compositor to start without input devices. */ - bool continue_without_input; -}; - -#ifdef __cplusplus -} -#endif - -#endif /* WESTON_COMPOSITOR_DRM_H */ diff --git a/include/libweston/backend-fbdev.h b/include/libweston/backend-fbdev.h deleted file mode 100644 index 4dbdce7..0000000 --- a/include/libweston/backend-fbdev.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright © 2016 Benoit Gschwind - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_COMPOSITOR_FBDEV_H -#define WESTON_COMPOSITOR_FBDEV_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#include - -#define WESTON_FBDEV_BACKEND_CONFIG_VERSION 2 - -struct libinput_device; - -struct weston_fbdev_backend_config { - struct weston_backend_config base; - - int tty; - char *device; - - /** Callback used to configure input devices. - * - * This function will be called by the backend when a new input device - * needs to be configured. - * If NULL the device will use the default configuration. - */ - void (*configure_device)(struct weston_compositor *compositor, - struct libinput_device *device); - - /** The seat to be used for input and output. - * - * If seat_id is NULL, the seat is taken from XDG_SEAT environment - * variable. If neither is set, "seat0" is used. The backend will - * take ownership of the seat_id pointer and will free it on - * backend destruction. - */ - char *seat_id; -}; - -#ifdef __cplusplus -} -#endif - -#endif /* WESTON_COMPOSITOR_FBDEV_H */ diff --git a/include/libweston/backend-headless.h b/include/libweston/backend-headless.h deleted file mode 100644 index 1f53835..0000000 --- a/include/libweston/backend-headless.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright © 2016 Benoit Gschwind - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_COMPOSITOR_HEADLESS_H -#define WESTON_COMPOSITOR_HEADLESS_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#include - -#define WESTON_HEADLESS_BACKEND_CONFIG_VERSION 2 - -struct weston_headless_backend_config { - struct weston_backend_config base; - - /** Whether to use the pixman renderer, default is no-op */ - bool use_pixman; - - /** Whether to use the GL renderer, conflicts with use_pixman */ - bool use_gl; -}; - -#ifdef __cplusplus -} -#endif - -#endif /* WESTON_COMPOSITOR_HEADLESS_H */ diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h deleted file mode 100644 index b354250..0000000 --- a/include/libweston/backend-rdp.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright © 2016 Benoit Gschwind - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_COMPOSITOR_RDP_H -#define WESTON_COMPOSITOR_RDP_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -#define WESTON_RDP_OUTPUT_API_NAME "weston_rdp_output_api_v1" - -struct weston_rdp_output_api { - /** Initialize a RDP output with specified width and height. - * - * Returns 0 on success, -1 on failure. - */ - int (*output_set_size)(struct weston_output *output, - int width, int height); -}; - -static inline const struct weston_rdp_output_api * -weston_rdp_output_get_api(struct weston_compositor *compositor) -{ - const void *api; - api = weston_plugin_api_get(compositor, WESTON_RDP_OUTPUT_API_NAME, - sizeof(struct weston_rdp_output_api)); - - return (const struct weston_rdp_output_api *)api; -} - -#define WESTON_RDP_BACKEND_CONFIG_VERSION 2 - -struct weston_rdp_backend_config { - struct weston_backend_config base; - char *bind_address; - int port; - char *rdp_key; - char *server_cert; - char *server_key; - int env_socket; - int no_clients_resize; - int force_no_compression; -}; - -#ifdef __cplusplus -} -#endif - -#endif /* WESTON_COMPOSITOR_RDP_H */ diff --git a/include/libweston/backend-wayland.h b/include/libweston/backend-wayland.h deleted file mode 100644 index 6d65610..0000000 --- a/include/libweston/backend-wayland.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright © 2016 Benoit Gschwind - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_COMPOSITOR_WAYLAND_H -#define WESTON_COMPOSITOR_WAYLAND_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#define WESTON_WAYLAND_BACKEND_CONFIG_VERSION 2 - -struct weston_wayland_backend_config { - struct weston_backend_config base; - bool use_pixman; - bool use_tde; // OHOS tde - bool sprawl; - char *display_name; - bool fullscreen; - char *cursor_theme; - int cursor_size; -}; - -#ifdef __cplusplus -} -#endif - -#endif /* WESTON_COMPOSITOR_WAYLAND_H */ diff --git a/include/libweston/backend-x11.h b/include/libweston/backend-x11.h deleted file mode 100644 index 1556e8e..0000000 --- a/include/libweston/backend-x11.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright © 2016 Benoit Gschwind - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_COMPOSITOR_X11_H -#define WESTON_COMPOSITOR_X11_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#include - -#define WESTON_X11_BACKEND_CONFIG_VERSION 2 - -struct weston_x11_backend_config { - struct weston_backend_config base; - - bool fullscreen; - bool no_input; - - /** Whether to use the pixman renderer instead of the OpenGL ES renderer. */ - bool use_pixman; -}; - -#ifdef __cplusplus -} -#endif - -#endif /* WESTON_COMPOSITOR_X11_H_ */ diff --git a/include/libweston/config-parser.h b/include/libweston/config-parser.h deleted file mode 100644 index d82197b..0000000 --- a/include/libweston/config-parser.h +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef CONFIGPARSER_H -#define CONFIGPARSER_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -#define WESTON_CONFIG_FILE_ENV_VAR "WESTON_CONFIG_FILE" - -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; - char 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_color(struct weston_config_section *section, - const char *key, - uint32_t *color, uint32_t default_color); -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, - bool *value, bool default_value); - -const char * -weston_config_get_name_from_env(void); - -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/include/libweston/libweston.h b/include/libweston/libweston.h deleted file mode 100755 index 03a3d0c..0000000 --- a/include/libweston/libweston.h +++ /dev/null @@ -1,2083 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2012, 2017, 2018 Collabora, Ltd. - * Copyright © 2017, 2018 General Electric Company - * - * 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. - */ -#define LOG_TAG "WAYLAND_SERVER" // OHOS logcat - -#ifndef _WAYLAND_SYSTEM_COMPOSITOR_H_ -#define _WAYLAND_SYSTEM_COMPOSITOR_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include -#include -#include // OHOS hilog -#include - -#define WL_HIDE_DEPRECATED -#include - -#include -#include - -struct weston_geometry { - int32_t x, y; - int32_t width, height; -}; - -struct weston_position { - int32_t x, y; -}; - -struct weston_size { - int32_t width, height; -}; - -struct weston_transform { - struct weston_matrix matrix; - struct wl_list link; -}; - -/** 2D device coordinates normalized to [0, 1] range */ -struct weston_point2d_device_normalized { - double x; - double y; -}; - -struct weston_surface; -struct weston_buffer; -struct shell_surface; -struct weston_seat; -struct weston_output; -struct input_method; -struct weston_pointer; -struct linux_dmabuf_buffer; -struct weston_recorder; -struct weston_pointer_constraint; -struct ro_anonymous_file; - -enum weston_keyboard_modifier { - MODIFIER_CTRL = (1 << 0), - MODIFIER_ALT = (1 << 1), - MODIFIER_SUPER = (1 << 2), - MODIFIER_SHIFT = (1 << 3), -}; - -enum weston_keyboard_locks { - WESTON_NUM_LOCK = (1 << 0), - WESTON_CAPS_LOCK = (1 << 1), -}; - -enum weston_led { - LED_NUM_LOCK = (1 << 0), - LED_CAPS_LOCK = (1 << 1), - LED_SCROLL_LOCK = (1 << 2), -}; - -enum weston_mode_aspect_ratio { - /** The picture aspect ratio values, for the aspect_ratio field of - * weston_mode. The values here, are taken from - * DRM_MODE_PICTURE_ASPECT_* from drm_mode.h. - */ - WESTON_MODE_PIC_AR_NONE = 0, /* DRM_MODE_PICTURE_ASPECT_NONE */ - WESTON_MODE_PIC_AR_4_3 = 1, /* DRM_MODE_PICTURE_ASPECT_4_3 */ - WESTON_MODE_PIC_AR_16_9 = 2, /* DRM_MODE_PICTURE_ASPECT_16_9 */ - WESTON_MODE_PIC_AR_64_27 = 3, /* DRM_MODE_PICTURE_ASPECT_64_27 */ - WESTON_MODE_PIC_AR_256_135 = 4, /* DRM_MODE_PICTURE_ASPECT_256_135*/ -}; - -enum weston_surface_protection_mode { - WESTON_SURFACE_PROTECTION_MODE_RELAXED, - WESTON_SURFACE_PROTECTION_MODE_ENFORCED -}; - -struct weston_mode { - uint32_t flags; - enum weston_mode_aspect_ratio aspect_ratio; - int32_t width, height; - uint32_t refresh; - struct wl_list link; -}; - -struct weston_animation { - void (*frame)(struct weston_animation *animation, - struct weston_output *output, - const struct timespec *time); - 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; - struct timespec timestamp; - uint32_t clip; -}; - -struct weston_output_zoom { - bool active; - float increment; - float level; - float max_level; - float trans_x, trans_y; - struct { - double x, y; - } current; - struct weston_seat *seat; - struct weston_animation animation_z; - struct weston_spring spring_z; - struct wl_listener motion_listener; -}; - -/* bit compatible with drm definitions. */ -enum dpms_enum { - WESTON_DPMS_ON, - WESTON_DPMS_STANDBY, - WESTON_DPMS_SUSPEND, - WESTON_DPMS_OFF -}; - -/* enum for content protection requests/status - * - * This enum represents the content protection requests and statuses in - * libweston and its enum values correspond to those of 'type' enum defined in - * weston-content-protection protocol. The values should exactly match to the - * values of the 'type' enum of the protocol. - */ - -enum weston_hdcp_protection { - WESTON_HDCP_DISABLE = 0, - WESTON_HDCP_ENABLE_TYPE_0, - WESTON_HDCP_ENABLE_TYPE_1 -}; - -/** Represents a head, usually a display connector - * - * \rst - See :ref:`libweston-head`. \endrst - * - * \ingroup head - */ -struct weston_head { - struct weston_compositor *compositor; /**< owning compositor */ - struct wl_list compositor_link; /**< in weston_compositor::head_list */ - struct wl_signal destroy_signal; /**< destroy callbacks */ - - struct weston_output *output; /**< the output driving this head */ - struct wl_list output_link; /**< in weston_output::head_list */ - - struct wl_list resource_list; /**< wl_output protocol objects */ - struct wl_global *global; /**< wl_output global */ - - struct wl_list xdg_output_resource_list; /**< xdg_output protocol objects */ - - int32_t mm_width; /**< physical image width in mm */ - int32_t mm_height; /**< physical image height in mm */ - - /** WL_OUTPUT_TRANSFORM enum to apply to match native orientation */ - uint32_t transform; - - char *make; /**< monitor manufacturer (PNP ID) */ - char *model; /**< monitor model */ - char *serial_number; /**< monitor serial */ - uint32_t subpixel; /**< enum wl_output_subpixel */ - bool connection_internal; /**< embedded monitor (e.g. laptop) */ - bool device_changed; /**< monitor information has changed */ - - char *name; /**< head name, e.g. connector name */ - bool connected; /**< is physically connected */ - bool non_desktop; /**< non-desktop display, e.g. HMD */ - - /** Current content protection status */ - enum weston_hdcp_protection current_protection; -}; - -/** Content producer for heads - * - * \rst - See :ref:`libweston-output`. \endrst - * - * \ingroup output - */ -struct weston_output { - uint32_t id; - char *name; - - /** Matches the lifetime from the user perspective */ - struct wl_signal user_destroy_signal; - - void *renderer_state; - - struct wl_list link; - struct weston_compositor *compositor; - - /** From global to output buffer coordinates. */ - struct weston_matrix matrix; - /** From output buffer to global coordinates. */ - struct weston_matrix inverse_matrix; - - struct wl_list animation_list; - int32_t x, y, width, height; - - /** Output area in global coordinates, simple rect */ - pixman_region32_t region; - - /** True if damage has occurred since the last repaint for this output; - * if set, a repaint will eventually occur. */ - bool repaint_needed; - - /** Used only between repaint_begin and repaint_cancel. */ - bool repainted; - - /** State of the repaint loop */ - enum { - REPAINT_NOT_SCHEDULED = 0, /**< idle; no repaint will occur */ - REPAINT_BEGIN_FROM_IDLE, /**< start_repaint_loop scheduled */ - REPAINT_SCHEDULED, /**< repaint is scheduled to occur */ - REPAINT_AWAITING_COMPLETION, /**< last repaint not yet finished */ - } repaint_status; - - /** If repaint_status is REPAINT_SCHEDULED, contains the time the - * next repaint should be run */ - struct timespec next_repaint; - - /** For cancelling the idle_repaint callback on output destruction. */ - struct wl_event_source *idle_repaint_source; - - struct weston_output_zoom zoom; - int dirty; - struct wl_signal frame_signal; - struct wl_signal destroy_signal; /**< sent when disabled */ - int move_x, move_y; - struct timespec frame_time; /* presentation timestamp */ - uint64_t msc; /* media stream counter */ - int disable_planes; - int destroying; - struct wl_list feedback_list; - - 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; - - struct wl_list head_list; /**< List of driven weston_heads */ - - enum weston_hdcp_protection desired_protection; - enum weston_hdcp_protection current_protection; - bool allow_protection; - - int (*start_repaint_loop)(struct weston_output *output); - int (*repaint)(struct weston_output *output, - pixman_region32_t *damage, - void *repaint_data); - void (*destroy)(struct weston_output *output); - void (*assign_planes)(struct weston_output *output, void *repaint_data); - 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); - - uint16_t gamma_size; - void (*set_gamma)(struct weston_output *output, - uint16_t size, - uint16_t *r, - uint16_t *g, - uint16_t *b); - - bool enabled; /**< is in the output_list, not pending list */ - int scale; - - int (*enable)(struct weston_output *output); - int (*disable)(struct weston_output *output); - - /** Attach a head in the backend - * - * @param output The output to attach to. - * @param head The head to attach. - * @return 0 on success, -1 on failure. - * - * Do anything necessary to account for a new head being attached to - * the output, and check any conditions possible. On failure, both - * the head and the output must be left as before the call. - * - * Libweston core will add the head to the head_list after a successful - * call. - */ - int (*attach_head)(struct weston_output *output, - struct weston_head *head); - - /** Detach a head in the backend - * - * @param output The output to detach from. - * @param head The head to detach. - * - * Do any clean-up necessary to detach this head from the output. - * The head has already been removed from the output's head_list. - */ - void (*detach_head)(struct weston_output *output, - struct weston_head *head); -}; - -enum weston_pointer_motion_mask { - WESTON_POINTER_MOTION_ABS = 1 << 0, - WESTON_POINTER_MOTION_REL = 1 << 1, - WESTON_POINTER_MOTION_REL_UNACCEL = 1 << 2, -}; - -struct weston_pointer_motion_event { - uint32_t mask; - struct timespec time; - double x; - double y; - double dx; - double dy; - double dx_unaccel; - double dy_unaccel; -}; - -struct weston_pointer_axis_event { - uint32_t axis; - double value; - bool has_discrete; - int32_t discrete; -}; - -struct weston_pointer_grab; -struct weston_pointer_grab_interface { - void (*focus)(struct weston_pointer_grab *grab); - void (*motion)(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_motion_event *event); - void (*button)(struct weston_pointer_grab *grab, - const struct timespec *time, - uint32_t button, uint32_t state); - void (*axis)(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_axis_event *event); - void (*axis_source)(struct weston_pointer_grab *grab, uint32_t source); - void (*frame)(struct weston_pointer_grab *grab); - 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, - const struct timespec *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, - const struct timespec *time, - int touch_id, - wl_fixed_t sx, - wl_fixed_t sy); - void (*up)(struct weston_touch_grab *grab, - const struct timespec *time, - int touch_id); - void (*motion)(struct weston_touch_grab *grab, - const struct timespec *time, - int touch_id, - wl_fixed_t sx, - wl_fixed_t sy); - void (*frame)(struct weston_touch_grab *grab); - 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; - uint32_t dnd_actions; - enum wl_data_device_manager_dnd_action preferred_dnd_action; - bool in_ask; -}; - -struct weston_data_source { - struct wl_resource *resource; - struct wl_signal destroy_signal; - struct wl_array mime_types; - struct weston_data_offer *offer; - struct weston_seat *seat; - bool accepted; - bool actions_set; - bool set_selection; - uint32_t dnd_actions; - enum wl_data_device_manager_dnd_action current_dnd_action; - enum wl_data_device_manager_dnd_action compositor_action; - - 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_client { - struct wl_list link; - struct wl_client *client; - struct wl_list pointer_resources; - struct wl_list relative_pointer_resources; -}; - -struct weston_pointer { - struct weston_seat *seat; - - struct wl_list pointer_clients; - - struct weston_view *focus; - struct weston_pointer_client *focus_client; - uint32_t focus_serial; - struct wl_listener focus_view_listener; - struct wl_listener focus_resource_listener; - struct wl_signal focus_signal; - struct wl_signal motion_signal; - struct wl_signal destroy_signal; - - struct weston_view *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; - struct timespec grab_time; - - wl_fixed_t x, y; - wl_fixed_t sx, sy; - uint32_t button_count; - - struct wl_listener output_destroy_listener; - - struct wl_list timestamps_list; -}; - -/** libinput style calibration matrix - * - * See https://wayland.freedesktop.org/libinput/doc/latest/absolute_axes.html - * and libinput_device_config_calibration_set_matrix(). - */ -struct weston_touch_device_matrix { - float m[6]; -}; - -struct weston_touch_device; - -/** Operations for a calibratable touchscreen */ -struct weston_touch_device_ops { - /** Get the associated output if existing. */ - struct weston_output *(*get_output)(struct weston_touch_device *device); - - /** Get the name of the associated head if existing. */ - const char * - (*get_calibration_head_name)(struct weston_touch_device *device); - - /** Retrieve the current calibration matrix. */ - void (*get_calibration)(struct weston_touch_device *device, - struct weston_touch_device_matrix *cal); - - /** Set a new calibration matrix. */ - void (*set_calibration)(struct weston_touch_device *device, - const struct weston_touch_device_matrix *cal); -}; - -enum weston_touch_mode { - /** Normal touch event handling */ - WESTON_TOUCH_MODE_NORMAL, - - /** Prepare moving to WESTON_TOUCH_MODE_CALIB. - * - * Move to WESTON_TOUCH_MODE_CALIB as soon as no touches are down on - * any seat. Until then, all touch events are routed normally. - */ - WESTON_TOUCH_MODE_PREP_CALIB, - - /** Calibration mode - * - * Only a single weston_touch_device forwards events to the calibrator - * all other touch device cause a calibrator "wrong device" event to - * be sent. - */ - WESTON_TOUCH_MODE_CALIB, - - /** Prepare moving to WESTON_TOUCH_MODE_NORMAL. - * - * Move to WESTON_TOUCH_MODE_NORMAL as soon as no touches are down on - * any seat. Until then, touch events are routed as in - * WESTON_TOUCH_MODE_CALIB except "wrong device" events are not sent. - */ - WESTON_TOUCH_MODE_PREP_NORMAL -}; - -/** Represents a physical touchscreen input device */ -struct weston_touch_device { - char *syspath; /**< unique name */ - - struct weston_touch *aggregate; /**< weston_touch this is part of */ - struct wl_list link; /**< in weston_touch::device_list */ - struct wl_signal destroy_signal; /**< destroy notifier */ - - void *backend_data; /**< backend-specific private */ - - const struct weston_touch_device_ops *ops; - struct weston_touch_device_matrix saved_calibration; -}; - -/** Represents a set of touchscreen devices aggregated under a seat */ -struct weston_touch { - struct weston_seat *seat; - - struct wl_list device_list; /* struct weston_touch_device::link */ - - struct wl_list resource_list; - struct wl_list focus_resource_list; - struct weston_view *focus; - struct wl_listener focus_view_listener; - struct wl_listener focus_resource_listener; - uint32_t focus_serial; - struct wl_signal focus_signal; - - uint32_t num_tp; - - 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; - struct timespec grab_time; - - struct wl_list timestamps_list; -}; - -void -weston_pointer_motion_to_abs(struct weston_pointer *pointer, - struct weston_pointer_motion_event *event, - wl_fixed_t *x, wl_fixed_t *y); - -void -weston_pointer_send_motion(struct weston_pointer *pointer, - const struct timespec *time, - struct weston_pointer_motion_event *event); -bool -weston_pointer_has_focus_resource(struct weston_pointer *pointer); -void -weston_pointer_send_button(struct weston_pointer *pointer, - const struct timespec *time, - uint32_t button, uint32_t state_w); -void -weston_pointer_send_axis(struct weston_pointer *pointer, - const struct timespec *time, - struct weston_pointer_axis_event *event); -void -weston_pointer_send_axis_source(struct weston_pointer *pointer, - uint32_t source); -void -weston_pointer_send_frame(struct weston_pointer *pointer); - -void -weston_pointer_set_focus(struct weston_pointer *pointer, - struct weston_view *view, - wl_fixed_t sx, wl_fixed_t sy); -void -weston_pointer_clear_focus(struct weston_pointer *pointer); -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_move(struct weston_pointer *pointer, - struct weston_pointer_motion_event *event); -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); -int -/* - * 'mask' and 'value' should be a bitwise mask of one or more - * valued of the weston_keyboard_locks enum. - */ -weston_keyboard_set_locks(struct weston_keyboard *keyboard, - uint32_t mask, uint32_t value); - -void -weston_keyboard_send_key(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, - enum wl_keyboard_key_state state); -void -weston_keyboard_send_modifiers(struct weston_keyboard *keyboard, - uint32_t serial, uint32_t mods_depressed, - uint32_t mods_latched, - uint32_t mods_locked, uint32_t group); - -void -weston_touch_set_focus(struct weston_touch *touch, - struct weston_view *view); -void -weston_touch_start_grab(struct weston_touch *touch, - struct weston_touch_grab *grab); -void -weston_touch_end_grab(struct weston_touch *touch); - -void -weston_touch_send_down(struct weston_touch *touch, const struct timespec *time, - int touch_id, wl_fixed_t x, wl_fixed_t y); -void -weston_touch_send_up(struct weston_touch *touch, const struct timespec *time, - int touch_id); -void -weston_touch_send_motion(struct weston_touch *touch, - const struct timespec *time, int touch_id, - wl_fixed_t x, wl_fixed_t y); -void -weston_touch_send_frame(struct weston_touch *touch); - - -void -weston_seat_set_selection(struct weston_seat *seat, - struct weston_data_source *source, uint32_t serial); - -int -weston_pointer_start_drag(struct weston_pointer *pointer, - struct weston_data_source *source, - struct weston_surface *icon, - struct wl_client *client); -struct weston_xkb_info { - struct xkb_keymap *keymap; - struct ro_anonymous_file *keymap_rofile; - 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; - struct wl_listener focus_resource_listener; - 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; - struct timespec 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_xkb_info *xkb_info; - struct { - struct xkb_state *state; - enum weston_led leds; - } xkb_state; - struct xkb_keymap *pending_keymap; - - struct wl_list timestamps_list; -}; - -struct weston_seat { - struct wl_list base_resource_list; - - struct wl_global *global; - struct weston_pointer *pointer_state; - struct weston_keyboard *keyboard_state; - struct weston_touch *touch_state; - int pointer_device_count; - int keyboard_device_count; - int touch_device_count; - - struct weston_output *output; /* constraint */ - - struct wl_signal destroy_signal; - struct wl_signal updated_caps_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; - - void (*led_update)(struct weston_seat *ws, enum weston_led leds); - - struct input_method *input_method; - char *seat_name; -}; - -enum { - WESTON_COMPOSITOR_ACTIVE, /* normal rendering and events */ - 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 dpms - * to off */ -}; - -struct weston_layer_entry { - struct wl_list link; - struct weston_layer *layer; -}; - -/** - * Higher value means higher in the stack. - * - * These values are based on well-known concepts in a classic desktop - * environment. Third-party modules based on libweston are encouraged to use - * them to integrate better with other projects. - * - * A fully integrated environment can use any value, based on these or not, - * at their discretion. - */ -enum weston_layer_position { - /* - * Special value to make the layer invisible and still rendered. - * This is used by compositors wanting e.g. minimized surfaces to still - * receive frame callbacks. - */ - WESTON_LAYER_POSITION_HIDDEN = 0x00000000, - - /* - * There should always be a background layer with a surface covering - * the visible area. - * - * If the compositor handles the background itself, it should use - * BACKGROUND. - * - * If the compositor supports runtime-loadable modules to set the - * background, it should put a solid color surface at (BACKGROUND - 1) - * and modules must use BACKGROUND. - */ - WESTON_LAYER_POSITION_BACKGROUND = 0x00000002, - - /* For "desktop widgets" and applications like conky. */ - WESTON_LAYER_POSITION_BOTTOM_UI = 0x30000000, - - /* For regular applications, only one layer should have this value - * to ensure proper stacking control. */ - WESTON_LAYER_POSITION_NORMAL = 0x50000000, - - /* For desktop UI, like panels. */ - WESTON_LAYER_POSITION_UI = 0x80000000, - - /* For fullscreen applications that should cover UI. */ - WESTON_LAYER_POSITION_FULLSCREEN = 0xb0000000, - - /* For special UI like on-screen keyboard that fullscreen applications - * will need. */ - WESTON_LAYER_POSITION_TOP_UI = 0xe0000000, - - /* For the lock surface. */ - WESTON_LAYER_POSITION_LOCK = 0xffff0000, - - /* Values reserved for libweston internal usage */ - WESTON_LAYER_POSITION_CURSOR = 0xfffffffe, - WESTON_LAYER_POSITION_FADE = 0xffffffff, -}; - -struct weston_layer { - struct weston_compositor *compositor; - struct wl_list link; /* weston_compositor::layer_list */ - enum weston_layer_position position; - pixman_box32_t mask; - struct weston_layer_entry view_list; -}; - -struct weston_plane { - struct weston_compositor *compositor; - pixman_region32_t damage; /**< in global coords */ - 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); - void (*surface_set_color)(struct weston_surface *surface, - float red, float green, - float blue, float alpha); - void (*destroy)(struct weston_compositor *ec); - - - /** See weston_surface_get_content_size() */ - void (*surface_get_content_size)(struct weston_surface *surface, - int *width, int *height); - - /** See weston_surface_copy_content() */ - int (*surface_copy_content)(struct weston_surface *surface, - void *target, size_t size, - int src_x, int src_y, - int width, int height); - - /** See weston_compositor_import_dmabuf() */ - bool (*import_dmabuf)(struct weston_compositor *ec, - struct linux_dmabuf_buffer *buffer); - - /** On error sets num_formats to zero */ - void (*query_dmabuf_formats)(struct weston_compositor *ec, - int **formats, int *num_formats); - - /** On error sets num_modifiers to zero */ - void (*query_dmabuf_modifiers)(struct weston_compositor *ec, - int format, uint64_t **modifiers, - int *num_modifiers); -}; - -enum weston_capability { - /* backend/renderer supports arbitrary rotation */ - WESTON_CAP_ROTATION_ANY = 0x0001, - - /* screencaptures need to be y-flipped */ - WESTON_CAP_CAPTURE_YFLIP = 0x0002, - - /* backend/renderer has a separate cursor plane */ - WESTON_CAP_CURSOR_PLANE = 0x0004, - - /* backend supports setting arbitrary resolutions */ - WESTON_CAP_ARBITRARY_MODES = 0x0008, - - /* renderer supports weston_view_set_mask() clipping */ - WESTON_CAP_VIEW_CLIP_MASK = 0x0010, - - /* renderer supports explicit synchronization */ - WESTON_CAP_EXPLICIT_SYNC = 0x0020, -}; - -/* Configuration struct for a backend. - * - * This struct carries the configuration for a backend, and it's - * passed to the backend's init entry point. The backend will - * likely want to subclass this in order to handle backend specific - * data. - * - * \rststar - * .. note: - * - * Alternate designs were proposed (Feb 2016) for using opaque structures[1] - * and for section+key/value getter/setters[2]. The rationale for selecting - * the transparent structure design is based on several assumptions[3] which - * may require re-evaluating the design choice if they fail to hold. - * - * 1. https://lists.freedesktop.org/archives/wayland-devel/2016-February/026989.html - * 2. https://lists.freedesktop.org/archives/wayland-devel/2016-February/026929.html - * 3. https://lists.freedesktop.org/archives/wayland-devel/2016-February/027228.html - * - * \endrststar - */ -struct weston_backend_config { - /** Major version for the backend-specific config struct - * - * This version must match exactly what the backend expects, otherwise - * the struct is incompatible. - */ - uint32_t struct_version; - - /** Minor version of the backend-specific config struct - * - * This must be set to sizeof(struct backend-specific config). - * If the value here is smaller than what the backend expects, the - * extra config members will assume their default values. - * - * A value greater than what the backend expects is incompatible. - */ - size_t struct_size; -}; - -struct weston_backend; - -/** Callback for saving calibration - * - * \param compositor The compositor. - * \param device The physical touch device to save for. - * \param calibration The new calibration from a client. - * \return -1 on failure, 0 on success. - * - * Failure will prevent taking the new calibration into use. - */ -typedef int (*weston_touch_calibration_save_func)( - struct weston_compositor *compositor, - struct weston_touch_device *device, - const struct weston_touch_device_matrix *calibration); -struct weston_touch_calibrator; - -struct weston_desktop_xwayland; -struct weston_desktop_xwayland_interface; -struct weston_debug_compositor; - -/** Main object, container-like structure which aggregates all other objects. - * - * \ingroup compositor - */ -struct weston_compositor { - struct wl_signal destroy_signal; - - struct wl_display *wl_display; - struct weston_desktop_xwayland *xwayland; - const struct weston_desktop_xwayland_interface *xwayland_interface; - - /* surface signals */ - struct wl_signal create_surface_signal; - 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_signal output_destroyed_signal; - struct wl_signal output_moved_signal; - struct wl_signal output_resized_signal; /* callback argument: resized output */ - - /* Signal for output changes triggered by configuration from frontend - * or head state changes from backend. - */ - struct wl_signal output_heads_changed_signal; /* arg: weston_output */ - - struct wl_signal session_signal; - bool session_active; - - struct weston_layer fade_layer; - struct weston_layer cursor_layer; - - struct wl_list pending_output_list; - struct wl_list output_list; - struct wl_list head_list; /* struct weston_head::compositor_link */ - struct wl_list seat_list; - struct wl_list layer_list; /* struct weston_layer::link */ - struct wl_list view_list; /* struct weston_view::link */ - struct wl_list plane_list; - struct wl_list key_binding_list; - struct wl_list modifier_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 */ - struct wl_event_source *repaint_timer; - - const struct weston_pointer_grab_interface *default_pointer_grab; - - /* Repaint state. */ - struct weston_plane primary_plane; - uint32_t capabilities; /* combination of enum weston_capability */ - - struct weston_renderer *renderer; - - pixman_format_code_t read_format; - - struct weston_backend *backend; - - struct weston_launcher *launcher; - - struct wl_list plugin_api_list; /* struct weston_plugin_api::link */ - - uint32_t output_id_pool; - - struct xkb_rule_names xkb_names; - struct xkb_context *xkb_context; - struct weston_xkb_info *xkb_info; - - int32_t kb_repeat_rate; - int32_t kb_repeat_delay; - - bool vt_switching; - - clockid_t presentation_clock; - int32_t repaint_msec; - - unsigned int activate_serial; - - struct wl_global *pointer_constraints; - - int exit_code; - - void *user_data; - void (*exit)(struct weston_compositor *c); - - /* Whether to let the compositor run without any input device. */ - bool require_input; - - /* Signal for a backend to inform a frontend about possible changes - * in head status. - */ - struct wl_signal heads_changed_signal; - struct wl_event_source *heads_changed_source; - - /* Touchscreen calibrator support: */ - enum weston_touch_mode touch_mode; - struct wl_global *touch_calibration; - weston_touch_calibration_save_func touch_calibration_save; - struct weston_layer calibrator_layer; - struct weston_touch_calibrator *touch_calibrator; - -// OHOS remove logger -// struct weston_log_context *weston_log_ctx; -// struct weston_log_scope *debug_scene; - struct weston_log_scope *timeline; - - struct content_protection *content_protection; -}; - -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_buffer_viewport { - struct { - /* wl_surface.set_buffer_transform */ - uint32_t transform; - - /* wl_surface.set_scaling_factor */ - int32_t scale; - - /* - * If src_width != wl_fixed_from_int(-1), - * then and only then src_* are used. - */ - wl_fixed_t src_x, src_y; - wl_fixed_t src_width, src_height; - } buffer; - - struct { - /* - * If width == -1, the size is inferred from the buffer. - */ - int32_t width, height; - } surface; - - int changed; -}; - -struct weston_buffer_release { - /* The associated zwp_linux_buffer_release_v1 resource. */ - struct wl_resource *resource; - /* How many weston_buffer_release_reference objects point to this - * object. */ - uint32_t ref_count; - /* The fence fd, if any, associated with this release. If the fence fd - * is -1 then this is considered an immediate release. */ - int fence_fd; -}; - -struct weston_buffer_release_reference { - struct weston_buffer_release *buffer_release; - /* Listener for the destruction of the wl_resource associated with the - * referenced buffer_release object. */ - struct wl_listener destroy_listener; -}; - -struct weston_region { - struct wl_resource *resource; - pixman_region32_t region; -}; - -/* Using weston_view transformations - * - * To add a transformation to a view, create a struct weston_transform, and - * add it to the list view->geometry.transformation_list. Whenever you - * change the list, anything under view->geometry, or anything in the - * weston_transforms linked into the list, you must call - * weston_view_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 view-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 view->transform.matrix, and the inverse of it in - * view->transform.inverse. - * - * The list always contains view->transform.position transformation, which - * is the translation by view->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 view->geometry.parent is set, the total transformation of this - * view will be the parent's total transformation and this transformation - * combined: - * Mparent * Mn * ... * M2 * M1 - */ - -struct weston_view { - struct weston_surface *surface; - struct wl_list surface_link; - struct wl_signal destroy_signal; - - struct wl_list link; /* weston_compositor::view_list */ - struct weston_layer_entry layer_link; /* part of geometry */ - struct weston_plane *plane; - - /* For weston_layer inheritance from another view */ - struct weston_view *parent_view; - - unsigned int click_to_activate_serial; - - pixman_region32_t clip; /* See weston_view_damage_below() */ - float alpha; /* part of geometry, see below */ - - 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 */ - - /* struct weston_transform */ - struct wl_list transformation_list; - - /* managed by weston_surface_set_transform_parent() */ - struct weston_view *parent; - struct wl_listener parent_destroy_listener; - struct wl_list child_list; /* geometry.parent_link */ - struct wl_list parent_link; - - /* managed by weston_view_set_mask() */ - bool scissor_enabled; - pixman_region32_t scissor; /* always a simple rect */ - } geometry; - - /* State derived from geometry state, read-only. - * This is updated by weston_view_update_transform(). - */ - struct { - int dirty; - - /* Approximations in global coordinates: - * - boundingbox is guaranteed to include the whole view in - * the smallest possible single rectangle. - * - opaque is guaranteed to be fully opaque, though not - * necessarily include all opaque areas. - */ - 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; - - /* - * The primary output for this view. - * Used for picking the output for driving internal animations on the - * view, inheriting the primary output for related views in shells, etc. - */ - struct weston_output *output; - struct wl_listener output_destroy_listener; - - /* - * A more complete representation of all outputs this surface is - * displayed on. - */ - uint32_t output_mask; - - /* Per-surface Presentation feedback flags, controlled by backend. */ - uint32_t psf_flags; - - bool is_mapped; -}; - -struct weston_surface_state { - /* 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_surface; - /* wl_surface.damage_buffer */ - pixman_region32_t damage_buffer; - - /* 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; - - /* presentation.feedback */ - struct wl_list feedback_list; - - /* wl_surface.set_buffer_transform */ - /* wl_surface.set_scaling_factor */ - /* wp_viewport.set_source */ - /* wp_viewport.set_destination */ - struct weston_buffer_viewport buffer_viewport; - - /* zwp_surface_synchronization_v1.set_acquire_fence */ - int acquire_fence_fd; - - /* zwp_surface_synchronization_v1.get_release */ - struct weston_buffer_release_reference buffer_release_ref; - - /* weston_protected_surface.set_type */ - enum weston_hdcp_protection desired_protection; - - /* weston_protected_surface.enforced/relaxed */ - enum weston_surface_protection_mode protection_mode; -}; - -struct weston_surface_activation_data { - struct weston_surface *surface; - struct weston_seat *seat; -}; - -struct weston_pointer_constraint { - struct wl_list link; - - struct weston_surface *surface; - struct weston_view *view; - struct wl_resource *resource; - struct weston_pointer_grab grab; - struct weston_pointer *pointer; - uint32_t lifetime; - - pixman_region32_t region; - pixman_region32_t region_pending; - bool region_is_pending; - - wl_fixed_t hint_x; - wl_fixed_t hint_y; - wl_fixed_t hint_x_pending; - wl_fixed_t hint_y_pending; - bool hint_is_pending; - - struct wl_listener pointer_destroy_listener; - struct wl_listener surface_destroy_listener; - struct wl_listener surface_commit_listener; - struct wl_listener surface_activate_listener; -}; - -struct weston_surface { - struct wl_resource *resource; - struct wl_signal destroy_signal; /* callback argument: this surface */ - struct weston_compositor *compositor; - struct wl_signal commit_signal; - - /** Damage in local coordinates from the client, for tex upload. */ - pixman_region32_t damage; - - pixman_region32_t opaque; /* part of geometry, see below */ - pixman_region32_t input; - int32_t width, height; - int32_t ref_count; - - /* Not for long-term storage. This exists for book-keeping while - * iterating over surfaces and views - */ - bool touched; - - void *renderer_state; - - struct wl_list views; - - /* - * Which output to vsync this surface to. - * Used to determine whether to send or queue frame events, and for - * other client-visible syncing/throttling tied to the output - * repaint cycle. - */ - 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 wl_list feedback_list; - - struct weston_buffer_reference buffer_ref; - struct weston_buffer_viewport buffer_viewport; - int32_t width_from_buffer; /* before applying viewport */ - int32_t height_from_buffer; - bool keep_buffer; /* for backends to prevent early release */ - - /* wp_viewport resource for this surface */ - struct wl_resource *viewport_resource; - - /* All the pending state, that wl_surface.commit will apply. */ - struct weston_surface_state pending; - - /* Matrices representating of the full transformation between - * buffer and surface coordinates. These matrices are updated - * using the weston_surface_build_buffer_matrix function. */ - struct weston_matrix buffer_to_surface_matrix; - struct weston_matrix surface_to_buffer_matrix; - - /* - * If non-NULL, this function will be called on - * wl_surface::commit after a new buffer has been set up for - * this surface. The integer params are the sx and sy - * parameters supplied to wl_surface::attach. - */ - void (*committed)(struct weston_surface *es, int32_t sx, int32_t sy); - void *committed_private; - int (*get_label)(struct weston_surface *surface, char *buf, size_t len); - - /* 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 */ - - /* - * For tracking protocol role assignments. Different roles may - * have the same configure hook, e.g. in shell.c. Configure hook - * may get reset, this will not. - * XXX: map configure functions 1:1 to roles, and never reset it, - * and replace role_name with configure. - */ - const char *role_name; - - bool is_mapped; - bool is_opaque; - - /* An list of per seat pointer constraints. */ - struct wl_list pointer_constraints; - - /* zwp_surface_synchronization_v1 resource for this surface */ - struct wl_resource *synchronization_resource; - int acquire_fence_fd; - struct weston_buffer_release_reference buffer_release_ref; - - enum weston_hdcp_protection desired_protection; - enum weston_hdcp_protection current_protection; - enum weston_surface_protection_mode protection_mode; - enum wl_surface_type type; // OHOS surface type -}; - -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; - - int has_cached_data; - struct weston_surface_state cached; - struct weston_buffer_reference cached_buffer_ref; - - /* Sub-surface has been reordered; need to apply damage. */ - bool reordered; - - int synchronized; - - /* Used for constructing the view tree */ - struct wl_list unused_views; -}; - -struct protected_surface { - struct weston_surface *surface; - struct wl_listener surface_destroy_listener; - struct wl_list link; - struct wl_resource *protection_resource; - struct content_protection *cp_backptr; -}; - -struct content_protection { - struct weston_compositor *compositor; - struct wl_listener destroy_listener; -// OHOS remove logger -// struct weston_log_scope *debug; - struct wl_list protected_list; - struct wl_event_source *surface_protection_update; -}; - - -enum weston_key_state_update { - STATE_UPDATE_AUTOMATIC, - STATE_UPDATE_NONE, -}; - -enum weston_activate_flag { - WESTON_ACTIVATE_FLAG_NONE = 0, - WESTON_ACTIVATE_FLAG_CONFIGURE = 1 << 0, - WESTON_ACTIVATE_FLAG_CLICKED = 1 << 1, -}; - -void -weston_version(int *major, int *minor, int *micro); - -void -weston_view_set_output(struct weston_view *view, struct weston_output *output); - -void -weston_view_update_transform(struct weston_view *view); - -void -weston_view_geometry_dirty(struct weston_view *view); - -void -weston_view_to_global_float(struct weston_view *view, - float sx, float sy, float *x, float *y); - -void -weston_view_from_global(struct weston_view *view, - int32_t x, int32_t y, int32_t *vx, int32_t *vy); -void -weston_view_from_global_fixed(struct weston_view *view, - wl_fixed_t x, wl_fixed_t y, - wl_fixed_t *vx, wl_fixed_t *vy); - -void -weston_view_activate(struct weston_view *view, - struct weston_seat *seat, - uint32_t flags); - -void -notify_modifiers(struct weston_seat *seat, uint32_t serial); - -void -weston_layer_entry_insert(struct weston_layer_entry *list, - struct weston_layer_entry *entry); -void -weston_layer_entry_remove(struct weston_layer_entry *entry); -void -weston_layer_init(struct weston_layer *layer, - struct weston_compositor *compositor); -void -weston_layer_set_position(struct weston_layer *layer, - enum weston_layer_position position); -void -weston_layer_unset_position(struct weston_layer *layer); - -void -weston_layer_set_mask(struct weston_layer *layer, int x, int y, int width, int height); - -void -weston_layer_set_mask_infinite(struct weston_layer *layer); - -bool -weston_layer_mask_is_infinite(struct weston_layer *layer); - -/* An invalid flag in presented_flags to catch logic errors. */ -#define WP_PRESENTATION_FEEDBACK_INVALID (1U << 31) - -void -weston_output_schedule_repaint(struct weston_output *output); -void -weston_compositor_schedule_repaint(struct weston_compositor *compositor); -void -weston_compositor_damage_all(struct weston_compositor *compositor); -void -weston_compositor_wake(struct weston_compositor *compositor); -void -weston_compositor_sleep(struct weston_compositor *compositor); -struct weston_view * -weston_compositor_pick_view(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_keyboard *keyboard, - const struct timespec *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); -// OHOS remove debugger -//struct weston_binding * -//weston_compositor_add_debug_binding(struct weston_compositor *compositor, -// uint32_t key, -// weston_key_binding_handler_t binding, -// void *data); - -typedef void (*weston_modifier_binding_handler_t)(struct weston_keyboard *keyboard, - enum weston_keyboard_modifier modifier, - void *data); -struct weston_binding * -weston_compositor_add_modifier_binding(struct weston_compositor *compositor, - enum weston_keyboard_modifier modifier, - weston_modifier_binding_handler_t binding, - void *data); - -typedef void (*weston_button_binding_handler_t)(struct weston_pointer *pointer, - const struct timespec *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_touch *touch, - const struct timespec *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_pointer *pointer, - const struct timespec *time, - struct weston_pointer_axis_event *event, - 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); - -void -weston_binding_destroy(struct weston_binding *binding); - -void -weston_install_debug_key_binding(struct weston_compositor *compositor, - uint32_t mod); - -void -weston_compositor_set_default_pointer_grab(struct weston_compositor *compositor, - const struct weston_pointer_grab_interface *interface); - -struct weston_surface * -weston_surface_create(struct weston_compositor *compositor); - -struct weston_view * -weston_view_create(struct weston_surface *surface); - -void -weston_view_destroy(struct weston_view *view); - -void -weston_view_set_position(struct weston_view *view, - float x, float y); - -void -weston_view_set_transform_parent(struct weston_view *view, - struct weston_view *parent); - -void -weston_view_set_mask(struct weston_view *view, - int x, int y, int width, int height); - -void -weston_view_set_mask_infinite(struct weston_view *view); - -bool -weston_view_is_mapped(struct weston_view *view); - -void -weston_view_schedule_repaint(struct weston_view *view); - -bool -weston_surface_is_mapped(struct weston_surface *surface); - -void -weston_surface_set_size(struct weston_surface *surface, - int32_t width, int32_t height); - -void -weston_surface_damage(struct weston_surface *surface); - -void -weston_view_damage_below(struct weston_view *view); - -void -weston_view_unmap(struct weston_view *view); - -void -weston_surface_unmap(struct weston_surface *surface); - -struct weston_surface * -weston_surface_get_main_surface(struct weston_surface *surface); - -int -weston_surface_set_role(struct weston_surface *surface, - const char *role_name, - struct wl_resource *error_resource, - uint32_t error_code); -const char * -weston_surface_get_role(struct weston_surface *surface); - -void -weston_surface_set_label_func(struct weston_surface *surface, - int (*desc)(struct weston_surface *, - char *, size_t)); - -void -weston_surface_get_content_size(struct weston_surface *surface, - int *width, int *height); - -struct weston_geometry -weston_surface_get_bounding_box(struct weston_surface *surface); - -int -weston_surface_copy_content(struct weston_surface *surface, - void *target, size_t size, - int src_x, int src_y, - int width, int height); - -struct weston_buffer * -weston_buffer_from_resource(struct wl_resource *resource); - -void -weston_compositor_get_time(struct timespec *time); - -void -weston_compositor_destroy(struct weston_compositor *ec); - -// OHOS remove logger -//struct weston_compositor * -//weston_compositor_create(struct wl_display *display, -// struct weston_log_context *log_ctx, void *user_data); -struct weston_compositor * -weston_compositor_create(struct wl_display *display, void *user_data); - -bool -weston_compositor_add_destroy_listener_once(struct weston_compositor *compositor, - struct wl_listener *listener, - wl_notify_func_t destroy_handler); - -enum weston_compositor_backend { - WESTON_BACKEND_DRM, - WESTON_BACKEND_FBDEV, - WESTON_BACKEND_HEADLESS, - WESTON_BACKEND_RDP, - WESTON_BACKEND_WAYLAND, - WESTON_BACKEND_X11, -}; - -int -weston_compositor_load_backend(struct weston_compositor *compositor, - enum weston_compositor_backend backend, - struct weston_backend_config *config_base); -void -weston_compositor_exit(struct weston_compositor *ec); -void * -weston_compositor_get_user_data(struct weston_compositor *compositor); -void -weston_compositor_exit_with_code(struct weston_compositor *compositor, - int exit_code); -void -weston_output_update_zoom(struct weston_output *output); -void -weston_output_activate_zoom(struct weston_output *output, - struct weston_seat *seat); -void -weston_output_add_destroy_listener(struct weston_output *output, - struct wl_listener *listener); -struct wl_listener * -weston_output_get_destroy_listener(struct weston_output *output, - wl_notify_func_t notify); -int -weston_compositor_set_xkb_rule_names(struct weston_compositor *ec, - struct xkb_rule_names *names); - -/* String literal of spaces, the same width as the timestamp. */ -#define STAMP_SPACE " " - -/** - * \ingroup wlog - */ -// OHOS remove logger -//typedef int (*log_func_t)(const char *fmt, va_list ap); -//void -//weston_log_set_handler(log_func_t log, log_func_t cont); -//int -//weston_log(const char *fmt, ...) -// __attribute__ ((format (printf, 1, 2))); -//int -//weston_log_continue(const char *fmt, ...) -// __attribute__ ((format (printf, 1, 2))); -// OHOS logcat -#define weston_log(fmt, ...) (HILOG_INFO(LOG_CORE, fmt, ##__VA_ARGS__)) -#define weston_vlog vprintf -#define weston_log_continue(fmt, ...) (HILOG_DEBUG(LOG_CORE, fmt, ##__VA_ARGS__)) - - -enum weston_screenshooter_outcome { - WESTON_SCREENSHOOTER_SUCCESS, - WESTON_SCREENSHOOTER_NO_MEMORY, - WESTON_SCREENSHOOTER_BAD_BUFFER -}; - -typedef void (*weston_screenshooter_done_func_t)(void *data, - enum weston_screenshooter_outcome outcome); -int -weston_screenshooter_shoot(struct weston_output *output, struct weston_buffer *buffer, - weston_screenshooter_done_func_t done, void *data); -struct weston_recorder * -weston_recorder_start(struct weston_output *output, const char *filename); -void -weston_recorder_stop(struct weston_recorder *recorder); - -struct weston_view_animation; -typedef void (*weston_view_animation_done_func_t)(struct weston_view_animation *animation, void *data); - -void -weston_view_animation_destroy(struct weston_view_animation *animation); - -struct weston_view_animation * -weston_zoom_run(struct weston_view *view, float start, float stop, - weston_view_animation_done_func_t done, void *data); - -struct weston_view_animation * -weston_fade_run(struct weston_view *view, - float start, float end, float k, - weston_view_animation_done_func_t done, void *data); - -struct weston_view_animation * -weston_move_scale_run(struct weston_view *view, int dx, int dy, - float start, float end, bool reverse, - weston_view_animation_done_func_t done, void *data); - -struct weston_view_animation * -weston_move_run(struct weston_view *view, int dx, int dy, - float start, float end, bool reverse, - weston_view_animation_done_func_t done, void *data); - -void -weston_fade_update(struct weston_view_animation *fade, float target); - -struct weston_view_animation * -weston_stable_fade_run(struct weston_view *front_view, float start, - struct weston_view *back_view, float end, - weston_view_animation_done_func_t done, void *data); - -struct weston_view_animation * -weston_slide_run(struct weston_view *view, float start, float stop, - weston_view_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_mode_switch_to_temporary(struct weston_output *output, - struct weston_mode *mode, - int32_t scale); -int -weston_output_mode_switch_to_native(struct weston_output *output); - -int -weston_backend_init(struct weston_compositor *c, - struct weston_backend_config *config_base); -int -weston_module_init(struct weston_compositor *compositor); - -void * -weston_load_module(const char *name, const char *entrypoint); - -size_t -weston_module_path_from_env(const char *name, char *path, size_t path_len); - -int -weston_parse_transform(const char *transform, uint32_t *out); - -const char * -weston_transform_to_string(uint32_t output_transform); - -struct weston_keyboard * -weston_seat_get_keyboard(struct weston_seat *seat); - -struct weston_pointer * -weston_seat_get_pointer(struct weston_seat *seat); - -struct weston_touch * -weston_seat_get_touch(struct weston_seat *seat); - -void -weston_seat_set_keyboard_focus(struct weston_seat *seat, - struct weston_surface *surface); - -void -weston_keyboard_send_keymap(struct weston_keyboard *kbd, - struct wl_resource *resource); - -int -weston_compositor_load_xwayland(struct weston_compositor *compositor); - -bool -weston_head_is_connected(struct weston_head *head); - -bool -weston_head_is_enabled(struct weston_head *head); - -bool -weston_head_is_device_changed(struct weston_head *head); - -bool -weston_head_is_non_desktop(struct weston_head *head); - -void -weston_head_reset_device_changed(struct weston_head *head); - -const char * -weston_head_get_name(struct weston_head *head); - -struct weston_output * -weston_head_get_output(struct weston_head *head); - -uint32_t -weston_head_get_transform(struct weston_head *head); - -void -weston_head_detach(struct weston_head *head); - -void -weston_head_add_destroy_listener(struct weston_head *head, - struct wl_listener *listener); - -struct wl_listener * -weston_head_get_destroy_listener(struct weston_head *head, - wl_notify_func_t notify); - -void -weston_head_set_content_protection_status(struct weston_head *head, - enum weston_hdcp_protection status); - -struct weston_head * -weston_compositor_iterate_heads(struct weston_compositor *compositor, - struct weston_head *iter); - -void -weston_compositor_add_heads_changed_listener(struct weston_compositor *compositor, - struct wl_listener *listener); - -struct weston_output * -weston_compositor_find_output_by_name(struct weston_compositor *compositor, - const char *name); - -struct weston_output * -weston_compositor_create_output(struct weston_compositor *compositor, - const char *name); - -struct weston_output * -weston_compositor_create_output_with_head(struct weston_compositor *compositor, - struct weston_head *head); - -void -weston_output_destroy(struct weston_output *output); - -int -weston_output_attach_head(struct weston_output *output, - struct weston_head *head); - -struct weston_head * -weston_output_iterate_heads(struct weston_output *output, - struct weston_head *iter); - -void -weston_output_set_scale(struct weston_output *output, - int32_t scale); - -void -weston_output_set_transform(struct weston_output *output, - uint32_t transform); - -void -weston_output_init(struct weston_output *output, - struct weston_compositor *compositor, - const char *name); - -void -weston_output_move(struct weston_output *output, int x, int y); - -int -weston_output_enable(struct weston_output *output); - -void -weston_output_disable(struct weston_output *output); - -void -weston_compositor_flush_heads_changed(struct weston_compositor *compositor); - -struct weston_head * -weston_head_from_resource(struct wl_resource *resource); - -struct weston_head * -weston_output_get_first_head(struct weston_output *output); - -void -weston_output_allow_protection(struct weston_output *output, - bool allow_protection); - -int -weston_compositor_enable_touch_calibrator(struct weston_compositor *compositor, - weston_touch_calibration_save_func save); - -// OHOS remove logger -//struct weston_log_context * -//weston_log_ctx_create(void); -// -//void -//weston_log_ctx_destroy(struct weston_log_context *log_ctx); - -int -weston_compositor_enable_content_protection(struct weston_compositor *compositor); - -// OHOS remove timeline -//void -//weston_timeline_refresh_subscription_objects(struct weston_compositor *wc, -// void *object); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/include/libweston/matrix.h b/include/libweston/matrix.h deleted file mode 100644 index be4d4eb..0000000 --- a/include/libweston/matrix.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2012 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef 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/include/libweston/meson.build b/include/libweston/meson.build deleted file mode 100644 index 2c2f772..0000000 --- a/include/libweston/meson.build +++ /dev/null @@ -1,31 +0,0 @@ -install_headers( - 'config-parser.h', - 'libweston.h', - 'matrix.h', - 'plugin-registry.h', - 'windowed-output-api.h', - 'weston-log.h', - 'zalloc.h', - subdir: dir_include_libweston_install -) - -backend_drm_h = files('backend-drm.h') -backend_fbdev_h = files('backend-fbdev.h') -backend_headless_h = files('backend-headless.h') -backend_rdp_h = files('backend-rdp.h') -backend_wayland_h = files('backend-wayland.h') -backend_x11_h = files('backend-x11.h') - -xwayland_api_h = files('xwayland-api.h') - -libweston_version_h = configuration_data() -libweston_version_h.set('WESTON_VERSION_MAJOR', version_weston_arr[0]) -libweston_version_h.set('WESTON_VERSION_MINOR', version_weston_arr[1]) -libweston_version_h.set('WESTON_VERSION_MICRO', version_weston_arr[2]) -libweston_version_h.set('WESTON_VERSION', version_weston) -version_h = configure_file( - input: 'version.h.in', - output: 'version.h', - configuration: libweston_version_h -) -install_headers(version_h, subdir: dir_include_libweston_install) diff --git a/include/libweston/plugin-registry.h b/include/libweston/plugin-registry.h deleted file mode 100644 index 3f5618d..0000000 --- a/include/libweston/plugin-registry.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2016 DENSO CORPORATION - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_PLUGIN_REGISTRY_H -#define WESTON_PLUGIN_REGISTRY_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -struct weston_compositor; - -int -weston_plugin_api_register(struct weston_compositor *compositor, - const char *api_name, - const void *vtable, - size_t vtable_size); - -const void * -weston_plugin_api_get(struct weston_compositor *compositor, - const char *api_name, - size_t vtable_size); - -void -weston_plugin_api_destroy_list(struct weston_compositor *compositor); - -#ifdef __cplusplus -} -#endif - -#endif /* WESTON_PLUGIN_REGISTRY_H */ diff --git a/include/libweston/version.h.in b/include/libweston/version.h.in deleted file mode 100644 index b2379d0..0000000 --- a/include/libweston/version.h.in +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright © 2013 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#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@" - -/* This macro may not do what you expect. Weston doesn't guarantee - * a stable API between 1.X and 1.Y, and thus this macro will return - * FALSE on any WESTON_VERSION_AT_LEAST(1,X,0) if the actual version - * is 1.Y.0 and X != Y). In particular, it fails if X < Y, that is, - * 1.3.0 is considered to not be "at least" 1.4.0. - * - * If you want to test for the version number being 1.3.0 or above or - * maybe in a range (eg 1.2.0 to 1.4.0), just use the WESTON_VERSION_* - * defines above directly. - */ - -#define WESTON_VERSION_AT_LEAST(major, minor, micro) \ - (WESTON_VERSION_MAJOR == (major) && \ - WESTON_VERSION_MINOR == (minor) && \ - WESTON_VERSION_MICRO >= (micro)) - -#endif diff --git a/include/libweston/weston-log.h b/include/libweston/weston-log.h deleted file mode 100755 index c17dc37..0000000 --- a/include/libweston/weston-log.h +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright © 2017 Pekka Paalanen - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_LOG_H -#define WESTON_LOG_H - -// OHOS remove logger -//#include -//#include -//#include -//#include -//#include -// -//#ifdef __cplusplus -//extern "C" { -//#endif -// -//struct weston_compositor; -//struct weston_log_context; -//struct wl_display; -//struct weston_log_subscriber; -//struct weston_log_subscription; -// -//void -//weston_compositor_enable_debug_protocol(struct weston_compositor *); -// -//bool -//weston_compositor_is_debug_protocol_enabled(struct weston_compositor *); -// -//struct weston_log_scope; -//struct weston_debug_stream; -// -///** weston_log_scope callback -// * -// * @param sub The subscription. -// * @param user_data The \c user_data argument given to -// * weston_log_ctx_add_log_scope() or weston_compositor_add_log_scope(). -// * -// * @memberof weston_log_scope -// */ -//typedef void (*weston_log_scope_cb)(struct weston_log_subscription *sub, -// void *user_data); -// -//struct weston_log_scope * -//weston_log_ctx_add_log_scope(struct weston_log_context *log_ctx, -// const char *name, -// const char *description, -// weston_log_scope_cb new_subscription, -// weston_log_scope_cb destroy_subscription, -// void *user_data); -// -//struct weston_log_scope * -//weston_compositor_add_log_scope(struct weston_compositor *compositor, -// const char *name, -// const char *description, -// weston_log_scope_cb new_subscription, -// weston_log_scope_cb destroy_subscription, -// void *user_data); -// -//void -//weston_log_scope_destroy(struct weston_log_scope *scope); -// -//bool -//weston_log_scope_is_enabled(struct weston_log_scope *scope); -// -//void -//weston_log_scope_write(struct weston_log_scope *scope, -// const char *data, size_t len); -// -//int -//weston_log_scope_vprintf(struct weston_log_scope *scope, -// const char *fmt, va_list ap); -// -//int -//weston_log_scope_printf(struct weston_log_scope *scope, -// const char *fmt, ...) -// __attribute__ ((format (printf, 2, 3))); -//void -//weston_log_subscription_printf(struct weston_log_subscription *sub, -// const char *fmt, ...) -// __attribute__ ((format (printf, 2, 3))); -//void -//weston_log_scope_complete(struct weston_log_scope *scope); -// -//void -//weston_log_subscription_complete(struct weston_log_subscription *sub); -// -//char * -//weston_log_scope_timestamp(struct weston_log_scope *scope, -// char *buf, size_t len); -// -//void -//weston_log_subscriber_destroy(struct weston_log_subscriber *subscriber); -// -//void -//weston_log_subscribe(struct weston_log_context *log_ctx, -// struct weston_log_subscriber *subscriber, -// const char *scope_name); -// -//struct weston_log_subscriber * -//weston_log_subscriber_create_log(FILE *dump_to); -// -//struct weston_log_subscriber * -//weston_log_subscriber_create_flight_rec(size_t size); -// -//void -//weston_log_subscriber_display_flight_rec(struct weston_log_subscriber *sub); -// -//struct weston_log_subscription * -//weston_log_subscription_iterate(struct weston_log_scope *scope, -// struct weston_log_subscription *sub_iter); -// -//void -//weston_log_flight_recorder_display_buffer(FILE *file); - -#ifdef __cplusplus -} -#endif - -#endif /* WESTON_LOG_H */ diff --git a/include/libweston/windowed-output-api.h b/include/libweston/windowed-output-api.h deleted file mode 100644 index be4dec6..0000000 --- a/include/libweston/windowed-output-api.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright © 2016 Armin Krezović - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_WINDOWED_OUTPUT_API_H -#define WESTON_WINDOWED_OUTPUT_API_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -struct weston_compositor; -struct weston_output; - -#define WESTON_WINDOWED_OUTPUT_API_NAME "weston_windowed_output_api_v1" - -struct weston_windowed_output_api { - /** Assign a given width and height to an output. - * - * \param output An output to be configured. - * \param width Desired width of the output. - * \param height Desired height of the output. - * - * Returns 0 on success, -1 on failure. - * - * This assigns a desired width and height to a windowed - * output. The backend decides what should be done and applies - * the desired configuration. After using this function and - * generic weston_output_set_*, a windowed - * output should be in a state where weston_output_enable() - * can be run. - */ - int (*output_set_size)(struct weston_output *output, - int width, int height); - - /** Create a new windowed head. - * - * \param compositor The compositor instance. - * \param name Desired name for a new head, not NULL. - * - * Returns 0 on success, -1 on failure. - * - * This creates a new head in the backend. The new head will - * be advertised in the compositor's head list and triggers a - * head_changed callback. - * - * A new output can be created for the head. The output must be - * configured with output_set_size() and - * weston_output_set_{scale,transform}() before enabling it. - * - * \sa weston_compositor_set_heads_changed_cb(), - * weston_compositor_create_output_with_head() - */ - int (*create_head)(struct weston_compositor *compositor, - const char *name); -}; - -static inline const struct weston_windowed_output_api * -weston_windowed_output_get_api(struct weston_compositor *compositor) -{ - const void *api; - api = weston_plugin_api_get(compositor, WESTON_WINDOWED_OUTPUT_API_NAME, - sizeof(struct weston_windowed_output_api)); - - return (const struct weston_windowed_output_api *)api; -} - -#ifdef __cplusplus -} -#endif - -#endif /* WESTON_WINDOWED_OUTPUT_API_H */ diff --git a/include/libweston/xwayland-api.h b/include/libweston/xwayland-api.h deleted file mode 100644 index ff71fdd..0000000 --- a/include/libweston/xwayland-api.h +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright © 2016 Giulio Camuffo - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef XWAYLAND_API_H -#define XWAYLAND_API_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#include - -struct weston_compositor; -struct weston_xwayland; - -#define WESTON_XWAYLAND_API_NAME "weston_xwayland_v1" -#define WESTON_XWAYLAND_SURFACE_API_NAME "weston_xwayland_surface_v1" - -typedef pid_t -(*weston_xwayland_spawn_xserver_func_t)( - void *user_data, const char *display, int abstract_fd, int unix_fd); - -/** The libweston Xwayland API - * - * This API allows control of the Xwayland libweston module. - * The module must be loaded at runtime with \a weston_compositor_load_xwayland, - * after which the API can be retrieved by using \a weston_xwayland_get_api. - */ -struct weston_xwayland_api { - /** Retrieve the Xwayland context object. - * - * Note that this function does not create a new object, but always - * returns the same object per compositor instance. - * This function cannot fail while this API object is valid. - * - * \param compositor The compositor instance. - */ - struct weston_xwayland * - (*get)(struct weston_compositor *compositor); - - /** Listen for X connections. - * - * This function tells the Xwayland module to begin creating an X socket - * and start listening for client connections. When one such connection is - * detected the given \a spawn_func callback will be called to start - * the Xwayland process. - * - * \param xwayland The Xwayland context object. - * \param user_data The user data pointer to be passed to \a spawn_func. - * \param spawn_func The callback function called to start the Xwayland - * server process. - * - * \return 0 on success, a negative number otherwise. - */ - int - (*listen)(struct weston_xwayland *xwayland, void *user_data, - weston_xwayland_spawn_xserver_func_t spawn_func); - - /** Notify the Xwayland module that the Xwayland server is loaded. - * - * After the Xwayland server process has been spawned it will notify - * the parent that is has finished the initialization by sending a - * SIGUSR1 signal. - * The caller should listen for that signal and call this function - * when it is received. - * - * \param xwayland The Xwayland context object. - * \param client The wl_client object representing the connection of - * the Xwayland server process. - * \param wm_fd The file descriptor for the wm. - */ - void - (*xserver_loaded)(struct weston_xwayland *xwayland, - struct wl_client *client, int wm_fd); - - /** Notify the Xwayland module that the Xwayland server has exited. - * - * Whenever the Xwayland server process quits this function should be - * called. - * The Xwayland module will keep listening for X connections on the - * socket, and may call the spawn function again. - * - * \param xwayland The Xwayland context object. - * \param exit_status The exit status of the Xwayland server process. - */ - void - (*xserver_exited)(struct weston_xwayland *xwayland, int exit_status); -}; - -/** Retrieve the API object for the libweston Xwayland module. - * - * The module must have been previously loaded by calling - * \a weston_compositor_load_xwayland. - * - * \param compositor The compositor instance. - */ -static inline const struct weston_xwayland_api * -weston_xwayland_get_api(struct weston_compositor *compositor) -{ - const void *api; - api = weston_plugin_api_get(compositor, WESTON_XWAYLAND_API_NAME, - sizeof(struct weston_xwayland_api)); - /* The cast is necessary to use this function in C++ code */ - return (const struct weston_xwayland_api *)api; -} - -/** The libweston Xwayland surface API - * - * This API allows control of the Xwayland libweston module surfaces. - * The module must be loaded at runtime with \a weston_compositor_load_xwayland, - * after which the API can be retrieved by using - * \a weston_xwayland_surface_get_api. - */ -struct weston_xwayland_surface_api { - /** Check if the surface is an Xwayland surface - * - * \param surface The surface. - */ - bool - (*is_xwayland_surface)(struct weston_surface *surface); - /** Notify the Xwayland surface that its position changed. - * - * \param surface The Xwayland surface. - * \param x The x-axis position. - * \param y The y-axis position. - */ - void - (*send_position)(struct weston_surface *surface, int32_t x, int32_t y); -}; - -/** Retrieve the API object for the libweston Xwayland surface. - * - * The module must have been previously loaded by calling - * \a weston_compositor_load_xwayland. - * - * \param compositor The compositor instance. - */ -static inline const struct weston_xwayland_surface_api * -weston_xwayland_surface_get_api(struct weston_compositor *compositor) -{ - const void *api; - api = weston_plugin_api_get(compositor, WESTON_XWAYLAND_SURFACE_API_NAME, - sizeof(struct weston_xwayland_surface_api)); - /* The cast is necessary to use this function in C++ code */ - return (const struct weston_xwayland_surface_api *)api; -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/include/libweston/zalloc.h b/include/libweston/zalloc.h deleted file mode 100644 index 8830df6..0000000 --- a/include/libweston/zalloc.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright © 2013 Red Hat, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#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/include/meson.build b/include/meson.build deleted file mode 100644 index 1ea6dd3..0000000 --- a/include/meson.build +++ /dev/null @@ -1,6 +0,0 @@ -subdir('libweston') - -install_headers( - 'libweston-desktop/libweston-desktop.h', - subdir: join_paths(dir_include_libweston, 'libweston-desktop') -) diff --git a/ivi-shell/README b/ivi-shell/README deleted file mode 100644 index 1d2212e..0000000 --- a/ivi-shell/README +++ /dev/null @@ -1,78 +0,0 @@ - In-vehicle infotainment (information and entertainment) - graphical environment support modules for Weston - - -IVI-shell is an alternative shell for Weston, a Wayland display server. -Window management and application interaction with the display server -are very different to that of a normal desktop, which is why this is -a separate shell and not an extension to the desktop-shell suite with -xdg_shell. As such, applications need to be specifically written to use -IVI-shell. - -IVI-shell contains two main features: -- Common layout library for surface, which allow ivi-shell developer - to develop own shell, linking Common layout library. - For the time being, the library refers Genivi ilm interface. - - https://at.projects.genivi.org/wiki/display/WIE/Wayland+IVI+Extension+Home - -- Extension protocol; ivi-application to tie wl_surface and a given ID. - With this ID, shell can identify which wl_surface is drawn by which - application. In in-vehicle infortainment system, a shell has to update - a property of a wl_surface. E.g. there may be a use case when vehicle - starts to move, the wl_surface drawn by Car navigation is expected to - move top of surfaces. - -The actual software components delivered with Weston are: - -- ivi-application.xml: - Wayland protocol extension for IVI-applications; the public - shell protocol (the same concept as xdg_shell). - Implemented by ivi-shell.so. - -- ivi-shell.so: - A Weston shell module that implements ivi-application.xml interfaces. - Loads ivi-layout.so. - -- ivi-layout.so: - Implements the IVI window management concepts: Screen, Layer, - Surface, groups of Layers, groups of Surfaces, see: - https://at.projects.genivi.org/wiki/display/WIE/Summary+of+Layer+manager+APIs - Offers a stable API for writing IVI-controller modules like - hmi-controller.so against the IVI concepts. In other words, - it offers an API to write IVI window manager modules. - -- hmi-controller.so: - A sample implementation of an IVI-controller module, usually - replaced by IVI system vendors. - Uses ivi-layout.so to perform essentially window manager tasks. - This implementation keeps all window management inside the module, - while IVI-systems may use another module that exposes all window - management via Wayland or other protocol for an external process - to control. - -- ivi-hmi-controller.xml: - Wayland protocol extension for IVI display control; the private - shell protocol for weston-ivi-shell-user-interface client - (the same concept as desktop-shell.xml). - Implemented by hmi-controller.so, and usually replaced by IVI - system vendors. - -- weston-ivi-shell-user-interface: - A sample implementation of an IVI shell helper client, usually - replaced by IVI system vendors. - A helper client for basic display content, similar to - weston-desktop-shell. - - -How to compile: -same as weston. To disable, use option: --disable-ivi-shell for configure. - -How to configure weston.ini: -reference ini file will be generated in /ivi-shell. - -How to run: -same as weston. exec weston. - -How to use UI: -http://lists.freedesktop.org/archives/wayland-devel/attachments/20140625/abbfc064/attachment-0001.png diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c deleted file mode 100644 index 230e788..0000000 --- a/ivi-shell/hmi-controller.c +++ /dev/null @@ -1,2006 +0,0 @@ -/* - * Copyright (C) 2014 DENSO CORPORATION - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/** - * A reference implementation how to use ivi-layout APIs in order to manage - * layout of ivi_surfaces/ivi_layers. Layout change is triggered by - * ivi-hmi-controller protocol, ivi-hmi-controller.xml. A reference how to - * use the protocol, see hmi-controller-homescreen. - * - * In-Vehicle Infotainment system usually manage properties of - * ivi_surfaces/ivi_layers by only a central component which decide where - * ivi_surfaces/ivi_layers shall be. This reference show examples to - * implement the central component as a module of weston. - * - * Default Scene graph of UI is defined in hmi_controller_create. It - * consists of - * - In the bottom, a base ivi_layer to group ivi_surfaces of background, - * panel, and buttons - * - Next, an application ivi_layer to show application ivi_surfaces. - * - Workspace background ivi_layer to show a ivi_surface of background image. - * - Workspace ivi_layer to show launcher to launch application with icons. - * Paths to binary and icon are defined in weston.ini. The width of this - * ivi_layer is longer than the size of ivi_screen because a workspace has - * several pages and is controlled by motion of input. - * - * TODO: animation method shall be refined - * TODO: support fade-in when UI is ready - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ivi-layout-export.h" -#include "ivi-hmi-controller-server-protocol.h" -#include "shared/helpers.h" -#include "shared/xalloc.h" -#include "compositor/weston.h" - -/***************************************************************************** - * structure, globals - ****************************************************************************/ -struct hmi_controller_layer { - struct ivi_layout_layer *ivilayer; - uint32_t id_layer; - int32_t x; - int32_t y; - int32_t width; - int32_t height; - struct wl_list link; -}; - -struct link_layer { - struct ivi_layout_layer *layout_layer; - struct wl_list link; -}; - -struct hmi_controller_fade { - uint32_t is_fade_in; - struct wl_list layer_list; -}; - -struct hmi_server_setting { - uint32_t base_layer_id; - uint32_t application_layer_id; - uint32_t workspace_background_layer_id; - uint32_t workspace_layer_id; - uint32_t base_layer_id_offset; - int32_t panel_height; - uint32_t transition_duration; - char *ivi_homescreen; -}; - -struct ui_setting { - uint32_t background_id; - uint32_t panel_id; - uint32_t tiling_id; - uint32_t sidebyside_id; - uint32_t fullscreen_id; - uint32_t random_id; - uint32_t home_id; - uint32_t workspace_background_id; - uint32_t surface_id_offset; -}; - -struct hmi_controller { - struct hmi_server_setting *hmi_setting; - /* List of struct hmi_controller_layer */ - struct wl_list base_layer_list; - struct wl_list application_layer_list; - struct hmi_controller_layer workspace_background_layer; - struct hmi_controller_layer workspace_layer; - enum ivi_hmi_controller_layout_mode layout_mode; - - struct hmi_controller_fade workspace_fade; - - int32_t workspace_count; - struct wl_array ui_widgets; - int32_t is_initialized; - - struct weston_compositor *compositor; - struct wl_listener destroy_listener; - - struct wl_listener surface_removed; - struct wl_listener surface_configured; - struct wl_listener desktop_surface_configured; - - struct wl_client *user_interface; - struct ui_setting ui_setting; - - struct weston_output * workspace_background_output; - int32_t screen_num; - - const struct ivi_layout_interface *interface; -}; - -struct launcher_info { - uint32_t surface_id; - uint32_t workspace_id; - int32_t index; -}; - -/***************************************************************************** - * local functions - ****************************************************************************/ -static void * -mem_alloc(size_t size, char *file, int32_t line) -{ - return fail_on_null(calloc(1, size), size, file, line); -} - -#define MEM_ALLOC(s) mem_alloc((s),__FILE__,__LINE__) - -static int32_t -is_surf_in_ui_widget(struct hmi_controller *hmi_ctrl, - struct ivi_layout_surface *ivisurf) -{ - uint32_t id = hmi_ctrl->interface->get_id_of_surface(ivisurf); - - uint32_t *ui_widget_id = NULL; - wl_array_for_each(ui_widget_id, &hmi_ctrl->ui_widgets) { - if (*ui_widget_id == id) - return 1; - } - - return 0; -} - -static int -compare_launcher_info(const void *lhs, const void *rhs) -{ - const struct launcher_info *left = lhs; - const struct launcher_info *right = rhs; - - if (left->workspace_id < right->workspace_id) - return -1; - - if (left->workspace_id > right->workspace_id) - return 1; - - if (left->index < right->index) - return -1; - - if (left->index > right->index) - return 1; - - return 0; -} - -/** - * Internal methods called by mainly ivi_hmi_controller_switch_mode - * This reference shows 4 examples how to use ivi_layout APIs. - */ -static void -mode_divided_into_tiling(struct hmi_controller *hmi_ctrl, - struct ivi_layout_surface **pp_surface, - int32_t surface_length, - struct wl_list *layer_list) -{ - struct hmi_controller_layer *layer = wl_container_of(layer_list->prev, layer, link); - const float surface_width = (float)layer->width * 0.25; - const float surface_height = (float)layer->height * 0.5; - int32_t surface_x = 0; - int32_t surface_y = 0; - struct ivi_layout_surface *ivisurf = NULL; - struct ivi_layout_surface **surfaces; - struct ivi_layout_surface **new_order; - const uint32_t duration = hmi_ctrl->hmi_setting->transition_duration; - struct ivi_layout_layer *ivilayer = NULL; - - int32_t i = 0; - int32_t surf_num = 0; - int32_t idx = 0; - - surfaces = MEM_ALLOC(sizeof(*surfaces) * surface_length); - new_order = MEM_ALLOC(sizeof(*surfaces) * surface_length); - - for (i = 0; i < surface_length; i++) { - ivisurf = pp_surface[i]; - - /* skip ui widgets */ - if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) - continue; - - surfaces[surf_num++] = ivisurf; - } - - wl_list_for_each_reverse(layer, layer_list, link) { - if (idx >= surf_num) - break; - - ivilayer = layer->ivilayer; - - for (i = 0; i < 8; i++, idx++) { - if (idx >= surf_num) - break; - - ivisurf = surfaces[idx]; - new_order[i] = ivisurf; - if (i < 4) { - surface_x = (int32_t)(i * (surface_width)); - surface_y = 0; - } else { - surface_x = (int32_t)((i - 4) * (surface_width)); - surface_y = (int32_t)surface_height; - } - - hmi_ctrl->interface->surface_set_transition(ivisurf, - IVI_LAYOUT_TRANSITION_VIEW_DEFAULT, - duration); - hmi_ctrl->interface->surface_set_visibility(ivisurf, true); - hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, - surface_x, surface_y, - (int32_t)surface_width, - (int32_t)surface_height); - - } - hmi_ctrl->interface->layer_set_render_order(ivilayer, new_order, i); - - hmi_ctrl->interface->layer_set_transition(ivilayer, - IVI_LAYOUT_TRANSITION_LAYER_VIEW_ORDER, - duration); - } - for (i = idx; i < surf_num; i++) - hmi_ctrl->interface->surface_set_visibility(surfaces[i], false); - - free(surfaces); - free(new_order); -} - -static void -mode_divided_into_sidebyside(struct hmi_controller *hmi_ctrl, - struct ivi_layout_surface **pp_surface, - int32_t surface_length, - struct wl_list *layer_list) -{ - struct hmi_controller_layer *layer = wl_container_of(layer_list->prev, layer, link); - int32_t surface_width = layer->width / 2; - int32_t surface_height = layer->height; - struct ivi_layout_surface *ivisurf = NULL; - - const uint32_t duration = hmi_ctrl->hmi_setting->transition_duration; - int32_t i = 0; - struct ivi_layout_surface **surfaces; - struct ivi_layout_surface **new_order; - struct ivi_layout_layer *ivilayer = NULL; - int32_t surf_num = 0; - int32_t idx = 0; - - surfaces = MEM_ALLOC(sizeof(*surfaces) * surface_length); - new_order = MEM_ALLOC(sizeof(*surfaces) * surface_length); - - for (i = 0; i < surface_length; i++) { - ivisurf = pp_surface[i]; - - /* skip ui widgets */ - if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) - continue; - - surfaces[surf_num++] = ivisurf; - } - - wl_list_for_each_reverse(layer, layer_list, link) { - if (idx >= surf_num) - break; - - ivilayer = layer->ivilayer; - - for (i = 0; i < 2; i++, idx++) { - if (idx >= surf_num) - break; - - ivisurf = surfaces[idx]; - new_order[i] = ivisurf; - - hmi_ctrl->interface->surface_set_transition(ivisurf, - IVI_LAYOUT_TRANSITION_VIEW_DEFAULT, - duration); - hmi_ctrl->interface->surface_set_visibility(ivisurf, true); - - hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, - i * surface_width, 0, - surface_width, - surface_height); - } - hmi_ctrl->interface->layer_set_render_order(ivilayer, new_order, i); - } - - for (i = idx; i < surf_num; i++) { - hmi_ctrl->interface->surface_set_transition(surfaces[i], - IVI_LAYOUT_TRANSITION_VIEW_FADE_ONLY, - duration); - hmi_ctrl->interface->surface_set_visibility(surfaces[i], false); - } - - free(surfaces); - free(new_order); -} - -static void -mode_fullscreen_someone(struct hmi_controller *hmi_ctrl, - struct ivi_layout_surface **pp_surface, - int32_t surface_length, - struct wl_list *layer_list) -{ - struct hmi_controller_layer *layer = wl_container_of(layer_list->prev, layer, link); - const int32_t surface_width = layer->width; - const int32_t surface_height = layer->height; - struct ivi_layout_surface *ivisurf = NULL; - int32_t i = 0; - const uint32_t duration = hmi_ctrl->hmi_setting->transition_duration; - int32_t surf_num = 0; - struct ivi_layout_surface **surfaces; - - surfaces = MEM_ALLOC(sizeof(*surfaces) * surface_length); - - for (i = 0; i < surface_length; i++) { - ivisurf = pp_surface[i]; - - /* skip ui widgets */ - if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) - continue; - - surfaces[surf_num++] = ivisurf; - } - hmi_ctrl->interface->layer_set_render_order(layer->ivilayer, surfaces, surf_num); - - for (i = 0; i < surf_num; i++) { - ivisurf = surfaces[i]; - - if ((i > 0) && (i < hmi_ctrl->screen_num)) { - layer = wl_container_of(layer->link.prev, layer, link); - hmi_ctrl->interface->layer_set_render_order(layer->ivilayer, &ivisurf, 1); - } - - hmi_ctrl->interface->surface_set_transition(ivisurf, - IVI_LAYOUT_TRANSITION_VIEW_DEFAULT, - duration); - hmi_ctrl->interface->surface_set_visibility(ivisurf, true); - hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, 0, 0, - surface_width, - surface_height); - } - - free(surfaces); -} - -static void -mode_random_replace(struct hmi_controller *hmi_ctrl, - struct ivi_layout_surface **pp_surface, - int32_t surface_length, - struct wl_list *layer_list) -{ - struct hmi_controller_layer *application_layer = NULL; - struct hmi_controller_layer **layers = NULL; - int32_t surface_width = 0; - int32_t surface_height = 0; - int32_t surface_x = 0; - int32_t surface_y = 0; - struct ivi_layout_surface *ivisurf = NULL; - const uint32_t duration = hmi_ctrl->hmi_setting->transition_duration; - int32_t i = 0; - int32_t layer_idx = 0; - - layers = MEM_ALLOC(sizeof(*layers) * hmi_ctrl->screen_num); - - wl_list_for_each(application_layer, layer_list, link) { - layers[layer_idx] = application_layer; - layer_idx++; - } - - for (i = 0; i < surface_length; i++) { - ivisurf = pp_surface[i]; - - /* skip ui widgets */ - if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) - continue; - - /* surface determined at random a layer that belongs */ - layer_idx = rand() % hmi_ctrl->screen_num; - - hmi_ctrl->interface->surface_set_transition(ivisurf, - IVI_LAYOUT_TRANSITION_VIEW_DEFAULT, - duration); - - hmi_ctrl->interface->surface_set_visibility(ivisurf, true); - - surface_width = (int32_t)(layers[layer_idx]->width * 0.25f); - surface_height = (int32_t)(layers[layer_idx]->height * 0.25f); - surface_x = rand() % (layers[layer_idx]->width - surface_width); - surface_y = rand() % (layers[layer_idx]->height - surface_height); - - hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, - surface_x, - surface_y, - surface_width, - surface_height); - - hmi_ctrl->interface->layer_add_surface(layers[layer_idx]->ivilayer, ivisurf); - } - - free(layers); -} - -static int32_t -has_application_surface(struct hmi_controller *hmi_ctrl, - struct ivi_layout_surface **pp_surface, - int32_t surface_length) -{ - struct ivi_layout_surface *ivisurf = NULL; - int32_t i = 0; - - for (i = 0; i < surface_length; i++) { - ivisurf = pp_surface[i]; - - /* skip ui widgets */ - if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) - continue; - - return 1; - } - - return 0; -} - -/** - * Supports 4 example to layout of application ivi_surfaces; - * tiling, side by side, fullscreen, and random. - */ -static void -switch_mode(struct hmi_controller *hmi_ctrl, - enum ivi_hmi_controller_layout_mode layout_mode) -{ - struct wl_list *layer = &hmi_ctrl->application_layer_list; - struct ivi_layout_surface **pp_surface = NULL; - int32_t surface_length = 0; - int32_t ret = 0; - - if (!hmi_ctrl->is_initialized) - return; - - hmi_ctrl->layout_mode = layout_mode; - - ret = hmi_ctrl->interface->get_surfaces(&surface_length, &pp_surface); - assert(!ret); - - if (!has_application_surface(hmi_ctrl, pp_surface, surface_length)) { - free(pp_surface); - pp_surface = NULL; - return; - } - - switch (layout_mode) { - case IVI_HMI_CONTROLLER_LAYOUT_MODE_TILING: - mode_divided_into_tiling(hmi_ctrl, pp_surface, surface_length, - layer); - break; - case IVI_HMI_CONTROLLER_LAYOUT_MODE_SIDE_BY_SIDE: - mode_divided_into_sidebyside(hmi_ctrl, pp_surface, - surface_length, layer); - break; - case IVI_HMI_CONTROLLER_LAYOUT_MODE_FULL_SCREEN: - mode_fullscreen_someone(hmi_ctrl, pp_surface, surface_length, - layer); - break; - case IVI_HMI_CONTROLLER_LAYOUT_MODE_RANDOM: - mode_random_replace(hmi_ctrl, pp_surface, surface_length, - layer); - break; - } - - hmi_ctrl->interface->commit_changes(); - free(pp_surface); -} - -/** - * Internal method for transition - */ -static void -hmi_controller_fade_run(struct hmi_controller *hmi_ctrl, uint32_t is_fade_in, - struct hmi_controller_fade *fade) -{ - double tint = is_fade_in ? 1.0 : 0.0; - struct link_layer *linklayer = NULL; - const uint32_t duration = hmi_ctrl->hmi_setting->transition_duration; - - fade->is_fade_in = is_fade_in; - - wl_list_for_each(linklayer, &fade->layer_list, link) { - hmi_ctrl->interface->layer_set_transition(linklayer->layout_layer, - IVI_LAYOUT_TRANSITION_LAYER_FADE, - duration); - hmi_ctrl->interface->layer_set_fade_info(linklayer->layout_layer, - is_fade_in, 1.0 - tint, tint); - } -} - -/** - * Internal method to create ivi_layer with hmi_controller_layer and - * add to a weston_output - */ -static void -create_layer(struct weston_output *output, - struct hmi_controller_layer *layer, - struct hmi_controller *hmi_ctrl) -{ - int32_t ret = 0; - - layer->ivilayer = - hmi_ctrl->interface->layer_create_with_dimension(layer->id_layer, - layer->width, - layer->height); - assert(layer->ivilayer != NULL); - - ret = hmi_ctrl->interface->screen_add_layer(output, layer->ivilayer); - assert(!ret); - - ret = hmi_ctrl->interface->layer_set_destination_rectangle(layer->ivilayer, - layer->x, layer->y, - layer->width, - layer->height); - assert(!ret); - - ret = hmi_ctrl->interface->layer_set_visibility(layer->ivilayer, true); - assert(!ret); -} - -/** - * Internal set notification - */ -static void -set_notification_remove_surface(struct wl_listener *listener, void *data) -{ - struct hmi_controller *hmi_ctrl = - wl_container_of(listener, hmi_ctrl, - surface_removed); - (void)data; - - switch_mode(hmi_ctrl, hmi_ctrl->layout_mode); -} - -static void -set_notification_configure_surface(struct wl_listener *listener, void *data) -{ - struct hmi_controller *hmi_ctrl = - wl_container_of(listener, hmi_ctrl, - surface_configured); - struct ivi_layout_surface *ivisurf = data; - struct hmi_controller_layer *layer_link = NULL; - struct ivi_layout_layer *application_layer = NULL; - struct weston_surface *surface; - struct ivi_layout_surface **ivisurfs = NULL; - int32_t length = 0; - int32_t i; - - /* return if the surface is not application content */ - if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) { - return; - } - - /* - * if application changes size of wl_buffer. The source rectangle shall be - * fit to the size. - */ - surface = hmi_ctrl->interface->surface_get_weston_surface(ivisurf); - if (surface) { - hmi_ctrl->interface->surface_set_source_rectangle( - ivisurf, 0, 0, surface->width, - surface->height); - } - - /* - * search if the surface is already added to layer. - * If not yet, it is newly invoded application to go to switch_mode. - */ - wl_list_for_each_reverse(layer_link, &hmi_ctrl->application_layer_list, link) { - application_layer = layer_link->ivilayer; - hmi_ctrl->interface->get_surfaces_on_layer(application_layer, - &length, &ivisurfs); - for (i = 0; i < length; i++) { - if (ivisurf == ivisurfs[i]) { - /* - * if it is non new invoked application, just call - * commit_changes to apply source_rectangle. - */ - hmi_ctrl->interface->commit_changes(); - free(ivisurfs); - return; - } - } - free(ivisurfs); - ivisurfs = NULL; - } - - switch_mode(hmi_ctrl, hmi_ctrl->layout_mode); -} - -static void -set_notification_configure_desktop_surface(struct wl_listener *listener, void *data) -{ - struct hmi_controller *hmi_ctrl = - wl_container_of(listener, hmi_ctrl, - desktop_surface_configured); - struct ivi_layout_surface *ivisurf = data; - struct hmi_controller_layer *layer_link = - wl_container_of(hmi_ctrl->application_layer_list.prev, - layer_link, - link); - struct ivi_layout_layer *application_layer = layer_link->ivilayer; - struct weston_surface *surface; - int32_t ret = 0; - - /* skip ui widgets */ - if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) - return; - - ret = hmi_ctrl->interface->layer_add_surface(application_layer, ivisurf); - assert(!ret); - - /* - * if application changes size of wl_buffer. The source rectangle shall be - * fit to the size. - */ - surface = hmi_ctrl->interface->surface_get_weston_surface(ivisurf); - if (surface) { - hmi_ctrl->interface->surface_set_source_rectangle(ivisurf, 0, - 0, surface->width, surface->height); - } - - hmi_ctrl->interface->commit_changes(); - switch_mode(hmi_ctrl, hmi_ctrl->layout_mode); -} - -/** - * A hmi_controller used 4 ivi_layers to manage ivi_surfaces. The IDs of - * corresponding ivi_layer are defined in weston.ini. Default scene graph - * of ivi_layers are initialized in hmi_controller_create - */ -static struct hmi_server_setting * -hmi_server_setting_create(struct weston_compositor *ec) -{ - struct hmi_server_setting *setting = MEM_ALLOC(sizeof(*setting)); - struct weston_config *config = wet_get_config(ec); - struct weston_config_section *shell_section = NULL; - char *ivi_ui_config; - - shell_section = weston_config_get_section(config, "ivi-shell", - NULL, NULL); - - weston_config_section_get_uint(shell_section, "base-layer-id", - &setting->base_layer_id, 1000); - - weston_config_section_get_uint(shell_section, - "workspace-background-layer-id", - &setting->workspace_background_layer_id, - 2000); - - weston_config_section_get_uint(shell_section, "workspace-layer-id", - &setting->workspace_layer_id, 3000); - - weston_config_section_get_uint(shell_section, "application-layer-id", - &setting->application_layer_id, 4000); - - weston_config_section_get_uint(shell_section, "base-layer-id-offset", - &setting->base_layer_id_offset, 10000); - - weston_config_section_get_uint(shell_section, "transition-duration", - &setting->transition_duration, 300); - - setting->panel_height = 70; - - weston_config_section_get_string(shell_section, - "ivi-shell-user-interface", - &ivi_ui_config, NULL); - if (ivi_ui_config && ivi_ui_config[0] != '/') { - setting->ivi_homescreen = wet_get_libexec_path(ivi_ui_config); - if (setting->ivi_homescreen) - free(ivi_ui_config); - else - setting->ivi_homescreen = ivi_ui_config; - } else { - setting->ivi_homescreen = ivi_ui_config; - } - - return setting; -} - -static void -hmi_controller_destroy(struct wl_listener *listener, void *data) -{ - struct link_layer *link = NULL; - struct link_layer *next = NULL; - struct hmi_controller_layer *ctrl_layer_link = NULL; - struct hmi_controller_layer *ctrl_layer_next = NULL; - struct hmi_controller *hmi_ctrl = - container_of(listener, struct hmi_controller, destroy_listener); - - wl_list_for_each_safe(link, next, - &hmi_ctrl->workspace_fade.layer_list, link) { - wl_list_remove(&link->link); - free(link); - } - - /* clear base_layer_list */ - wl_list_for_each_safe(ctrl_layer_link, ctrl_layer_next, - &hmi_ctrl->base_layer_list, link) { - wl_list_remove(&ctrl_layer_link->link); - free(ctrl_layer_link); - } - - /* clear application_layer_list */ - wl_list_for_each_safe(ctrl_layer_link, ctrl_layer_next, - &hmi_ctrl->application_layer_list, link) { - wl_list_remove(&ctrl_layer_link->link); - free(ctrl_layer_link); - } - - wl_array_release(&hmi_ctrl->ui_widgets); - free(hmi_ctrl->hmi_setting); - free(hmi_ctrl); -} - -/** - * This is a starting method called from module_init. - * This sets up scene graph of ivi_layers; base, application, workspace - * background, and workspace. These ivi_layers are created/added to - * ivi_screen in create_layer - * - * base: to group ivi_surfaces of panel and background - * application: to group ivi_surfaces of ivi_applications - * workspace background: to group a ivi_surface of background in workspace - * workspace: to group ivi_surfaces for launching ivi_applications - * - * ivi_layers of workspace background and workspace is set to invisible at - * first. The properties of it is updated with animation when - * ivi_hmi_controller_home is requested. - */ -static struct hmi_controller * -hmi_controller_create(struct weston_compositor *ec) -{ - struct link_layer *tmp_link_layer = NULL; - int32_t panel_height = 0; - struct hmi_controller *hmi_ctrl; - const struct ivi_layout_interface *interface; - struct hmi_controller_layer *base_layer = NULL; - struct hmi_controller_layer *application_layer = NULL; - struct weston_output *output; - int32_t i; - - interface = ivi_layout_get_api(ec); - - if (!interface) { - weston_log("Cannot use ivi_layout_interface.\n"); - return NULL; - } - - hmi_ctrl = MEM_ALLOC(sizeof(*hmi_ctrl)); - i = 0; - - wl_array_init(&hmi_ctrl->ui_widgets); - hmi_ctrl->layout_mode = IVI_HMI_CONTROLLER_LAYOUT_MODE_TILING; - hmi_ctrl->hmi_setting = hmi_server_setting_create(ec); - hmi_ctrl->compositor = ec; - hmi_ctrl->screen_num = wl_list_length(&ec->output_list); - hmi_ctrl->interface = interface; - - /* init base ivi_layer*/ - wl_list_init(&hmi_ctrl->base_layer_list); - wl_list_for_each(output, &ec->output_list, link) { - base_layer = MEM_ALLOC(1 * sizeof(struct hmi_controller_layer)); - base_layer->x = 0; - base_layer->y = 0; - base_layer->width = output->current_mode->width; - base_layer->height = output->current_mode->height; - base_layer->id_layer = - hmi_ctrl->hmi_setting->base_layer_id + - (i * hmi_ctrl->hmi_setting->base_layer_id_offset); - wl_list_insert(&hmi_ctrl->base_layer_list, &base_layer->link); - - create_layer(output, base_layer, hmi_ctrl); - i++; - } - - i = 0; - panel_height = hmi_ctrl->hmi_setting->panel_height; - - /* init application ivi_layer */ - wl_list_init(&hmi_ctrl->application_layer_list); - wl_list_for_each(output, &ec->output_list, link) { - application_layer = MEM_ALLOC(1 * sizeof(struct hmi_controller_layer)); - application_layer->x = 0; - application_layer->y = 0; - application_layer->width = output->current_mode->width; - application_layer->height = output->current_mode->height - panel_height; - application_layer->id_layer = - hmi_ctrl->hmi_setting->application_layer_id + - (i * hmi_ctrl->hmi_setting->base_layer_id_offset); - wl_list_insert(&hmi_ctrl->application_layer_list, &application_layer->link); - - create_layer(output, application_layer, hmi_ctrl); - i++; - } - - /* init workspace background ivi_layer */ - output = wl_container_of(ec->output_list.next, output, link); - hmi_ctrl->workspace_background_output = output; - hmi_ctrl->workspace_background_layer.x = 0; - hmi_ctrl->workspace_background_layer.y = 0; - hmi_ctrl->workspace_background_layer.width = - output->current_mode->width; - hmi_ctrl->workspace_background_layer.height = - output->current_mode->height - panel_height; - - hmi_ctrl->workspace_background_layer.id_layer = - hmi_ctrl->hmi_setting->workspace_background_layer_id; - - create_layer(output, &hmi_ctrl->workspace_background_layer, hmi_ctrl); - hmi_ctrl->interface->layer_set_opacity( - hmi_ctrl->workspace_background_layer.ivilayer, 0); - hmi_ctrl->interface->layer_set_visibility( - hmi_ctrl->workspace_background_layer.ivilayer, false); - - - wl_list_init(&hmi_ctrl->workspace_fade.layer_list); - tmp_link_layer = MEM_ALLOC(sizeof(*tmp_link_layer)); - tmp_link_layer->layout_layer = - hmi_ctrl->workspace_background_layer.ivilayer; - wl_list_insert(&hmi_ctrl->workspace_fade.layer_list, - &tmp_link_layer->link); - - hmi_ctrl->surface_removed.notify = set_notification_remove_surface; - hmi_ctrl->interface->add_listener_remove_surface(&hmi_ctrl->surface_removed); - - hmi_ctrl->surface_configured.notify = set_notification_configure_surface; - hmi_ctrl->interface->add_listener_configure_surface(&hmi_ctrl->surface_configured); - - hmi_ctrl->desktop_surface_configured.notify = set_notification_configure_desktop_surface; - hmi_ctrl->interface->add_listener_configure_desktop_surface(&hmi_ctrl->desktop_surface_configured); - - hmi_ctrl->destroy_listener.notify = hmi_controller_destroy; - wl_signal_add(&hmi_ctrl->compositor->destroy_signal, - &hmi_ctrl->destroy_listener); - - return hmi_ctrl; -} - -/** - * Implementations of ivi-hmi-controller.xml - */ - -/** - * A ivi_surface drawing background is identified by id_surface. - * Properties of the ivi_surface is set by using ivi_layout APIs according to - * the scene graph of UI defined in hmi_controller_create. - * - * UI ivi_layer is used to add this ivi_surface. - */ -static void -ivi_hmi_controller_set_background(struct hmi_controller *hmi_ctrl, - uint32_t id_surface) -{ - struct ivi_layout_surface *ivisurf = NULL; - struct hmi_controller_layer *base_layer = NULL; - struct ivi_layout_layer *ivilayer = NULL; - int32_t dstx; - int32_t dsty; - int32_t width; - int32_t height; - int32_t ret = 0; - int32_t i = 0; - - wl_list_for_each_reverse(base_layer, &hmi_ctrl->base_layer_list, link) { - uint32_t *add_surface_id = wl_array_add(&hmi_ctrl->ui_widgets, - sizeof(*add_surface_id)); - *add_surface_id = id_surface + (i * hmi_ctrl->ui_setting.surface_id_offset); - dstx = base_layer->x; - dsty = base_layer->y; - width = base_layer->width; - height = base_layer->height; - ivilayer = base_layer->ivilayer; - - ivisurf = hmi_ctrl->interface->get_surface_from_id(*add_surface_id); - assert(ivisurf != NULL); - - ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); - assert(!ret); - - ret = hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, - dstx, dsty, width, height); - assert(!ret); - - ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); - assert(!ret); - - i++; - } -} - -/** - * A ivi_surface drawing panel is identified by id_surface. - * Properties of the ivi_surface is set by using ivi_layout APIs according to - * the scene graph of UI defined in hmi_controller_create. - * - * UI ivi_layer is used to add this ivi_surface. - */ -static void -ivi_hmi_controller_set_panel(struct hmi_controller *hmi_ctrl, - uint32_t id_surface) -{ - struct ivi_layout_surface *ivisurf = NULL; - struct hmi_controller_layer *base_layer; - struct ivi_layout_layer *ivilayer = NULL; - int32_t width; - int32_t ret = 0; - int32_t panel_height = hmi_ctrl->hmi_setting->panel_height; - const int32_t dstx = 0; - int32_t dsty = 0; - int32_t i = 0; - - wl_list_for_each_reverse(base_layer, &hmi_ctrl->base_layer_list, link) { - uint32_t *add_surface_id = wl_array_add(&hmi_ctrl->ui_widgets, - sizeof(*add_surface_id)); - *add_surface_id = id_surface + (i * hmi_ctrl->ui_setting.surface_id_offset); - - ivilayer = base_layer->ivilayer; - ivisurf = hmi_ctrl->interface->get_surface_from_id(*add_surface_id); - assert(ivisurf != NULL); - - ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); - assert(!ret); - - dsty = base_layer->height - panel_height; - width = base_layer->width; - - ret = hmi_ctrl->interface->surface_set_destination_rectangle( - ivisurf, dstx, dsty, width, panel_height); - assert(!ret); - - ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); - assert(!ret); - - i++; - } -} - -/** - * A ivi_surface drawing buttons in panel is identified by id_surface. - * It can set several buttons. Properties of the ivi_surface is set by - * using ivi_layout APIs according to the scene graph of UI defined in - * hmi_controller_create. Additionally, the position of it is shifted to - * right when new one is requested. - * - * UI ivi_layer is used to add these ivi_surfaces. - */ -static void -ivi_hmi_controller_set_button(struct hmi_controller *hmi_ctrl, - uint32_t id_surface, int32_t number) -{ - struct ivi_layout_surface *ivisurf = NULL; - struct hmi_controller_layer *base_layer = - wl_container_of(hmi_ctrl->base_layer_list.prev, - base_layer, - link); - struct ivi_layout_layer *ivilayer = base_layer->ivilayer; - const int32_t width = 48; - const int32_t height = 48; - int32_t ret = 0; - int32_t panel_height = 0; - int32_t dstx = 0; - int32_t dsty = 0; - uint32_t *add_surface_id = wl_array_add(&hmi_ctrl->ui_widgets, - sizeof(*add_surface_id)); - *add_surface_id = id_surface; - - ivisurf = hmi_ctrl->interface->get_surface_from_id(id_surface); - assert(ivisurf != NULL); - - ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); - assert(!ret); - - panel_height = hmi_ctrl->hmi_setting->panel_height; - - dstx = (60 * number) + 15; - dsty = (base_layer->height - panel_height) + 5; - - ret = hmi_ctrl->interface->surface_set_destination_rectangle( - ivisurf,dstx, dsty, width, height); - assert(!ret); - - ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); - assert(!ret); -} - -/** - * A ivi_surface drawing home button in panel is identified by id_surface. - * Properties of the ivi_surface is set by using ivi_layout APIs according to - * the scene graph of UI defined in hmi_controller_create. - * - * UI ivi_layer is used to add these ivi_surfaces. - */ -static void -ivi_hmi_controller_set_home_button(struct hmi_controller *hmi_ctrl, - uint32_t id_surface) -{ - struct ivi_layout_surface *ivisurf = NULL; - struct hmi_controller_layer *base_layer = - wl_container_of(hmi_ctrl->base_layer_list.prev, - base_layer, - link); - struct ivi_layout_layer *ivilayer = base_layer->ivilayer; - int32_t ret = 0; - int32_t size = 48; - int32_t panel_height = hmi_ctrl->hmi_setting->panel_height; - const int32_t dstx = (base_layer->width - size) / 2; - const int32_t dsty = (base_layer->height - panel_height) + 5; - - uint32_t *add_surface_id = wl_array_add(&hmi_ctrl->ui_widgets, - sizeof(*add_surface_id)); - *add_surface_id = id_surface; - - ivisurf = hmi_ctrl->interface->get_surface_from_id(id_surface); - assert(ivisurf != NULL); - - ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); - assert(!ret); - - ret = hmi_ctrl->interface->surface_set_destination_rectangle( - ivisurf, dstx, dsty, size, size); - assert(!ret); - - ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); - assert(!ret); -} - -/** - * A ivi_surface drawing background of workspace is identified by id_surface. - * Properties of the ivi_surface is set by using ivi_layout APIs according to - * the scene graph of UI defined in hmi_controller_create. - * - * A ivi_layer of workspace_background is used to add this ivi_surface. - */ -static void -ivi_hmi_controller_set_workspacebackground(struct hmi_controller *hmi_ctrl, - uint32_t id_surface) -{ - struct ivi_layout_surface *ivisurf = NULL; - struct ivi_layout_layer *ivilayer = NULL; - const int32_t width = hmi_ctrl->workspace_background_layer.width; - const int32_t height = hmi_ctrl->workspace_background_layer.height; - int32_t ret = 0; - - uint32_t *add_surface_id = wl_array_add(&hmi_ctrl->ui_widgets, - sizeof(*add_surface_id)); - *add_surface_id = id_surface; - ivilayer = hmi_ctrl->workspace_background_layer.ivilayer; - - ivisurf = hmi_ctrl->interface->get_surface_from_id(id_surface); - assert(ivisurf != NULL); - - ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); - assert(!ret); - - ret = hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, - 0, 0, width, height); - assert(!ret); - - ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); - assert(!ret); -} - -/** - * A list of ivi_surfaces drawing launchers in workspace is identified by - * id_surfaces. Properties of the ivi_surface is set by using ivi_layout - * APIs according to the scene graph of UI defined in hmi_controller_create. - * - * The workspace can have several pages to group ivi_surfaces of launcher. - * Each call of this interface increments a number of page to add a group - * of ivi_surfaces - */ -static void -ivi_hmi_controller_add_launchers(struct hmi_controller *hmi_ctrl, - int32_t icon_size) -{ - int32_t minspace_x = 10; - int32_t minspace_y = minspace_x; - - int32_t width = hmi_ctrl->workspace_background_layer.width; - int32_t height = hmi_ctrl->workspace_background_layer.height; - - int32_t x_count = (width - minspace_x) / (minspace_x + icon_size); - int32_t space_x = (int32_t)((width - x_count * icon_size) / (1.0 + x_count)); - float fcell_size_x = icon_size + space_x; - - int32_t y_count = (height - minspace_y) / (minspace_y + icon_size); - int32_t space_y = (int32_t)((height - y_count * icon_size) / (1.0 + y_count)); - float fcell_size_y = icon_size + space_y; - - struct weston_config *config = NULL; - struct weston_config_section *section = NULL; - const char *name = NULL; - int launcher_count = 0; - struct wl_array launchers; - int32_t nx = 0; - int32_t ny = 0; - int32_t prev = -1; - struct launcher_info *data = NULL; - - uint32_t surfaceid = 0; - uint32_t workspaceid = 0; - struct launcher_info *info = NULL; - - int32_t x = 0; - int32_t y = 0; - int32_t ret = 0; - struct ivi_layout_surface* layout_surface = NULL; - uint32_t *add_surface_id = NULL; - - struct link_layer *tmp_link_layer = NULL; - - if (0 == x_count) - x_count = 1; - - if (0 == y_count) - y_count = 1; - - config = wet_get_config(hmi_ctrl->compositor); - if (!config) - return; - - section = weston_config_get_section(config, "ivi-shell", NULL, NULL); - if (!section) - return; - - wl_array_init(&launchers); - - while (weston_config_next_section(config, §ion, &name)) { - surfaceid = 0; - workspaceid = 0; - info = NULL; - if (0 != strcmp(name, "ivi-launcher")) - continue; - - if (0 != weston_config_section_get_uint(section, "icon-id", - &surfaceid, 0)) - continue; - - if (0 != weston_config_section_get_uint(section, - "workspace-id", - &workspaceid, 0)) - continue; - - info = wl_array_add(&launchers, sizeof(*info)); - - if (info) { - info->surface_id = surfaceid; - info->workspace_id = workspaceid; - info->index = launcher_count; - ++launcher_count; - } - } - - qsort(launchers.data, launcher_count, sizeof(struct launcher_info), - compare_launcher_info); - - wl_array_for_each(data, &launchers) { - add_surface_id = wl_array_add(&hmi_ctrl->ui_widgets, - sizeof(*add_surface_id)); - - *add_surface_id = data->surface_id; - - if (0 > prev || (uint32_t)prev != data->workspace_id) { - nx = 0; - ny = 0; - prev = data->workspace_id; - - if (0 <= prev) - hmi_ctrl->workspace_count++; - } - - if (y_count == ny) { - ny = 0; - hmi_ctrl->workspace_count++; - } - - x = nx * fcell_size_x + (hmi_ctrl->workspace_count - 1) * width + space_x; - y = ny * fcell_size_y + space_y; - - layout_surface = - hmi_ctrl->interface->get_surface_from_id(data->surface_id); - assert(layout_surface); - - ret = hmi_ctrl->interface->surface_set_destination_rectangle( - layout_surface, x, y, icon_size, icon_size); - assert(!ret); - - nx++; - - if (x_count == nx) { - ny++; - nx = 0; - } - } - - /* init workspace ivi_layer */ - hmi_ctrl->workspace_layer.x = hmi_ctrl->workspace_background_layer.x; - hmi_ctrl->workspace_layer.y = hmi_ctrl->workspace_background_layer.y; - hmi_ctrl->workspace_layer.width = - hmi_ctrl->workspace_background_layer.width * hmi_ctrl->workspace_count; - hmi_ctrl->workspace_layer.height = - hmi_ctrl->workspace_background_layer.height; - hmi_ctrl->workspace_layer.id_layer = - hmi_ctrl->hmi_setting->workspace_layer_id; - - create_layer(hmi_ctrl->workspace_background_output, - &hmi_ctrl->workspace_layer, hmi_ctrl); - hmi_ctrl->interface->layer_set_opacity(hmi_ctrl->workspace_layer.ivilayer, 0); - hmi_ctrl->interface->layer_set_visibility(hmi_ctrl->workspace_layer.ivilayer, - false); - - tmp_link_layer = MEM_ALLOC(sizeof(*tmp_link_layer)); - tmp_link_layer->layout_layer = hmi_ctrl->workspace_layer.ivilayer; - wl_list_insert(&hmi_ctrl->workspace_fade.layer_list, - &tmp_link_layer->link); - - /* Add surface to layer */ - wl_array_for_each(data, &launchers) { - layout_surface = - hmi_ctrl->interface->get_surface_from_id(data->surface_id); - assert(layout_surface); - - ret = hmi_ctrl->interface->layer_add_surface(hmi_ctrl->workspace_layer.ivilayer, - layout_surface); - assert(!ret); - - ret = hmi_ctrl->interface->surface_set_visibility(layout_surface, true); - assert(!ret); - } - - wl_array_release(&launchers); - hmi_ctrl->interface->commit_changes(); -} - -static void -ivi_hmi_controller_UI_ready(struct wl_client *client, - struct wl_resource *resource) -{ - struct hmi_controller *hmi_ctrl = wl_resource_get_user_data(resource); - - ivi_hmi_controller_set_background(hmi_ctrl, hmi_ctrl->ui_setting.background_id); - ivi_hmi_controller_set_panel(hmi_ctrl, hmi_ctrl->ui_setting.panel_id); - ivi_hmi_controller_set_button(hmi_ctrl, hmi_ctrl->ui_setting.tiling_id, 0); - ivi_hmi_controller_set_button(hmi_ctrl, hmi_ctrl->ui_setting.sidebyside_id, 1); - ivi_hmi_controller_set_button(hmi_ctrl, hmi_ctrl->ui_setting.fullscreen_id, 2); - ivi_hmi_controller_set_button(hmi_ctrl, hmi_ctrl->ui_setting.random_id, 3); - ivi_hmi_controller_set_home_button(hmi_ctrl, hmi_ctrl->ui_setting.home_id); - ivi_hmi_controller_set_workspacebackground(hmi_ctrl, hmi_ctrl->ui_setting.workspace_background_id); - hmi_ctrl->interface->commit_changes(); - - ivi_hmi_controller_add_launchers(hmi_ctrl, 256); - hmi_ctrl->is_initialized = 1; -} - -/** - * Implementation of request and event of ivi_hmi_controller_workspace_control - * and controlling workspace. - * - * When motion of input is detected in a ivi_surface of workspace background, - * ivi_hmi_controller_workspace_control shall be invoked and to start - * controlling of workspace. The workspace has several pages to show several - * groups of applications. - * The workspace is slid by using ivi-layout to select a page in layer_set_pos - * according to motion. When motion finished, e.g. touch up detected, control is - * terminated and event:ivi_hmi_controller_workspace_control is notified. - */ -struct pointer_grab { - struct weston_pointer_grab grab; - struct ivi_layout_layer *layer; - struct wl_resource *resource; -}; - -struct touch_grab { - struct weston_touch_grab grab; - struct ivi_layout_layer *layer; - struct wl_resource *resource; -}; - -struct move_grab { - wl_fixed_t dst[2]; - wl_fixed_t rgn[2][2]; - double v[2]; - struct timespec start_time; - struct timespec pre_time; - wl_fixed_t start_pos[2]; - wl_fixed_t pos[2]; - int32_t is_moved; -}; - -struct pointer_move_grab { - struct pointer_grab base; - struct move_grab move; -}; - -struct touch_move_grab { - struct touch_grab base; - struct move_grab move; - int32_t is_active; -}; - -static void -pointer_grab_start(struct pointer_grab *grab, - struct ivi_layout_layer *layer, - const struct weston_pointer_grab_interface *interface, - struct weston_pointer *pointer) -{ - grab->grab.interface = interface; - grab->layer = layer; - weston_pointer_start_grab(pointer, &grab->grab); -} - -static void -touch_grab_start(struct touch_grab *grab, - struct ivi_layout_layer *layer, - const struct weston_touch_grab_interface *interface, - struct weston_touch* touch) -{ - grab->grab.interface = interface; - grab->layer = layer; - weston_touch_start_grab(touch, &grab->grab); -} - -static int32_t -clamp(int32_t val, int32_t min, int32_t max) -{ - if (val < min) - return min; - - if (max < val) - return max; - - return val; -} - -static void -move_workspace_grab_end(struct move_grab *move, struct wl_resource* resource, - wl_fixed_t grab_x, struct ivi_layout_layer *layer) -{ - struct hmi_controller *hmi_ctrl = wl_resource_get_user_data(resource); - int32_t width = hmi_ctrl->workspace_background_layer.width; - const struct ivi_layout_layer_properties *prop; - - struct timespec time = {0}; - double grab_time = 0.0; - double from_motion_time = 0.0; - double pointer_v = 0.0; - int32_t is_flick = 0; - int32_t pos_x = 0; - int32_t pos_y = 0; - int page_no = 0; - double end_pos = 0.0; - uint32_t duration = 0; - - clock_gettime(CLOCK_MONOTONIC, &time); - - grab_time = 1e+3 * (time.tv_sec - move->start_time.tv_sec) + - 1e-6 * (time.tv_nsec - move->start_time.tv_nsec); - - from_motion_time = 1e+3 * (time.tv_sec - move->pre_time.tv_sec) + - 1e-6 * (time.tv_nsec - move->pre_time.tv_nsec); - - pointer_v = move->v[0]; - - is_flick = grab_time < 400 && 0.4 < fabs(pointer_v); - if (200 < from_motion_time) - pointer_v = 0.0; - - prop = hmi_ctrl->interface->get_properties_of_layer(layer); - pos_x = prop->dest_x; - pos_y = prop->dest_y; - - if (is_flick) { - int orgx = wl_fixed_to_int(move->dst[0] + grab_x); - page_no = (-orgx + width / 2) / width; - - if (pointer_v < 0.0) - page_no++; - else - page_no--; - } else { - page_no = (-pos_x + width / 2) / width; - } - - page_no = clamp(page_no, 0, hmi_ctrl->workspace_count - 1); - end_pos = -page_no * width; - - duration = hmi_ctrl->hmi_setting->transition_duration; - ivi_hmi_controller_send_workspace_end_control(resource, move->is_moved); - hmi_ctrl->interface->layer_set_transition(layer, - IVI_LAYOUT_TRANSITION_LAYER_MOVE, - duration); - hmi_ctrl->interface->layer_set_destination_rectangle(layer, - end_pos, pos_y, - hmi_ctrl->workspace_layer.width, - hmi_ctrl->workspace_layer.height); - hmi_ctrl->interface->commit_changes(); -} - -static void -pointer_move_workspace_grab_end(struct pointer_grab *grab) -{ - struct pointer_move_grab *pnt_move_grab = - (struct pointer_move_grab *)grab; - struct ivi_layout_layer *layer = pnt_move_grab->base.layer; - - move_workspace_grab_end(&pnt_move_grab->move, grab->resource, - grab->grab.pointer->grab_x, layer); - - weston_pointer_end_grab(grab->grab.pointer); -} - -static void -touch_move_workspace_grab_end(struct touch_grab *grab) -{ - struct touch_move_grab *tch_move_grab = (struct touch_move_grab *)grab; - struct ivi_layout_layer *layer = tch_move_grab->base.layer; - - move_workspace_grab_end(&tch_move_grab->move, grab->resource, - grab->grab.touch->grab_x, layer); - - weston_touch_end_grab(grab->grab.touch); -} - -static void -pointer_noop_grab_focus(struct weston_pointer_grab *grab) -{ -} - -static void -pointer_default_grab_axis(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_axis_event *event) -{ - weston_pointer_send_axis(grab->pointer, time, event); -} - -static void -pointer_default_grab_axis_source(struct weston_pointer_grab *grab, - uint32_t source) -{ - weston_pointer_send_axis_source(grab->pointer, source); -} - -static void -pointer_default_grab_frame(struct weston_pointer_grab *grab) -{ - weston_pointer_send_frame(grab->pointer); -} - -static void -move_grab_update(struct move_grab *move, wl_fixed_t pointer[2]) -{ - struct timespec timestamp = {0}; - int32_t ii = 0; - double dt = 0.0; - - clock_gettime(CLOCK_MONOTONIC, ×tamp); //FIXME - dt = (1e+3 * (timestamp.tv_sec - move->pre_time.tv_sec) + - 1e-6 * (timestamp.tv_nsec - move->pre_time.tv_nsec)); - - if (dt < 1e-6) - dt = 1e-6; - - move->pre_time = timestamp; - - for (ii = 0; ii < 2; ii++) { - wl_fixed_t prepos = move->pos[ii]; - move->pos[ii] = pointer[ii] + move->dst[ii]; - - if (move->pos[ii] < move->rgn[0][ii]) { - move->pos[ii] = move->rgn[0][ii]; - move->dst[ii] = move->pos[ii] - pointer[ii]; - } else if (move->rgn[1][ii] < move->pos[ii]) { - move->pos[ii] = move->rgn[1][ii]; - move->dst[ii] = move->pos[ii] - pointer[ii]; - } - - move->v[ii] = wl_fixed_to_double(move->pos[ii] - prepos) / dt; - - if (!move->is_moved && - 0 < wl_fixed_to_int(move->pos[ii] - move->start_pos[ii])) - move->is_moved = 1; - } -} - -static void -layer_set_pos(struct hmi_controller *hmi_ctrl, struct ivi_layout_layer *layer, - wl_fixed_t pos_x, wl_fixed_t pos_y) -{ - const struct ivi_layout_layer_properties *prop; - int32_t layout_pos_x = 0; - int32_t layout_pos_y = 0; - - prop = hmi_ctrl->interface->get_properties_of_layer(layer); - - layout_pos_x = wl_fixed_to_int(pos_x); - layout_pos_y = wl_fixed_to_int(pos_y); - hmi_ctrl->interface->layer_set_destination_rectangle(layer, - layout_pos_x, layout_pos_y, prop->dest_width, prop->dest_height); - hmi_ctrl->interface->commit_changes(); -} - -static void -pointer_move_grab_motion(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_motion_event *event) -{ - struct pointer_move_grab *pnt_move_grab = - (struct pointer_move_grab *)grab; - struct hmi_controller *hmi_ctrl = - wl_resource_get_user_data(pnt_move_grab->base.resource); - wl_fixed_t pointer_pos[2]; - - weston_pointer_motion_to_abs(grab->pointer, event, - &pointer_pos[0], &pointer_pos[1]); - move_grab_update(&pnt_move_grab->move, pointer_pos); - layer_set_pos(hmi_ctrl, pnt_move_grab->base.layer, - pnt_move_grab->move.pos[0], pnt_move_grab->move.pos[1]); - weston_pointer_move(pnt_move_grab->base.grab.pointer, event); -} - -static void -touch_move_grab_motion(struct weston_touch_grab *grab, - const struct timespec *time, int touch_id, - wl_fixed_t x, wl_fixed_t y) -{ - struct touch_move_grab *tch_move_grab = (struct touch_move_grab *)grab; - struct hmi_controller *hmi_ctrl = - wl_resource_get_user_data(tch_move_grab->base.resource); - - if (!tch_move_grab->is_active) - return; - - wl_fixed_t pointer_pos[2] = { - grab->touch->grab_x, - grab->touch->grab_y - }; - - move_grab_update(&tch_move_grab->move, pointer_pos); - layer_set_pos(hmi_ctrl, tch_move_grab->base.layer, - tch_move_grab->move.pos[0], tch_move_grab->move.pos[1]); -} - -static void -pointer_move_workspace_grab_button(struct weston_pointer_grab *grab, - const struct timespec *time, uint32_t button, - uint32_t state_w) -{ - if (BTN_LEFT == button && - WL_POINTER_BUTTON_STATE_RELEASED == state_w) { - struct pointer_grab *pg = (struct pointer_grab *)grab; - - pointer_move_workspace_grab_end(pg); - free(grab); - } -} - -static void -touch_nope_grab_down(struct weston_touch_grab *grab, - const struct timespec *time, - int touch_id, wl_fixed_t sx, wl_fixed_t sy) -{ -} - -static void -touch_move_workspace_grab_up(struct weston_touch_grab *grab, - const struct timespec *time, - int touch_id) -{ - struct touch_move_grab *tch_move_grab = (struct touch_move_grab *)grab; - - if (0 == touch_id) - tch_move_grab->is_active = 0; - - if (0 == grab->touch->num_tp) { - touch_move_workspace_grab_end(&tch_move_grab->base); - free(grab); - } -} - -static void -pointer_move_workspace_grab_cancel(struct weston_pointer_grab *grab) -{ - struct pointer_grab *pg = (struct pointer_grab *)grab; - - pointer_move_workspace_grab_end(pg); - free(grab); -} - -static void -touch_move_workspace_grab_frame(struct weston_touch_grab *grab) -{ -} - -static void -touch_move_workspace_grab_cancel(struct weston_touch_grab *grab) -{ - struct touch_grab *tg = (struct touch_grab *)grab; - - touch_move_workspace_grab_end(tg); - free(grab); -} - -static const struct weston_pointer_grab_interface pointer_move_grab_workspace_interface = { - pointer_noop_grab_focus, - pointer_move_grab_motion, - pointer_move_workspace_grab_button, - pointer_default_grab_axis, - pointer_default_grab_axis_source, - pointer_default_grab_frame, - pointer_move_workspace_grab_cancel -}; - -static const struct weston_touch_grab_interface touch_move_grab_workspace_interface = { - touch_nope_grab_down, - touch_move_workspace_grab_up, - touch_move_grab_motion, - touch_move_workspace_grab_frame, - touch_move_workspace_grab_cancel -}; - -enum HMI_GRAB_DEVICE { - HMI_GRAB_DEVICE_NONE, - HMI_GRAB_DEVICE_POINTER, - HMI_GRAB_DEVICE_TOUCH -}; - -static enum HMI_GRAB_DEVICE -get_hmi_grab_device(struct weston_seat *seat, uint32_t serial) -{ - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_touch *touch = weston_seat_get_touch(seat); - - if (pointer && - pointer->focus && - pointer->button_count && - pointer->grab_serial == serial) - return HMI_GRAB_DEVICE_POINTER; - - if (touch && - touch->focus && - touch->grab_serial == serial) - return HMI_GRAB_DEVICE_TOUCH; - - return HMI_GRAB_DEVICE_NONE; -} - -static void -move_grab_init(struct move_grab* move, wl_fixed_t start_pos[2], - wl_fixed_t grab_pos[2], wl_fixed_t rgn[2][2], - struct wl_resource* resource) -{ - clock_gettime(CLOCK_MONOTONIC, &move->start_time); //FIXME - move->pre_time = move->start_time; - move->pos[0] = start_pos[0]; - move->pos[1] = start_pos[1]; - move->start_pos[0] = start_pos[0]; - move->start_pos[1] = start_pos[1]; - move->dst[0] = start_pos[0] - grab_pos[0]; - move->dst[1] = start_pos[1] - grab_pos[1]; - memcpy(move->rgn, rgn, sizeof(move->rgn)); -} - -static void -move_grab_init_workspace(struct move_grab* move, - wl_fixed_t grab_x, wl_fixed_t grab_y, - struct wl_resource *resource) -{ - struct hmi_controller *hmi_ctrl = wl_resource_get_user_data(resource); - struct ivi_layout_layer *layer = hmi_ctrl->workspace_layer.ivilayer; - const struct ivi_layout_layer_properties *prop; - int32_t workspace_count = hmi_ctrl->workspace_count; - int32_t workspace_width = hmi_ctrl->workspace_background_layer.width; - int32_t layer_pos_x = 0; - int32_t layer_pos_y = 0; - wl_fixed_t start_pos[2] = {0}; - wl_fixed_t rgn[2][2] = {{0}}; - wl_fixed_t grab_pos[2] = { grab_x, grab_y }; - - prop = hmi_ctrl->interface->get_properties_of_layer(layer); - layer_pos_x = prop->dest_x; - layer_pos_y = prop->dest_y; - - start_pos[0] = wl_fixed_from_int(layer_pos_x); - start_pos[1] = wl_fixed_from_int(layer_pos_y); - - rgn[0][0] = wl_fixed_from_int(-workspace_width * (workspace_count - 1)); - - rgn[0][1] = wl_fixed_from_int(0); - rgn[1][0] = wl_fixed_from_int(0); - rgn[1][1] = wl_fixed_from_int(0); - - move_grab_init(move, start_pos, grab_pos, rgn, resource); -} - -static struct pointer_move_grab * -create_workspace_pointer_move(struct weston_pointer *pointer, - struct wl_resource* resource) -{ - struct pointer_move_grab *pnt_move_grab = - MEM_ALLOC(sizeof(*pnt_move_grab)); - - pnt_move_grab->base.resource = resource; - move_grab_init_workspace(&pnt_move_grab->move, pointer->grab_x, - pointer->grab_y, resource); - - return pnt_move_grab; -} - -static struct touch_move_grab * -create_workspace_touch_move(struct weston_touch *touch, - struct wl_resource* resource) -{ - struct touch_move_grab *tch_move_grab = - MEM_ALLOC(sizeof(*tch_move_grab)); - - tch_move_grab->base.resource = resource; - tch_move_grab->is_active = 1; - move_grab_init_workspace(&tch_move_grab->move, touch->grab_x, - touch->grab_y, resource); - - return tch_move_grab; -} - -static void -ivi_hmi_controller_workspace_control(struct wl_client *client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial) -{ - struct hmi_controller *hmi_ctrl = wl_resource_get_user_data(resource); - struct ivi_layout_layer *layer = NULL; - struct pointer_move_grab *pnt_move_grab = NULL; - struct touch_move_grab *tch_move_grab = NULL; - struct weston_seat *seat = NULL; - struct weston_pointer *pointer; - struct weston_touch *touch; - - enum HMI_GRAB_DEVICE device; - - if (hmi_ctrl->workspace_count < 2) - return; - - seat = wl_resource_get_user_data(seat_resource); - device = get_hmi_grab_device(seat, serial); - - if (HMI_GRAB_DEVICE_POINTER != device && - HMI_GRAB_DEVICE_TOUCH != device) - return; - - layer = hmi_ctrl->workspace_layer.ivilayer; - - hmi_ctrl->interface->transition_move_layer_cancel(layer); - - switch (device) { - case HMI_GRAB_DEVICE_POINTER: - pointer = weston_seat_get_pointer(seat); - pnt_move_grab = create_workspace_pointer_move(pointer, - resource); - - pointer_grab_start(&pnt_move_grab->base, layer, - &pointer_move_grab_workspace_interface, - pointer); - break; - - case HMI_GRAB_DEVICE_TOUCH: - touch = weston_seat_get_touch(seat); - tch_move_grab = create_workspace_touch_move(touch, - resource); - - touch_grab_start(&tch_move_grab->base, layer, - &touch_move_grab_workspace_interface, - touch); - break; - - default: - break; - } -} - -/** - * Implementation of switch_mode - */ -static void -ivi_hmi_controller_switch_mode(struct wl_client *client, - struct wl_resource *resource, - uint32_t layout_mode) -{ - struct hmi_controller *hmi_ctrl = wl_resource_get_user_data(resource); - - switch_mode(hmi_ctrl, layout_mode); -} - -/** - * Implementation of on/off displaying workspace and workspace background - * ivi_layers. - */ -static void -ivi_hmi_controller_home(struct wl_client *client, - struct wl_resource *resource, - uint32_t home) -{ - struct hmi_controller *hmi_ctrl = wl_resource_get_user_data(resource); - uint32_t is_fade_in; - - if ((IVI_HMI_CONTROLLER_HOME_ON == home && - !hmi_ctrl->workspace_fade.is_fade_in) || - (IVI_HMI_CONTROLLER_HOME_OFF == home && - hmi_ctrl->workspace_fade.is_fade_in)) { - is_fade_in = !hmi_ctrl->workspace_fade.is_fade_in; - hmi_controller_fade_run(hmi_ctrl, is_fade_in, - &hmi_ctrl->workspace_fade); - } - - hmi_ctrl->interface->commit_changes(); -} - -/** - * binding ivi-hmi-controller implementation - */ -static const struct ivi_hmi_controller_interface ivi_hmi_controller_implementation = { - ivi_hmi_controller_UI_ready, - ivi_hmi_controller_workspace_control, - ivi_hmi_controller_switch_mode, - ivi_hmi_controller_home -}; - -static void -unbind_hmi_controller(struct wl_resource *resource) -{ -} - -static void -bind_hmi_controller(struct wl_client *client, - void *data, uint32_t version, uint32_t id) -{ - struct wl_resource *resource = NULL; - struct hmi_controller *hmi_ctrl = data; - - if (hmi_ctrl->user_interface != client) { - struct wl_resource *res = wl_client_get_object(client, 1); - wl_resource_post_error(res, - WL_DISPLAY_ERROR_INVALID_OBJECT, - "hmi-controller failed: permission denied"); - return; - } - - resource = wl_resource_create( - client, &ivi_hmi_controller_interface, 1, id); - - wl_resource_set_implementation( - resource, &ivi_hmi_controller_implementation, - hmi_ctrl, unbind_hmi_controller); -} - -static int32_t -initialize(struct hmi_controller *hmi_ctrl) -{ - struct config_command { - char *key; - uint32_t *dest; - }; - - struct weston_config *config = wet_get_config(hmi_ctrl->compositor); - struct weston_config_section *section = NULL; - int result = 0; - int i = 0; - - const struct config_command uint_commands[] = { - { "background-id", &hmi_ctrl->ui_setting.background_id }, - { "panel-id", &hmi_ctrl->ui_setting.panel_id }, - { "tiling-id", &hmi_ctrl->ui_setting.tiling_id }, - { "sidebyside-id", &hmi_ctrl->ui_setting.sidebyside_id }, - { "fullscreen-id", &hmi_ctrl->ui_setting.fullscreen_id }, - { "random-id", &hmi_ctrl->ui_setting.random_id }, - { "home-id", &hmi_ctrl->ui_setting.home_id }, - { "workspace-background-id", &hmi_ctrl->ui_setting.workspace_background_id }, - { "surface-id-offset", &hmi_ctrl->ui_setting.surface_id_offset }, - { NULL, NULL } - }; - - section = weston_config_get_section(config, "ivi-shell", NULL, NULL); - - for (i = 0; -1 != result; ++i) { - const struct config_command *command = &uint_commands[i]; - - if (!command->key) - break; - - if (weston_config_section_get_uint( - section, command->key, command->dest, 0) != 0) - result = -1; - } - - if (-1 == result) { - weston_log("Failed to initialize hmi-controller\n"); - return 0; - } - - return 1; -} - -static void -launch_hmi_client_process(void *data) -{ - struct hmi_controller *hmi_ctrl = - (struct hmi_controller *)data; - - hmi_ctrl->user_interface = - weston_client_start(hmi_ctrl->compositor, - hmi_ctrl->hmi_setting->ivi_homescreen); - - free(hmi_ctrl->hmi_setting->ivi_homescreen); -} - -/***************************************************************************** - * exported functions - ****************************************************************************/ -WL_EXPORT int -wet_module_init(struct weston_compositor *ec, - int *argc, char *argv[]) -{ - struct hmi_controller *hmi_ctrl = NULL; - struct wl_event_loop *loop = NULL; - - /* ad hoc weston_compositor_add_destroy_listener_once() */ - if (wl_signal_get(&ec->destroy_signal, hmi_controller_destroy)) - return 0; - - hmi_ctrl = hmi_controller_create(ec); - if (hmi_ctrl == NULL) - return -1; - - if (!initialize(hmi_ctrl)) { - return -1; - } - - if (wl_global_create(ec->wl_display, - &ivi_hmi_controller_interface, 1, - hmi_ctrl, bind_hmi_controller) == NULL) { - return -1; - } - - loop = wl_display_get_event_loop(ec->wl_display); - wl_event_loop_add_idle(loop, launch_hmi_client_process, hmi_ctrl); - - return 0; -} diff --git a/ivi-shell/ivi-layout-export.h b/ivi-shell/ivi-layout-export.h deleted file mode 100644 index 740e1ac..0000000 --- a/ivi-shell/ivi-layout-export.h +++ /dev/null @@ -1,610 +0,0 @@ -/* - * Copyright (C) 2013 DENSO CORPORATION - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/** - * The ivi-layout library supports API set of controlling properties of - * surface and layer which groups surfaces. A unique ID whose type is integer is - * required to create surface and layer. With the unique ID, surface and layer - * are identified to control them. The API set consists of APIs to control - * properties of surface and layers about the following: - * - visibility. - * - opacity. - * - clipping (x,y,width,height). - * - position and size of it to be displayed. - * - orientation per 90 degree. - * - add or remove surfaces to a layer. - * - order of surfaces/layers in layer/screen to be displayed. - * - commit to apply property changes. - * - notifications of property change. - * - * Management of surfaces and layers grouping these surfaces are common - * way in In-Vehicle Infotainment system, which integrate several domains - * in one system. A layer is allocated to a domain in order to control - * application surfaces grouped to the layer all together. - * - * This API and ABI follow following specifications. - * https://at.projects.genivi.org/wiki/display/PROJ/Wayland+IVI+Extension+Design - */ - -#ifndef _IVI_LAYOUT_EXPORT_H_ -#define _IVI_LAYOUT_EXPORT_H_ - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -#include -#include - -#include "stdbool.h" -#include -#include - -#define IVI_SUCCEEDED (0) -#define IVI_FAILED (-1) -#define IVI_INVALID_ID UINT_MAX - -struct ivi_layout_layer; -struct ivi_layout_screen; -struct ivi_layout_surface; - -struct ivi_layout_surface_properties -{ - wl_fixed_t opacity; - int32_t source_x; - int32_t source_y; - int32_t source_width; - int32_t source_height; - int32_t start_x; - int32_t start_y; - int32_t start_width; - int32_t start_height; - int32_t dest_x; - int32_t dest_y; - int32_t dest_width; - int32_t dest_height; - enum wl_output_transform orientation; - bool visibility; - int32_t transition_type; - uint32_t transition_duration; - uint32_t event_mask; -}; - -struct ivi_layout_layer_properties -{ - wl_fixed_t opacity; - int32_t source_x; - int32_t source_y; - int32_t source_width; - int32_t source_height; - int32_t dest_x; - int32_t dest_y; - int32_t dest_width; - int32_t dest_height; - enum wl_output_transform orientation; - bool visibility; - int32_t transition_type; - uint32_t transition_duration; - double start_alpha; - double end_alpha; - uint32_t is_fade_in; - uint32_t event_mask; -}; - -enum ivi_layout_notification_mask { - IVI_NOTIFICATION_NONE = 0, - IVI_NOTIFICATION_OPACITY = (1 << 1), - IVI_NOTIFICATION_SOURCE_RECT = (1 << 2), - IVI_NOTIFICATION_DEST_RECT = (1 << 3), - IVI_NOTIFICATION_DIMENSION = (1 << 4), - IVI_NOTIFICATION_POSITION = (1 << 5), - IVI_NOTIFICATION_ORIENTATION = (1 << 6), - IVI_NOTIFICATION_VISIBILITY = (1 << 7), - IVI_NOTIFICATION_PIXELFORMAT = (1 << 8), - IVI_NOTIFICATION_ADD = (1 << 9), - IVI_NOTIFICATION_REMOVE = (1 << 10), - IVI_NOTIFICATION_CONFIGURE = (1 << 11), - IVI_NOTIFICATION_ALL = 0xFFFF -}; - -enum ivi_layout_transition_type{ - IVI_LAYOUT_TRANSITION_NONE, - IVI_LAYOUT_TRANSITION_VIEW_DEFAULT, - IVI_LAYOUT_TRANSITION_VIEW_DEST_RECT_ONLY, - IVI_LAYOUT_TRANSITION_VIEW_FADE_ONLY, - IVI_LAYOUT_TRANSITION_LAYER_FADE, - IVI_LAYOUT_TRANSITION_LAYER_MOVE, - IVI_LAYOUT_TRANSITION_LAYER_VIEW_ORDER, - IVI_LAYOUT_TRANSITION_VIEW_MOVE_RESIZE, - IVI_LAYOUT_TRANSITION_VIEW_RESIZE, - IVI_LAYOUT_TRANSITION_VIEW_FADE, - IVI_LAYOUT_TRANSITION_MAX, -}; - -#define IVI_LAYOUT_API_NAME "ivi_layout_api_v1" - -struct ivi_layout_interface { - - /** - * \brief Commit all changes and execute all enqueued commands since - * last commit. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*commit_changes)(void); - - /** - * surface controller interface - */ - - /** - * \brief add a listener for notification when ivi_surface is created - * - * When an ivi_surface is created, a signal is emitted - * to the listening controller plugins. - * The pointer of the created ivi_surface is sent as the void *data argument - * to the wl_listener::notify callback function of the listener. - */ - int32_t (*add_listener_create_surface)(struct wl_listener *listener); - - /** - * \brief add a listener for notification when ivi_surface is removed - * - * When an ivi_surface is removed, a signal is emitted - * to the listening controller plugins. - * The pointer of the removed ivi_surface is sent as the void *data argument - * to the wl_listener::notify callback function of the listener. - */ - int32_t (*add_listener_remove_surface)(struct wl_listener *listener); - - /** - * \brief add a listener for notification when ivi_surface is configured - * - * When an ivi_surface is configured, a signal is emitted - * to the listening controller plugins. - * The pointer of the configured ivi_surface is sent as the void *data argument - * to the wl_listener::notify callback function of the listener. - */ - int32_t (*add_listener_configure_surface)(struct wl_listener *listener); - - /** - * \brief add a listener for notification when desktop_surface is configured - * - * When an desktop_surface is configured, a signal is emitted - * to the listening controller plugins. - * The pointer of the configured desktop_surface is sent as the void *data argument - * to the wl_listener::notify callback function of the listener. - */ - int32_t (*add_listener_configure_desktop_surface)(struct wl_listener *listener); - - /** - * \brief Get all ivi_surfaces which are currently registered and managed - * by the services - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*get_surfaces)(int32_t *pLength, struct ivi_layout_surface ***ppArray); - - /** - * \brief get id of ivi_surface from ivi_layout_surface - * - * \return id of ivi_surface - */ - uint32_t (*get_id_of_surface)(struct ivi_layout_surface *ivisurf); - - /** - * \brief get ivi_layout_surface from id of ivi_surface - * - * \return (struct ivi_layout_surface *) - * if the method call was successful - * \return NULL if the method call was failed - */ - struct ivi_layout_surface * - (*get_surface_from_id)(uint32_t id_surface); - - /** - * \brief get ivi_layout_surface_properties from ivisurf - * - * \return (struct ivi_layout_surface_properties *) - * if the method call was successful - * \return NULL if the method call was failed - */ - const struct ivi_layout_surface_properties * - (*get_properties_of_surface)(struct ivi_layout_surface *ivisurf); - - /** - * \brief Get all Surfaces which are currently registered to a given - * layer and are managed by the services - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*get_surfaces_on_layer)(struct ivi_layout_layer *ivilayer, - int32_t *pLength, - struct ivi_layout_surface ***ppArray); - - /** - * \brief Set the visibility of a ivi_surface. - * - * If a surface is not visible it will not be rendered. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*surface_set_visibility)(struct ivi_layout_surface *ivisurf, - bool newVisibility); - - /** - * \brief Set the opacity of a surface. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*surface_set_opacity)(struct ivi_layout_surface *ivisurf, - wl_fixed_t opacity); - - /** - * \brief Set the area of a ivi_surface which should be used for the rendering. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*surface_set_source_rectangle)(struct ivi_layout_surface *ivisurf, - int32_t x, int32_t y, - int32_t width, int32_t height); - - /** - * \brief Set the destination area of a ivi_surface within a ivi_layer - * for rendering. - * - * The surface will be scaled to this rectangle for rendering. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*surface_set_destination_rectangle)(struct ivi_layout_surface *ivisurf, - int32_t x, int32_t y, - int32_t width, int32_t height); - - /** - * \brief add a listener to listen property changes of ivi_surface - * - * When a property of the ivi_surface is changed, the property_changed - * signal is emitted to the listening controller plugins. - * The pointer of the ivi_surface is sent as the void *data argument - * to the wl_listener::notify callback function of the listener. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*surface_add_listener)(struct ivi_layout_surface *ivisurf, - struct wl_listener *listener); - - /** - * \brief get weston_surface of ivi_surface - */ - struct weston_surface * - (*surface_get_weston_surface)(struct ivi_layout_surface *ivisurf); - - /** - * \brief set type of transition animation - */ - int32_t (*surface_set_transition)(struct ivi_layout_surface *ivisurf, - enum ivi_layout_transition_type type, - uint32_t duration); - - /** - * \brief set duration of transition animation - */ - int32_t (*surface_set_transition_duration)( - struct ivi_layout_surface *ivisurf, - uint32_t duration); - - /** - * \brief set id of ivi_layout_surface - */ - int32_t (*surface_set_id)(struct ivi_layout_surface *ivisurf, - uint32_t id_surface); - - /** - * layer controller interface - */ - - /** - * \brief add a listener for notification when ivi_layer is created - * - * When an ivi_layer is created, a signal is emitted - * to the listening controller plugins. - * The pointer of the created ivi_layer is sent as the void *data argument - * to the wl_listener::notify callback function of the listener. - */ - int32_t (*add_listener_create_layer)(struct wl_listener *listener); - - /** - * \brief add a listener for notification when ivi_layer is removed - * - * When an ivi_layer is removed, a signal is emitted - * to the listening controller plugins. - * The pointer of the removed ivi_layer is sent as the void *data argument - * to the wl_listener::notify callback function of the listener. - */ - int32_t (*add_listener_remove_layer)(struct wl_listener *listener); - - /** - * \brief Create a ivi_layer which should be managed by the service - * - * \return (struct ivi_layout_layer *) - * if the method call was successful - * \return NULL if the method call was failed - */ - struct ivi_layout_layer * - (*layer_create_with_dimension)(uint32_t id_layer, - int32_t width, int32_t height); - - /** - * \brief Removes a ivi_layer which is currently managed by the service - */ - void (*layer_destroy)(struct ivi_layout_layer *ivilayer); - - /** - * \brief Get all ivi_layers which are currently registered and managed - * by the services - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*get_layers)(int32_t *pLength, struct ivi_layout_layer ***ppArray); - - /** - * \brief get id of ivi_layer from ivi_layout_layer - * - * - * \return id of ivi_layer - */ - uint32_t (*get_id_of_layer)(struct ivi_layout_layer *ivilayer); - - /** - * \brief get ivi_layout_layer from id of layer - * - * \return (struct ivi_layout_layer *) - * if the method call was successful - * \return NULL if the method call was failed - */ - struct ivi_layout_layer * (*get_layer_from_id)(uint32_t id_layer); - - /** - * \brief Get the ivi_layer properties - * - * \return (const struct ivi_layout_layer_properties *) - * if the method call was successful - * \return NULL if the method call was failed - */ - const struct ivi_layout_layer_properties * - (*get_properties_of_layer)(struct ivi_layout_layer *ivilayer); - - /** - * \brief Get all ivi-layers under the given ivi-surface - * - * This means all the ivi-layers the ivi-surface was added to. It has - * no relation to geometric overlaps. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*get_layers_under_surface)(struct ivi_layout_surface *ivisurf, - int32_t *pLength, - struct ivi_layout_layer ***ppArray); - - /** - * \brief Get all Layers of the given weston_output - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*get_layers_on_screen)(struct weston_output *output, - int32_t *pLength, - struct ivi_layout_layer ***ppArray); - - /** - * \brief Set the visibility of a ivi_layer. If a ivi_layer is not visible, - * the ivi_layer and its ivi_surfaces will not be rendered. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*layer_set_visibility)(struct ivi_layout_layer *ivilayer, - bool newVisibility); - - /** - * \brief Set the opacity of a ivi_layer. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*layer_set_opacity)(struct ivi_layout_layer *ivilayer, - wl_fixed_t opacity); - - /** - * \brief Set the area of a ivi_layer which should be used for the rendering. - * - * Only this part will be visible. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*layer_set_source_rectangle)(struct ivi_layout_layer *ivilayer, - int32_t x, int32_t y, - int32_t width, int32_t height); - - /** - * \brief Set the destination area on the display for a ivi_layer. - * - * The ivi_layer will be scaled and positioned to this rectangle - * for rendering - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*layer_set_destination_rectangle)(struct ivi_layout_layer *ivilayer, - int32_t x, int32_t y, - int32_t width, int32_t height); - - /** - * \brief Add a ivi_surface to a ivi_layer which is currently managed by the service - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*layer_add_surface)(struct ivi_layout_layer *ivilayer, - struct ivi_layout_surface *addsurf); - - /** - * \brief Removes a surface from a layer which is currently managed by the service - */ - void (*layer_remove_surface)(struct ivi_layout_layer *ivilayer, - struct ivi_layout_surface *remsurf); - - /** - * \brief Sets render order of ivi_surfaces within a ivi_layer - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*layer_set_render_order)(struct ivi_layout_layer *ivilayer, - struct ivi_layout_surface **pSurface, - int32_t number); - - /** - * \brief add a listener to listen property changes of ivi_layer - * - * When a property of the ivi_layer is changed, the property_changed - * signal is emitted to the listening controller plugins. - * The pointer of the ivi_layer is sent as the void *data argument - * to the wl_listener::notify callback function of the listener. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*layer_add_listener)(struct ivi_layout_layer *ivilayer, - struct wl_listener *listener); - - /** - * \brief set type of transition animation - */ - int32_t (*layer_set_transition)(struct ivi_layout_layer *ivilayer, - enum ivi_layout_transition_type type, - uint32_t duration); - - /** - * screen controller interface - */ - - /** - * \brief Get the weston_outputs under the given ivi_layer - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*get_screens_under_layer)(struct ivi_layout_layer *ivilayer, - int32_t *pLength, - struct weston_output ***ppArray); - - /** - * \brief Add a ivi_layer to a weston_output which is currently managed - * by the service - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*screen_add_layer)(struct weston_output *output, - struct ivi_layout_layer *addlayer); - - /** - * \brief Sets render order of ivi_layers on a weston_output - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*screen_set_render_order)(struct weston_output *output, - struct ivi_layout_layer **pLayer, - const int32_t number); - - /** - * transition animation for layer - */ - void (*transition_move_layer_cancel)(struct ivi_layout_layer *layer); - int32_t (*layer_set_fade_info)(struct ivi_layout_layer* ivilayer, - uint32_t is_fade_in, - double start_alpha, double end_alpha); - - /** - * surface content dumping for debugging - */ - int32_t (*surface_get_size)(struct ivi_layout_surface *ivisurf, - int32_t *width, int32_t *height, - int32_t *stride); - - int32_t (*surface_dump)(struct weston_surface *surface, - void *target, size_t size, - int32_t x, int32_t y, - int32_t width, int32_t height); - - /** - * Returns the ivi_layout_surface or NULL - * - * NULL is returned if there is no ivi_layout_surface corresponding - * to the given weston_surface. - */ - struct ivi_layout_surface * - (*get_surface)(struct weston_surface *surface); - - /** - * \brief Remove a ivi_layer to a weston_output which is currently managed - * by the service - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*screen_remove_layer)(struct weston_output *output, - struct ivi_layout_layer *removelayer); -}; - -static inline const struct ivi_layout_interface * -ivi_layout_get_api(struct weston_compositor *compositor) -{ - const void *api; - api = weston_plugin_api_get(compositor, IVI_LAYOUT_API_NAME, - sizeof(struct ivi_layout_interface)); - - return (const struct ivi_layout_interface *)api; -} - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* _IVI_LAYOUT_EXPORT_H_ */ diff --git a/ivi-shell/ivi-layout-private.h b/ivi-shell/ivi-layout-private.h deleted file mode 100644 index 5a0119c..0000000 --- a/ivi-shell/ivi-layout-private.h +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (C) 2014 DENSO CORPORATION - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _ivi_layout_PRIVATE_H_ -#define _ivi_layout_PRIVATE_H_ - -#include - -#include -#include "ivi-layout-export.h" -#include - -struct ivi_layout_view { - struct wl_list link; /* ivi_layout::view_list */ - struct wl_list surf_link; /*ivi_layout_surface::view_list */ - struct wl_list pending_link; /* ivi_layout_layer::pending.view_list */ - struct wl_list order_link; /* ivi_layout_layer::order.view_list */ - - struct weston_view *view; - struct weston_transform transform; - - struct ivi_layout_surface *ivisurf; - struct ivi_layout_layer *on_layer; -}; - -struct ivi_layout_surface { - struct wl_list link; /* ivi_layout::surface_list */ - struct wl_signal property_changed; - int32_t update_count; - uint32_t id_surface; - - struct ivi_layout *layout; - struct weston_surface *surface; - struct weston_desktop_surface *weston_desktop_surface; - - struct ivi_layout_surface_properties prop; - - struct { - struct ivi_layout_surface_properties prop; - } pending; - - struct wl_list view_list; /* ivi_layout_view::surf_link */ -}; - -struct ivi_layout_layer { - struct wl_list link; /* ivi_layout::layer_list */ - struct wl_signal property_changed; - uint32_t id_layer; - - struct ivi_layout *layout; - struct ivi_layout_screen *on_screen; - - struct ivi_layout_layer_properties prop; - - struct { - struct ivi_layout_layer_properties prop; - struct wl_list view_list; /* ivi_layout_view::pending_link */ - struct wl_list link; /* ivi_layout_screen::pending.layer_list */ - } pending; - - struct { - int dirty; - struct wl_list view_list; /* ivi_layout_view::order_link */ - struct wl_list link; /* ivi_layout_screen::order.layer_list */ - } order; - - int32_t ref_count; -}; - -struct ivi_layout { - struct weston_compositor *compositor; - - struct wl_list surface_list; /* ivi_layout_surface::link */ - struct wl_list layer_list; /* ivi_layout_layer::link */ - struct wl_list screen_list; /* ivi_layout_screen::link */ - struct wl_list view_list; /* ivi_layout_view::link */ - - struct { - struct wl_signal created; - struct wl_signal removed; - } layer_notification; - - struct { - struct wl_signal created; - struct wl_signal removed; - struct wl_signal configure_changed; - struct wl_signal configure_desktop_changed; - } surface_notification; - - struct weston_layer layout_layer; - - struct ivi_layout_transition_set *transitions; - struct wl_list pending_transition_list; /* transition_node::link */ -}; - -struct ivi_layout *get_instance(void); - -struct ivi_layout_transition; - -struct ivi_layout_transition_set { - struct wl_event_source *event_source; - struct wl_list transition_list; -}; - -typedef void (*ivi_layout_transition_destroy_user_func)(void *user_data); - -struct ivi_layout_transition_set * -ivi_layout_transition_set_create(struct weston_compositor *ec); - -void -ivi_layout_transition_move_resize_view(struct ivi_layout_surface *surface, - int32_t dest_x, int32_t dest_y, - int32_t dest_width, int32_t dest_height, - uint32_t duration); - -void -ivi_layout_transition_visibility_on(struct ivi_layout_surface *surface, - uint32_t duration); - -void -ivi_layout_transition_visibility_off(struct ivi_layout_surface *surface, - uint32_t duration); - - -void -ivi_layout_transition_move_layer(struct ivi_layout_layer *layer, - int32_t dest_x, int32_t dest_y, - uint32_t duration); - -void -ivi_layout_transition_fade_layer(struct ivi_layout_layer *layer, - uint32_t is_fade_in, - double start_alpha, double end_alpha, - void *user_data, - ivi_layout_transition_destroy_user_func destroy_func, - uint32_t duration); - -int32_t -is_surface_transition(struct ivi_layout_surface *surface); - -void -ivi_layout_remove_all_surface_transitions(struct ivi_layout_surface *surface); - -/** - * methods of interaction between transition animation with ivi-layout - */ -int32_t -ivi_layout_commit_changes(void); -uint32_t -ivi_layout_get_id_of_surface(struct ivi_layout_surface *ivisurf); -int32_t -ivi_layout_surface_set_destination_rectangle(struct ivi_layout_surface *ivisurf, - int32_t x, int32_t y, - int32_t width, int32_t height); -int32_t -ivi_layout_surface_set_opacity(struct ivi_layout_surface *ivisurf, - wl_fixed_t opacity); -int32_t -ivi_layout_surface_set_visibility(struct ivi_layout_surface *ivisurf, - bool newVisibility); -void -ivi_layout_surface_set_size(struct ivi_layout_surface *ivisurf, - int32_t width, int32_t height); -struct ivi_layout_surface * -ivi_layout_get_surface_from_id(uint32_t id_surface); -int32_t -ivi_layout_layer_set_opacity(struct ivi_layout_layer *ivilayer, - wl_fixed_t opacity); -int32_t -ivi_layout_layer_set_visibility(struct ivi_layout_layer *ivilayer, - bool newVisibility); -int32_t -ivi_layout_layer_set_destination_rectangle(struct ivi_layout_layer *ivilayer, - int32_t x, int32_t y, - int32_t width, int32_t height); -int32_t -ivi_layout_layer_set_render_order(struct ivi_layout_layer *ivilayer, - struct ivi_layout_surface **pSurface, - int32_t number); -void -ivi_layout_transition_move_layer_cancel(struct ivi_layout_layer *layer); - -#endif diff --git a/ivi-shell/ivi-layout-shell.h b/ivi-shell/ivi-layout-shell.h deleted file mode 100644 index bbbb77d..0000000 --- a/ivi-shell/ivi-layout-shell.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2014 DENSO CORPORATION - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef IVI_LAYOUT_SHELL_H -#define IVI_LAYOUT_SHELL_H - -#include - -/* - * This is the interface that ivi-layout exposes to ivi-shell. - * It is private to ivi-shell.so plugin. - */ - -struct wl_listener; -struct weston_compositor; -struct weston_view; -struct weston_surface; -struct ivi_layout_surface; - -void -ivi_layout_desktop_surface_configure(struct ivi_layout_surface *ivisurf, - int32_t width, int32_t height); - -struct ivi_layout_surface* -ivi_layout_desktop_surface_create(struct weston_surface *wl_surface); - -void -ivi_layout_surface_configure(struct ivi_layout_surface *ivisurf, - int32_t width, int32_t height); - -struct ivi_layout_surface* -ivi_layout_surface_create(struct weston_surface *wl_surface, - uint32_t id_surface); - -void -ivi_layout_init_with_compositor(struct weston_compositor *ec); - -void -ivi_layout_surface_destroy(struct ivi_layout_surface *ivisurf); - -int -load_controller_modules(struct weston_compositor *compositor, const char *modules, - int *argc, char *argv[]); - -#endif /* IVI_LAYOUT_SHELL_H */ diff --git a/ivi-shell/ivi-layout-transition.c b/ivi-shell/ivi-layout-transition.c deleted file mode 100644 index b07c9d0..0000000 --- a/ivi-shell/ivi-layout-transition.c +++ /dev/null @@ -1,908 +0,0 @@ -/* - * Copyright (C) 2014 DENSO CORPORATION - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "ivi-shell.h" -#include "ivi-layout-export.h" -#include "ivi-layout-private.h" - -struct ivi_layout_transition; - -typedef void (*ivi_layout_transition_frame_func)( - struct ivi_layout_transition *transition); -typedef void (*ivi_layout_transition_destroy_func)( - struct ivi_layout_transition *transition); -typedef int32_t (*ivi_layout_is_transition_func)(void *private_data, void *id); - -struct ivi_layout_transition { - enum ivi_layout_transition_type type; - void *private_data; - void *user_data; - - uint32_t time_start; - uint32_t time_duration; - uint32_t time_elapsed; - uint32_t is_done; - ivi_layout_is_transition_func is_transition_func; - ivi_layout_transition_frame_func frame_func; - ivi_layout_transition_destroy_func destroy_func; -}; - -struct transition_node { - struct ivi_layout_transition *transition; - - /* ivi_layout::pending_transition_list - * ivi_layout_transition_set::transition_list - */ - struct wl_list link; -}; - -static void layout_transition_destroy(struct ivi_layout_transition *transition); - -static struct ivi_layout_transition * -get_transition_from_type_and_id(enum ivi_layout_transition_type type, - void *id_data) -{ - struct ivi_layout *layout = get_instance(); - struct transition_node *node; - struct ivi_layout_transition *tran; - - wl_list_for_each(node, &layout->transitions->transition_list, link) { - tran = node->transition; - - if (tran->type == type && - tran->is_transition_func(tran->private_data, id_data)) - return tran; - } - - return NULL; -} - -int32_t -is_surface_transition(struct ivi_layout_surface *surface) -{ - struct ivi_layout *layout = get_instance(); - struct transition_node *node; - struct ivi_layout_transition *tran; - - wl_list_for_each(node, &layout->transitions->transition_list, link) { - tran = node->transition; - - if ((tran->type == IVI_LAYOUT_TRANSITION_VIEW_MOVE_RESIZE || - tran->type == IVI_LAYOUT_TRANSITION_VIEW_RESIZE) && - tran->is_transition_func(tran->private_data, surface)) - return 1; - } - - return 0; -} - -void -ivi_layout_remove_all_surface_transitions(struct ivi_layout_surface *surface) -{ - struct ivi_layout *layout = get_instance(); - struct transition_node *node; - struct transition_node *tmp; - struct ivi_layout_transition *tran; - - wl_list_for_each_safe(node, tmp, &layout->transitions->transition_list, link) { - tran = node->transition; - if (tran->is_transition_func(tran->private_data, surface)) { - layout_transition_destroy(tran); - } - }; -} - -static void -tick_transition(struct ivi_layout_transition *transition, uint32_t timestamp) -{ - const double t = timestamp - transition->time_start; - - if (transition->time_duration <= t) { - transition->time_elapsed = transition->time_duration; - transition->is_done = 1; - } else { - transition->time_elapsed = t; - } -} - -static float time_to_nowpos(struct ivi_layout_transition *transition) -{ - return sin((float)transition->time_elapsed / - (float)transition->time_duration * M_PI_2); -} - -static void -do_transition_frame(struct ivi_layout_transition *transition, - uint32_t timestamp) -{ - if (0 == transition->time_start) - transition->time_start = timestamp; - - tick_transition(transition, timestamp); - transition->frame_func(transition); - - if (transition->is_done) - layout_transition_destroy(transition); -} - -static int32_t -layout_transition_frame(void *data) -{ - struct ivi_layout_transition_set *transitions = data; - uint32_t fps = 30; - struct timespec timestamp = {}; - uint32_t msec = 0; - struct transition_node *node = NULL; - struct transition_node *next = NULL; - - if (wl_list_empty(&transitions->transition_list)) { - wl_event_source_timer_update(transitions->event_source, 0); - return 1; - } - - wl_event_source_timer_update(transitions->event_source, 1000 / fps); - - clock_gettime(CLOCK_MONOTONIC, ×tamp);/* FIXME */ - msec = (1e+3 * timestamp.tv_sec + 1e-6 * timestamp.tv_nsec); - - wl_list_for_each_safe(node, next, &transitions->transition_list, link) { - do_transition_frame(node->transition, msec); - } - - ivi_layout_commit_changes(); - return 1; -} - -struct ivi_layout_transition_set * -ivi_layout_transition_set_create(struct weston_compositor *ec) -{ - struct ivi_layout_transition_set *transitions; - struct wl_event_loop *loop; - - transitions = malloc(sizeof(*transitions)); - if (transitions == NULL) { - weston_log("%s: memory allocation fails\n", __func__); - return NULL; - } - - wl_list_init(&transitions->transition_list); - - loop = wl_display_get_event_loop(ec->wl_display); - transitions->event_source = - wl_event_loop_add_timer(loop, layout_transition_frame, - transitions); - - return transitions; -} - -static bool -layout_transition_register(struct ivi_layout_transition *trans) -{ - struct ivi_layout *layout = get_instance(); - struct transition_node *node; - - node = malloc(sizeof(*node)); - if (node == NULL) { - weston_log("%s: memory allocation fails\n", __func__); - return false; - } - - node->transition = trans; - wl_list_insert(&layout->pending_transition_list, &node->link); - return true; -} - -static void -remove_transition(struct ivi_layout *layout, - struct ivi_layout_transition *trans) -{ - struct transition_node *node; - struct transition_node *next; - - wl_list_for_each_safe(node, next, - &layout->transitions->transition_list, link) { - if (node->transition == trans) { - wl_list_remove(&node->link); - free(node); - return; - } - } - - wl_list_for_each_safe(node, next, - &layout->pending_transition_list, link) { - if (node->transition == trans) { - wl_list_remove(&node->link); - free(node); - return; - } - } -} - -static void -layout_transition_destroy(struct ivi_layout_transition *transition) -{ - struct ivi_layout *layout = get_instance(); - - remove_transition(layout, transition); - if (transition->destroy_func) - transition->destroy_func(transition); - free(transition); -} - -static struct ivi_layout_transition * -create_layout_transition(void) -{ - struct ivi_layout_transition *transition = malloc(sizeof(*transition)); - - if (transition == NULL) { - weston_log("%s: memory allocation fails\n", __func__); - return NULL; - } - - transition->type = IVI_LAYOUT_TRANSITION_MAX; - transition->time_start = 0; - transition->time_duration = 300; /* 300ms */ - transition->time_elapsed = 0; - - transition->is_done = 0; - - transition->is_transition_func = NULL; - transition->private_data = NULL; - transition->user_data = NULL; - - transition->frame_func = NULL; - transition->destroy_func = NULL; - - return transition; -} - -/* move and resize view transition */ - -struct move_resize_view_data { - struct ivi_layout_surface *surface; - int32_t start_x; - int32_t start_y; - int32_t end_x; - int32_t end_y; - int32_t start_width; - int32_t start_height; - int32_t end_width; - int32_t end_height; -}; - -static void -transition_move_resize_view_destroy(struct ivi_layout_transition *transition) -{ - struct move_resize_view_data *data = - (struct move_resize_view_data *)transition->private_data; - struct ivi_layout_surface *layout_surface = data->surface; - - ivi_layout_surface_set_size(layout_surface, - layout_surface->prop.dest_width, - layout_surface->prop.dest_height); - - if (transition->private_data) { - free(transition->private_data); - transition->private_data = NULL; - } -} - -static void -transition_move_resize_view_user_frame(struct ivi_layout_transition *transition) -{ - struct move_resize_view_data *mrv = transition->private_data; - const double current = time_to_nowpos(transition); - - const int32_t destx = mrv->start_x + - (mrv->end_x - mrv->start_x) * current; - - const int32_t desty = mrv->start_y + - (mrv->end_y - mrv->start_y) * current; - - const int32_t dest_width = mrv->start_width + - (mrv->end_width - mrv->start_width) * current; - - const int32_t dest_height = mrv->start_height + - (mrv->end_height - mrv->start_height) * current; - - ivi_layout_surface_set_destination_rectangle(mrv->surface, - destx, desty, - dest_width, dest_height); -} - -static int32_t -is_transition_move_resize_view_func(struct move_resize_view_data *data, - struct ivi_layout_surface *view) -{ - return data->surface == view; -} - -static struct ivi_layout_transition * -create_move_resize_view_transition( - struct ivi_layout_surface *surface, - int32_t start_x, int32_t start_y, - int32_t end_x, int32_t end_y, - int32_t start_width, int32_t start_height, - int32_t end_width, int32_t end_height, - ivi_layout_transition_frame_func frame_func, - ivi_layout_transition_destroy_func destroy_func, - uint32_t duration) -{ - struct ivi_layout_transition *transition; - struct move_resize_view_data *data; - - transition = create_layout_transition(); - if (transition == NULL) - return NULL; - - data = malloc(sizeof(*data)); - if (data == NULL) { - weston_log("%s: memory allocation fails\n", __func__); - free(transition); - return NULL; - } - - transition->type = IVI_LAYOUT_TRANSITION_VIEW_MOVE_RESIZE; - transition->is_transition_func = (ivi_layout_is_transition_func)is_transition_move_resize_view_func; - - transition->frame_func = frame_func; - transition->destroy_func = destroy_func; - transition->private_data = data; - - if (duration != 0) - transition->time_duration = duration; - - data->surface = surface; - data->start_x = start_x; - data->start_y = start_y; - data->end_x = end_x; - data->end_y = end_y; - - data->start_width = start_width; - data->start_height = start_height; - data->end_width = end_width; - data->end_height = end_height; - - return transition; -} - -void -ivi_layout_transition_move_resize_view(struct ivi_layout_surface *surface, - int32_t dest_x, int32_t dest_y, - int32_t dest_width, int32_t dest_height, - uint32_t duration) -{ - struct ivi_layout_transition *transition; - int32_t start_pos[2] = { - surface->pending.prop.start_x, - surface->pending.prop.start_y - }; - - int32_t start_size[2] = { - surface->pending.prop.start_width, - surface->pending.prop.start_height - }; - - transition = get_transition_from_type_and_id( - IVI_LAYOUT_TRANSITION_VIEW_MOVE_RESIZE, - surface); - if (transition) { - struct move_resize_view_data *data = transition->private_data; - transition->time_start = 0; - transition->time_duration = duration; - - data->start_x = start_pos[0]; - data->start_y = start_pos[1]; - data->end_x = dest_x; - data->end_y = dest_y; - - data->start_width = start_size[0]; - data->start_height = start_size[1]; - data->end_width = dest_width; - data->end_height = dest_height; - return; - } - - transition = create_move_resize_view_transition( - surface, - start_pos[0], start_pos[1], - dest_x, dest_y, - start_size[0], start_size[1], - dest_width, dest_height, - transition_move_resize_view_user_frame, - transition_move_resize_view_destroy, - duration); - - if (transition && layout_transition_register(transition)) - return; - layout_transition_destroy(transition); -} - -/* fade transition */ -struct fade_view_data { - struct ivi_layout_surface *surface; - double start_alpha; - double end_alpha; -}; - -struct store_alpha{ - double alpha; -}; - -static void -fade_view_user_frame(struct ivi_layout_transition *transition) -{ - struct fade_view_data *fade = transition->private_data; - struct ivi_layout_surface *surface = fade->surface; - - const double current = time_to_nowpos(transition); - const double alpha = fade->start_alpha + - (fade->end_alpha - fade->start_alpha) * current; - - ivi_layout_surface_set_opacity(surface, wl_fixed_from_double(alpha)); - ivi_layout_surface_set_visibility(surface, true); -} - -static int32_t -is_transition_fade_view_func(struct fade_view_data *data, - struct ivi_layout_surface *view) -{ - return data->surface == view; -} - -static struct ivi_layout_transition * -create_fade_view_transition( - struct ivi_layout_surface *surface, - double start_alpha, double end_alpha, - ivi_layout_transition_frame_func frame_func, - void *user_data, - ivi_layout_transition_destroy_func destroy_func, - uint32_t duration) -{ - struct ivi_layout_transition *transition; - struct fade_view_data *data; - - transition = create_layout_transition(); - if (transition == NULL) - return NULL; - - data = malloc(sizeof(*data)); - if (data == NULL) { - weston_log("%s: memory allocation fails\n", __func__); - free(transition); - return NULL; - } - - transition->type = IVI_LAYOUT_TRANSITION_VIEW_FADE; - transition->is_transition_func = (ivi_layout_is_transition_func)is_transition_fade_view_func; - - transition->user_data = user_data; - transition->private_data = data; - transition->frame_func = frame_func; - transition->destroy_func = destroy_func; - - if (duration != 0) - transition->time_duration = duration; - - data->surface = surface; - data->start_alpha = start_alpha; - data->end_alpha = end_alpha; - - return transition; -} - -static void -create_visibility_transition(struct ivi_layout_surface *surface, - double start_alpha, - double dest_alpha, - void *user_data, - ivi_layout_transition_destroy_func destroy_func, - uint32_t duration) -{ - struct ivi_layout_transition *transition = NULL; - - transition = create_fade_view_transition( - surface, - start_alpha, dest_alpha, - fade_view_user_frame, - user_data, - destroy_func, - duration); - - if (transition && layout_transition_register(transition)) - return; - layout_transition_destroy(transition); -} - -static void -visibility_on_transition_destroy(struct ivi_layout_transition *transition) -{ - struct fade_view_data *data = transition->private_data; - struct store_alpha *user_data = transition->user_data; - - ivi_layout_surface_set_visibility(data->surface, true); - - free(data); - transition->private_data = NULL; - - free(user_data); - transition->user_data = NULL; -} - -void -ivi_layout_transition_visibility_on(struct ivi_layout_surface *surface, - uint32_t duration) -{ - struct ivi_layout_transition *transition; - bool is_visible = surface->prop.visibility; - wl_fixed_t dest_alpha = surface->prop.opacity; - struct store_alpha *user_data = NULL; - wl_fixed_t start_alpha = 0.0; - struct fade_view_data *data = NULL; - - transition = get_transition_from_type_and_id( - IVI_LAYOUT_TRANSITION_VIEW_FADE, - surface); - if (transition) { - start_alpha = surface->prop.opacity; - user_data = transition->user_data; - data = transition->private_data; - - transition->time_start = 0; - transition->time_duration = duration; - transition->destroy_func = visibility_on_transition_destroy; - - data->start_alpha = wl_fixed_to_double(start_alpha); - data->end_alpha = user_data->alpha; - return; - } - - if (is_visible) - return; - - user_data = malloc(sizeof(*user_data)); - if (user_data == NULL) { - weston_log("%s: memory allocation fails\n", __func__); - return; - } - - user_data->alpha = wl_fixed_to_double(dest_alpha); - - create_visibility_transition(surface, - 0.0, // start_alpha - wl_fixed_to_double(dest_alpha), - user_data, - visibility_on_transition_destroy, - duration); -} - -static void -visibility_off_transition_destroy(struct ivi_layout_transition *transition) -{ - struct fade_view_data *data = transition->private_data; - struct store_alpha *user_data = transition->user_data; - - ivi_layout_surface_set_visibility(data->surface, false); - - ivi_layout_surface_set_opacity(data->surface, - wl_fixed_from_double(user_data->alpha)); - - free(data); - transition->private_data = NULL; - - free(user_data); - transition->user_data= NULL; -} - -void -ivi_layout_transition_visibility_off(struct ivi_layout_surface *surface, - uint32_t duration) -{ - struct ivi_layout_transition *transition; - wl_fixed_t start_alpha = surface->prop.opacity; - struct store_alpha* user_data = NULL; - struct fade_view_data* data = NULL; - - transition = - get_transition_from_type_and_id(IVI_LAYOUT_TRANSITION_VIEW_FADE, - surface); - if (transition) { - data = transition->private_data; - - transition->time_start = 0; - transition->time_duration = duration; - transition->destroy_func = visibility_off_transition_destroy; - - data->start_alpha = wl_fixed_to_double(start_alpha); - data->end_alpha = 0; - return; - } - - user_data = malloc(sizeof(*user_data)); - if (user_data == NULL) { - weston_log("%s: memory allocation fails\n", __func__); - return; - } - - user_data->alpha = wl_fixed_to_double(start_alpha); - - create_visibility_transition(surface, - wl_fixed_to_double(start_alpha), - 0.0, // dest_alpha - user_data, - visibility_off_transition_destroy, - duration); -} - -/* move layer transition */ - -struct move_layer_data { - struct ivi_layout_layer *layer; - int32_t start_x; - int32_t start_y; - int32_t end_x; - int32_t end_y; - ivi_layout_transition_destroy_user_func destroy_func; -}; - -static void -transition_move_layer_user_frame(struct ivi_layout_transition *transition) -{ - struct move_layer_data *data = transition->private_data; - struct ivi_layout_layer *layer = data->layer; - - const float current = time_to_nowpos(transition); - - const int32_t dest_x = data->start_x + - (data->end_x - data->start_x) * current; - - const int32_t dest_y = data->start_y + - (data->end_y - data->start_y) * current; - - ivi_layout_layer_set_destination_rectangle(layer, dest_x, dest_y, - layer->prop.dest_width, layer->prop.dest_height); -} - -static void -transition_move_layer_destroy(struct ivi_layout_transition *transition) -{ - struct move_layer_data *data = transition->private_data; - - if (data->destroy_func) - data->destroy_func(transition->user_data); - - free(data); - transition->private_data = NULL; -} - -static int32_t -is_transition_move_layer_func(struct move_layer_data *data, - struct ivi_layout_layer *layer) -{ - return data->layer == layer; -} - - -static struct ivi_layout_transition * -create_move_layer_transition( - struct ivi_layout_layer *layer, - int32_t start_x, int32_t start_y, - int32_t end_x, int32_t end_y, - void *user_data, - ivi_layout_transition_destroy_user_func destroy_user_func, - uint32_t duration) -{ - struct ivi_layout_transition *transition; - struct move_layer_data *data; - - transition = create_layout_transition(); - if (transition == NULL) - return NULL; - - data = malloc(sizeof(*data)); - if (data == NULL) { - weston_log("%s: memory allocation fails\n", __func__); - free(transition); - return NULL; - } - - transition->type = IVI_LAYOUT_TRANSITION_LAYER_MOVE; - transition->is_transition_func = (ivi_layout_is_transition_func)is_transition_move_layer_func; - - transition->frame_func = transition_move_layer_user_frame; - transition->destroy_func = transition_move_layer_destroy; - transition->private_data = data; - transition->user_data = user_data; - - if (duration != 0) - transition->time_duration = duration; - - data->layer = layer; - data->start_x = start_x; - data->start_y = start_y; - data->end_x = end_x; - data->end_y = end_y; - data->destroy_func = destroy_user_func; - - return transition; -} - -void -ivi_layout_transition_move_layer(struct ivi_layout_layer *layer, - int32_t dest_x, int32_t dest_y, - uint32_t duration) -{ - int32_t start_pos_x = layer->prop.dest_x; - int32_t start_pos_y = layer->prop.dest_y; - struct ivi_layout_transition *transition = NULL; - - transition = create_move_layer_transition( - layer, - start_pos_x, start_pos_y, - dest_x, dest_y, - NULL, NULL, - duration); - - if (transition && layout_transition_register(transition)) - return; - - free(transition); -} - -void -ivi_layout_transition_move_layer_cancel(struct ivi_layout_layer *layer) -{ - struct ivi_layout_transition *transition = - get_transition_from_type_and_id( - IVI_LAYOUT_TRANSITION_LAYER_MOVE, - layer); - if (transition) { - layout_transition_destroy(transition); - } -} - -/* fade layer transition */ -struct fade_layer_data { - struct ivi_layout_layer *layer; - uint32_t is_fade_in; - double start_alpha; - double end_alpha; - ivi_layout_transition_destroy_user_func destroy_func; -}; - -static void -transition_fade_layer_destroy(struct ivi_layout_transition *transition) -{ - struct fade_layer_data *data = transition->private_data; - transition->private_data = NULL; - - free(data); -} - -static void -transition_fade_layer_user_frame(struct ivi_layout_transition *transition) -{ - double current = time_to_nowpos(transition); - struct fade_layer_data *data = transition->private_data; - double alpha = data->start_alpha + - (data->end_alpha - data->start_alpha) * current; - wl_fixed_t fixed_alpha = wl_fixed_from_double(alpha); - - int32_t is_done = transition->is_done; - bool is_visible = !is_done || data->is_fade_in; - - ivi_layout_layer_set_opacity(data->layer, fixed_alpha); - ivi_layout_layer_set_visibility(data->layer, is_visible); -} - -static int32_t -is_transition_fade_layer_func(struct fade_layer_data *data, - struct ivi_layout_layer *layer) -{ - return data->layer == layer; -} - -void -ivi_layout_transition_fade_layer( - struct ivi_layout_layer *layer, - uint32_t is_fade_in, - double start_alpha, double end_alpha, - void* user_data, - ivi_layout_transition_destroy_user_func destroy_func, - uint32_t duration) -{ - struct ivi_layout_transition *transition; - struct fade_layer_data *data; - wl_fixed_t fixed_opacity; - double now_opacity; - double remain; - - transition = get_transition_from_type_and_id( - IVI_LAYOUT_TRANSITION_LAYER_FADE, - layer); - if (transition) { - /* transition update */ - data = transition->private_data; - - /* FIXME */ - fixed_opacity = layer->prop.opacity; - now_opacity = wl_fixed_to_double(fixed_opacity); - - data->is_fade_in = is_fade_in; - data->start_alpha = now_opacity; - data->end_alpha = end_alpha; - - remain = is_fade_in? 1.0 - now_opacity : now_opacity; - transition->time_start = 0; - transition->time_elapsed = 0; - transition->time_duration = duration * remain; - - return; - } - - transition = create_layout_transition(); - if (transition == NULL) - return; - - data = malloc(sizeof(*data)); - if (data == NULL) { - weston_log("%s: memory allocation fails\n", __func__); - free(transition); - return; - } - - transition->type = IVI_LAYOUT_TRANSITION_LAYER_FADE; - transition->is_transition_func = (ivi_layout_is_transition_func)is_transition_fade_layer_func; - - transition->private_data = data; - transition->user_data = user_data; - - transition->frame_func = transition_fade_layer_user_frame; - transition->destroy_func = transition_fade_layer_destroy; - - if (duration != 0) - transition->time_duration = duration; - - data->layer = layer; - data->is_fade_in = is_fade_in; - data->start_alpha = start_alpha; - data->end_alpha = end_alpha; - data->destroy_func = destroy_func; - - if (!layout_transition_register(transition)) - layout_transition_destroy(transition); - - return; -} - diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c deleted file mode 100644 index b2f8323..0000000 --- a/ivi-shell/ivi-layout.c +++ /dev/null @@ -1,2127 +0,0 @@ -/* - * Copyright (C) 2013 DENSO CORPORATION - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/** - * Implementation of ivi-layout library. The actual view on ivi_screen is - * not updated until ivi_layout_commit_changes is called. An overview from - * calling API for updating properties of ivi_surface/ivi_layer to asking - * compositor to compose them by using weston_view_schedule_repaint, - * 0/ initialize this library by ivi_layout_init_with_compositor - * with (struct weston_compositor *ec) from ivi-shell. - * 1/ When an API for updating properties of ivi_surface/ivi_layer, it updates - * pending prop of ivi_surface/ivi_layer/ivi_screen which are structure to - * store properties. - * 2/ Before calling commitChanges, in case of calling an API to get a property, - * return current property, not pending property. - * 3/ At the timing of calling ivi_layout_commitChanges, pending properties - * are applied to properties. - * - * *) ivi_layout_commitChanges is also called by transition animation - * per each frame. See ivi-layout-transition.c in details. Transition - * animation interpolates frames between previous properties of ivi_surface - * and new ones. - * For example, when a property of ivi_surface is changed from invisible - * to visible, it behaves like fade-in. When ivi_layout_commitChange is - * called during transition animation, it cancels the transition and - * re-start transition to new properties from current properties of final - * frame just before the cancellation. - * - * 4/ According properties, set transformation by using weston_matrix and - * weston_view per ivi_surfaces and ivi_layers in while loop. - * 5/ Set damage and trigger transform by using weston_view_geometry_dirty. - * 6/ Schedule repaint for each view by using weston_view_schedule_repaint. - * 7/ Notify update of properties. - * - */ -#include "config.h" - -#include -#include -#include - -#include "compositor/weston.h" -#include -#include "ivi-shell.h" -#include "ivi-layout-export.h" -#include "ivi-layout-private.h" -#include "ivi-layout-shell.h" - -#include "shared/helpers.h" -#include "shared/os-compatibility.h" - -#define max(a, b) ((a) > (b) ? (a) : (b)) - -struct ivi_layout; - -struct ivi_layout_screen { - struct wl_list link; /* ivi_layout::screen_list */ - - struct ivi_layout *layout; - struct weston_output *output; - - struct { - struct wl_list layer_list; /* ivi_layout_layer::pending.link */ - } pending; - - struct { - int dirty; - struct wl_list layer_list; /* ivi_layout_layer::order.link */ - } order; -}; - -struct ivi_rectangle -{ - int32_t x; - int32_t y; - int32_t width; - int32_t height; -}; - -static struct ivi_layout ivilayout = {0}; - -struct ivi_layout * -get_instance(void) -{ - return &ivilayout; -} - -/** - * Internal API to add/remove an ivi_layer to/from ivi_screen. - */ -static struct ivi_layout_surface * -get_surface(struct wl_list *surf_list, uint32_t id_surface) -{ - struct ivi_layout_surface *ivisurf; - - wl_list_for_each(ivisurf, surf_list, link) { - if (ivisurf->id_surface == id_surface) { - return ivisurf; - } - } - - return NULL; -} - -static struct ivi_layout_layer * -get_layer(struct wl_list *layer_list, uint32_t id_layer) -{ - struct ivi_layout_layer *ivilayer; - - wl_list_for_each(ivilayer, layer_list, link) { - if (ivilayer->id_layer == id_layer) { - return ivilayer; - } - } - - return NULL; -} - -static bool -ivi_view_is_rendered(struct ivi_layout_view *view) -{ - return !wl_list_empty(&view->order_link); -} - -static void -ivi_view_destroy(struct ivi_layout_view *ivi_view) -{ - wl_list_remove(&ivi_view->transform.link); - wl_list_remove(&ivi_view->link); - wl_list_remove(&ivi_view->surf_link); - wl_list_remove(&ivi_view->pending_link); - wl_list_remove(&ivi_view->order_link); - - if (weston_surface_is_desktop_surface(ivi_view->ivisurf->surface)) - weston_desktop_surface_unlink_view(ivi_view->view); - weston_view_destroy(ivi_view->view); - - free(ivi_view); -} - -static struct ivi_layout_view* -ivi_view_create(struct ivi_layout_layer *ivilayer, - struct ivi_layout_surface *ivisurf) -{ - struct ivi_layout_view *ivi_view; - - ivi_view = calloc(1, sizeof *ivi_view); - if (ivi_view == NULL) { - weston_log("fails to allocate memory\n"); - return NULL; - } - - if (weston_surface_is_desktop_surface(ivisurf->surface)) { - ivi_view->view = weston_desktop_surface_create_view( - ivisurf->weston_desktop_surface); - } else { - ivi_view->view = weston_view_create(ivisurf->surface); - } - - if (ivi_view->view == NULL) { - weston_log("fails to allocate memory\n"); - free(ivi_view); - return NULL; - } - - weston_matrix_init(&ivi_view->transform.matrix); - wl_list_init(&ivi_view->transform.link); - - ivi_view->ivisurf = ivisurf; - ivi_view->on_layer = ivilayer; - wl_list_insert(&ivilayer->layout->view_list, - &ivi_view->link); - wl_list_insert(&ivisurf->view_list, - &ivi_view->surf_link); - - wl_list_init(&ivi_view->pending_link); - wl_list_init(&ivi_view->order_link); - - return ivi_view; -} - -static struct ivi_layout_view * -get_ivi_view(struct ivi_layout_layer *ivilayer, - struct ivi_layout_surface *ivisurf) -{ - struct ivi_layout_view *ivi_view; - - assert(ivisurf->surface != NULL); - - wl_list_for_each(ivi_view, &ivisurf->view_list, surf_link) { - if (ivi_view->on_layer == ivilayer) - return ivi_view; - } - - return NULL; -} - -static struct ivi_layout_screen * -get_screen_from_output(struct weston_output *output) -{ - struct ivi_layout *layout = get_instance(); - struct ivi_layout_screen *iviscrn = NULL; - - wl_list_for_each(iviscrn, &layout->screen_list, link) { - if (iviscrn->output == output) - return iviscrn; - } - - return NULL; -} - -/** - * Called at destruction of wl_surface/ivi_surface - */ -void -ivi_layout_surface_destroy(struct ivi_layout_surface *ivisurf) -{ - struct ivi_layout *layout = get_instance(); - struct ivi_layout_view *ivi_view ,*next; - - if (ivisurf == NULL) { - weston_log("%s: invalid argument\n", __func__); - return; - } - - wl_list_remove(&ivisurf->link); - - wl_list_for_each_safe(ivi_view, next, &ivisurf->view_list, surf_link) { - ivi_view_destroy(ivi_view); - } - - wl_signal_emit(&layout->surface_notification.removed, ivisurf); - - ivi_layout_remove_all_surface_transitions(ivisurf); - - free(ivisurf); -} - -/** - * Internal API to initialize ivi_screens found from output_list of weston_compositor. - * Called by ivi_layout_init_with_compositor. - */ -static void -create_screen(struct weston_compositor *ec) -{ - struct ivi_layout *layout = get_instance(); - struct ivi_layout_screen *iviscrn = NULL; - struct weston_output *output = NULL; - - wl_list_for_each(output, &ec->output_list, link) { - iviscrn = calloc(1, sizeof *iviscrn); - if (iviscrn == NULL) { - weston_log("fails to allocate memory\n"); - continue; - } - - iviscrn->layout = layout; - - iviscrn->output = output; - - wl_list_init(&iviscrn->pending.layer_list); - - wl_list_init(&iviscrn->order.layer_list); - - wl_list_insert(&layout->screen_list, &iviscrn->link); - } -} - -/** - * Internal APIs to initialize properties of ivi_surface/ivi_layer when they are created. - */ -static void -init_layer_properties(struct ivi_layout_layer_properties *prop, - int32_t width, int32_t height) -{ - memset(prop, 0, sizeof *prop); - prop->opacity = wl_fixed_from_double(1.0); - prop->source_width = width; - prop->source_height = height; - prop->dest_width = width; - prop->dest_height = height; -} - -static void -init_surface_properties(struct ivi_layout_surface_properties *prop) -{ - memset(prop, 0, sizeof *prop); - prop->opacity = wl_fixed_from_double(1.0); - /* - * FIXME: this shall be fixed by ivi-layout-transition. - */ - prop->dest_width = 1; - prop->dest_height = 1; -} - -/** - * Internal APIs to be called from ivi_layout_commit_changes. - */ -static void -update_opacity(struct ivi_layout_layer *ivilayer, - struct ivi_layout_surface *ivisurf, - struct weston_view *view) -{ - double layer_alpha = wl_fixed_to_double(ivilayer->prop.opacity); - double surf_alpha = wl_fixed_to_double(ivisurf->prop.opacity); - - view->alpha = layer_alpha * surf_alpha; -} - -static void -calc_transformation_matrix(struct ivi_rectangle *source_rect, - struct ivi_rectangle *dest_rect, - struct weston_matrix *m) -{ - float source_center_x; - float source_center_y; - float scale_x; - float scale_y; - float translate_x; - float translate_y; - - source_center_x = source_rect->x + source_rect->width * 0.5f; - source_center_y = source_rect->y + source_rect->height * 0.5f; - weston_matrix_translate(m, -source_center_x, -source_center_y, 0.0f); - - if ((dest_rect->width != source_rect->width) || - (dest_rect->height != source_rect->height)) - { - scale_x = (float) dest_rect->width / (float) source_rect->width; - scale_y = (float) dest_rect->height / (float) source_rect->height; - weston_matrix_scale(m, scale_x, scale_y, 1.0f); - } - - translate_x = dest_rect->width * 0.5f + dest_rect->x; - translate_y = dest_rect->height * 0.5f + dest_rect->y; - weston_matrix_translate(m, translate_x, translate_y, 0.0f); -} - -/* - * This computes intersected rect_output from two ivi_rectangles - */ -static void -ivi_rectangle_intersect(const struct ivi_rectangle *rect1, - const struct ivi_rectangle *rect2, - struct ivi_rectangle *rect_output) -{ - int32_t rect1_right = rect1->x + rect1->width; - int32_t rect1_bottom = rect1->y + rect1->height; - int32_t rect2_right = rect2->x + rect2->width; - int32_t rect2_bottom = rect2->y + rect2->height; - - rect_output->x = max(rect1->x, rect2->x); - rect_output->y = max(rect1->y, rect2->y); - rect_output->width = rect1_right < rect2_right ? - rect1_right - rect_output->x : - rect2_right - rect_output->x; - rect_output->height = rect1_bottom < rect2_bottom ? - rect1_bottom - rect_output->y : - rect2_bottom - rect_output->y; - - if (rect_output->width < 0 || rect_output->height < 0) { - rect_output->width = 0; - rect_output->height = 0; - } -} - -/* - * Transform rect_input by the inverse of matrix, intersect with boundingbox, - * and store the result in rect_output. - * The boundingbox must be given in the same coordinate space as rect_output. - * Additionally, there are the following restrictions on the matrix: - * - no projective transformations - * - no skew - * - only multiples of 90-degree rotations supported - * - * In failure case of weston_matrix_invert, rect_output is set to boundingbox - * as a fail-safe with log. - */ -static void -calc_inverse_matrix_transform(const struct weston_matrix *matrix, - const struct ivi_rectangle *rect_input, - const struct ivi_rectangle *boundingbox, - struct ivi_rectangle *rect_output) -{ - struct weston_matrix m; - struct weston_vector top_left; - struct weston_vector bottom_right; - - assert(boundingbox != rect_output); - - if (weston_matrix_invert(&m, matrix) < 0) { - weston_log("ivi-shell: calc_inverse_matrix_transform fails to invert a matrix.\n"); - weston_log("ivi-shell: boundingbox is set to the rect_output.\n"); - rect_output->x = boundingbox->x; - rect_output->y = boundingbox->y; - rect_output->width = boundingbox->width; - rect_output->height = boundingbox->height; - } - - /* The vectors and matrices involved will always produce f[3] == 1.0. */ - top_left.f[0] = rect_input->x; - top_left.f[1] = rect_input->y; - top_left.f[2] = 0.0f; - top_left.f[3] = 1.0f; - - bottom_right.f[0] = rect_input->x + rect_input->width; - bottom_right.f[1] = rect_input->y + rect_input->height; - bottom_right.f[2] = 0.0f; - bottom_right.f[3] = 1.0f; - - weston_matrix_transform(&m, &top_left); - weston_matrix_transform(&m, &bottom_right); - - if (top_left.f[0] < bottom_right.f[0]) { - rect_output->x = top_left.f[0]; - rect_output->width = bottom_right.f[0] - rect_output->x; - } else { - rect_output->x = bottom_right.f[0]; - rect_output->width = top_left.f[0] - rect_output->x; - } - - if (top_left.f[1] < bottom_right.f[1]) { - rect_output->y = top_left.f[1]; - rect_output->height = bottom_right.f[1] - rect_output->y; - } else { - rect_output->y = bottom_right.f[1]; - rect_output->height = top_left.f[1] - rect_output->y; - } - - ivi_rectangle_intersect(rect_output, boundingbox, rect_output); -} - -/** - * This computes the whole transformation matrix:m from surface-local - * coordinates to multi-screen coordinates, which are global coordinates. - * It is assumed that weston_view::geometry.{x,y} are zero. - * - * Additionally, this computes the mask on surface-local coordinates as an - * ivi_rectangle. This can be set to weston_view_set_mask. - * - * The mask is computed by following steps - * - destination rectangle of layer is transformed to multi-screen coordinates, - * global coordinates. This is done by adding weston_output.{x,y} in simple - * because there is no scaled and rotated transformation. - * - destination rectangle of layer in multi-screen coordinates needs to be - * intersected inside of a screen the layer is assigned to. This is because - * overlapped region of weston surface in another screen shall not be - * displayed according to ivi use case. - * - destination rectangle of layer - * - in multi-screen coordinates, - * - and intersected inside of an assigned screen, - * is inversed to surface-local coordinates by inversed matrix:m. - * - the area is intersected by intersected area between weston_surface and - * source rectangle of ivi_surface. - */ -static void -calc_surface_to_global_matrix_and_mask_to_weston_surface( - struct ivi_layout_screen *iviscrn, - struct ivi_layout_layer *ivilayer, - struct ivi_layout_surface *ivisurf, - struct weston_matrix *m, - struct ivi_rectangle *result) -{ - const struct ivi_layout_surface_properties *sp = &ivisurf->prop; - const struct ivi_layout_layer_properties *lp = &ivilayer->prop; - struct weston_output *output = iviscrn->output; - struct ivi_rectangle weston_surface_rect = { 0, - 0, - ivisurf->surface->width, - ivisurf->surface->height }; - struct ivi_rectangle surface_source_rect = { sp->source_x, - sp->source_y, - sp->source_width, - sp->source_height }; - struct ivi_rectangle surface_dest_rect = { sp->dest_x, - sp->dest_y, - sp->dest_width, - sp->dest_height }; - struct ivi_rectangle layer_source_rect = { lp->source_x, - lp->source_y, - lp->source_width, - lp->source_height }; - struct ivi_rectangle layer_dest_rect = { lp->dest_x, - lp->dest_y, - lp->dest_width, - lp->dest_height }; - struct ivi_rectangle screen_dest_rect = { output->x, - output->y, - output->width, - output->height }; - struct ivi_rectangle layer_dest_rect_in_global = - { lp->dest_x + output->x, - lp->dest_y + output->y, - lp->dest_width, - lp->dest_height }; - struct ivi_rectangle surface_result; - struct ivi_rectangle layer_dest_rect_in_global_intersected; - - /* - * the whole transformation matrix:m from surface-local - * coordinates to global coordinates, which is computed by - * two steps, - * - surface-local coordinates to layer-local coordinates - * - layer-local coordinates to single screen-local coordinates - * - single screen-local coordinates to multi-screen coordinates, - * which are global coordinates. - */ - calc_transformation_matrix(&surface_source_rect, &surface_dest_rect, m); - calc_transformation_matrix(&layer_source_rect, &layer_dest_rect, m); - - weston_matrix_translate(m, output->x, output->y, 0.0f); - - /* this intersected ivi_rectangle would be used for masking - * weston_surface - */ - ivi_rectangle_intersect(&surface_source_rect, &weston_surface_rect, - &surface_result); - - /* - * destination rectangle of layer in multi screens coordinate - * is intersected to avoid displaying outside of an assigned screen. - */ - ivi_rectangle_intersect(&layer_dest_rect_in_global, &screen_dest_rect, - &layer_dest_rect_in_global_intersected); - - /* calc masking area of weston_surface from m */ - calc_inverse_matrix_transform(m, - &layer_dest_rect_in_global_intersected, - &surface_result, - result); -} - -static void -update_prop(struct ivi_layout_view *ivi_view) -{ - struct ivi_layout_surface *ivisurf = ivi_view->ivisurf; - struct ivi_layout_layer *ivilayer = ivi_view->on_layer; - struct ivi_layout_screen *iviscrn = ivilayer->on_screen; - struct ivi_rectangle r; - bool can_calc = true; - - /*In case of no prop change, this just returns*/ - if (!ivilayer->prop.event_mask && !ivisurf->prop.event_mask) - return; - - update_opacity(ivilayer, ivisurf, ivi_view->view); - - if (ivisurf->prop.source_width == 0 || ivisurf->prop.source_height == 0) { - weston_log("ivi-shell: source rectangle is not yet set by ivi_layout_surface_set_source_rectangle\n"); - can_calc = false; - } - - if (ivisurf->prop.dest_width == 0 || ivisurf->prop.dest_height == 0) { - weston_log("ivi-shell: destination rectangle is not yet set by ivi_layout_surface_set_destination_rectangle\n"); - can_calc = false; - } - - if (can_calc) { - wl_list_remove(&ivi_view->transform.link); - weston_matrix_init(&ivi_view->transform.matrix); - - calc_surface_to_global_matrix_and_mask_to_weston_surface( - iviscrn, ivilayer, ivisurf, &ivi_view->transform.matrix, &r); - - weston_view_set_mask(ivi_view->view, r.x, r.y, r.width, r.height); - wl_list_insert(&ivi_view->view->geometry.transformation_list, - &ivi_view->transform.link); - - weston_view_set_transform_parent(ivi_view->view, NULL); - weston_view_geometry_dirty(ivi_view->view); - weston_view_update_transform(ivi_view->view); - } - - ivisurf->update_count++; - - weston_view_schedule_repaint(ivi_view->view); -} - -static bool -ivi_view_is_mapped(struct ivi_layout_view *ivi_view) -{ - return (!wl_list_empty(&ivi_view->order_link) && - ivi_view->on_layer->on_screen && - ivi_view->on_layer->prop.visibility && - ivi_view->ivisurf->prop.visibility); -} - -static void -commit_changes(struct ivi_layout *layout) -{ - struct ivi_layout_view *ivi_view = NULL; - - wl_list_for_each(ivi_view, &layout->view_list, link) { - /* - * If the view is not on the currently rendered scenegraph, - * we do not need to update its properties. - */ - if (!ivi_view_is_mapped(ivi_view)) - continue; - - update_prop(ivi_view); - } -} - -static void -commit_surface_list(struct ivi_layout *layout) -{ - struct ivi_layout_surface *ivisurf = NULL; - int32_t dest_x = 0; - int32_t dest_y = 0; - int32_t dest_width = 0; - int32_t dest_height = 0; - int32_t configured = 0; - - wl_list_for_each(ivisurf, &layout->surface_list, link) { - if (ivisurf->pending.prop.transition_type == IVI_LAYOUT_TRANSITION_VIEW_DEFAULT) { - dest_x = ivisurf->prop.dest_x; - dest_y = ivisurf->prop.dest_y; - dest_width = ivisurf->prop.dest_width; - dest_height = ivisurf->prop.dest_height; - - ivi_layout_transition_move_resize_view(ivisurf, - ivisurf->pending.prop.dest_x, - ivisurf->pending.prop.dest_y, - ivisurf->pending.prop.dest_width, - ivisurf->pending.prop.dest_height, - ivisurf->pending.prop.transition_duration); - - if (ivisurf->pending.prop.visibility) { - ivi_layout_transition_visibility_on(ivisurf, ivisurf->pending.prop.transition_duration); - } else { - ivi_layout_transition_visibility_off(ivisurf, ivisurf->pending.prop.transition_duration); - } - - ivisurf->prop = ivisurf->pending.prop; - ivisurf->prop.dest_x = dest_x; - ivisurf->prop.dest_y = dest_y; - ivisurf->prop.dest_width = dest_width; - ivisurf->prop.dest_height = dest_height; - ivisurf->prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; - ivisurf->pending.prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; - - } else if (ivisurf->pending.prop.transition_type == IVI_LAYOUT_TRANSITION_VIEW_DEST_RECT_ONLY) { - dest_x = ivisurf->prop.dest_x; - dest_y = ivisurf->prop.dest_y; - dest_width = ivisurf->prop.dest_width; - dest_height = ivisurf->prop.dest_height; - - ivi_layout_transition_move_resize_view(ivisurf, - ivisurf->pending.prop.dest_x, - ivisurf->pending.prop.dest_y, - ivisurf->pending.prop.dest_width, - ivisurf->pending.prop.dest_height, - ivisurf->pending.prop.transition_duration); - - ivisurf->prop = ivisurf->pending.prop; - ivisurf->prop.dest_x = dest_x; - ivisurf->prop.dest_y = dest_y; - ivisurf->prop.dest_width = dest_width; - ivisurf->prop.dest_height = dest_height; - - ivisurf->prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; - ivisurf->pending.prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; - - } else if (ivisurf->pending.prop.transition_type == IVI_LAYOUT_TRANSITION_VIEW_FADE_ONLY) { - configured = 0; - if (ivisurf->pending.prop.visibility) { - ivi_layout_transition_visibility_on(ivisurf, ivisurf->pending.prop.transition_duration); - } else { - ivi_layout_transition_visibility_off(ivisurf, ivisurf->pending.prop.transition_duration); - } - - if (ivisurf->prop.dest_width != ivisurf->pending.prop.dest_width || - ivisurf->prop.dest_height != ivisurf->pending.prop.dest_height) { - configured = 1; - } - - ivisurf->prop = ivisurf->pending.prop; - ivisurf->prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; - ivisurf->pending.prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; - - if (configured && !is_surface_transition(ivisurf)) { - ivi_layout_surface_set_size(ivisurf, - ivisurf->prop.dest_width, - ivisurf->prop.dest_height); - } - } else { - configured = 0; - if (ivisurf->prop.dest_width != ivisurf->pending.prop.dest_width || - ivisurf->prop.dest_height != ivisurf->pending.prop.dest_height) { - configured = 1; - } - - ivisurf->prop = ivisurf->pending.prop; - ivisurf->prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; - ivisurf->pending.prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; - - if (configured && !is_surface_transition(ivisurf)) { - ivi_layout_surface_set_size(ivisurf, - ivisurf->prop.dest_width, - ivisurf->prop.dest_height); - } - } - } -} - -static void -commit_layer_list(struct ivi_layout *layout) -{ - struct ivi_layout_view *ivi_view = NULL; - struct ivi_layout_layer *ivilayer = NULL; - struct ivi_layout_view *next = NULL; - - wl_list_for_each(ivilayer, &layout->layer_list, link) { - if (ivilayer->pending.prop.transition_type == IVI_LAYOUT_TRANSITION_LAYER_MOVE) { - ivi_layout_transition_move_layer(ivilayer, ivilayer->pending.prop.dest_x, ivilayer->pending.prop.dest_y, ivilayer->pending.prop.transition_duration); - } else if (ivilayer->pending.prop.transition_type == IVI_LAYOUT_TRANSITION_LAYER_FADE) { - ivi_layout_transition_fade_layer(ivilayer,ivilayer->pending.prop.is_fade_in, - ivilayer->pending.prop.start_alpha,ivilayer->pending.prop.end_alpha, - NULL, NULL, - ivilayer->pending.prop.transition_duration); - } - ivilayer->pending.prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; - - ivilayer->prop = ivilayer->pending.prop; - - if (!ivilayer->order.dirty) { - continue; - } - - wl_list_for_each_safe(ivi_view, next, &ivilayer->order.view_list, - order_link) { - wl_list_remove(&ivi_view->order_link); - wl_list_init(&ivi_view->order_link); - ivi_view->ivisurf->prop.event_mask |= IVI_NOTIFICATION_REMOVE; - } - - assert(wl_list_empty(&ivilayer->order.view_list)); - - wl_list_for_each(ivi_view, &ivilayer->pending.view_list, - pending_link) { - wl_list_remove(&ivi_view->order_link); - wl_list_insert(&ivilayer->order.view_list, &ivi_view->order_link); - ivi_view->ivisurf->prop.event_mask |= IVI_NOTIFICATION_ADD; - } - - ivilayer->order.dirty = 0; - } -} - -static void -commit_screen_list(struct ivi_layout *layout) -{ - struct ivi_layout_screen *iviscrn = NULL; - struct ivi_layout_layer *ivilayer = NULL; - struct ivi_layout_layer *next = NULL; - - wl_list_for_each(iviscrn, &layout->screen_list, link) { - if (iviscrn->order.dirty) { - wl_list_for_each_safe(ivilayer, next, - &iviscrn->order.layer_list, order.link) { - ivilayer->on_screen = NULL; - wl_list_remove(&ivilayer->order.link); - wl_list_init(&ivilayer->order.link); - ivilayer->prop.event_mask |= IVI_NOTIFICATION_REMOVE; - } - - assert(wl_list_empty(&iviscrn->order.layer_list)); - - wl_list_for_each(ivilayer, &iviscrn->pending.layer_list, - pending.link) { - /* FIXME: avoid to insert order.link to multiple screens */ - wl_list_remove(&ivilayer->order.link); - - wl_list_insert(&iviscrn->order.layer_list, - &ivilayer->order.link); - ivilayer->on_screen = iviscrn; - ivilayer->prop.event_mask |= IVI_NOTIFICATION_ADD; - } - - iviscrn->order.dirty = 0; - } - } -} - -static void -build_view_list(struct ivi_layout *layout) -{ - struct ivi_layout_screen *iviscrn; - struct ivi_layout_layer *ivilayer; - struct ivi_layout_view *ivi_view; - - /* If ivi_view is not part of the scenegrapgh, we have to unmap - * weston_views - */ - wl_list_for_each(ivi_view, &layout->view_list, link) { - if (!ivi_view_is_mapped(ivi_view)) - weston_view_unmap(ivi_view->view); - } - - /* Clear view list of layout ivi_layer */ - wl_list_init(&layout->layout_layer.view_list.link); - - wl_list_for_each(iviscrn, &layout->screen_list, link) { - wl_list_for_each(ivilayer, &iviscrn->order.layer_list, order.link) { - if (ivilayer->prop.visibility == false) - continue; - - wl_list_for_each(ivi_view, &ivilayer->order.view_list, order_link) { - if (ivi_view->ivisurf->prop.visibility == false) - continue; - - weston_layer_entry_insert(&layout->layout_layer.view_list, - &ivi_view->view->layer_link); - - ivi_view->ivisurf->surface->is_mapped = true; - ivi_view->view->is_mapped = true; - } - } - } -} - -static void -commit_transition(struct ivi_layout* layout) -{ - if (wl_list_empty(&layout->pending_transition_list)) { - return; - } - - wl_list_insert_list(&layout->transitions->transition_list, - &layout->pending_transition_list); - - wl_list_init(&layout->pending_transition_list); - - wl_event_source_timer_update(layout->transitions->event_source, 1); -} - -static void -send_surface_prop(struct ivi_layout_surface *ivisurf) -{ - wl_signal_emit(&ivisurf->property_changed, ivisurf); - ivisurf->pending.prop.event_mask = 0; -} - -static void -send_layer_prop(struct ivi_layout_layer *ivilayer) -{ - wl_signal_emit(&ivilayer->property_changed, ivilayer); - ivilayer->pending.prop.event_mask = 0; -} - -static void -send_prop(struct ivi_layout *layout) -{ - struct ivi_layout_layer *ivilayer = NULL; - struct ivi_layout_surface *ivisurf = NULL; - - wl_list_for_each_reverse(ivilayer, &layout->layer_list, link) { - if (ivilayer->prop.event_mask) - send_layer_prop(ivilayer); - } - - wl_list_for_each_reverse(ivisurf, &layout->surface_list, link) { - if (ivisurf->prop.event_mask) - send_surface_prop(ivisurf); - } -} - -static void -clear_view_pending_list(struct ivi_layout_layer *ivilayer) -{ - struct ivi_layout_view *view_link = NULL; - struct ivi_layout_view *view_next = NULL; - - wl_list_for_each_safe(view_link, view_next, - &ivilayer->pending.view_list, pending_link) { - wl_list_remove(&view_link->pending_link); - wl_list_init(&view_link->pending_link); - } -} - -/** - * Exported APIs of ivi-layout library are implemented from here. - * Brief of APIs is described in ivi-layout-export.h. - */ -static int32_t -ivi_layout_add_listener_create_layer(struct wl_listener *listener) -{ - struct ivi_layout *layout = get_instance(); - - if (listener == NULL) { - weston_log("ivi_layout_add_listener_create_layer: invalid argument\n"); - return IVI_FAILED; - } - - wl_signal_add(&layout->layer_notification.created, listener); - - return IVI_SUCCEEDED; -} - -static int32_t -ivi_layout_add_listener_remove_layer(struct wl_listener *listener) -{ - struct ivi_layout *layout = get_instance(); - - if (listener == NULL) { - weston_log("ivi_layout_add_listener_remove_layer: invalid argument\n"); - return IVI_FAILED; - } - - wl_signal_add(&layout->layer_notification.removed, listener); - - return IVI_SUCCEEDED; -} - -static int32_t -ivi_layout_add_listener_create_surface(struct wl_listener *listener) -{ - struct ivi_layout *layout = get_instance(); - - if (listener == NULL) { - weston_log("ivi_layout_add_listener_create_surface: invalid argument\n"); - return IVI_FAILED; - } - - wl_signal_add(&layout->surface_notification.created, listener); - - return IVI_SUCCEEDED; -} - -static int32_t -ivi_layout_add_listener_remove_surface(struct wl_listener *listener) -{ - struct ivi_layout *layout = get_instance(); - - if (listener == NULL) { - weston_log("ivi_layout_add_listener_remove_surface: invalid argument\n"); - return IVI_FAILED; - } - - wl_signal_add(&layout->surface_notification.removed, listener); - - return IVI_SUCCEEDED; -} - -static int32_t -ivi_layout_add_listener_configure_surface(struct wl_listener *listener) -{ - struct ivi_layout *layout = get_instance(); - - if (listener == NULL) { - weston_log("ivi_layout_add_listener_configure_surface: invalid argument\n"); - return IVI_FAILED; - } - - wl_signal_add(&layout->surface_notification.configure_changed, listener); - - return IVI_SUCCEEDED; -} - -static int32_t -ivi_layout_add_listener_configure_desktop_surface(struct wl_listener *listener) -{ - struct ivi_layout *layout = get_instance(); - - if (!listener) { - weston_log("ivi_layout_add_listener_configure_desktop_surface: invalid argument\n"); - return IVI_FAILED; - } - - wl_signal_add(&layout->surface_notification.configure_desktop_changed, listener); - - return IVI_SUCCEEDED; -} - -uint32_t -ivi_layout_get_id_of_surface(struct ivi_layout_surface *ivisurf) -{ - return ivisurf->id_surface; -} - -static uint32_t -ivi_layout_get_id_of_layer(struct ivi_layout_layer *ivilayer) -{ - return ivilayer->id_layer; -} - -static struct ivi_layout_layer * -ivi_layout_get_layer_from_id(uint32_t id_layer) -{ - struct ivi_layout *layout = get_instance(); - struct ivi_layout_layer *ivilayer = NULL; - - wl_list_for_each(ivilayer, &layout->layer_list, link) { - if (ivilayer->id_layer == id_layer) { - return ivilayer; - } - } - - return NULL; -} - -struct ivi_layout_surface * -ivi_layout_get_surface_from_id(uint32_t id_surface) -{ - struct ivi_layout *layout = get_instance(); - struct ivi_layout_surface *ivisurf = NULL; - - wl_list_for_each(ivisurf, &layout->surface_list, link) { - if (ivisurf->id_surface == id_surface) { - return ivisurf; - } - } - - return NULL; -} - -static int32_t -ivi_layout_surface_add_listener(struct ivi_layout_surface *ivisurf, - struct wl_listener *listener) -{ - if (ivisurf == NULL || listener == NULL) { - weston_log("ivi_layout_surface_add_listener: invalid argument\n"); - return IVI_FAILED; - } - - wl_signal_add(&ivisurf->property_changed, listener); - - return IVI_SUCCEEDED; -} - -static const struct ivi_layout_layer_properties * -ivi_layout_get_properties_of_layer(struct ivi_layout_layer *ivilayer) -{ - if (ivilayer == NULL) { - weston_log("ivi_layout_get_properties_of_layer: invalid argument\n"); - return NULL; - } - - return &ivilayer->prop; -} - -static int32_t -ivi_layout_get_screens_under_layer(struct ivi_layout_layer *ivilayer, - int32_t *pLength, - struct weston_output ***ppArray) -{ - int32_t length = 0; - int32_t n = 0; - - if (ivilayer == NULL || pLength == NULL || ppArray == NULL) { - weston_log("ivi_layout_get_screens_under_layer: invalid argument\n"); - return IVI_FAILED; - } - - if (ivilayer->on_screen != NULL) - length = 1; - - if (length != 0) { - /* the Array must be free by module which called this function */ - *ppArray = calloc(length, sizeof(struct weston_output *)); - if (*ppArray == NULL) { - weston_log("fails to allocate memory\n"); - return IVI_FAILED; - } - - (*ppArray)[n++] = ivilayer->on_screen->output; - } - - *pLength = length; - - return IVI_SUCCEEDED; -} - -static int32_t -ivi_layout_get_layers(int32_t *pLength, struct ivi_layout_layer ***ppArray) -{ - struct ivi_layout *layout = get_instance(); - struct ivi_layout_layer *ivilayer = NULL; - int32_t length = 0; - int32_t n = 0; - - if (pLength == NULL || ppArray == NULL) { - weston_log("ivi_layout_get_layers: invalid argument\n"); - return IVI_FAILED; - } - - length = wl_list_length(&layout->layer_list); - - if (length != 0) { - /* the Array must be freed by module which called this function */ - *ppArray = calloc(length, sizeof(struct ivi_layout_layer *)); - if (*ppArray == NULL) { - weston_log("fails to allocate memory\n"); - return IVI_FAILED; - } - - wl_list_for_each(ivilayer, &layout->layer_list, link) { - (*ppArray)[n++] = ivilayer; - } - } - - *pLength = length; - - return IVI_SUCCEEDED; -} - -static int32_t -ivi_layout_get_layers_on_screen(struct weston_output *output, - int32_t *pLength, - struct ivi_layout_layer ***ppArray) -{ - struct ivi_layout_screen *iviscrn = NULL; - struct ivi_layout_layer *ivilayer = NULL; - int32_t length = 0; - int32_t n = 0; - - if (output == NULL || pLength == NULL || ppArray == NULL) { - weston_log("ivi_layout_get_layers_on_screen: invalid argument\n"); - return IVI_FAILED; - } - - iviscrn = get_screen_from_output(output); - length = wl_list_length(&iviscrn->order.layer_list); - - if (length != 0) { - /* the Array must be freed by module which called this function */ - *ppArray = calloc(length, sizeof(struct ivi_layout_layer *)); - if (*ppArray == NULL) { - weston_log("fails to allocate memory\n"); - return IVI_FAILED; - } - - wl_list_for_each(ivilayer, &iviscrn->order.layer_list, order.link) { - (*ppArray)[n++] = ivilayer; - } - } - - *pLength = length; - - return IVI_SUCCEEDED; -} - -static int32_t -ivi_layout_get_layers_under_surface(struct ivi_layout_surface *ivisurf, - int32_t *pLength, - struct ivi_layout_layer ***ppArray) -{ - struct ivi_layout_view *ivi_view; - int32_t length = 0; - int32_t n = 0; - - if (ivisurf == NULL || pLength == NULL || ppArray == NULL) { - weston_log("ivi_layout_getLayers: invalid argument\n"); - return IVI_FAILED; - } - - if (!wl_list_empty(&ivisurf->view_list)) { - /* the Array must be free by module which called this function */ - length = wl_list_length(&ivisurf->view_list); - *ppArray = calloc(length, sizeof(struct ivi_layout_layer *)); - if (*ppArray == NULL) { - weston_log("fails to allocate memory\n"); - return IVI_FAILED; - } - - wl_list_for_each_reverse(ivi_view, &ivisurf->view_list, surf_link) { - if (ivi_view_is_rendered(ivi_view)) - (*ppArray)[n++] = ivi_view->on_layer; - else - length--; - } - } - - *pLength = length; - - if (!length) { - free(*ppArray); - *ppArray = NULL; - } - - return IVI_SUCCEEDED; -} - -static -int32_t -ivi_layout_get_surfaces(int32_t *pLength, struct ivi_layout_surface ***ppArray) -{ - struct ivi_layout *layout = get_instance(); - struct ivi_layout_surface *ivisurf = NULL; - int32_t length = 0; - int32_t n = 0; - - if (pLength == NULL || ppArray == NULL) { - weston_log("ivi_layout_get_surfaces: invalid argument\n"); - return IVI_FAILED; - } - - length = wl_list_length(&layout->surface_list); - - if (length != 0) { - /* the Array must be freed by module which called this function */ - *ppArray = calloc(length, sizeof(struct ivi_layout_surface *)); - if (*ppArray == NULL) { - weston_log("fails to allocate memory\n"); - return IVI_FAILED; - } - - wl_list_for_each(ivisurf, &layout->surface_list, link) { - (*ppArray)[n++] = ivisurf; - } - } - - *pLength = length; - - return IVI_SUCCEEDED; -} - -static int32_t -ivi_layout_get_surfaces_on_layer(struct ivi_layout_layer *ivilayer, - int32_t *pLength, - struct ivi_layout_surface ***ppArray) -{ - struct ivi_layout_view *ivi_view = NULL; - int32_t length = 0; - int32_t n = 0; - - if (ivilayer == NULL || pLength == NULL || ppArray == NULL) { - weston_log("ivi_layout_getSurfaceIDsOnLayer: invalid argument\n"); - return IVI_FAILED; - } - - length = wl_list_length(&ivilayer->order.view_list); - - if (length != 0) { - /* the Array must be freed by module which called this function */ - *ppArray = calloc(length, sizeof(struct ivi_layout_surface *)); - if (*ppArray == NULL) { - weston_log("fails to allocate memory\n"); - return IVI_FAILED; - } - - wl_list_for_each(ivi_view, &ivilayer->order.view_list, order_link) { - (*ppArray)[n++] = ivi_view->ivisurf; - } - } - - *pLength = length; - - return IVI_SUCCEEDED; -} - -static struct ivi_layout_layer * -ivi_layout_layer_create_with_dimension(uint32_t id_layer, - int32_t width, int32_t height) -{ - struct ivi_layout *layout = get_instance(); - struct ivi_layout_layer *ivilayer = NULL; - - ivilayer = get_layer(&layout->layer_list, id_layer); - if (ivilayer != NULL) { - weston_log("id_layer is already created\n"); - ++ivilayer->ref_count; - return ivilayer; - } - - ivilayer = calloc(1, sizeof *ivilayer); - if (ivilayer == NULL) { - weston_log("fails to allocate memory\n"); - return NULL; - } - - ivilayer->ref_count = 1; - wl_signal_init(&ivilayer->property_changed); - ivilayer->layout = layout; - ivilayer->id_layer = id_layer; - - init_layer_properties(&ivilayer->prop, width, height); - - wl_list_init(&ivilayer->pending.view_list); - wl_list_init(&ivilayer->pending.link); - ivilayer->pending.prop = ivilayer->prop; - - wl_list_init(&ivilayer->order.view_list); - wl_list_init(&ivilayer->order.link); - - wl_list_insert(&layout->layer_list, &ivilayer->link); - - wl_signal_emit(&layout->layer_notification.created, ivilayer); - - return ivilayer; -} - -static void -ivi_layout_layer_destroy(struct ivi_layout_layer *ivilayer) -{ - struct ivi_layout *layout = get_instance(); - struct ivi_layout_view *ivi_view, *next; - - if (ivilayer == NULL) { - weston_log("ivi_layout_layer_destroy: invalid argument\n"); - return; - } - - if (--ivilayer->ref_count > 0) - return; - - /*Destroy all ivi_views*/ - wl_list_for_each_safe(ivi_view, next, &layout->view_list, link) { - if (ivi_view->on_layer == ivilayer) - ivi_view_destroy(ivi_view); - } - - wl_signal_emit(&layout->layer_notification.removed, ivilayer); - - wl_list_remove(&ivilayer->pending.link); - wl_list_remove(&ivilayer->order.link); - wl_list_remove(&ivilayer->link); - - free(ivilayer); -} - -int32_t -ivi_layout_layer_set_visibility(struct ivi_layout_layer *ivilayer, - bool newVisibility) -{ - struct ivi_layout_layer_properties *prop = NULL; - - if (ivilayer == NULL) { - weston_log("ivi_layout_layer_set_visibility: invalid argument\n"); - return IVI_FAILED; - } - - prop = &ivilayer->pending.prop; - prop->visibility = newVisibility; - - if (ivilayer->prop.visibility != newVisibility) - prop->event_mask |= IVI_NOTIFICATION_VISIBILITY; - else - prop->event_mask &= ~IVI_NOTIFICATION_VISIBILITY; - - return IVI_SUCCEEDED; -} - -int32_t -ivi_layout_layer_set_opacity(struct ivi_layout_layer *ivilayer, - wl_fixed_t opacity) -{ - struct ivi_layout_layer_properties *prop = NULL; - - if (ivilayer == NULL || - opacity < wl_fixed_from_double(0.0) || - wl_fixed_from_double(1.0) < opacity) { - weston_log("ivi_layout_layer_set_opacity: invalid argument\n"); - return IVI_FAILED; - } - - prop = &ivilayer->pending.prop; - prop->opacity = opacity; - - if (ivilayer->prop.opacity != opacity) - prop->event_mask |= IVI_NOTIFICATION_OPACITY; - else - prop->event_mask &= ~IVI_NOTIFICATION_OPACITY; - - return IVI_SUCCEEDED; -} - -static int32_t -ivi_layout_layer_set_source_rectangle(struct ivi_layout_layer *ivilayer, - int32_t x, int32_t y, - int32_t width, int32_t height) -{ - struct ivi_layout_layer_properties *prop = NULL; - - if (ivilayer == NULL) { - weston_log("ivi_layout_layer_set_source_rectangle: invalid argument\n"); - return IVI_FAILED; - } - - prop = &ivilayer->pending.prop; - prop->source_x = x; - prop->source_y = y; - prop->source_width = width; - prop->source_height = height; - - if (ivilayer->prop.source_x != x || ivilayer->prop.source_y != y || - ivilayer->prop.source_width != width || - ivilayer->prop.source_height != height) - prop->event_mask |= IVI_NOTIFICATION_SOURCE_RECT; - else - prop->event_mask &= ~IVI_NOTIFICATION_SOURCE_RECT; - - return IVI_SUCCEEDED; -} - -int32_t -ivi_layout_layer_set_destination_rectangle(struct ivi_layout_layer *ivilayer, - int32_t x, int32_t y, - int32_t width, int32_t height) -{ - struct ivi_layout_layer_properties *prop = NULL; - - if (ivilayer == NULL) { - weston_log("ivi_layout_layer_set_destination_rectangle: invalid argument\n"); - return IVI_FAILED; - } - - prop = &ivilayer->pending.prop; - prop->dest_x = x; - prop->dest_y = y; - prop->dest_width = width; - prop->dest_height = height; - - if (ivilayer->prop.dest_x != x || ivilayer->prop.dest_y != y || - ivilayer->prop.dest_width != width || - ivilayer->prop.dest_height != height) - prop->event_mask |= IVI_NOTIFICATION_DEST_RECT; - else - prop->event_mask &= ~IVI_NOTIFICATION_DEST_RECT; - - return IVI_SUCCEEDED; -} - -int32_t -ivi_layout_layer_set_render_order(struct ivi_layout_layer *ivilayer, - struct ivi_layout_surface **pSurface, - int32_t number) -{ - int32_t i = 0; - struct ivi_layout_view * ivi_view; - - if (ivilayer == NULL) { - weston_log("ivi_layout_layer_set_render_order: invalid argument\n"); - return IVI_FAILED; - } - - clear_view_pending_list(ivilayer); - - for (i = 0; i < number; i++) { - ivi_view = get_ivi_view(ivilayer, pSurface[i]); - if (!ivi_view) - ivi_view = ivi_view_create(ivilayer, pSurface[i]); - - assert(ivi_view != NULL); - - wl_list_remove(&ivi_view->pending_link); - wl_list_insert(&ivilayer->pending.view_list, &ivi_view->pending_link); - } - - ivilayer->order.dirty = 1; - - return IVI_SUCCEEDED; -} - -int32_t -ivi_layout_surface_set_visibility(struct ivi_layout_surface *ivisurf, - bool newVisibility) -{ - struct ivi_layout_surface_properties *prop = NULL; - - if (ivisurf == NULL) { - weston_log("ivi_layout_surface_set_visibility: invalid argument\n"); - return IVI_FAILED; - } - - prop = &ivisurf->pending.prop; - prop->visibility = newVisibility; - - if (ivisurf->prop.visibility != newVisibility) - prop->event_mask |= IVI_NOTIFICATION_VISIBILITY; - else - prop->event_mask &= ~IVI_NOTIFICATION_VISIBILITY; - - return IVI_SUCCEEDED; -} - -int32_t -ivi_layout_surface_set_opacity(struct ivi_layout_surface *ivisurf, - wl_fixed_t opacity) -{ - struct ivi_layout_surface_properties *prop = NULL; - - if (ivisurf == NULL || - opacity < wl_fixed_from_double(0.0) || - wl_fixed_from_double(1.0) < opacity) { - weston_log("ivi_layout_surface_set_opacity: invalid argument\n"); - return IVI_FAILED; - } - - prop = &ivisurf->pending.prop; - prop->opacity = opacity; - - if (ivisurf->prop.opacity != opacity) - prop->event_mask |= IVI_NOTIFICATION_OPACITY; - else - prop->event_mask &= ~IVI_NOTIFICATION_OPACITY; - - return IVI_SUCCEEDED; -} - -int32_t -ivi_layout_surface_set_destination_rectangle(struct ivi_layout_surface *ivisurf, - int32_t x, int32_t y, - int32_t width, int32_t height) -{ - struct ivi_layout_surface_properties *prop = NULL; - - if (ivisurf == NULL) { - weston_log("ivi_layout_surface_set_destination_rectangle: invalid argument\n"); - return IVI_FAILED; - } - - prop = &ivisurf->pending.prop; - prop->start_x = prop->dest_x; - prop->start_y = prop->dest_y; - prop->dest_x = x; - prop->dest_y = y; - prop->start_width = prop->dest_width; - prop->start_height = prop->dest_height; - prop->dest_width = width; - prop->dest_height = height; - - if (ivisurf->prop.dest_x != x || ivisurf->prop.dest_y != y || - ivisurf->prop.dest_width != width || - ivisurf->prop.dest_height != height) - prop->event_mask |= IVI_NOTIFICATION_DEST_RECT; - else - prop->event_mask &= ~IVI_NOTIFICATION_DEST_RECT; - - return IVI_SUCCEEDED; -} - -void -ivi_layout_surface_set_size(struct ivi_layout_surface *ivisurf, - int32_t width, int32_t height) -{ - if (weston_surface_is_desktop_surface(ivisurf->surface)) { - weston_desktop_surface_set_size(ivisurf->weston_desktop_surface, - width, height); - } else { - shell_surface_send_configure(ivisurf->surface, - width, height); - } -} - -static int32_t -ivi_layout_screen_add_layer(struct weston_output *output, - struct ivi_layout_layer *addlayer) -{ - struct ivi_layout_screen *iviscrn; - - if (output == NULL || addlayer == NULL) { - weston_log("ivi_layout_screen_add_layer: invalid argument\n"); - return IVI_FAILED; - } - - iviscrn = get_screen_from_output(output); - - /*if layer is already assigned to screen make order of it dirty - * we are going to remove it (in commit_screen_list)*/ - if (addlayer->on_screen) - addlayer->on_screen->order.dirty = 1; - - wl_list_remove(&addlayer->pending.link); - wl_list_insert(&iviscrn->pending.layer_list, &addlayer->pending.link); - - iviscrn->order.dirty = 1; - - return IVI_SUCCEEDED; -} - -static int32_t -ivi_layout_screen_remove_layer(struct weston_output *output, - struct ivi_layout_layer *removelayer) -{ - struct ivi_layout_screen *iviscrn; - - if (output == NULL || removelayer == NULL) { - weston_log("ivi_layout_screen_remove_layer: invalid argument\n"); - return IVI_FAILED; - } - - iviscrn = get_screen_from_output(output); - - wl_list_remove(&removelayer->pending.link); - wl_list_init(&removelayer->pending.link); - - iviscrn->order.dirty = 1; - - return IVI_SUCCEEDED; -} - -static int32_t -ivi_layout_screen_set_render_order(struct weston_output *output, - struct ivi_layout_layer **pLayer, - const int32_t number) -{ - struct ivi_layout_screen *iviscrn; - struct ivi_layout_layer *ivilayer = NULL; - struct ivi_layout_layer *next = NULL; - int32_t i = 0; - - if (output == NULL) { - weston_log("ivi_layout_screen_set_render_order: invalid argument\n"); - return IVI_FAILED; - } - - iviscrn = get_screen_from_output(output); - - wl_list_for_each_safe(ivilayer, next, - &iviscrn->pending.layer_list, pending.link) { - wl_list_remove(&ivilayer->pending.link); - wl_list_init(&ivilayer->pending.link); - } - - assert(wl_list_empty(&iviscrn->pending.layer_list)); - - for (i = 0; i < number; i++) { - wl_list_remove(&pLayer[i]->pending.link); - wl_list_insert(&iviscrn->pending.layer_list, - &pLayer[i]->pending.link); - } - - iviscrn->order.dirty = 1; - - return IVI_SUCCEEDED; -} - -/** - * This function is used by the additional ivi-module because of dumping ivi_surface sceenshot. - * The ivi-module, e.g. ivi-controller.so, is in wayland-ivi-extension of Genivi's Layer Management. - * This function is used to get the result of drawing by clients. - */ -static struct weston_surface * -ivi_layout_surface_get_weston_surface(struct ivi_layout_surface *ivisurf) -{ - return ivisurf != NULL ? ivisurf->surface : NULL; -} - -static int32_t -ivi_layout_surface_get_size(struct ivi_layout_surface *ivisurf, - int32_t *width, int32_t *height, - int32_t *stride) -{ - int32_t w; - int32_t h; - const size_t bytespp = 4; /* PIXMAN_a8b8g8r8 */ - - if (ivisurf == NULL || ivisurf->surface == NULL) { - weston_log("%s: invalid argument\n", __func__); - return IVI_FAILED; - } - - weston_surface_get_content_size(ivisurf->surface, &w, &h); - - if (width != NULL) - *width = w; - - if (height != NULL) - *height = h; - - if (stride != NULL) - *stride = w * bytespp; - - return IVI_SUCCEEDED; -} - -static int32_t -ivi_layout_layer_add_listener(struct ivi_layout_layer *ivilayer, - struct wl_listener *listener) -{ - if (ivilayer == NULL || listener == NULL) { - weston_log("ivi_layout_layer_add_listener: invalid argument\n"); - return IVI_FAILED; - } - - wl_signal_add(&ivilayer->property_changed, listener); - - return IVI_SUCCEEDED; -} - -static const struct ivi_layout_surface_properties * -ivi_layout_get_properties_of_surface(struct ivi_layout_surface *ivisurf) -{ - if (ivisurf == NULL) { - weston_log("ivi_layout_get_properties_of_surface: invalid argument\n"); - return NULL; - } - - return &ivisurf->prop; -} - -static int32_t -ivi_layout_layer_add_surface(struct ivi_layout_layer *ivilayer, - struct ivi_layout_surface *addsurf) -{ - struct ivi_layout_view *ivi_view; - - if (ivilayer == NULL || addsurf == NULL) { - weston_log("ivi_layout_layer_add_surface: invalid argument\n"); - return IVI_FAILED; - } - - ivi_view = get_ivi_view(ivilayer, addsurf); - if (!ivi_view) - ivi_view = ivi_view_create(ivilayer, addsurf); - - wl_list_remove(&ivi_view->pending_link); - wl_list_insert(&ivilayer->pending.view_list, &ivi_view->pending_link); - - ivilayer->order.dirty = 1; - - return IVI_SUCCEEDED; -} - -static void -ivi_layout_layer_remove_surface(struct ivi_layout_layer *ivilayer, - struct ivi_layout_surface *remsurf) -{ - struct ivi_layout_view *ivi_view; - - if (ivilayer == NULL || remsurf == NULL) { - weston_log("ivi_layout_layer_remove_surface: invalid argument\n"); - return; - } - - ivi_view = get_ivi_view(ivilayer, remsurf); - if (ivi_view) { - wl_list_remove(&ivi_view->pending_link); - wl_list_init(&ivi_view->pending_link); - - ivilayer->order.dirty = 1; - } -} - -static int32_t -ivi_layout_surface_set_source_rectangle(struct ivi_layout_surface *ivisurf, - int32_t x, int32_t y, - int32_t width, int32_t height) -{ - struct ivi_layout_surface_properties *prop = NULL; - - if (ivisurf == NULL) { - weston_log("ivi_layout_surface_set_source_rectangle: invalid argument\n"); - return IVI_FAILED; - } - - prop = &ivisurf->pending.prop; - prop->source_x = x; - prop->source_y = y; - prop->source_width = width; - prop->source_height = height; - - if (ivisurf->prop.source_x != x || ivisurf->prop.source_y != y || - ivisurf->prop.source_width != width || - ivisurf->prop.source_height != height) - prop->event_mask |= IVI_NOTIFICATION_SOURCE_RECT; - else - prop->event_mask &= ~IVI_NOTIFICATION_SOURCE_RECT; - - return IVI_SUCCEEDED; -} - -int32_t -ivi_layout_commit_changes(void) -{ - struct ivi_layout *layout = get_instance(); - - commit_surface_list(layout); - commit_layer_list(layout); - commit_screen_list(layout); - build_view_list(layout); - - commit_transition(layout); - - commit_changes(layout); - send_prop(layout); - - return IVI_SUCCEEDED; -} - -static int32_t -ivi_layout_layer_set_transition(struct ivi_layout_layer *ivilayer, - enum ivi_layout_transition_type type, - uint32_t duration) -{ - if (ivilayer == NULL) { - weston_log("%s: invalid argument\n", __func__); - return -1; - } - - ivilayer->pending.prop.transition_type = type; - ivilayer->pending.prop.transition_duration = duration; - - return 0; -} - -static int32_t -ivi_layout_layer_set_fade_info(struct ivi_layout_layer* ivilayer, - uint32_t is_fade_in, - double start_alpha, double end_alpha) -{ - if (ivilayer == NULL) { - weston_log("%s: invalid argument\n", __func__); - return -1; - } - - ivilayer->pending.prop.is_fade_in = is_fade_in; - ivilayer->pending.prop.start_alpha = start_alpha; - ivilayer->pending.prop.end_alpha = end_alpha; - - return 0; -} - -static int32_t -ivi_layout_surface_set_transition_duration(struct ivi_layout_surface *ivisurf, - uint32_t duration) -{ - struct ivi_layout_surface_properties *prop; - - if (ivisurf == NULL) { - weston_log("%s: invalid argument\n", __func__); - return -1; - } - - prop = &ivisurf->pending.prop; - prop->transition_duration = duration*10; - return 0; -} - -/* - * This interface enables e.g. an id agent to set the id of an ivi-layout - * surface, that has been created by a desktop application. This can only be - * done once as long as the initial surface id equals IVI_INVALID_ID. Afterwards - * two events are emitted, namely surface_created and surface_configured. - */ -static int32_t -ivi_layout_surface_set_id(struct ivi_layout_surface *ivisurf, - uint32_t id_surface) -{ - struct ivi_layout *layout = get_instance(); - struct ivi_layout_surface *search_ivisurf = NULL; - - if (!ivisurf) { - weston_log("%s: invalid argument\n", __func__); - return IVI_FAILED; - } - - if (ivisurf->id_surface != IVI_INVALID_ID) { - weston_log("surface id can only be set once\n"); - return IVI_FAILED; - } - - search_ivisurf = get_surface(&layout->surface_list, id_surface); - if (search_ivisurf) { - weston_log("id_surface(%d) is already created\n", id_surface); - return IVI_FAILED; - } - - ivisurf->id_surface = id_surface; - - wl_signal_emit(&layout->surface_notification.created, ivisurf); - wl_signal_emit(&layout->surface_notification.configure_changed, - ivisurf); - - return IVI_SUCCEEDED; -} - -static int32_t -ivi_layout_surface_set_transition(struct ivi_layout_surface *ivisurf, - enum ivi_layout_transition_type type, - uint32_t duration) -{ - struct ivi_layout_surface_properties *prop; - - if (ivisurf == NULL) { - weston_log("%s: invalid argument\n", __func__); - return -1; - } - - prop = &ivisurf->pending.prop; - prop->transition_type = type; - prop->transition_duration = duration; - return 0; -} - -static int32_t -ivi_layout_surface_dump(struct weston_surface *surface, - void *target, size_t size,int32_t x, int32_t y, - int32_t width, int32_t height) -{ - int result = 0; - - if (surface == NULL) { - weston_log("%s: invalid argument\n", __func__); - return IVI_FAILED; - } - - result = weston_surface_copy_content( - surface, target, size, - x, y, width, height); - - return result == 0 ? IVI_SUCCEEDED : IVI_FAILED; -} - -/** - * methods of interaction between ivi-shell with ivi-layout - */ - -static struct ivi_layout_surface* -surface_create(struct weston_surface *wl_surface, uint32_t id_surface) -{ - struct ivi_layout *layout = get_instance(); - struct ivi_layout_surface *ivisurf = NULL; - - if (wl_surface == NULL) { - weston_log("ivi_layout_surface_create: invalid argument\n"); - return NULL; - } - - ivisurf = calloc(1, sizeof *ivisurf); - if (ivisurf == NULL) { - weston_log("fails to allocate memory\n"); - return NULL; - } - - wl_signal_init(&ivisurf->property_changed); - ivisurf->id_surface = id_surface; - ivisurf->layout = layout; - - ivisurf->surface = wl_surface; - - ivisurf->surface->width_from_buffer = 0; - ivisurf->surface->height_from_buffer = 0; - - init_surface_properties(&ivisurf->prop); - - ivisurf->pending.prop = ivisurf->prop; - - wl_list_init(&ivisurf->view_list); - - wl_list_insert(&layout->surface_list, &ivisurf->link); - - return ivisurf; -} - -void -ivi_layout_desktop_surface_configure(struct ivi_layout_surface *ivisurf, - int32_t width, int32_t height) -{ - struct ivi_layout *layout = get_instance(); - - /* emit callback which is set by ivi-layout api user */ - wl_signal_emit(&layout->surface_notification.configure_desktop_changed, - ivisurf); -} - -struct ivi_layout_surface* -ivi_layout_desktop_surface_create(struct weston_surface *wl_surface) -{ - return surface_create(wl_surface, IVI_INVALID_ID); -} - -void -ivi_layout_surface_configure(struct ivi_layout_surface *ivisurf, - int32_t width, int32_t height) -{ - struct ivi_layout *layout = get_instance(); - - /* emit callback which is set by ivi-layout api user */ - wl_signal_emit(&layout->surface_notification.configure_changed, - ivisurf); -} - -struct ivi_layout_surface* -ivi_layout_surface_create(struct weston_surface *wl_surface, - uint32_t id_surface) -{ - struct ivi_layout *layout = get_instance(); - struct ivi_layout_surface *ivisurf = NULL; - - ivisurf = get_surface(&layout->surface_list, id_surface); - if (ivisurf) { - weston_log("id_surface(%d) is already created\n", id_surface); - return NULL; - } - - ivisurf = surface_create(wl_surface, id_surface); - - if (ivisurf) - wl_signal_emit(&layout->surface_notification.created, ivisurf); - - return ivisurf; -} - -static struct ivi_layout_interface ivi_layout_interface; - -void -ivi_layout_init_with_compositor(struct weston_compositor *ec) -{ - struct ivi_layout *layout = get_instance(); - - layout->compositor = ec; - - wl_list_init(&layout->surface_list); - wl_list_init(&layout->layer_list); - wl_list_init(&layout->screen_list); - wl_list_init(&layout->view_list); - - wl_signal_init(&layout->layer_notification.created); - wl_signal_init(&layout->layer_notification.removed); - - wl_signal_init(&layout->surface_notification.created); - wl_signal_init(&layout->surface_notification.removed); - wl_signal_init(&layout->surface_notification.configure_changed); - wl_signal_init(&layout->surface_notification.configure_desktop_changed); - - /* Add layout_layer at the last of weston_compositor.layer_list */ - weston_layer_init(&layout->layout_layer, ec); - weston_layer_set_position(&layout->layout_layer, - WESTON_LAYER_POSITION_NORMAL); - - create_screen(ec); - - layout->transitions = ivi_layout_transition_set_create(ec); - wl_list_init(&layout->pending_transition_list); - - weston_plugin_api_register(ec, IVI_LAYOUT_API_NAME, - &ivi_layout_interface, - sizeof(struct ivi_layout_interface)); -} - -static struct ivi_layout_interface ivi_layout_interface = { - /** - * commit all changes - */ - .commit_changes = ivi_layout_commit_changes, - - /** - * surface controller interfaces - */ - .add_listener_create_surface = ivi_layout_add_listener_create_surface, - .add_listener_remove_surface = ivi_layout_add_listener_remove_surface, - .add_listener_configure_surface = ivi_layout_add_listener_configure_surface, - .add_listener_configure_desktop_surface = ivi_layout_add_listener_configure_desktop_surface, - .get_surface = shell_get_ivi_layout_surface, - .get_surfaces = ivi_layout_get_surfaces, - .get_id_of_surface = ivi_layout_get_id_of_surface, - .get_surface_from_id = ivi_layout_get_surface_from_id, - .get_properties_of_surface = ivi_layout_get_properties_of_surface, - .get_surfaces_on_layer = ivi_layout_get_surfaces_on_layer, - .surface_set_visibility = ivi_layout_surface_set_visibility, - .surface_set_opacity = ivi_layout_surface_set_opacity, - .surface_set_source_rectangle = ivi_layout_surface_set_source_rectangle, - .surface_set_destination_rectangle = ivi_layout_surface_set_destination_rectangle, - .surface_add_listener = ivi_layout_surface_add_listener, - .surface_get_weston_surface = ivi_layout_surface_get_weston_surface, - .surface_set_transition = ivi_layout_surface_set_transition, - .surface_set_transition_duration = ivi_layout_surface_set_transition_duration, - .surface_set_id = ivi_layout_surface_set_id, - - /** - * layer controller interfaces - */ - .add_listener_create_layer = ivi_layout_add_listener_create_layer, - .add_listener_remove_layer = ivi_layout_add_listener_remove_layer, - .layer_create_with_dimension = ivi_layout_layer_create_with_dimension, - .layer_destroy = ivi_layout_layer_destroy, - .get_layers = ivi_layout_get_layers, - .get_id_of_layer = ivi_layout_get_id_of_layer, - .get_layer_from_id = ivi_layout_get_layer_from_id, - .get_properties_of_layer = ivi_layout_get_properties_of_layer, - .get_layers_under_surface = ivi_layout_get_layers_under_surface, - .get_layers_on_screen = ivi_layout_get_layers_on_screen, - .layer_set_visibility = ivi_layout_layer_set_visibility, - .layer_set_opacity = ivi_layout_layer_set_opacity, - .layer_set_source_rectangle = ivi_layout_layer_set_source_rectangle, - .layer_set_destination_rectangle = ivi_layout_layer_set_destination_rectangle, - .layer_add_surface = ivi_layout_layer_add_surface, - .layer_remove_surface = ivi_layout_layer_remove_surface, - .layer_set_render_order = ivi_layout_layer_set_render_order, - .layer_add_listener = ivi_layout_layer_add_listener, - .layer_set_transition = ivi_layout_layer_set_transition, - - /** - * screen controller interfaces - */ - .get_screens_under_layer = ivi_layout_get_screens_under_layer, - .screen_add_layer = ivi_layout_screen_add_layer, - .screen_remove_layer = ivi_layout_screen_remove_layer, - .screen_set_render_order = ivi_layout_screen_set_render_order, - - /** - * animation - */ - .transition_move_layer_cancel = ivi_layout_transition_move_layer_cancel, - .layer_set_fade_info = ivi_layout_layer_set_fade_info, - - /** - * surface content dumping for debugging - */ - .surface_get_size = ivi_layout_surface_get_size, - .surface_dump = ivi_layout_surface_dump, -}; diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c deleted file mode 100644 index 0fb6b29..0000000 --- a/ivi-shell/ivi-shell.c +++ /dev/null @@ -1,660 +0,0 @@ -/* - * Copyright (C) 2013 DENSO CORPORATION - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* - * ivi-shell supports a type of shell for In-Vehicle Infotainment system. - * In-Vehicle Infotainment system traditionally manages surfaces with global - * identification. A protocol, ivi_application, supports such a feature - * by implementing a request, ivi_application::surface_creation defined in - * ivi_application.xml. - * - * The ivi-shell explicitly loads a module to add business logic like how to - * layout surfaces by using internal ivi-layout APIs. - */ -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "ivi-shell.h" -#include "ivi-application-server-protocol.h" -#include "ivi-layout-private.h" -#include "ivi-layout-shell.h" -#include "shared/helpers.h" -#include "compositor/weston.h" - -/* Representation of ivi_surface protocol object. */ -struct ivi_shell_surface -{ - struct wl_resource* resource; - struct ivi_shell *shell; - struct ivi_layout_surface *layout_surface; - - struct weston_surface *surface; - struct wl_listener surface_destroy_listener; - - uint32_t id_surface; - - int32_t width; - int32_t height; - - struct wl_list link; -}; - -/* - * Implementation of ivi_surface - */ - -static void -ivi_shell_surface_committed(struct weston_surface *, int32_t, int32_t); - -static struct ivi_shell_surface * -get_ivi_shell_surface(struct weston_surface *surface) -{ - struct ivi_shell_surface *shsurf; - - if (surface->committed != ivi_shell_surface_committed) - return NULL; - - shsurf = surface->committed_private; - assert(shsurf); - assert(shsurf->surface == surface); - - return shsurf; -} - -struct ivi_layout_surface * -shell_get_ivi_layout_surface(struct weston_surface *surface) -{ - struct ivi_shell_surface *shsurf; - - shsurf = get_ivi_shell_surface(surface); - if (!shsurf) - return NULL; - - return shsurf->layout_surface; -} - -void -shell_surface_send_configure(struct weston_surface *surface, - int32_t width, int32_t height) -{ - struct ivi_shell_surface *shsurf; - - shsurf = get_ivi_shell_surface(surface); - if (!shsurf) - return; - - if (shsurf->resource) - ivi_surface_send_configure(shsurf->resource, width, height); -} - -static void -ivi_shell_surface_committed(struct weston_surface *surface, - int32_t sx, int32_t sy) -{ - struct ivi_shell_surface *ivisurf = get_ivi_shell_surface(surface); - - assert(ivisurf); - if (!ivisurf) - return; - - if (surface->width == 0 || surface->height == 0) - return; - - if (ivisurf->width != surface->width || - ivisurf->height != surface->height) { - ivisurf->width = surface->width; - ivisurf->height = surface->height; - - ivi_layout_surface_configure(ivisurf->layout_surface, - surface->width, surface->height); - } -} - -static int -ivi_shell_surface_get_label(struct weston_surface *surface, - char *buf, - size_t len) -{ - struct ivi_shell_surface *shell_surf = get_ivi_shell_surface(surface); - - if (!shell_surf) - return snprintf(buf, len, "unidentified window in ivi-shell"); - - return snprintf(buf, len, "ivi-surface %#x", shell_surf->id_surface); -} - -static void -layout_surface_cleanup(struct ivi_shell_surface *ivisurf) -{ - assert(ivisurf->layout_surface != NULL); - - /* destroy weston_surface destroy signal. */ - if (!ivisurf->layout_surface->weston_desktop_surface) - wl_list_remove(&ivisurf->surface_destroy_listener.link); - - ivi_layout_surface_destroy(ivisurf->layout_surface); - ivisurf->layout_surface = NULL; - - ivisurf->surface->committed = NULL; - ivisurf->surface->committed_private = NULL; - weston_surface_set_label_func(ivisurf->surface, NULL); - ivisurf->surface = NULL; -} - -/* - * The ivi_surface wl_resource destructor. - * - * Gets called via ivi_surface.destroy request or automatic wl_client clean-up. - */ -static void -shell_destroy_shell_surface(struct wl_resource *resource) -{ - struct ivi_shell_surface *ivisurf = wl_resource_get_user_data(resource); - - if (ivisurf == NULL) - return; - - assert(ivisurf->resource == resource); - - if (ivisurf->layout_surface != NULL) - layout_surface_cleanup(ivisurf); - - wl_list_remove(&ivisurf->link); - - free(ivisurf); -} - -/* Gets called through the weston_surface destroy signal. */ -static void -shell_handle_surface_destroy(struct wl_listener *listener, void *data) -{ - struct ivi_shell_surface *ivisurf = - container_of(listener, struct ivi_shell_surface, - surface_destroy_listener); - - assert(ivisurf != NULL); - - if (ivisurf->layout_surface != NULL) - layout_surface_cleanup(ivisurf); -} - -/* Gets called, when a client sends ivi_surface.destroy request. */ -static void -surface_destroy(struct wl_client *client, struct wl_resource *resource) -{ - /* - * Fires the wl_resource destroy signal, and then calls - * ivi_surface wl_resource destructor: shell_destroy_shell_surface() - */ - wl_resource_destroy(resource); -} - -static const struct ivi_surface_interface surface_implementation = { - surface_destroy, -}; - -/** - * Request handler for ivi_application.surface_create. - * - * Creates an ivi_surface protocol object associated with the given wl_surface. - * ivi_surface protocol object is represented by struct ivi_shell_surface. - * - * \param client The client. - * \param resource The ivi_application protocol object. - * \param id_surface The IVI surface ID. - * \param surface_resource The wl_surface protocol object. - * \param id The protocol object id for the new ivi_surface protocol object. - * - * The wl_surface is given the ivi_surface role and associated with a unique - * IVI ID which is used to identify the surface in a controller - * (window manager). - */ -static void -application_surface_create(struct wl_client *client, - struct wl_resource *resource, - uint32_t id_surface, - struct wl_resource *surface_resource, - uint32_t id) -{ - struct ivi_shell *shell = wl_resource_get_user_data(resource); - struct ivi_shell_surface *ivisurf; - struct ivi_layout_surface *layout_surface; - struct weston_surface *weston_surface = - wl_resource_get_user_data(surface_resource); - struct wl_resource *res; - - if (weston_surface_set_role(weston_surface, "ivi_surface", - resource, IVI_APPLICATION_ERROR_ROLE) < 0) - return; - - layout_surface = ivi_layout_surface_create(weston_surface, id_surface); - - /* check if id_ivi is already used for wl_surface*/ - if (layout_surface == NULL) { - wl_resource_post_error(resource, - IVI_APPLICATION_ERROR_IVI_ID, - "surface_id is already assigned " - "by another app"); - return; - } - - layout_surface->weston_desktop_surface = NULL; - - ivisurf = zalloc(sizeof *ivisurf); - if (ivisurf == NULL) { - wl_resource_post_no_memory(resource); - return; - } - - wl_list_init(&ivisurf->link); - wl_list_insert(&shell->ivi_surface_list, &ivisurf->link); - - ivisurf->shell = shell; - ivisurf->id_surface = id_surface; - - ivisurf->width = 0; - ivisurf->height = 0; - ivisurf->layout_surface = layout_surface; - - /* - * The following code relies on wl_surface destruction triggering - * immediateweston_surface destruction - */ - ivisurf->surface_destroy_listener.notify = shell_handle_surface_destroy; - wl_signal_add(&weston_surface->destroy_signal, - &ivisurf->surface_destroy_listener); - - ivisurf->surface = weston_surface; - - weston_surface->committed = ivi_shell_surface_committed; - weston_surface->committed_private = ivisurf; - weston_surface_set_label_func(weston_surface, - ivi_shell_surface_get_label); - - res = wl_resource_create(client, &ivi_surface_interface, 1, id); - if (res == NULL) { - wl_client_post_no_memory(client); - return; - } - - ivisurf->resource = res; - - wl_resource_set_implementation(res, &surface_implementation, - ivisurf, shell_destroy_shell_surface); -} - -static const struct ivi_application_interface application_implementation = { - application_surface_create -}; - -/* - * Handle wl_registry.bind of ivi_application global singleton. - */ -static void -bind_ivi_application(struct wl_client *client, - void *data, uint32_t version, uint32_t id) -{ - struct ivi_shell *shell = data; - struct wl_resource *resource; - - resource = wl_resource_create(client, &ivi_application_interface, - 1, id); - - wl_resource_set_implementation(resource, - &application_implementation, - shell, NULL); -} - -/* - * Called through the compositor's destroy signal. - */ -static void -shell_destroy(struct wl_listener *listener, void *data) -{ - struct ivi_shell *shell = - container_of(listener, struct ivi_shell, destroy_listener); - struct ivi_shell_surface *ivisurf, *next; - - wl_list_remove(&shell->destroy_listener.link); - wl_list_remove(&shell->wake_listener.link); - - wl_list_for_each_safe(ivisurf, next, &shell->ivi_surface_list, link) { - wl_list_remove(&ivisurf->link); - free(ivisurf); - } - - free(shell); -} - -/* - * Called through the compositor's wake signal. - */ -static void -wake_handler(struct wl_listener *listener, void *data) -{ - struct weston_compositor *compositor = data; - - weston_compositor_damage_all(compositor); -} - -static void -terminate_binding(struct weston_keyboard *keyboard, const struct timespec *time, - uint32_t key, void *data) -{ - struct weston_compositor *compositor = data; - - weston_compositor_exit(compositor); -} - -static void -init_ivi_shell(struct weston_compositor *compositor, struct ivi_shell *shell) -{ - struct weston_config *config = wet_get_config(compositor); - struct weston_config_section *section; - bool developermode; - - shell->compositor = compositor; - - wl_list_init(&shell->ivi_surface_list); - - section = weston_config_get_section(config, "ivi-shell", NULL, NULL); - - weston_config_section_get_bool(section, "developermode", - &developermode, 0); - - if (developermode) { - weston_install_debug_key_binding(compositor, MODIFIER_SUPER); - - weston_compositor_add_key_binding(compositor, KEY_BACKSPACE, - MODIFIER_CTRL | MODIFIER_ALT, - terminate_binding, - compositor); - } -} - -static void -activate_binding(struct weston_seat *seat, - struct weston_view *focus_view) -{ - struct weston_surface *focus = focus_view->surface; - struct weston_surface *main_surface = - weston_surface_get_main_surface(focus); - - if (get_ivi_shell_surface(main_surface) == NULL) - return; - - weston_seat_set_keyboard_focus(seat, focus); -} - -static void -click_to_activate_binding(struct weston_pointer *pointer, - const struct timespec *time, - uint32_t button, void *data) -{ - if (pointer->grab != &pointer->default_grab) - return; - if (pointer->focus == NULL) - return; - - activate_binding(pointer->seat, pointer->focus); -} - -static void -touch_to_activate_binding(struct weston_touch *touch, - const struct timespec *time, - void *data) -{ - if (touch->grab != &touch->default_grab) - return; - if (touch->focus == NULL) - return; - - activate_binding(touch->seat, touch->focus); -} - -static void -shell_add_bindings(struct weston_compositor *compositor, - struct ivi_shell *shell) -{ - weston_compositor_add_button_binding(compositor, BTN_LEFT, 0, - click_to_activate_binding, - shell); - weston_compositor_add_button_binding(compositor, BTN_RIGHT, 0, - click_to_activate_binding, - shell); - weston_compositor_add_touch_binding(compositor, 0, - touch_to_activate_binding, - shell); -} - -/* - * libweston-desktop - */ - -static void -desktop_surface_ping_timeout(struct weston_desktop_client *client, - void *user_data) -{ - /* Not supported */ -} - -static void -desktop_surface_pong(struct weston_desktop_client *client, - void *user_data) -{ - /* Not supported */ -} - -static void -desktop_surface_added(struct weston_desktop_surface *surface, - void *user_data) -{ - struct ivi_shell *shell = (struct ivi_shell *) user_data; - struct ivi_layout_surface *layout_surface; - struct ivi_shell_surface *ivisurf; - struct weston_surface *weston_surf = - weston_desktop_surface_get_surface(surface); - - layout_surface = ivi_layout_desktop_surface_create(weston_surf); - if (!layout_surface) { - return; - } - - layout_surface->weston_desktop_surface = surface; - - ivisurf = zalloc(sizeof *ivisurf); - if (!ivisurf) { - return; - } - - ivisurf->shell = shell; - ivisurf->id_surface = IVI_INVALID_ID; - - ivisurf->width = 0; - ivisurf->height = 0; - ivisurf->layout_surface = layout_surface; - ivisurf->surface = weston_surf; - - weston_desktop_surface_set_user_data(surface, ivisurf); -} - -static void -desktop_surface_removed(struct weston_desktop_surface *surface, - void *user_data) -{ - struct ivi_shell_surface *ivisurf = (struct ivi_shell_surface *) - weston_desktop_surface_get_user_data(surface); - - assert(ivisurf != NULL); - - if (ivisurf->layout_surface) - layout_surface_cleanup(ivisurf); -} - -static void -desktop_surface_committed(struct weston_desktop_surface *surface, - int32_t sx, int32_t sy, void *user_data) -{ - struct ivi_shell_surface *ivisurf = (struct ivi_shell_surface *) - weston_desktop_surface_get_user_data(surface); - struct weston_surface *weston_surf = - weston_desktop_surface_get_surface(surface); - - if(!ivisurf) - return; - - if (weston_surf->width == 0 || weston_surf->height == 0) - return; - - if (ivisurf->width != weston_surf->width || - ivisurf->height != weston_surf->height) { - ivisurf->width = weston_surf->width; - ivisurf->height = weston_surf->height; - - ivi_layout_desktop_surface_configure(ivisurf->layout_surface, - weston_surf->width, - weston_surf->height); - } -} - -static void -desktop_surface_move(struct weston_desktop_surface *surface, - struct weston_seat *seat, uint32_t serial, void *user_data) -{ - /* Not supported */ -} - -static void -desktop_surface_resize(struct weston_desktop_surface *surface, - struct weston_seat *seat, uint32_t serial, - enum weston_desktop_surface_edge edges, void *user_data) -{ - /* Not supported */ -} - -static void -desktop_surface_fullscreen_requested(struct weston_desktop_surface *surface, - bool fullscreen, - struct weston_output *output, - void *user_data) -{ - /* Not supported */ -} - -static void -desktop_surface_maximized_requested(struct weston_desktop_surface *surface, - bool maximized, void *user_data) -{ - /* Not supported */ -} - -static void -desktop_surface_minimized_requested(struct weston_desktop_surface *surface, - void *user_data) -{ - /* Not supported */ -} - -static void -desktop_surface_set_xwayland_position(struct weston_desktop_surface *surface, - int32_t x, int32_t y, void *user_data) -{ - /* Not supported */ -} - -static const struct weston_desktop_api shell_desktop_api = { - .struct_size = sizeof(struct weston_desktop_api), - .ping_timeout = desktop_surface_ping_timeout, - .pong = desktop_surface_pong, - .surface_added = desktop_surface_added, - .surface_removed = desktop_surface_removed, - .committed = desktop_surface_committed, - - .move = desktop_surface_move, - .resize = desktop_surface_resize, - .fullscreen_requested = desktop_surface_fullscreen_requested, - .maximized_requested = desktop_surface_maximized_requested, - .minimized_requested = desktop_surface_minimized_requested, - .set_xwayland_position = desktop_surface_set_xwayland_position, -}; - -/* - * end of libweston-desktop - */ - -/* - * Initialization of ivi-shell. - */ -WL_EXPORT int -wet_shell_init(struct weston_compositor *compositor, - int *argc, char *argv[]) -{ - struct ivi_shell *shell; - - shell = zalloc(sizeof *shell); - if (shell == NULL) - return -1; - - if (!weston_compositor_add_destroy_listener_once(compositor, - &shell->destroy_listener, - shell_destroy)) { - free(shell); - return 0; - } - - init_ivi_shell(compositor, shell); - - shell->wake_listener.notify = wake_handler; - wl_signal_add(&compositor->wake_signal, &shell->wake_listener); - - shell->desktop = weston_desktop_create(compositor, &shell_desktop_api, shell); - if (!shell->desktop) - goto err_shell; - - if (wl_global_create(compositor->wl_display, - &ivi_application_interface, 1, - shell, bind_ivi_application) == NULL) - goto err_desktop; - - ivi_layout_init_with_compositor(compositor); - shell_add_bindings(compositor, shell); - - return IVI_SUCCEEDED; - -err_desktop: - weston_desktop_destroy(shell->desktop); - -err_shell: - wl_list_remove(&shell->destroy_listener.link); - free(shell); - - return IVI_FAILED; -} diff --git a/ivi-shell/ivi-shell.h b/ivi-shell/ivi-shell.h deleted file mode 100644 index d7f1cdb..0000000 --- a/ivi-shell/ivi-shell.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2013 DENSO CORPORATION - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_IVI_SHELL_H -#define WESTON_IVI_SHELL_H - -#include -#include - -#include -#include - -struct ivi_shell -{ - struct wl_listener destroy_listener; - struct wl_listener wake_listener; - - struct weston_compositor *compositor; - - struct weston_desktop *desktop; - struct wl_list ivi_surface_list; /* struct ivi_shell_surface::link */ -}; - -void -shell_surface_send_configure(struct weston_surface *surface, - int32_t width, int32_t height); - -struct ivi_layout_surface; - -struct ivi_layout_surface * -shell_get_ivi_layout_surface(struct weston_surface *surface); - -#endif /* WESTON_IVI_SHELL_H */ diff --git a/ivi-shell/meson.build b/ivi-shell/meson.build deleted file mode 100644 index 03fc717..0000000 --- a/ivi-shell/meson.build +++ /dev/null @@ -1,61 +0,0 @@ -if get_option('shell-ivi') - srcs_shell_ivi = [ - 'ivi-shell.c', - 'ivi-layout.c', - 'ivi-layout-transition.c', - ivi_application_server_protocol_h, - ivi_application_protocol_c, - input_method_unstable_v1_server_protocol_h, - input_method_unstable_v1_protocol_c, - ] - plugin_shell_ivi = shared_library( - 'ivi-shell', - srcs_shell_ivi, - include_directories: common_inc, - dependencies: [ - dep_libm, - dep_libexec_weston, - dep_lib_desktop, - dep_libweston_public - ], - name_prefix: '', - install: true, - install_dir: dir_module_weston, - install_rpath: '$ORIGIN' - ) - env_modmap += 'ivi-shell.so=@0@;'.format(plugin_shell_ivi.full_path()) - - install_headers('ivi-layout-export.h', subdir: 'weston') - - srcs_ivi_hmi = [ - 'hmi-controller.c', - ivi_hmi_controller_server_protocol_h, - ivi_hmi_controller_protocol_c, - ] - plugin_ivi_hmi = shared_library( - 'hmi-controller', - srcs_ivi_hmi, - include_directories: common_inc, - dependencies: [ - dep_libexec_weston, - dep_libweston_public, - dep_libshared - ], - name_prefix: '', - install: true, - install_dir: dir_module_weston, - install_rpath: '$ORIGIN' - ) - env_modmap += 'hmi-controller.so=@0@;'.format(plugin_ivi_hmi.full_path()) - - ivi_test_config = configuration_data() - ivi_test_config.set('bindir', dir_bin) - ivi_test_config.set('libexecdir', dir_libexec) - ivi_test_config.set('plugin_prefix', meson.current_build_dir()) - ivi_test_config.set('westondatadir', join_paths(dir_data, 'weston')) - ivi_test_ini = configure_file( - input: '../ivi-shell/weston.ini.in', - output: 'weston-ivi-test.ini', - configuration: ivi_test_config - ) -endif diff --git a/ivi-shell/weston.ini.in b/ivi-shell/weston.ini.in deleted file mode 100644 index 3bdfbeb..0000000 --- a/ivi-shell/weston.ini.in +++ /dev/null @@ -1,98 +0,0 @@ -[core] -shell=ivi-shell.so -modules=hmi-controller.so - -[ivi-shell] -ivi-shell-user-interface=weston-ivi-shell-user-interface - -#developermode=true - -cursor-theme=default -cursor-size=32 - -base-layer-id=1000 -base-layer-id-offset=10000 - -workspace-background-layer-id=2000 -workspace-layer-id=3000 -application-layer-id=4000 - -transition-duration=300 - -background-image=@westondatadir@/background.png -background-id=1001 -panel-image=@westondatadir@/panel.png -panel-id=1002 -surface-id-offset=10 -tiling-image=@westondatadir@/tiling.png -tiling-id=1003 -sidebyside-image=@westondatadir@/sidebyside.png -sidebyside-id=1004 -fullscreen-image=@westondatadir@/fullscreen.png -fullscreen-id=1005 -random-image=@westondatadir@/random.png -random-id=1006 -home-image=@westondatadir@/home.png -home-id=1007 -workspace-background-color=0x99000000 -workspace-background-id=2001 - -[ivi-launcher] -workspace-id=0 -icon-id=4001 -icon=@westondatadir@/icon_ivi_flower.png -path=@bindir@/weston-flower - -[ivi-launcher] -workspace-id=0 -icon-id=4002 -icon=@westondatadir@/icon_ivi_clickdot.png -path=@bindir@/weston-clickdot - -[ivi-launcher] -workspace-id=1 -icon-id=4003 -icon=@westondatadir@/icon_ivi_simple-egl.png -path=@bindir@/weston-simple-egl - -[ivi-launcher] -workspace-id=1 -icon-id=4004 -icon=@westondatadir@/icon_ivi_simple-shm.png -path=@bindir@/weston-simple-shm - -[ivi-launcher] -workspace-id=2 -icon-id=4005 -icon=@westondatadir@/icon_ivi_smoke.png -path=@bindir@/weston-smoke - -[ivi-launcher] -workspace-id=3 -icon-id=4006 -icon=@westondatadir@/icon_ivi_flower.png -path=@bindir@/weston-flower - -[ivi-launcher] -workspace-id=3 -icon-id=4007 -icon=@westondatadir@/icon_ivi_clickdot.png -path=@bindir@/weston-clickdot - -[ivi-launcher] -workspace-id=3 -icon-id=4008 -icon=@westondatadir@/icon_ivi_simple-egl.png -path=@bindir@/weston-simple-egl - -[ivi-launcher] -workspace-id=3 -icon-id=4009 -icon=@westondatadir@/icon_ivi_simple-shm.png -path=@bindir@/weston-simple-shm - -[ivi-launcher] -workspace-id=3 -icon-id=4010 -icon=@westondatadir@/icon_ivi_smoke.png -path=@bindir@/weston-smoke diff --git a/kiosk-shell/kiosk-shell-grab.c b/kiosk-shell/kiosk-shell-grab.c deleted file mode 100644 index 3ea0156..0000000 --- a/kiosk-shell/kiosk-shell-grab.c +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright 2010-2012 Intel Corporation - * Copyright 2013 Raspberry Pi Foundation - * Copyright 2011-2012,2020 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "kiosk-shell-grab.h" -#include "shared/helpers.h" - -struct kiosk_shell_grab { - struct kiosk_shell_surface *shsurf; - struct wl_listener shsurf_destroy_listener; - - struct weston_pointer_grab pointer_grab; - struct weston_touch_grab touch_grab; - wl_fixed_t dx, dy; - bool active; -}; - -static void -kiosk_shell_grab_destroy(struct kiosk_shell_grab *shgrab); - -/* - * pointer_move_grab_interface - */ - -static void -pointer_move_grab_focus(struct weston_pointer_grab *grab) -{ -} - -static void -pointer_move_grab_axis(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_axis_event *event) -{ -} - -static void -pointer_move_grab_axis_source(struct weston_pointer_grab *grab, - uint32_t source) -{ -} - -static void -pointer_move_grab_frame(struct weston_pointer_grab *grab) -{ -} - -static void -pointer_move_grab_motion(struct weston_pointer_grab *pointer_grab, - const struct timespec *time, - struct weston_pointer_motion_event *event) -{ - struct kiosk_shell_grab *shgrab = - container_of(pointer_grab, struct kiosk_shell_grab, pointer_grab); - struct weston_pointer *pointer = pointer_grab->pointer; - struct kiosk_shell_surface *shsurf = shgrab->shsurf; - struct weston_surface *surface; - int dx, dy; - - weston_pointer_move(pointer, event); - - if (!shsurf) - return; - - surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); - - dx = wl_fixed_to_int(pointer->x + shgrab->dx); - dy = wl_fixed_to_int(pointer->y + shgrab->dy); - - weston_view_set_position(shsurf->view, dx, dy); - - weston_compositor_schedule_repaint(surface->compositor); -} - -static void -pointer_move_grab_button(struct weston_pointer_grab *pointer_grab, - const struct timespec *time, - uint32_t button, uint32_t state_w) -{ - struct kiosk_shell_grab *shgrab = - container_of(pointer_grab, struct kiosk_shell_grab, pointer_grab); - struct weston_pointer *pointer = pointer_grab->pointer; - enum wl_pointer_button_state state = state_w; - - if (pointer->button_count == 0 && - state == WL_POINTER_BUTTON_STATE_RELEASED) - kiosk_shell_grab_destroy(shgrab); -} - -static void -pointer_move_grab_cancel(struct weston_pointer_grab *pointer_grab) -{ - struct kiosk_shell_grab *shgrab = - container_of(pointer_grab, struct kiosk_shell_grab, pointer_grab); - - kiosk_shell_grab_destroy(shgrab); -} - -static const struct weston_pointer_grab_interface pointer_move_grab_interface = { - pointer_move_grab_focus, - pointer_move_grab_motion, - pointer_move_grab_button, - pointer_move_grab_axis, - pointer_move_grab_axis_source, - pointer_move_grab_frame, - pointer_move_grab_cancel, -}; - -/* - * touch_move_grab_interface - */ - -static void -touch_move_grab_down(struct weston_touch_grab *grab, - const struct timespec *time, - int touch_id, wl_fixed_t x, wl_fixed_t y) -{ -} - -static void -touch_move_grab_up(struct weston_touch_grab *touch_grab, - const struct timespec *time, int touch_id) -{ - struct kiosk_shell_grab *shgrab = - container_of(touch_grab, struct kiosk_shell_grab, touch_grab); - - if (touch_id == 0) - shgrab->active = false; - - if (touch_grab->touch->num_tp == 0) - kiosk_shell_grab_destroy(shgrab); -} - -static void -touch_move_grab_motion(struct weston_touch_grab *touch_grab, - const struct timespec *time, int touch_id, - wl_fixed_t x, wl_fixed_t y) -{ - struct kiosk_shell_grab *shgrab = - container_of(touch_grab, struct kiosk_shell_grab, touch_grab); - struct weston_touch *touch = touch_grab->touch; - struct kiosk_shell_surface *shsurf = shgrab->shsurf; - struct weston_surface *surface; - int dx, dy; - - if (!shsurf || !shgrab->active) - return; - - surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); - - dx = wl_fixed_to_int(touch->grab_x + shgrab->dx); - dy = wl_fixed_to_int(touch->grab_y + shgrab->dy); - - weston_view_set_position(shsurf->view, dx, dy); - - weston_compositor_schedule_repaint(surface->compositor); -} - -static void -touch_move_grab_frame(struct weston_touch_grab *grab) -{ -} - -static void -touch_move_grab_cancel(struct weston_touch_grab *touch_grab) -{ - struct kiosk_shell_grab *shgrab = - container_of(touch_grab, struct kiosk_shell_grab, touch_grab); - - kiosk_shell_grab_destroy(shgrab); -} - -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_frame, - touch_move_grab_cancel, -}; - -/* - * kiosk_shell_grab - */ - -static void -kiosk_shell_grab_handle_shsurf_destroy(struct wl_listener *listener, void *data) -{ - struct kiosk_shell_grab *shgrab = - container_of(listener, struct kiosk_shell_grab, - shsurf_destroy_listener); - - shgrab->shsurf = NULL; -} - -static struct kiosk_shell_grab * -kiosk_shell_grab_create(struct kiosk_shell_surface *shsurf) -{ - struct kiosk_shell_grab *shgrab; - - shgrab = zalloc(sizeof *shgrab); - if (!shgrab) - return NULL; - - shgrab->shsurf = shsurf; - shgrab->shsurf_destroy_listener.notify = - kiosk_shell_grab_handle_shsurf_destroy; - wl_signal_add(&shsurf->destroy_signal, - &shgrab->shsurf_destroy_listener); - - shsurf->grabbed = true; - - return shgrab; -} - -enum kiosk_shell_grab_result -kiosk_shell_grab_start_for_pointer_move(struct kiosk_shell_surface *shsurf, - struct weston_pointer *pointer) -{ - struct kiosk_shell_grab *shgrab; - - if (!shsurf) - return KIOSK_SHELL_GRAB_RESULT_ERROR; - - if (shsurf->grabbed || - weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || - weston_desktop_surface_get_maximized(shsurf->desktop_surface)) - return KIOSK_SHELL_GRAB_RESULT_IGNORED; - - shgrab = kiosk_shell_grab_create(shsurf); - if (!shgrab) - return KIOSK_SHELL_GRAB_RESULT_ERROR; - - shgrab->dx = wl_fixed_from_double(shsurf->view->geometry.x) - - pointer->grab_x; - shgrab->dy = wl_fixed_from_double(shsurf->view->geometry.y) - - pointer->grab_y; - shgrab->active = true; - - weston_seat_break_desktop_grabs(pointer->seat); - - shgrab->pointer_grab.interface = &pointer_move_grab_interface; - weston_pointer_start_grab(pointer, &shgrab->pointer_grab); - - return KIOSK_SHELL_GRAB_RESULT_OK; -} - -enum kiosk_shell_grab_result -kiosk_shell_grab_start_for_touch_move(struct kiosk_shell_surface *shsurf, - struct weston_touch *touch) -{ - struct kiosk_shell_grab *shgrab; - - if (!shsurf) - return KIOSK_SHELL_GRAB_RESULT_ERROR; - - if (shsurf->grabbed || - weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || - weston_desktop_surface_get_maximized(shsurf->desktop_surface)) - return KIOSK_SHELL_GRAB_RESULT_IGNORED; - - shgrab = kiosk_shell_grab_create(shsurf); - if (!shgrab) - return KIOSK_SHELL_GRAB_RESULT_ERROR; - - shgrab->dx = wl_fixed_from_double(shsurf->view->geometry.x) - - touch->grab_x; - shgrab->dy = wl_fixed_from_double(shsurf->view->geometry.y) - - touch->grab_y; - shgrab->active = true; - - weston_seat_break_desktop_grabs(touch->seat); - - shgrab->touch_grab.interface = &touch_move_grab_interface; - weston_touch_start_grab(touch, &shgrab->touch_grab); - - return KIOSK_SHELL_GRAB_RESULT_OK; -} - -static void -kiosk_shell_grab_destroy(struct kiosk_shell_grab *shgrab) -{ - if (shgrab->shsurf) { - wl_list_remove(&shgrab->shsurf_destroy_listener.link); - shgrab->shsurf->grabbed = false; - } - - if (shgrab->pointer_grab.pointer) - weston_pointer_end_grab(shgrab->pointer_grab.pointer); - else if (shgrab->touch_grab.touch) - weston_touch_end_grab(shgrab->touch_grab.touch); - - free(shgrab); -} diff --git a/kiosk-shell/kiosk-shell-grab.h b/kiosk-shell/kiosk-shell-grab.h deleted file mode 100644 index bf78384..0000000 --- a/kiosk-shell/kiosk-shell-grab.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright © 2020 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#ifndef WESTON_KIOSK_SHELL_GRAB_H -#define WESTON_KIOSK_SHELL_GRAB_H - -#include "kiosk-shell.h" - -enum kiosk_shell_grab_result { - KIOSK_SHELL_GRAB_RESULT_OK, - KIOSK_SHELL_GRAB_RESULT_IGNORED, - KIOSK_SHELL_GRAB_RESULT_ERROR, -}; - -enum kiosk_shell_grab_result -kiosk_shell_grab_start_for_pointer_move(struct kiosk_shell_surface *shsurf, - struct weston_pointer *pointer); - -enum kiosk_shell_grab_result -kiosk_shell_grab_start_for_touch_move(struct kiosk_shell_surface *shsurf, - struct weston_touch *touch); - -#endif /* WESTON_KIOSK_SHELL_GRAB_H */ diff --git a/kiosk-shell/kiosk-shell.c b/kiosk-shell/kiosk-shell.c deleted file mode 100644 index ac9c868..0000000 --- a/kiosk-shell/kiosk-shell.c +++ /dev/null @@ -1,1071 +0,0 @@ -/* - * Copyright 2010-2012 Intel Corporation - * Copyright 2013 Raspberry Pi Foundation - * Copyright 2011-2012,2020 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include - -#include "kiosk-shell.h" -#include "kiosk-shell-grab.h" -#include "compositor/weston.h" -#include "shared/helpers.h" -#include "util.h" - -static struct kiosk_shell_surface * -get_kiosk_shell_surface(struct weston_surface *surface) -{ - struct weston_desktop_surface *desktop_surface = - weston_surface_get_desktop_surface(surface); - - if (desktop_surface) - return weston_desktop_surface_get_user_data(desktop_surface); - - return NULL; -} - -static void -kiosk_shell_seat_handle_destroy(struct wl_listener *listener, void *data); - -static struct kiosk_shell_seat * -get_kiosk_shell_seat(struct weston_seat *seat) -{ - struct wl_listener *listener; - - listener = wl_signal_get(&seat->destroy_signal, - kiosk_shell_seat_handle_destroy); - assert(listener != NULL); - - return container_of(listener, - struct kiosk_shell_seat, seat_destroy_listener); -} - -/* - * kiosk_shell_surface - */ - -static void -kiosk_shell_surface_set_output(struct kiosk_shell_surface *shsurf, - struct weston_output *output); -static void -kiosk_shell_surface_set_parent(struct kiosk_shell_surface *shsurf, - struct kiosk_shell_surface *parent); - -static void -kiosk_shell_surface_notify_parent_destroy(struct wl_listener *listener, void *data) -{ - struct kiosk_shell_surface *shsurf = - container_of(listener, - struct kiosk_shell_surface, parent_destroy_listener); - - kiosk_shell_surface_set_parent(shsurf, shsurf->parent->parent); -} - -static void -kiosk_shell_surface_notify_output_destroy(struct wl_listener *listener, void *data) -{ - struct kiosk_shell_surface *shsurf = - container_of(listener, - struct kiosk_shell_surface, output_destroy_listener); - - kiosk_shell_surface_set_output(shsurf, NULL); -} - -static struct kiosk_shell_surface * -kiosk_shell_surface_get_parent_root(struct kiosk_shell_surface *shsurf) -{ - struct kiosk_shell_surface *root = shsurf; - while (root->parent) - root = root->parent; - return root; -} - -static bool -kiosk_shell_output_has_app_id(struct kiosk_shell_output *shoutput, - const char *app_id); - -static struct weston_output * -kiosk_shell_surface_find_best_output(struct kiosk_shell_surface *shsurf) -{ - struct weston_output *output; - struct kiosk_shell_output *shoutput; - struct kiosk_shell_surface *root; - const char *app_id; - - /* Always use current output if any. */ - if (shsurf->output) - return shsurf->output; - - /* Check if we have a designated output for this app. */ - app_id = weston_desktop_surface_get_app_id(shsurf->desktop_surface); - if (app_id) { - wl_list_for_each(shoutput, &shsurf->shell->output_list, link) { - if (kiosk_shell_output_has_app_id(shoutput, app_id)) - return shoutput->output; - } - } - - /* Group all related windows in the same output. */ - root = kiosk_shell_surface_get_parent_root(shsurf); - if (root->output) - return root->output; - - output = get_focused_output(shsurf->shell->compositor); - if (output) - return output; - - output = get_default_output(shsurf->shell->compositor); - if (output) - return output; - - return NULL; -} - -static void -kiosk_shell_surface_set_output(struct kiosk_shell_surface *shsurf, - struct weston_output *output) -{ - shsurf->output = output; - - if (shsurf->output_destroy_listener.notify) { - wl_list_remove(&shsurf->output_destroy_listener.link); - shsurf->output_destroy_listener.notify = NULL; - } - - if (!shsurf->output) - return; - - shsurf->output_destroy_listener.notify = - kiosk_shell_surface_notify_output_destroy; - wl_signal_add(&shsurf->output->destroy_signal, - &shsurf->output_destroy_listener); -} - -static void -kiosk_shell_surface_set_fullscreen(struct kiosk_shell_surface *shsurf, - struct weston_output *output) -{ - if (!output) - output = kiosk_shell_surface_find_best_output(shsurf); - - kiosk_shell_surface_set_output(shsurf, output); - - weston_desktop_surface_set_fullscreen(shsurf->desktop_surface, true); - if (shsurf->output) - weston_desktop_surface_set_size(shsurf->desktop_surface, - shsurf->output->width, - shsurf->output->height); -} - -static void -kiosk_shell_surface_set_maximized(struct kiosk_shell_surface *shsurf) -{ - struct weston_output *output = - kiosk_shell_surface_find_best_output(shsurf); - - kiosk_shell_surface_set_output(shsurf, output); - - weston_desktop_surface_set_maximized(shsurf->desktop_surface, true); - if (shsurf->output) - weston_desktop_surface_set_size(shsurf->desktop_surface, - shsurf->output->width, - shsurf->output->height); -} - -static void -kiosk_shell_surface_set_normal(struct kiosk_shell_surface *shsurf) -{ - if (!shsurf->output) - kiosk_shell_surface_set_output(shsurf, - kiosk_shell_surface_find_best_output(shsurf)); - - weston_desktop_surface_set_fullscreen(shsurf->desktop_surface, false); - weston_desktop_surface_set_maximized(shsurf->desktop_surface, false); - weston_desktop_surface_set_size(shsurf->desktop_surface, 0, 0); -} - -static void -kiosk_shell_surface_set_parent(struct kiosk_shell_surface *shsurf, - struct kiosk_shell_surface *parent) -{ - if (shsurf->parent_destroy_listener.notify) { - wl_list_remove(&shsurf->parent_destroy_listener.link); - shsurf->parent_destroy_listener.notify = NULL; - } - - shsurf->parent = parent; - - if (shsurf->parent) { - shsurf->parent_destroy_listener.notify = - kiosk_shell_surface_notify_parent_destroy; - wl_signal_add(&shsurf->parent->destroy_signal, - &shsurf->parent_destroy_listener); - kiosk_shell_surface_set_output(shsurf, NULL); - kiosk_shell_surface_set_normal(shsurf); - } else { - kiosk_shell_surface_set_fullscreen(shsurf, shsurf->output); - } -} - -static void -kiosk_shell_surface_reconfigure_for_output(struct kiosk_shell_surface *shsurf) -{ - struct weston_desktop_surface *desktop_surface; - - if (!shsurf->output) - return; - - desktop_surface = shsurf->desktop_surface; - - if (weston_desktop_surface_get_maximized(desktop_surface) || - weston_desktop_surface_get_fullscreen(desktop_surface)) { - weston_desktop_surface_set_size(desktop_surface, - shsurf->output->width, - shsurf->output->height); - } - - center_on_output(shsurf->view, shsurf->output); - weston_view_update_transform(shsurf->view); -} - -static void -kiosk_shell_surface_destroy(struct kiosk_shell_surface *shsurf) -{ - wl_signal_emit(&shsurf->destroy_signal, shsurf); - - weston_desktop_surface_set_user_data(shsurf->desktop_surface, NULL); - shsurf->desktop_surface = NULL; - - weston_desktop_surface_unlink_view(shsurf->view); - - weston_view_destroy(shsurf->view); - - if (shsurf->output_destroy_listener.notify) { - wl_list_remove(&shsurf->output_destroy_listener.link); - shsurf->output_destroy_listener.notify = NULL; - } - - if (shsurf->parent_destroy_listener.notify) { - wl_list_remove(&shsurf->parent_destroy_listener.link); - shsurf->parent_destroy_listener.notify = NULL; - shsurf->parent = NULL; - } - - free(shsurf); -} - -static struct kiosk_shell_surface * -kiosk_shell_surface_create(struct kiosk_shell *shell, - struct weston_desktop_surface *desktop_surface) -{ - struct weston_desktop_client *client = - weston_desktop_surface_get_client(desktop_surface); - struct wl_client *wl_client = - weston_desktop_client_get_client(client); - struct weston_view *view; - struct kiosk_shell_surface *shsurf; - - view = weston_desktop_surface_create_view(desktop_surface); - if (!view) - return NULL; - - shsurf = zalloc(sizeof *shsurf); - if (!shsurf) { - if (wl_client) - wl_client_post_no_memory(wl_client); - else - weston_log("no memory to allocate shell surface\n"); - return NULL; - } - - shsurf->desktop_surface = desktop_surface; - shsurf->view = view; - shsurf->shell = shell; - - weston_desktop_surface_set_user_data(desktop_surface, shsurf); - - wl_signal_init(&shsurf->destroy_signal); - - return shsurf; -} - -/* - * kiosk_shell_seat - */ - -static void -kiosk_shell_seat_handle_keyboard_focus(struct wl_listener *listener, void *data) -{ - struct weston_keyboard *keyboard = data; - struct kiosk_shell_seat *shseat = get_kiosk_shell_seat(keyboard->seat); - - if (shseat->focused_surface) { - struct kiosk_shell_surface *shsurf = - get_kiosk_shell_surface(shseat->focused_surface); - if (shsurf && --shsurf->focus_count == 0) - weston_desktop_surface_set_activated(shsurf->desktop_surface, - false); - } - - shseat->focused_surface = weston_surface_get_main_surface(keyboard->focus); - - if (shseat->focused_surface) { - struct kiosk_shell_surface *shsurf = - get_kiosk_shell_surface(shseat->focused_surface); - if (shsurf && shsurf->focus_count++ == 0) - weston_desktop_surface_set_activated(shsurf->desktop_surface, - true); - } -} - -static void -kiosk_shell_seat_handle_destroy(struct wl_listener *listener, void *data) -{ - struct kiosk_shell_seat *shseat = - container_of(listener, - struct kiosk_shell_seat, seat_destroy_listener); - - wl_list_remove(&shseat->keyboard_focus_listener.link); - wl_list_remove(&shseat->caps_changed_listener.link); - wl_list_remove(&shseat->seat_destroy_listener.link); - free(shseat); -} - -static void -kiosk_shell_seat_handle_caps_changed(struct wl_listener *listener, void *data) -{ - struct weston_keyboard *keyboard; - struct kiosk_shell_seat *shseat; - - shseat = container_of(listener, struct kiosk_shell_seat, - caps_changed_listener); - keyboard = weston_seat_get_keyboard(shseat->seat); - - if (keyboard && - wl_list_empty(&shseat->keyboard_focus_listener.link)) { - wl_signal_add(&keyboard->focus_signal, - &shseat->keyboard_focus_listener); - } else if (!keyboard) { - wl_list_remove(&shseat->keyboard_focus_listener.link); - wl_list_init(&shseat->keyboard_focus_listener.link); - } -} - -static struct kiosk_shell_seat * -kiosk_shell_seat_create(struct weston_seat *seat) -{ - struct kiosk_shell_seat *shseat; - - shseat = zalloc(sizeof *shseat); - if (!shseat) { - weston_log("no memory to allocate shell seat\n"); - return NULL; - } - - shseat->seat = seat; - - shseat->seat_destroy_listener.notify = kiosk_shell_seat_handle_destroy; - wl_signal_add(&seat->destroy_signal, &shseat->seat_destroy_listener); - - shseat->keyboard_focus_listener.notify = kiosk_shell_seat_handle_keyboard_focus; - wl_list_init(&shseat->keyboard_focus_listener.link); - - shseat->caps_changed_listener.notify = kiosk_shell_seat_handle_caps_changed; - wl_signal_add(&seat->updated_caps_signal, - &shseat->caps_changed_listener); - kiosk_shell_seat_handle_caps_changed(&shseat->caps_changed_listener, NULL); - - return shseat; -} - -/* - * kiosk_shell_output - */ - -static int -kiosk_shell_background_surface_get_label(struct weston_surface *surface, - char *buf, size_t len) -{ - return snprintf(buf, len, "kiosk shell background surface"); -} - -static void -kiosk_shell_output_recreate_background(struct kiosk_shell_output *shoutput) -{ - struct kiosk_shell *shell = shoutput->shell; - struct weston_output *output = shoutput->output; - - if (shoutput->background_view) - weston_surface_destroy(shoutput->background_view->surface); - - if (!output) - return; - - shoutput->background_view = - create_colored_surface(shoutput->shell->compositor, - 0.5, 0.5, 0.5, - output->x, output->y, - output->width, - output->height); - - weston_surface_set_role(shoutput->background_view->surface, - "kiosk-shell-background", NULL, 0); - weston_surface_set_label_func(shoutput->background_view->surface, - kiosk_shell_background_surface_get_label); - - weston_layer_entry_insert(&shell->background_layer.view_list, - &shoutput->background_view->layer_link); - - shoutput->background_view->is_mapped = true; - shoutput->background_view->surface->is_mapped = true; - shoutput->background_view->surface->output = output; - weston_view_set_output(shoutput->background_view, output); -} - -static void -kiosk_shell_output_destroy(struct kiosk_shell_output *shoutput) -{ - shoutput->output = NULL; - shoutput->output_destroy_listener.notify = NULL; - - if (shoutput->background_view) - weston_surface_destroy(shoutput->background_view->surface); - - wl_list_remove(&shoutput->output_destroy_listener.link); - wl_list_remove(&shoutput->link); - - free(shoutput->app_ids); - - free(shoutput); -} - -static bool -kiosk_shell_output_has_app_id(struct kiosk_shell_output *shoutput, - const char *app_id) -{ - char *cur; - size_t app_id_len; - - if (!shoutput->app_ids) - return false; - - cur = shoutput->app_ids; - app_id_len = strlen(app_id); - - while ((cur = strstr(cur, app_id))) { - /* Check whether we have found a complete match of app_id. */ - if ((cur[app_id_len] == ',' || cur[app_id_len] == '\0') && - (cur == shoutput->app_ids || cur[-1] == ',')) - return true; - cur++; - } - - return false; -} - -static void -kiosk_shell_output_configure(struct kiosk_shell_output *shoutput) -{ - struct weston_config *wc = wet_get_config(shoutput->shell->compositor); - struct weston_config_section *section = - weston_config_get_section(wc, "output", "name", shoutput->output->name); - - assert(shoutput->app_ids == NULL); - - if (section) { - weston_config_section_get_string(section, "app-ids", - &shoutput->app_ids, NULL); - } -} - -static void -kiosk_shell_output_notify_output_destroy(struct wl_listener *listener, void *data) -{ - struct kiosk_shell_output *shoutput = - container_of(listener, - struct kiosk_shell_output, output_destroy_listener); - - kiosk_shell_output_destroy(shoutput); -} - -static struct kiosk_shell_output * -kiosk_shell_output_create(struct kiosk_shell *shell, struct weston_output *output) -{ - struct kiosk_shell_output *shoutput; - - shoutput = zalloc(sizeof *shoutput); - if (shoutput == NULL) - return NULL; - - shoutput->output = output; - shoutput->shell = shell; - - shoutput->output_destroy_listener.notify = - kiosk_shell_output_notify_output_destroy; - wl_signal_add(&shoutput->output->destroy_signal, - &shoutput->output_destroy_listener); - - wl_list_insert(shell->output_list.prev, &shoutput->link); - - kiosk_shell_output_recreate_background(shoutput); - kiosk_shell_output_configure(shoutput); - - return shoutput; -} - -/* - * libweston-desktop - */ - -static void -desktop_surface_added(struct weston_desktop_surface *desktop_surface, - void *data) -{ - struct kiosk_shell *shell = data; - struct kiosk_shell_surface *shsurf; - struct weston_seat *seat; - - shsurf = kiosk_shell_surface_create(shell, desktop_surface); - if (!shsurf) - return; - - kiosk_shell_surface_set_fullscreen(shsurf, NULL); - - wl_list_for_each(seat, &shell->compositor->seat_list, link) - weston_view_activate(shsurf->view, seat, 0); -} - -/* Return the view that should gain focus after the specified shsurf is - * destroyed. We prefer the top remaining view from the same parent surface, - * but if we can't find one we fall back to the top view regardless of - * parentage. */ -static struct weston_view * -find_focus_successor(struct weston_layer *layer, - struct kiosk_shell_surface *shsurf) -{ - struct kiosk_shell_surface *parent_root = - kiosk_shell_surface_get_parent_root(shsurf); - struct weston_view *top_view = NULL; - struct weston_view *view; - - wl_list_for_each(view, &layer->view_list.link, layer_link.link) { - struct kiosk_shell_surface *view_shsurf; - struct kiosk_shell_surface *root; - - if (!view->is_mapped || view == shsurf->view) - continue; - - view_shsurf = get_kiosk_shell_surface(view->surface); - if (!view_shsurf) - continue; - - if (!top_view) - top_view = view; - - root = kiosk_shell_surface_get_parent_root(view_shsurf); - if (root == parent_root) - return view; - } - - return top_view; -} - -static void -desktop_surface_removed(struct weston_desktop_surface *desktop_surface, - void *data) -{ - struct kiosk_shell *shell = data; - struct kiosk_shell_surface *shsurf = - weston_desktop_surface_get_user_data(desktop_surface); - struct weston_surface *surface = - weston_desktop_surface_get_surface(desktop_surface); - struct weston_view *focus_view; - struct weston_seat *seat; - - if (!shsurf) - return; - - focus_view = find_focus_successor(&shell->normal_layer, shsurf); - - if (focus_view) { - wl_list_for_each(seat, &shell->compositor->seat_list, link) { - struct weston_keyboard *keyboard = seat->keyboard_state; - if (keyboard && keyboard->focus == surface) - weston_view_activate(focus_view, seat, 0); - } - } - - kiosk_shell_surface_destroy(shsurf); -} - -static void -desktop_surface_committed(struct weston_desktop_surface *desktop_surface, - int32_t sx, int32_t sy, void *data) -{ - struct kiosk_shell_surface *shsurf = - weston_desktop_surface_get_user_data(desktop_surface); - struct weston_surface *surface = - weston_desktop_surface_get_surface(desktop_surface); - bool is_resized; - bool is_fullscreen; - - if (surface->width == 0) - return; - - /* TODO: When the top-level surface is committed with a new size after an - * output resize, sometimes the view appears scaled. What state are we not - * updating? - */ - - is_resized = surface->width != shsurf->last_width || - surface->height != shsurf->last_height; - is_fullscreen = weston_desktop_surface_get_maximized(desktop_surface) || - weston_desktop_surface_get_fullscreen(desktop_surface); - - if (!weston_surface_is_mapped(surface) || (is_resized && is_fullscreen)) { - if (is_fullscreen || !shsurf->xwayland.is_set) { - center_on_output(shsurf->view, shsurf->output); - } else { - struct weston_geometry geometry = - weston_desktop_surface_get_geometry(desktop_surface); - float x = shsurf->xwayland.x - geometry.x; - float y = shsurf->xwayland.y - geometry.y; - - weston_view_set_position(shsurf->view, x, y); - } - - weston_view_update_transform(shsurf->view); - } - - if (!weston_surface_is_mapped(surface)) { - weston_layer_entry_insert(&shsurf->shell->normal_layer.view_list, - &shsurf->view->layer_link); - shsurf->view->is_mapped = true; - surface->is_mapped = true; - } - - if (!is_fullscreen && (sx != 0 || sy != 0)) { - float from_x, from_y; - float to_x, to_y; - float x, y; - - weston_view_to_global_float(shsurf->view, 0, 0, &from_x, &from_y); - weston_view_to_global_float(shsurf->view, sx, sy, &to_x, &to_y); - x = shsurf->view->geometry.x + to_x - from_x; - y = shsurf->view->geometry.y + to_y - from_y; - - weston_view_set_position(shsurf->view, x, y); - weston_view_update_transform(shsurf->view); - } - - shsurf->last_width = surface->width; - shsurf->last_height = surface->height; -} - -static void -desktop_surface_move(struct weston_desktop_surface *desktop_surface, - struct weston_seat *seat, uint32_t serial, void *shell) -{ - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_touch *touch = weston_seat_get_touch(seat); - struct kiosk_shell_surface *shsurf = - weston_desktop_surface_get_user_data(desktop_surface); - struct weston_surface *surface = - weston_desktop_surface_get_surface(shsurf->desktop_surface); - struct weston_surface *focus; - - if (pointer && - pointer->focus && - pointer->button_count > 0 && - pointer->grab_serial == serial) { - focus = weston_surface_get_main_surface(pointer->focus->surface); - if ((focus == surface) && - (kiosk_shell_grab_start_for_pointer_move(shsurf, pointer) == - KIOSK_SHELL_GRAB_RESULT_ERROR)) - wl_resource_post_no_memory(surface->resource); - } - else if (touch && - touch->focus && - touch->grab_serial == serial) { - focus = weston_surface_get_main_surface(touch->focus->surface); - if ((focus == surface) && - (kiosk_shell_grab_start_for_touch_move(shsurf, touch) == - KIOSK_SHELL_GRAB_RESULT_ERROR)) - wl_resource_post_no_memory(surface->resource); - } -} - -static void -desktop_surface_resize(struct weston_desktop_surface *desktop_surface, - struct weston_seat *seat, uint32_t serial, - enum weston_desktop_surface_edge edges, void *shell) -{ -} - -static void -desktop_surface_set_parent(struct weston_desktop_surface *desktop_surface, - struct weston_desktop_surface *parent, - void *shell) -{ - struct kiosk_shell_surface *shsurf = - weston_desktop_surface_get_user_data(desktop_surface); - struct kiosk_shell_surface *shsurf_parent = - parent ? weston_desktop_surface_get_user_data(parent) : NULL; - - kiosk_shell_surface_set_parent(shsurf, shsurf_parent); -} - -static void -desktop_surface_fullscreen_requested(struct weston_desktop_surface *desktop_surface, - bool fullscreen, - struct weston_output *output, void *shell) -{ - struct kiosk_shell_surface *shsurf = - weston_desktop_surface_get_user_data(desktop_surface); - - /* We should normally be able to ignore fullscreen requests for - * top-level surfaces, since we set them as fullscreen at creation - * time. However, xwayland surfaces set their internal WM state - * regardless of what the shell wants, so they may remove fullscreen - * state before informing weston-desktop of this request. Since we - * always want top-level surfaces to be fullscreen, we need to reapply - * the fullscreen state to force the correct xwayland WM state. - * - * TODO: Explore a model where the XWayland WM doesn't set the internal - * WM surface state itself, rather letting the shell make the decision. - */ - - if (!shsurf->parent || fullscreen) - kiosk_shell_surface_set_fullscreen(shsurf, output); - else - kiosk_shell_surface_set_normal(shsurf); -} - -static void -desktop_surface_maximized_requested(struct weston_desktop_surface *desktop_surface, - bool maximized, void *shell) -{ - struct kiosk_shell_surface *shsurf = - weston_desktop_surface_get_user_data(desktop_surface); - - /* Since xwayland surfaces may have already applied the max/min states - * internally, reapply fullscreen to force the correct xwayland WM state. - * Also see comment in desktop_surface_fullscreen_requested(). */ - if (!shsurf->parent) - kiosk_shell_surface_set_fullscreen(shsurf, NULL); - else if (maximized) - kiosk_shell_surface_set_maximized(shsurf); - else - kiosk_shell_surface_set_normal(shsurf); -} - -static void -desktop_surface_minimized_requested(struct weston_desktop_surface *desktop_surface, - void *shell) -{ -} - -static void -desktop_surface_ping_timeout(struct weston_desktop_client *desktop_client, - void *shell_) -{ -} - -static void -desktop_surface_pong(struct weston_desktop_client *desktop_client, - void *shell_) -{ -} - -static void -desktop_surface_set_xwayland_position(struct weston_desktop_surface *desktop_surface, - int32_t x, int32_t y, void *shell) -{ - struct kiosk_shell_surface *shsurf = - weston_desktop_surface_get_user_data(desktop_surface); - - shsurf->xwayland.x = x; - shsurf->xwayland.y = y; - shsurf->xwayland.is_set = true; -} - -static const struct weston_desktop_api kiosk_shell_desktop_api = { - .struct_size = sizeof(struct weston_desktop_api), - .surface_added = desktop_surface_added, - .surface_removed = desktop_surface_removed, - .committed = desktop_surface_committed, - .move = desktop_surface_move, - .resize = desktop_surface_resize, - .set_parent = desktop_surface_set_parent, - .fullscreen_requested = desktop_surface_fullscreen_requested, - .maximized_requested = desktop_surface_maximized_requested, - .minimized_requested = desktop_surface_minimized_requested, - .ping_timeout = desktop_surface_ping_timeout, - .pong = desktop_surface_pong, - .set_xwayland_position = desktop_surface_set_xwayland_position, -}; - -/* - * kiosk_shell - */ - -static struct kiosk_shell_output * -kiosk_shell_find_shell_output(struct kiosk_shell *shell, - struct weston_output *output) -{ - struct kiosk_shell_output *shoutput; - - wl_list_for_each(shoutput, &shell->output_list, link) { - if (shoutput->output == output) - return shoutput; - } - - return NULL; -} - -static void -kiosk_shell_activate_view(struct kiosk_shell *shell, - struct weston_view *view, - struct weston_seat *seat, - uint32_t flags) -{ - struct weston_surface *main_surface = - weston_surface_get_main_surface(view->surface); - struct kiosk_shell_surface *shsurf = - get_kiosk_shell_surface(main_surface); - - if (!shsurf) - return; - - /* If the view belongs to a child window bring it to the front. - * We don't do this for the parent top-level, since that would - * obscure all children. - */ - if (shsurf->parent) { - weston_layer_entry_remove(&view->layer_link); - weston_layer_entry_insert(&shell->normal_layer.view_list, - &view->layer_link); - weston_view_geometry_dirty(view); - weston_surface_damage(view->surface); - } - - weston_view_activate(view, seat, flags); -} - -static void -kiosk_shell_click_to_activate_binding(struct weston_pointer *pointer, - const struct timespec *time, - uint32_t button, void *data) -{ - struct kiosk_shell *shell = data; - - if (pointer->grab != &pointer->default_grab) - return; - if (pointer->focus == NULL) - return; - - kiosk_shell_activate_view(shell, pointer->focus, pointer->seat, - WESTON_ACTIVATE_FLAG_CLICKED); -} - -static void -kiosk_shell_touch_to_activate_binding(struct weston_touch *touch, - const struct timespec *time, - void *data) -{ - struct kiosk_shell *shell = data; - - if (touch->grab != &touch->default_grab) - return; - if (touch->focus == NULL) - return; - - kiosk_shell_activate_view(shell, touch->focus, touch->seat, - WESTON_ACTIVATE_FLAG_NONE); -} - -static void -kiosk_shell_add_bindings(struct kiosk_shell *shell) -{ - weston_compositor_add_button_binding(shell->compositor, BTN_LEFT, 0, - kiosk_shell_click_to_activate_binding, - shell); - weston_compositor_add_button_binding(shell->compositor, BTN_RIGHT, 0, - kiosk_shell_click_to_activate_binding, - shell); - weston_compositor_add_touch_binding(shell->compositor, 0, - kiosk_shell_touch_to_activate_binding, - shell); -} - -static void -kiosk_shell_handle_output_created(struct wl_listener *listener, void *data) -{ - struct kiosk_shell *shell = - container_of(listener, struct kiosk_shell, output_created_listener); - struct weston_output *output = data; - - kiosk_shell_output_create(shell, output); -} - -static void -kiosk_shell_handle_output_resized(struct wl_listener *listener, void *data) -{ - struct kiosk_shell *shell = - container_of(listener, struct kiosk_shell, output_resized_listener); - struct weston_output *output = data; - struct kiosk_shell_output *shoutput = - kiosk_shell_find_shell_output(shell, output); - struct weston_view *view; - - kiosk_shell_output_recreate_background(shoutput); - - wl_list_for_each(view, &shell->normal_layer.view_list.link, - layer_link.link) { - struct kiosk_shell_surface *shsurf; - if (view->output != output) - continue; - shsurf = get_kiosk_shell_surface(view->surface); - if (!shsurf) - continue; - kiosk_shell_surface_reconfigure_for_output(shsurf); - } -} - -static void -kiosk_shell_handle_output_moved(struct wl_listener *listener, void *data) -{ - struct kiosk_shell *shell = - container_of(listener, struct kiosk_shell, output_moved_listener); - struct weston_output *output = data; - struct weston_view *view; - - wl_list_for_each(view, &shell->background_layer.view_list.link, - layer_link.link) { - if (view->output != output) - continue; - weston_view_set_position(view, - view->geometry.x + output->move_x, - view->geometry.y + output->move_y); - } - - wl_list_for_each(view, &shell->normal_layer.view_list.link, - layer_link.link) { - if (view->output != output) - continue; - weston_view_set_position(view, - view->geometry.x + output->move_x, - view->geometry.y + output->move_y); - } -} - -static void -kiosk_shell_handle_seat_created(struct wl_listener *listener, void *data) -{ - struct weston_seat *seat = data; - kiosk_shell_seat_create(seat); -} - -static void -kiosk_shell_destroy(struct wl_listener *listener, void *data) -{ - struct kiosk_shell *shell = - container_of(listener, struct kiosk_shell, destroy_listener); - struct kiosk_shell_output *shoutput, *tmp; - - wl_list_remove(&shell->destroy_listener.link); - wl_list_remove(&shell->output_created_listener.link); - wl_list_remove(&shell->output_resized_listener.link); - wl_list_remove(&shell->output_moved_listener.link); - wl_list_remove(&shell->seat_created_listener.link); - - wl_list_for_each_safe(shoutput, tmp, &shell->output_list, link) { - kiosk_shell_output_destroy(shoutput); - } - - weston_desktop_destroy(shell->desktop); - - free(shell); -} - -WL_EXPORT int -wet_shell_init(struct weston_compositor *ec, - int *argc, char *argv[]) -{ - struct kiosk_shell *shell; - struct weston_seat *seat; - struct weston_output *output; - - shell = zalloc(sizeof *shell); - if (shell == NULL) - return -1; - - shell->compositor = ec; - - if (!weston_compositor_add_destroy_listener_once(ec, - &shell->destroy_listener, - kiosk_shell_destroy)) { - free(shell); - return 0; - } - - weston_layer_init(&shell->background_layer, ec); - weston_layer_init(&shell->normal_layer, ec); - - weston_layer_set_position(&shell->background_layer, - WESTON_LAYER_POSITION_BACKGROUND); - /* We use the NORMAL layer position, so that xwayland surfaces, which - * are placed at NORMAL+1, are visible. */ - weston_layer_set_position(&shell->normal_layer, - WESTON_LAYER_POSITION_NORMAL); - - shell->desktop = weston_desktop_create(ec, &kiosk_shell_desktop_api, - shell); - if (!shell->desktop) - return -1; - - wl_list_for_each(seat, &ec->seat_list, link) - kiosk_shell_seat_create(seat); - shell->seat_created_listener.notify = kiosk_shell_handle_seat_created; - wl_signal_add(&ec->seat_created_signal, &shell->seat_created_listener); - - wl_list_init(&shell->output_list); - wl_list_for_each(output, &ec->output_list, link) - kiosk_shell_output_create(shell, output); - - shell->output_created_listener.notify = kiosk_shell_handle_output_created; - wl_signal_add(&ec->output_created_signal, &shell->output_created_listener); - - shell->output_resized_listener.notify = kiosk_shell_handle_output_resized; - wl_signal_add(&ec->output_resized_signal, &shell->output_resized_listener); - - shell->output_moved_listener.notify = kiosk_shell_handle_output_moved; - wl_signal_add(&ec->output_moved_signal, &shell->output_moved_listener); - - kiosk_shell_add_bindings(shell); - - return 0; -} diff --git a/kiosk-shell/kiosk-shell.h b/kiosk-shell/kiosk-shell.h deleted file mode 100644 index 09f5a77..0000000 --- a/kiosk-shell/kiosk-shell.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2020 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#ifndef WESTON_KIOSK_SHELL_H -#define WESTON_KIOSK_SHELL_H - -#include -#include - -struct kiosk_shell { - struct weston_compositor *compositor; - struct weston_desktop *desktop; - - struct wl_listener destroy_listener; - struct wl_listener output_created_listener; - struct wl_listener output_resized_listener; - struct wl_listener output_moved_listener; - struct wl_listener seat_created_listener; - - struct weston_layer background_layer; - struct weston_layer normal_layer; - - struct wl_list output_list; -}; - -struct kiosk_shell_surface { - struct weston_desktop_surface *desktop_surface; - struct weston_view *view; - - struct kiosk_shell *shell; - - struct weston_output *output; - struct wl_listener output_destroy_listener; - - struct wl_signal destroy_signal; - struct wl_listener parent_destroy_listener; - struct kiosk_shell_surface *parent; - - int focus_count; - - int32_t last_width, last_height; - bool grabbed; - - struct { - bool is_set; - int32_t x; - int32_t y; - } xwayland; -}; - -struct kiosk_shell_seat { - struct weston_seat *seat; - struct wl_listener seat_destroy_listener; - struct weston_surface *focused_surface; - - struct wl_listener caps_changed_listener; - struct wl_listener keyboard_focus_listener; -}; - -struct kiosk_shell_output { - struct weston_output *output; - struct wl_listener output_destroy_listener; - struct weston_view *background_view; - - struct kiosk_shell *shell; - struct wl_list link; - - char *app_ids; -}; - -#endif /* WESTON_KIOSK_SHELL_H */ diff --git a/kiosk-shell/meson.build b/kiosk-shell/meson.build deleted file mode 100644 index e838614..0000000 --- a/kiosk-shell/meson.build +++ /dev/null @@ -1,29 +0,0 @@ -if get_option('shell-kiosk') - srcs_shell_kiosk = [ - 'kiosk-shell.c', - 'kiosk-shell-grab.c', - 'util.c', - weston_desktop_shell_server_protocol_h, - weston_desktop_shell_protocol_c, - input_method_unstable_v1_server_protocol_h, - input_method_unstable_v1_protocol_c, - ] - deps_shell_kiosk = [ - dep_libm, - dep_libexec_weston, - dep_libshared, - dep_lib_desktop, - dep_libweston_public, - ] - plugin_shell_kiosk = shared_library( - 'kiosk-shell', - srcs_shell_kiosk, - include_directories: common_inc, - dependencies: deps_shell_kiosk, - name_prefix: '', - install: true, - install_dir: dir_module_weston, - install_rpath: '$ORIGIN' - ) - env_modmap += 'kiosk-shell.so=@0@;'.format(plugin_shell_kiosk.full_path()) -endif diff --git a/kiosk-shell/util.c b/kiosk-shell/util.c deleted file mode 100644 index ad3e0d9..0000000 --- a/kiosk-shell/util.c +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2010-2012 Intel Corporation - * Copyright 2013 Raspberry Pi Foundation - * Copyright 2011-2012,2020 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -/* Helper functions for kiosk-shell */ - -/* TODO: These functions are useful to many shells, and, in fact, - * much of content in this file was copied from desktop-shell. We should - * create a shared shell utility collection to deduplicate this code. */ - -#include "util.h" -#include "shared/helpers.h" -#include - -struct weston_output * -get_default_output(struct weston_compositor *compositor) -{ - if (wl_list_empty(&compositor->output_list)) - return NULL; - - return container_of(compositor->output_list.next, - struct weston_output, link); -} - -struct weston_output * -get_focused_output(struct weston_compositor *compositor) -{ - struct weston_seat *seat; - struct weston_output *output = NULL; - - wl_list_for_each(seat, &compositor->seat_list, link) { - struct weston_touch *touch = weston_seat_get_touch(seat); - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(seat); - - /* Priority has touch focus, then pointer and - * then keyboard focus. We should probably have - * three for loops and check first for touch, - * then for pointer, etc. but unless somebody has some - * objections, I think this is sufficient. */ - if (touch && touch->focus) - output = touch->focus->output; - else if (pointer && pointer->focus) - output = pointer->focus->output; - else if (keyboard && keyboard->focus) - output = keyboard->focus->output; - - if (output) - break; - } - - return output; -} - -/* This is a copy of the same function from desktop-shell. - * TODO: Fix this function to take into account nested subsurfaces. */ -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->width, - surface->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->width, - subsurface->surface->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); -} - -void -center_on_output(struct weston_view *view, struct weston_output *output) -{ - int32_t surf_x, surf_y, width, height; - float x, y; - - if (!output) { - weston_view_set_position(view, 0, 0); - return; - } - - surface_subsurfaces_boundingbox(view->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_view_set_position(view, x, y); -} - -static void -colored_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) -{ -} - -struct weston_view * -create_colored_surface(struct weston_compositor *compositor, - float r, float g, float b, - float x, float y, int w, int h) -{ - struct weston_surface *surface = NULL; - struct weston_view *view; - - surface = weston_surface_create(compositor); - if (surface == NULL) { - weston_log("no memory\n"); - return NULL; - } - view = weston_view_create(surface); - if (surface == NULL) { - weston_log("no memory\n"); - weston_surface_destroy(surface); - return NULL; - } - - surface->committed = colored_surface_committed; - surface->committed_private = NULL; - - weston_surface_set_color(surface, r, g, b, 1.0); - 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); - - weston_surface_set_size(surface, w, h); - weston_view_set_position(view, x, y); - - return view; -} diff --git a/kiosk-shell/util.h b/kiosk-shell/util.h deleted file mode 100644 index e60aa3b..0000000 --- a/kiosk-shell/util.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2010-2012 Intel Corporation - * Copyright 2013 Raspberry Pi Foundation - * Copyright 2011-2012,2020 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -/* Helper functions adapted from desktop-shell */ - -#include - -struct weston_compositor; -struct weston_layer; -struct weston_output; -struct weston_surface; -struct weston_view; - -struct weston_output * -get_default_output(struct weston_compositor *compositor); - -struct weston_output * -get_focused_output(struct weston_compositor *compositor); - -void -center_on_output(struct weston_view *view, struct weston_output *output); - -struct weston_view * -create_colored_surface(struct weston_compositor *compositor, - float r, float g, float b, - float x, float y, int w, int h); diff --git a/libweston-desktop/client.c b/libweston-desktop/client.c deleted file mode 100644 index 56413f7..0000000 --- a/libweston-desktop/client.c +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright © 2016 Quentin "Sardem FF7" Glidic - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include - -#include -#include - -#include -#include "internal.h" - -struct weston_desktop_client { - struct weston_desktop *desktop; - struct wl_client *client; - struct wl_resource *resource; - struct wl_list surface_list; - uint32_t ping_serial; - struct wl_event_source *ping_timer; - struct wl_signal destroy_signal; -}; - -void -weston_desktop_client_add_destroy_listener(struct weston_desktop_client *client, - struct wl_listener *listener) -{ - wl_signal_add(&client->destroy_signal, listener); -} - -static void -weston_desktop_client_destroy(struct wl_resource *resource) -{ - struct weston_desktop_client *client = - wl_resource_get_user_data(resource); - struct wl_list *list = &client->surface_list; - struct wl_list *link, *tmp; - - wl_signal_emit(&client->destroy_signal, client); - - for (link = list->next, tmp = link->next; - link != list; - link = tmp, tmp = link->next) { - wl_list_remove(link); - wl_list_init(link); - } - - if (client->ping_timer != NULL) - wl_event_source_remove(client->ping_timer); - - free(client); -} - -static int -weston_desktop_client_ping_timeout(void *user_data) -{ - struct weston_desktop_client *client = user_data; - - weston_desktop_api_ping_timeout(client->desktop, client); - return 1; -} - -struct weston_desktop_client * -weston_desktop_client_create(struct weston_desktop *desktop, - struct wl_client *wl_client, - wl_dispatcher_func_t dispatcher, - const struct wl_interface *interface, - const void *implementation, uint32_t version, - uint32_t id) -{ - struct weston_desktop_client *client; - struct wl_display *display; - struct wl_event_loop *loop; - - client = zalloc(sizeof(struct weston_desktop_client)); - if (client == NULL) { - if (wl_client != NULL) - wl_client_post_no_memory(wl_client); - return NULL; - } - - client->desktop = desktop; - client->client = wl_client; - - wl_list_init(&client->surface_list); - wl_signal_init(&client->destroy_signal); - - if (wl_client == NULL) - return client; - - client->resource = wl_resource_create(wl_client, interface, version, id); - if (client->resource == NULL) { - wl_client_post_no_memory(wl_client); - free(client); - return NULL; - } - - if (dispatcher != NULL) - wl_resource_set_dispatcher(client->resource, dispatcher, - weston_desktop_client_destroy, client, - weston_desktop_client_destroy); - else - wl_resource_set_implementation(client->resource, implementation, - client, - weston_desktop_client_destroy); - - - display = wl_client_get_display(client->client); - loop = wl_display_get_event_loop(display); - client->ping_timer = - wl_event_loop_add_timer(loop, - weston_desktop_client_ping_timeout, - client); - if (client->ping_timer == NULL) - wl_client_post_no_memory(wl_client); - - return client; -} - -struct weston_desktop * -weston_desktop_client_get_desktop(struct weston_desktop_client *client) -{ - return client->desktop; -} - -struct wl_resource * -weston_desktop_client_get_resource(struct weston_desktop_client *client) -{ - return client->resource; -} - -struct wl_list * -weston_desktop_client_get_surface_list(struct weston_desktop_client *client) -{ - return &client->surface_list; -} - -WL_EXPORT struct wl_client * -weston_desktop_client_get_client(struct weston_desktop_client *client) -{ - return client->client; -} - -WL_EXPORT void -weston_desktop_client_for_each_surface(struct weston_desktop_client *client, - void (*callback)(struct weston_desktop_surface *surface, void *user_data), - void *user_data) -{ - struct wl_list *list = &client->surface_list; - struct wl_list *link; - - for (link = list->next; link != list; link = link->next) - callback(weston_desktop_surface_from_client_link(link), - user_data); -} - -WL_EXPORT int -weston_desktop_client_ping(struct weston_desktop_client *client) -{ - struct weston_desktop_surface *surface = - weston_desktop_surface_from_client_link(client->surface_list.next); - const struct weston_desktop_surface_implementation *implementation = - weston_desktop_surface_get_implementation(surface); - void *implementation_data = - weston_desktop_surface_get_implementation_data(surface); - - if (implementation->ping == NULL) - return -1; - - if (client->ping_serial != 0) - return 1; - - client->ping_serial = - wl_display_next_serial(wl_client_get_display(client->client)); - wl_event_source_timer_update(client->ping_timer, 10000); - - implementation->ping(surface, client->ping_serial, implementation_data); - - return 0; -} - -void -weston_desktop_client_pong(struct weston_desktop_client *client, uint32_t serial) -{ - if (client->ping_serial != serial) - return; - - weston_desktop_api_pong(client->desktop, client); - - wl_event_source_timer_update(client->ping_timer, 0); - client->ping_serial = 0; -} diff --git a/libweston-desktop/internal.h b/libweston-desktop/internal.h deleted file mode 100644 index e4ab270..0000000 --- a/libweston-desktop/internal.h +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright © 2016 Quentin "Sardem FF7" Glidic - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#ifndef WESTON_DESKTOP_INTERNAL_H -#define WESTON_DESKTOP_INTERNAL_H - -#include - -struct weston_desktop_seat; -struct weston_desktop_client; - -struct weston_compositor * -weston_desktop_get_compositor(struct weston_desktop *desktop); -struct wl_display * -weston_desktop_get_display(struct weston_desktop *desktop); - -void -weston_desktop_api_ping_timeout(struct weston_desktop *desktop, - struct weston_desktop_client *client); -void -weston_desktop_api_pong(struct weston_desktop *desktop, - struct weston_desktop_client *client); -void -weston_desktop_api_surface_added(struct weston_desktop *desktop, - struct weston_desktop_surface *surface); -void -weston_desktop_api_surface_removed(struct weston_desktop *desktop, - struct weston_desktop_surface *surface); -void -weston_desktop_api_committed(struct weston_desktop *desktop, - struct weston_desktop_surface *surface, - int32_t sx, int32_t sy); -void -weston_desktop_api_show_window_menu(struct weston_desktop *desktop, - struct weston_desktop_surface *surface, - struct weston_seat *seat, - int32_t x, int32_t y); -void -weston_desktop_api_set_parent(struct weston_desktop *desktop, - struct weston_desktop_surface *surface, - struct weston_desktop_surface *parent); -void -weston_desktop_api_move(struct weston_desktop *desktop, - struct weston_desktop_surface *surface, - struct weston_seat *seat, uint32_t serial); -void -weston_desktop_api_resize(struct weston_desktop *desktop, - struct weston_desktop_surface *surface, - struct weston_seat *seat, uint32_t serial, - enum weston_desktop_surface_edge edges); -void -weston_desktop_api_fullscreen_requested(struct weston_desktop *desktop, - struct weston_desktop_surface *surface, - bool fullscreen, - struct weston_output *output); -void -weston_desktop_api_maximized_requested(struct weston_desktop *desktop, - struct weston_desktop_surface *surface, - bool maximized); -void -weston_desktop_api_minimized_requested(struct weston_desktop *desktop, - struct weston_desktop_surface *surface); - -void -weston_desktop_api_set_xwayland_position(struct weston_desktop *desktop, - struct weston_desktop_surface *surface, - int32_t x, int32_t y); - -struct weston_desktop_seat * -weston_desktop_seat_from_seat(struct weston_seat *wseat); - -struct weston_desktop_surface_implementation { - void (*set_activated)(struct weston_desktop_surface *surface, - void *user_data, bool activated); - void (*set_fullscreen)(struct weston_desktop_surface *surface, - void *user_data, bool fullscreen); - void (*set_maximized)(struct weston_desktop_surface *surface, - void *user_data, bool maximized); - void (*set_resizing)(struct weston_desktop_surface *surface, - void *user_data, bool resizing); - void (*set_size)(struct weston_desktop_surface *surface, - void *user_data, int32_t width, int32_t height); - void (*committed)(struct weston_desktop_surface *surface, void *user_data, - int32_t sx, int32_t sy); - void (*update_position)(struct weston_desktop_surface *surface, - void *user_data); - void (*ping)(struct weston_desktop_surface *surface, uint32_t serial, - void *user_data); - void (*close)(struct weston_desktop_surface *surface, void *user_data); - - bool (*get_activated)(struct weston_desktop_surface *surface, - void *user_data); - bool (*get_fullscreen)(struct weston_desktop_surface *surface, - void *user_data); - bool (*get_maximized)(struct weston_desktop_surface *surface, - void *user_data); - bool (*get_resizing)(struct weston_desktop_surface *surface, - void *user_data); - struct weston_size - (*get_max_size)(struct weston_desktop_surface *surface, - void *user_data); - struct weston_size - (*get_min_size)(struct weston_desktop_surface *surface, - void *user_data); - - void (*destroy)(struct weston_desktop_surface *surface, - void *user_data); -}; - -struct weston_desktop_client * -weston_desktop_client_create(struct weston_desktop *desktop, - struct wl_client *client, - wl_dispatcher_func_t dispatcher, - const struct wl_interface *interface, - const void *implementation, uint32_t version, - uint32_t id); - -void -weston_desktop_client_add_destroy_listener(struct weston_desktop_client *client, - struct wl_listener *listener); -struct weston_desktop * -weston_desktop_client_get_desktop(struct weston_desktop_client *client); -struct wl_resource * -weston_desktop_client_get_resource(struct weston_desktop_client *client); -struct wl_list * -weston_desktop_client_get_surface_list(struct weston_desktop_client *client); - -void -weston_desktop_client_pong(struct weston_desktop_client *client, - uint32_t serial); - -struct weston_desktop_surface * -weston_desktop_surface_create(struct weston_desktop *desktop, - struct weston_desktop_client *client, - struct weston_surface *surface, - const struct weston_desktop_surface_implementation *implementation, - void *implementation_data); -void -weston_desktop_surface_destroy(struct weston_desktop_surface *surface); -void -weston_desktop_surface_resource_destroy(struct wl_resource *resource); -struct wl_resource * -weston_desktop_surface_add_resource(struct weston_desktop_surface *surface, - const struct wl_interface *interface, - const void *implementation, uint32_t id, - wl_resource_destroy_func_t destroy); -struct weston_desktop_surface * -weston_desktop_surface_from_grab_link(struct wl_list *grab_link); - -struct wl_list * -weston_desktop_surface_get_client_link(struct weston_desktop_surface *surface); -struct weston_desktop_surface * -weston_desktop_surface_from_client_link(struct wl_list *link); -bool -weston_desktop_surface_has_implementation(struct weston_desktop_surface *surface, - const struct weston_desktop_surface_implementation *implementation); -const struct weston_desktop_surface_implementation * -weston_desktop_surface_get_implementation(struct weston_desktop_surface *surface); -void * -weston_desktop_surface_get_implementation_data(struct weston_desktop_surface *surface); -struct weston_desktop_surface * -weston_desktop_surface_get_parent(struct weston_desktop_surface *surface); -bool -weston_desktop_surface_get_grab(struct weston_desktop_surface *surface); - -void -weston_desktop_surface_set_title(struct weston_desktop_surface *surface, - const char *title); -void -weston_desktop_surface_set_app_id(struct weston_desktop_surface *surface, - const char *app_id); -void -weston_desktop_surface_set_pid(struct weston_desktop_surface *surface, - pid_t pid); -void -weston_desktop_surface_set_geometry(struct weston_desktop_surface *surface, - struct weston_geometry geometry); -void -weston_desktop_surface_set_relative_to(struct weston_desktop_surface *surface, - struct weston_desktop_surface *parent, - int32_t x, int32_t y, bool use_geometry); -void -weston_desktop_surface_unset_relative_to(struct weston_desktop_surface *surface); -void -weston_desktop_surface_popup_grab(struct weston_desktop_surface *popup, - struct weston_desktop_seat *seat, - uint32_t serial); -void -weston_desktop_surface_popup_ungrab(struct weston_desktop_surface *popup, - struct weston_desktop_seat *seat); -void -weston_desktop_surface_popup_dismiss(struct weston_desktop_surface *surface); - -struct weston_desktop_surface * -weston_desktop_seat_popup_grab_get_topmost_surface(struct weston_desktop_seat *seat); -bool -weston_desktop_seat_popup_grab_start(struct weston_desktop_seat *seat, - struct wl_client *client, uint32_t serial); -void -weston_desktop_seat_popup_grab_add_surface(struct weston_desktop_seat *seat, - struct wl_list *link); -void -weston_desktop_seat_popup_grab_remove_surface(struct weston_desktop_seat *seat, - struct wl_list *link); - -void -weston_desktop_destroy_request(struct wl_client *client, - struct wl_resource *resource); -struct wl_global * -weston_desktop_xdg_wm_base_create(struct weston_desktop *desktop, - struct wl_display *display); -struct wl_global * -weston_desktop_xdg_shell_v6_create(struct weston_desktop *desktop, - struct wl_display *display); -struct wl_global * -weston_desktop_wl_shell_create(struct weston_desktop *desktop, - struct wl_display *display); - -void -weston_desktop_xwayland_init(struct weston_desktop *desktop); - -#endif /* WESTON_DESKTOP_INTERNAL_H */ diff --git a/libweston-desktop/libweston-desktop.c b/libweston-desktop/libweston-desktop.c deleted file mode 100755 index 0744ec6..0000000 --- a/libweston-desktop/libweston-desktop.c +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright © 2016 Quentin "Sardem FF7" Glidic - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include - -#include -#include - -#include -#include -#include "shared/helpers.h" - -#include -#include "internal.h" - - -struct weston_desktop { - struct weston_compositor *compositor; - struct weston_desktop_api api; - void *user_data; - struct wl_global *xdg_wm_base; /* Stable protocol xdg_shell replaces xdg_shell_unstable_v6 */ - struct wl_global *xdg_shell_v6; /* Unstable xdg_shell_unstable_v6 protocol. */ - struct wl_global *wl_shell; -}; - -void -weston_desktop_destroy_request(struct wl_client *client, - struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -WL_EXPORT struct weston_desktop * -weston_desktop_create(struct weston_compositor *compositor, - const struct weston_desktop_api *api, void *user_data) -{ - struct weston_desktop *desktop; - struct wl_display *display = compositor->wl_display; - - assert(api->surface_added); - assert(api->surface_removed); - - desktop = zalloc(sizeof(struct weston_desktop)); - desktop->compositor = compositor; - desktop->user_data = user_data; - - desktop->api.struct_size = - MIN(sizeof(struct weston_desktop_api), api->struct_size); - memcpy(&desktop->api, api, desktop->api.struct_size); - - desktop->xdg_wm_base = - weston_desktop_xdg_wm_base_create(desktop, display); - if (desktop->xdg_wm_base == NULL) { - weston_desktop_destroy(desktop); - return NULL; - } - - desktop->xdg_shell_v6 = - weston_desktop_xdg_shell_v6_create(desktop, display); - if (desktop->xdg_shell_v6 == NULL) { - weston_desktop_destroy(desktop); - return NULL; - } - - desktop->wl_shell = - weston_desktop_wl_shell_create(desktop, display); - if (desktop->wl_shell == NULL) { - weston_desktop_destroy(desktop); - return NULL; - } - -// OHOS -// weston_desktop_xwayland_init(desktop); - - return desktop; -} - -WL_EXPORT void -weston_desktop_destroy(struct weston_desktop *desktop) -{ - if (desktop == NULL) - return; - - if (desktop->wl_shell != NULL) - wl_global_destroy(desktop->wl_shell); - if (desktop->xdg_shell_v6 != NULL) - wl_global_destroy(desktop->xdg_shell_v6); - if (desktop->xdg_wm_base != NULL) - wl_global_destroy(desktop->xdg_wm_base); - - free(desktop); -} - - -struct weston_compositor * -weston_desktop_get_compositor(struct weston_desktop *desktop) -{ - return desktop->compositor; -} - -struct wl_display * -weston_desktop_get_display(struct weston_desktop *desktop) -{ - return desktop->compositor->wl_display; -} - -void -weston_desktop_api_ping_timeout(struct weston_desktop *desktop, - struct weston_desktop_client *client) -{ - if (desktop->api.ping_timeout != NULL) - desktop->api.ping_timeout(client, desktop->user_data); -} - -void -weston_desktop_api_pong(struct weston_desktop *desktop, - struct weston_desktop_client *client) -{ - if (desktop->api.pong != NULL) - desktop->api.pong(client, desktop->user_data); -} - -void -weston_desktop_api_surface_added(struct weston_desktop *desktop, - struct weston_desktop_surface *surface) -{ - struct weston_desktop_client *client = - weston_desktop_surface_get_client(surface); - struct wl_list *list = weston_desktop_client_get_surface_list(client); - struct wl_list *link = weston_desktop_surface_get_client_link(surface); - - desktop->api.surface_added(surface, desktop->user_data); - wl_list_insert(list, link); -} - -void -weston_desktop_api_surface_removed(struct weston_desktop *desktop, - struct weston_desktop_surface *surface) -{ - struct wl_list *link = weston_desktop_surface_get_client_link(surface); - - wl_list_remove(link); - wl_list_init(link); - desktop->api.surface_removed(surface, desktop->user_data); -} - -void -weston_desktop_api_committed(struct weston_desktop *desktop, - struct weston_desktop_surface *surface, - int32_t sx, int32_t sy) -{ - if (desktop->api.committed != NULL) - desktop->api.committed(surface, sx, sy, desktop->user_data); -} - -void -weston_desktop_api_show_window_menu(struct weston_desktop *desktop, - struct weston_desktop_surface *surface, - struct weston_seat *seat, - int32_t x, int32_t y) -{ - if (desktop->api.show_window_menu != NULL) - desktop->api.show_window_menu(surface, seat, x, y, - desktop->user_data); -} - -void -weston_desktop_api_set_parent(struct weston_desktop *desktop, - struct weston_desktop_surface *surface, - struct weston_desktop_surface *parent) -{ - if (desktop->api.set_parent != NULL) - desktop->api.set_parent(surface, parent, desktop->user_data); -} - -void -weston_desktop_api_move(struct weston_desktop *desktop, - struct weston_desktop_surface *surface, - struct weston_seat *seat, uint32_t serial) -{ - if (desktop->api.move != NULL) - desktop->api.move(surface, seat, serial, desktop->user_data); -} - -void -weston_desktop_api_resize(struct weston_desktop *desktop, - struct weston_desktop_surface *surface, - struct weston_seat *seat, uint32_t serial, - enum weston_desktop_surface_edge edges) -{ - if (desktop->api.resize != NULL) - desktop->api.resize(surface, seat, serial, edges, - desktop->user_data); -} - -void -weston_desktop_api_fullscreen_requested(struct weston_desktop *desktop, - struct weston_desktop_surface *surface, - bool fullscreen, - struct weston_output *output) -{ - if (desktop->api.fullscreen_requested != NULL) - desktop->api.fullscreen_requested(surface, fullscreen, output, - desktop->user_data); -} - -void -weston_desktop_api_maximized_requested(struct weston_desktop *desktop, - struct weston_desktop_surface *surface, - bool maximized) -{ - if (desktop->api.maximized_requested != NULL) - desktop->api.maximized_requested(surface, maximized, - desktop->user_data); -} - -void -weston_desktop_api_minimized_requested(struct weston_desktop *desktop, - struct weston_desktop_surface *surface) -{ - if (desktop->api.minimized_requested != NULL) - desktop->api.minimized_requested(surface, desktop->user_data); -} - -void -weston_desktop_api_set_xwayland_position(struct weston_desktop *desktop, - struct weston_desktop_surface *surface, - int32_t x, int32_t y) -{ - if (desktop->api.set_xwayland_position != NULL) - desktop->api.set_xwayland_position(surface, x, y, - desktop->user_data); -} diff --git a/libweston-desktop/meson.build b/libweston-desktop/meson.build deleted file mode 100644 index 0a45d94..0000000 --- a/libweston-desktop/meson.build +++ /dev/null @@ -1,36 +0,0 @@ -srcs_libdesktop = [ - 'libweston-desktop.c', - 'client.c', - 'seat.c', - 'surface.c', - 'xwayland.c', - 'wl-shell.c', - 'xdg-shell.c', - 'xdg-shell-v6.c', - xdg_shell_unstable_v6_server_protocol_h, - xdg_shell_unstable_v6_protocol_c, - xdg_shell_server_protocol_h, - xdg_shell_protocol_c, -] -lib_desktop = shared_library( - 'weston-desktop-@0@'.format(libweston_major), - srcs_libdesktop, - include_directories: common_inc, - install: true, - version: '0.0.@0@'.format(libweston_revision), - dependencies: dep_libweston_public -) -dep_lib_desktop = declare_dependency( - link_with: lib_desktop, - dependencies: dep_libweston_public -) - -pkgconfig.generate( - lib_desktop, - filebase: 'libweston-desktop-@0@'.format(libweston_major), - name: 'libweston-desktop', - version: version_weston, - description: 'Desktop shells abstraction library for libweston compositors', - requires_private: [ lib_weston, dep_wayland_server ], - subdirs: dir_include_libweston -) diff --git a/libweston-desktop/seat.c b/libweston-desktop/seat.c deleted file mode 100644 index b92546d..0000000 --- a/libweston-desktop/seat.c +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright © 2010-2012 Intel Corporation - * Copyright © 2011-2012 Collabora, Ltd. - * Copyright © 2013 Raspberry Pi Foundation - * Copyright © 2016 Quentin "Sardem FF7" Glidic - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include - -#include - -#include -#include - -#include -#include "internal.h" -#include "shared/timespec-util.h" - -struct weston_desktop_seat { - struct wl_listener seat_destroy_listener; - struct weston_seat *seat; - struct { - struct weston_keyboard_grab keyboard; - struct weston_pointer_grab pointer; - struct weston_touch_grab touch; - bool initial_up; - struct wl_client *client; - struct wl_list surfaces; - } popup_grab; -}; - -static void weston_desktop_seat_popup_grab_end(struct weston_desktop_seat *seat); - -static void -weston_desktop_seat_popup_grab_keyboard_key(struct weston_keyboard_grab *grab, - const struct timespec *time, - uint32_t key, - enum wl_keyboard_key_state state) -{ - weston_keyboard_send_key(grab->keyboard, time, key, state); -} - -static void -weston_desktop_seat_popup_grab_keyboard_modifiers(struct weston_keyboard_grab *grab, - uint32_t serial, - uint32_t mods_depressed, - uint32_t mods_latched, - uint32_t mods_locked, - uint32_t group) -{ - weston_keyboard_send_modifiers(grab->keyboard, serial, mods_depressed, - mods_latched, mods_locked, group); -} - -static void -weston_desktop_seat_popup_grab_keyboard_cancel(struct weston_keyboard_grab *grab) -{ - struct weston_desktop_seat *seat = - wl_container_of(grab, seat, popup_grab.keyboard); - - weston_desktop_seat_popup_grab_end(seat); -} - -static const struct weston_keyboard_grab_interface weston_desktop_seat_keyboard_popup_grab_interface = { - .key = weston_desktop_seat_popup_grab_keyboard_key, - .modifiers = weston_desktop_seat_popup_grab_keyboard_modifiers, - .cancel = weston_desktop_seat_popup_grab_keyboard_cancel, -}; - -static void -weston_desktop_seat_popup_grab_pointer_focus(struct weston_pointer_grab *grab) -{ - struct weston_desktop_seat *seat = - wl_container_of(grab, seat, popup_grab.pointer); - struct weston_pointer *pointer = grab->pointer; - struct weston_view *view; - wl_fixed_t sx, sy; - - view = weston_compositor_pick_view(pointer->seat->compositor, - pointer->x, pointer->y, &sx, &sy); - - if (view != NULL && - view->surface->resource != NULL && - wl_resource_get_client(view->surface->resource) == seat->popup_grab.client) - weston_pointer_set_focus(pointer, view, sx, sy); - else - weston_pointer_clear_focus(pointer); -} - -static void -weston_desktop_seat_popup_grab_pointer_motion(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_motion_event *event) -{ - weston_pointer_send_motion(grab->pointer, time, event); -} - -static void -weston_desktop_seat_popup_grab_pointer_button(struct weston_pointer_grab *grab, - const struct timespec *time, - uint32_t button, - enum wl_pointer_button_state state) -{ - struct weston_desktop_seat *seat = - wl_container_of(grab, seat, popup_grab.pointer); - struct weston_pointer *pointer = grab->pointer; - bool initial_up = seat->popup_grab.initial_up; - - if (state == WL_POINTER_BUTTON_STATE_RELEASED) - seat->popup_grab.initial_up = true; - - if (weston_pointer_has_focus_resource(pointer)) - weston_pointer_send_button(pointer, time, button, state); - else if (state == WL_POINTER_BUTTON_STATE_RELEASED && - (initial_up || - (timespec_sub_to_msec(time, &grab->pointer->grab_time) > 500))) - weston_desktop_seat_popup_grab_end(seat); -} - -static void -weston_desktop_seat_popup_grab_pointer_axis(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_axis_event *event) -{ - weston_pointer_send_axis(grab->pointer, time, event); -} - -static void -weston_desktop_seat_popup_grab_pointer_axis_source(struct weston_pointer_grab *grab, - uint32_t source) -{ - weston_pointer_send_axis_source(grab->pointer, source); -} - -static void -weston_desktop_seat_popup_grab_pointer_frame(struct weston_pointer_grab *grab) -{ - weston_pointer_send_frame(grab->pointer); -} - -static void -weston_desktop_seat_popup_grab_pointer_cancel(struct weston_pointer_grab *grab) -{ - struct weston_desktop_seat *seat = - wl_container_of(grab, seat, popup_grab.pointer); - - weston_desktop_seat_popup_grab_end(seat); -} - -static const struct weston_pointer_grab_interface weston_desktop_seat_pointer_popup_grab_interface = { - .focus = weston_desktop_seat_popup_grab_pointer_focus, - .motion = weston_desktop_seat_popup_grab_pointer_motion, - .button = weston_desktop_seat_popup_grab_pointer_button, - .axis = weston_desktop_seat_popup_grab_pointer_axis, - .axis_source = weston_desktop_seat_popup_grab_pointer_axis_source, - .frame = weston_desktop_seat_popup_grab_pointer_frame, - .cancel = weston_desktop_seat_popup_grab_pointer_cancel, -}; - -static void -weston_desktop_seat_popup_grab_touch_down(struct weston_touch_grab *grab, - const struct timespec *time, - int touch_id, - wl_fixed_t sx, wl_fixed_t sy) -{ - weston_touch_send_down(grab->touch, time, touch_id, sx, sy); -} - -static void -weston_desktop_seat_popup_grab_touch_up(struct weston_touch_grab *grab, - const struct timespec *time, - int touch_id) -{ - weston_touch_send_up(grab->touch, time, touch_id); -} - -static void -weston_desktop_seat_popup_grab_touch_motion(struct weston_touch_grab *grab, - const struct timespec *time, - int touch_id, - wl_fixed_t sx, wl_fixed_t sy) -{ - weston_touch_send_motion(grab->touch, time, touch_id, sx, sy); -} - -static void -weston_desktop_seat_popup_grab_touch_frame(struct weston_touch_grab *grab) -{ - weston_touch_send_frame(grab->touch); -} - -static void -weston_desktop_seat_popup_grab_touch_cancel(struct weston_touch_grab *grab) -{ - struct weston_desktop_seat *seat = - wl_container_of(grab, seat, popup_grab.touch); - - weston_desktop_seat_popup_grab_end(seat); -} - -static const struct weston_touch_grab_interface weston_desktop_seat_touch_popup_grab_interface = { - .down = weston_desktop_seat_popup_grab_touch_down, - .up = weston_desktop_seat_popup_grab_touch_up, - .motion = weston_desktop_seat_popup_grab_touch_motion, - .frame = weston_desktop_seat_popup_grab_touch_frame, - .cancel = weston_desktop_seat_popup_grab_touch_cancel, -}; - -static void -weston_desktop_seat_destroy(struct wl_listener *listener, void *data) -{ - struct weston_desktop_seat *seat = - wl_container_of(listener, seat, seat_destroy_listener); - - free(seat); -} - -struct weston_desktop_seat * -weston_desktop_seat_from_seat(struct weston_seat *wseat) -{ - struct wl_listener *listener; - struct weston_desktop_seat *seat; - - if (wseat == NULL) - return NULL; - - listener = wl_signal_get(&wseat->destroy_signal, - weston_desktop_seat_destroy); - if (listener != NULL) - return wl_container_of(listener, seat, seat_destroy_listener); - - seat = zalloc(sizeof(struct weston_desktop_seat)); - if (seat == NULL) - return NULL; - - seat->seat = wseat; - - seat->seat_destroy_listener.notify = weston_desktop_seat_destroy; - wl_signal_add(&wseat->destroy_signal, &seat->seat_destroy_listener); - - seat->popup_grab.keyboard.interface = - &weston_desktop_seat_keyboard_popup_grab_interface; - seat->popup_grab.pointer.interface = - &weston_desktop_seat_pointer_popup_grab_interface; - seat->popup_grab.touch.interface = - &weston_desktop_seat_touch_popup_grab_interface; - wl_list_init(&seat->popup_grab.surfaces); - - return seat; -} - -struct weston_desktop_surface * -weston_desktop_seat_popup_grab_get_topmost_surface(struct weston_desktop_seat *seat) -{ - if (seat == NULL || wl_list_empty(&seat->popup_grab.surfaces)) - return NULL; - - struct wl_list *grab_link = seat->popup_grab.surfaces.next; - - return weston_desktop_surface_from_grab_link(grab_link); -} - -bool -weston_desktop_seat_popup_grab_start(struct weston_desktop_seat *seat, - struct wl_client *client, uint32_t serial) -{ - assert(seat == NULL || seat->popup_grab.client == NULL || - seat->popup_grab.client == client); - - struct weston_seat *wseat = seat != NULL ? seat->seat : NULL; - /* weston_seat_get_* functions can properly handle a NULL wseat */ - struct weston_keyboard *keyboard = weston_seat_get_keyboard(wseat); - struct weston_pointer *pointer = weston_seat_get_pointer(wseat); - struct weston_touch *touch = weston_seat_get_touch(wseat); - - if ((keyboard == NULL || keyboard->grab_serial != serial) && - (pointer == NULL || pointer->grab_serial != serial) && - (touch == NULL || touch->grab_serial != serial)) { - return false; - } - - if (keyboard != NULL && - keyboard->grab->interface != &weston_desktop_seat_keyboard_popup_grab_interface) - weston_keyboard_start_grab(keyboard, &seat->popup_grab.keyboard); - - if (pointer != NULL && - pointer->grab->interface != &weston_desktop_seat_pointer_popup_grab_interface) - weston_pointer_start_grab(pointer, &seat->popup_grab.pointer); - - if (touch != NULL && - touch->grab->interface != &weston_desktop_seat_touch_popup_grab_interface) - weston_touch_start_grab(touch, &seat->popup_grab.touch); - - seat->popup_grab.initial_up = - (pointer == NULL || pointer->button_count == 0); - seat->popup_grab.client = client; - - return true; -} - -static void -weston_desktop_seat_popup_grab_end(struct weston_desktop_seat *seat) -{ - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat->seat); - struct weston_pointer *pointer = weston_seat_get_pointer(seat->seat); - struct weston_touch *touch = weston_seat_get_touch(seat->seat); - - while (!wl_list_empty(&seat->popup_grab.surfaces)) { - struct wl_list *link = seat->popup_grab.surfaces.prev; - struct weston_desktop_surface *surface = - weston_desktop_surface_from_grab_link(link); - - wl_list_remove(link); - wl_list_init(link); - weston_desktop_surface_popup_dismiss(surface); - } - - if (keyboard != NULL && - keyboard->grab->interface == &weston_desktop_seat_keyboard_popup_grab_interface) - weston_keyboard_end_grab(keyboard); - - if (pointer != NULL && - pointer->grab->interface == &weston_desktop_seat_pointer_popup_grab_interface) - weston_pointer_end_grab(pointer); - - if (touch != NULL && - touch->grab->interface == &weston_desktop_seat_touch_popup_grab_interface) - weston_touch_end_grab(touch); - - seat->popup_grab.client = NULL; -} - -void -weston_desktop_seat_popup_grab_add_surface(struct weston_desktop_seat *seat, - struct wl_list *link) -{ - assert(seat->popup_grab.client != NULL); - - wl_list_insert(&seat->popup_grab.surfaces, link); -} - -void -weston_desktop_seat_popup_grab_remove_surface(struct weston_desktop_seat *seat, - struct wl_list *link) -{ - assert(seat->popup_grab.client != NULL); - - wl_list_remove(link); - wl_list_init(link); - if (wl_list_empty(&seat->popup_grab.surfaces)) - weston_desktop_seat_popup_grab_end(seat); -} - -WL_EXPORT void -weston_seat_break_desktop_grabs(struct weston_seat *wseat) -{ - struct weston_desktop_seat *seat = weston_desktop_seat_from_seat(wseat); - - weston_desktop_seat_popup_grab_end(seat); -} diff --git a/libweston-desktop/surface.c b/libweston-desktop/surface.c deleted file mode 100644 index 433f08a..0000000 --- a/libweston-desktop/surface.c +++ /dev/null @@ -1,830 +0,0 @@ -/* - * Copyright © 2016 Quentin "Sardem FF7" Glidic - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include - -#include -#include - -#include -#include "internal.h" - -struct weston_desktop_view { - struct wl_list link; - struct weston_view *view; - struct weston_desktop_view *parent; - struct wl_list children_list; - struct wl_list children_link; -}; - -struct weston_desktop_surface { - struct weston_desktop *desktop; - struct weston_desktop_client *client; - struct wl_list client_link; - const struct weston_desktop_surface_implementation *implementation; - void *implementation_data; - void *user_data; - struct weston_surface *surface; - struct wl_list view_list; - struct weston_position buffer_move; - struct wl_listener surface_commit_listener; - struct wl_listener surface_destroy_listener; - struct wl_listener client_destroy_listener; - struct wl_list children_list; - - struct wl_list resource_list; - bool has_geometry; - struct weston_geometry geometry; - struct { - char *title; - char *app_id; - pid_t pid; - struct wl_signal metadata_signal; - }; - struct { - struct weston_desktop_surface *parent; - struct wl_list children_link; - struct weston_position position; - bool use_geometry; - }; - struct { - struct wl_list grab_link; - }; -}; - -static void -weston_desktop_surface_update_view_position(struct weston_desktop_surface *surface) -{ - struct weston_desktop_view *view; - int32_t x, y; - - x = surface->position.x; - y = surface->position.y; - - if (surface->use_geometry) { - struct weston_desktop_surface *parent = - weston_desktop_surface_get_parent(surface); - struct weston_geometry geometry, parent_geometry; - - geometry = weston_desktop_surface_get_geometry(surface); - parent_geometry = weston_desktop_surface_get_geometry(parent); - - x += parent_geometry.x - geometry.x; - y += parent_geometry.y - geometry.y; - } - wl_list_for_each(view, &surface->view_list, link) - weston_view_set_position(view->view, x, y); -} - - -static void -weston_desktop_view_propagate_layer(struct weston_desktop_view *view); - -static void -weston_desktop_view_destroy(struct weston_desktop_view *view) -{ - struct weston_desktop_view *child_view, *tmp; - - wl_list_for_each_safe(child_view, tmp, &view->children_list, children_link) - weston_desktop_view_destroy(child_view); - - wl_list_remove(&view->children_link); - wl_list_remove(&view->link); - - weston_view_damage_below(view->view); - if (view->parent != NULL) - weston_view_destroy(view->view); - - free(view); -} - -void -weston_desktop_surface_destroy(struct weston_desktop_surface *surface) -{ - struct weston_desktop_view *view, *next_view; - struct weston_desktop_surface *child, *next_child; - - wl_list_remove(&surface->surface_commit_listener.link); - wl_list_remove(&surface->surface_destroy_listener.link); - wl_list_remove(&surface->client_destroy_listener.link); - - if (!wl_list_empty(&surface->resource_list)) { - struct wl_resource *resource, *tmp; - wl_resource_for_each_safe(resource, tmp, &surface->resource_list) { - wl_resource_set_user_data(resource, NULL); - wl_list_remove(wl_resource_get_link(resource)); - } - } - - surface->implementation->destroy(surface, surface->implementation_data); - - surface->surface->committed = NULL; - surface->surface->committed_private = NULL; - - weston_desktop_surface_unset_relative_to(surface); - wl_list_remove(&surface->client_link); - - wl_list_for_each_safe(child, next_child, - &surface->children_list, - children_link) - weston_desktop_surface_unset_relative_to(child); - - wl_list_for_each_safe(view, next_view, &surface->view_list, link) - weston_desktop_view_destroy(view); - - free(surface->title); - free(surface->app_id); - - free(surface); -} - -static void -weston_desktop_surface_surface_committed(struct wl_listener *listener, - void *data) -{ - struct weston_desktop_surface *surface = - wl_container_of(listener, surface, surface_commit_listener); - - if (surface->implementation->committed != NULL) - surface->implementation->committed(surface, - surface->implementation_data, - surface->buffer_move.x, - surface->buffer_move.y); - - if (surface->parent != NULL) { - struct weston_desktop_view *view; - - wl_list_for_each(view, &surface->view_list, link) { - weston_view_set_transform_parent(view->view, - view->parent->view); - weston_desktop_view_propagate_layer(view->parent); - } - weston_desktop_surface_update_view_position(surface); - } - - if (!wl_list_empty(&surface->children_list)) { - struct weston_desktop_surface *child; - - wl_list_for_each(child, &surface->children_list, children_link) - weston_desktop_surface_update_view_position(child); - } - - surface->buffer_move.x = 0; - surface->buffer_move.y = 0; -} - -static void -weston_desktop_surface_surface_destroyed(struct wl_listener *listener, - void *data) -{ - struct weston_desktop_surface *surface = - wl_container_of(listener, surface, surface_destroy_listener); - - weston_desktop_surface_destroy(surface); -} - -void -weston_desktop_surface_resource_destroy(struct wl_resource *resource) -{ - struct weston_desktop_surface *surface = - wl_resource_get_user_data(resource); - - if (surface != NULL) - weston_desktop_surface_destroy(surface); -} - -static void -weston_desktop_surface_committed(struct weston_surface *wsurface, - int32_t sx, int32_t sy) -{ - struct weston_desktop_surface *surface = wsurface->committed_private; - - surface->buffer_move.x = sx; - surface->buffer_move.y = sy; -} - -static void -weston_desktop_surface_client_destroyed(struct wl_listener *listener, - void *data) -{ - struct weston_desktop_surface *surface = - wl_container_of(listener, surface, client_destroy_listener); - - weston_desktop_surface_destroy(surface); -} - -struct weston_desktop_surface * -weston_desktop_surface_create(struct weston_desktop *desktop, - struct weston_desktop_client *client, - struct weston_surface *wsurface, - const struct weston_desktop_surface_implementation *implementation, - void *implementation_data) -{ - assert(implementation->destroy != NULL); - - struct weston_desktop_surface *surface; - - surface = zalloc(sizeof(struct weston_desktop_surface)); - if (surface == NULL) { - if (client != NULL) - wl_client_post_no_memory(weston_desktop_client_get_client(client)); - return NULL; - } - - surface->desktop = desktop; - surface->implementation = implementation; - surface->implementation_data = implementation_data; - surface->surface = wsurface; - - surface->client = client; - surface->client_destroy_listener.notify = - weston_desktop_surface_client_destroyed; - weston_desktop_client_add_destroy_listener( - client, &surface->client_destroy_listener); - - wsurface->committed = weston_desktop_surface_committed; - wsurface->committed_private = surface; - - surface->pid = -1; - - surface->surface_commit_listener.notify = - weston_desktop_surface_surface_committed; - wl_signal_add(&surface->surface->commit_signal, - &surface->surface_commit_listener); - surface->surface_destroy_listener.notify = - weston_desktop_surface_surface_destroyed; - wl_signal_add(&surface->surface->destroy_signal, - &surface->surface_destroy_listener); - - wl_list_init(&surface->client_link); - wl_list_init(&surface->resource_list); - wl_list_init(&surface->children_list); - wl_list_init(&surface->children_link); - wl_list_init(&surface->view_list); - wl_list_init(&surface->grab_link); - - wl_signal_init(&surface->metadata_signal); - - return surface; -} - -struct wl_resource * -weston_desktop_surface_add_resource(struct weston_desktop_surface *surface, - const struct wl_interface *interface, - const void *implementation, uint32_t id, - wl_resource_destroy_func_t destroy) -{ - struct wl_resource *client_resource = - weston_desktop_client_get_resource(surface->client); - struct wl_client *wl_client = - weston_desktop_client_get_client(surface->client); - struct wl_resource *resource; - - resource = wl_resource_create(wl_client, - interface, - wl_resource_get_version(client_resource), - id); - if (resource == NULL) { - wl_client_post_no_memory(wl_client); - weston_desktop_surface_destroy(surface); - return NULL; - } - if (destroy == NULL) - destroy = weston_desktop_surface_resource_destroy; - wl_resource_set_implementation(resource, implementation, surface, destroy); - wl_list_insert(&surface->resource_list, wl_resource_get_link(resource)); - - return resource; -} - -struct weston_desktop_surface * -weston_desktop_surface_from_grab_link(struct wl_list *grab_link) -{ - struct weston_desktop_surface *surface = - wl_container_of(grab_link, surface, grab_link); - - return surface; -} - -WL_EXPORT bool -weston_surface_is_desktop_surface(struct weston_surface *wsurface) -{ - return wsurface->committed == weston_desktop_surface_committed; -} - -WL_EXPORT struct weston_desktop_surface * -weston_surface_get_desktop_surface(struct weston_surface *wsurface) -{ - if (!weston_surface_is_desktop_surface(wsurface)) - return NULL; - return wsurface->committed_private; -} - -WL_EXPORT void -weston_desktop_surface_set_user_data(struct weston_desktop_surface *surface, - void *user_data) -{ - surface->user_data = user_data; -} - -static struct weston_desktop_view * -weston_desktop_surface_create_desktop_view(struct weston_desktop_surface *surface) -{ - struct wl_client *wl_client= - weston_desktop_client_get_client(surface->client); - struct weston_desktop_view *view, *child_view; - struct weston_view *wview; - struct weston_desktop_surface *child; - - wview = weston_view_create(surface->surface); - if (wview == NULL) { - if (wl_client != NULL) - wl_client_post_no_memory(wl_client); - return NULL; - } - - view = zalloc(sizeof(struct weston_desktop_view)); - if (view == NULL) { - if (wl_client != NULL) - wl_client_post_no_memory(wl_client); - return NULL; - } - - view->view = wview; - wl_list_init(&view->children_list); - wl_list_init(&view->children_link); - wl_list_insert(surface->view_list.prev, &view->link); - - wl_list_for_each(child, &surface->children_list, children_link) { - child_view = - weston_desktop_surface_create_desktop_view(child); - if (child_view == NULL) { - weston_desktop_view_destroy(view); - return NULL; - } - - child_view->parent = view; - wl_list_insert(view->children_list.prev, - &child_view->children_link); - } - - return view; -} - -WL_EXPORT struct weston_view * -weston_desktop_surface_create_view(struct weston_desktop_surface *surface) -{ - struct weston_desktop_view *view; - - view = weston_desktop_surface_create_desktop_view(surface); - if (view == NULL) - return NULL; - - return view->view; -} - -WL_EXPORT void -weston_desktop_surface_unlink_view(struct weston_view *wview) -{ - struct weston_desktop_surface *surface; - struct weston_desktop_view *view; - - if (!weston_surface_is_desktop_surface(wview->surface)) - return; - - surface = weston_surface_get_desktop_surface(wview->surface); - wl_list_for_each(view, &surface->view_list, link) { - if (view->view == wview) { - weston_desktop_view_destroy(view); - return; - } - } -} - -static void -weston_desktop_view_propagate_layer(struct weston_desktop_view *view) -{ - struct weston_desktop_view *child; - struct wl_list *link = &view->view->layer_link.link; - - wl_list_for_each_reverse(child, &view->children_list, children_link) { - struct weston_layer_entry *prev = - wl_container_of(link->prev, prev, link); - - if (prev == &child->view->layer_link) - continue; - - child->view->is_mapped = true; - weston_view_damage_below(child->view); - weston_view_geometry_dirty(child->view); - weston_layer_entry_remove(&child->view->layer_link); - weston_layer_entry_insert(prev, &child->view->layer_link); - weston_view_geometry_dirty(child->view); - weston_surface_damage(child->view->surface); - weston_view_update_transform(child->view); - - weston_desktop_view_propagate_layer(child); - } -} - -WL_EXPORT void -weston_desktop_surface_propagate_layer(struct weston_desktop_surface *surface) -{ - struct weston_desktop_view *view; - - wl_list_for_each(view, &surface->view_list, link) - weston_desktop_view_propagate_layer(view); -} - -WL_EXPORT void -weston_desktop_surface_set_activated(struct weston_desktop_surface *surface, bool activated) -{ - if (surface->implementation->set_activated != NULL) - surface->implementation->set_activated(surface, - surface->implementation_data, - activated); -} - -WL_EXPORT void -weston_desktop_surface_set_fullscreen(struct weston_desktop_surface *surface, bool fullscreen) -{ - if (surface->implementation->set_fullscreen != NULL) - surface->implementation->set_fullscreen(surface, - surface->implementation_data, - fullscreen); -} - -WL_EXPORT void -weston_desktop_surface_set_maximized(struct weston_desktop_surface *surface, bool maximized) -{ - if (surface->implementation->set_maximized != NULL) - surface->implementation->set_maximized(surface, - surface->implementation_data, - maximized); -} - -WL_EXPORT void -weston_desktop_surface_set_resizing(struct weston_desktop_surface *surface, bool resizing) -{ - if (surface->implementation->set_resizing != NULL) - surface->implementation->set_resizing(surface, - surface->implementation_data, - resizing); -} - -WL_EXPORT void -weston_desktop_surface_set_size(struct weston_desktop_surface *surface, int32_t width, int32_t height) -{ - if (surface->implementation->set_size != NULL) - surface->implementation->set_size(surface, - surface->implementation_data, - width, height); -} - -WL_EXPORT void -weston_desktop_surface_close(struct weston_desktop_surface *surface) -{ - if (surface->implementation->close != NULL) - surface->implementation->close(surface, - surface->implementation_data); -} - -WL_EXPORT void -weston_desktop_surface_add_metadata_listener(struct weston_desktop_surface *surface, - struct wl_listener *listener) -{ - wl_signal_add(&surface->metadata_signal, listener); -} - -struct weston_desktop_surface * -weston_desktop_surface_from_client_link(struct wl_list *link) -{ - struct weston_desktop_surface *surface; - - surface = wl_container_of(link, surface, client_link); - return surface; -} - -struct wl_list * -weston_desktop_surface_get_client_link(struct weston_desktop_surface *surface) -{ - return &surface->client_link; -} - -bool -weston_desktop_surface_has_implementation(struct weston_desktop_surface *surface, - const struct weston_desktop_surface_implementation *implementation) -{ - return surface->implementation == implementation; -} - -const struct weston_desktop_surface_implementation * -weston_desktop_surface_get_implementation(struct weston_desktop_surface *surface) -{ - return surface->implementation; -} - -void * -weston_desktop_surface_get_implementation_data(struct weston_desktop_surface *surface) -{ - return surface->implementation_data; -} - -struct weston_desktop_surface * -weston_desktop_surface_get_parent(struct weston_desktop_surface *surface) -{ - return surface->parent; -} - -bool -weston_desktop_surface_get_grab(struct weston_desktop_surface *surface) -{ - return !wl_list_empty(&surface->grab_link); -} - -WL_EXPORT struct weston_desktop_client * -weston_desktop_surface_get_client(struct weston_desktop_surface *surface) -{ - return surface->client; -} - -WL_EXPORT void * -weston_desktop_surface_get_user_data(struct weston_desktop_surface *surface) -{ - return surface->user_data; -} - -WL_EXPORT struct weston_surface * -weston_desktop_surface_get_surface(struct weston_desktop_surface *surface) -{ - return surface->surface; -} - -WL_EXPORT const char * -weston_desktop_surface_get_title(struct weston_desktop_surface *surface) -{ - return surface->title; -} - -WL_EXPORT const char * -weston_desktop_surface_get_app_id(struct weston_desktop_surface *surface) -{ - return surface->app_id; -} - -WL_EXPORT pid_t -weston_desktop_surface_get_pid(struct weston_desktop_surface *surface) -{ - pid_t pid; - - if (surface->pid != -1) { - pid = surface->pid; - } else { - struct weston_desktop_client *client = - weston_desktop_surface_get_client(surface); - struct wl_client *wl_client = - weston_desktop_client_get_client(client); - - /* wl_client should always be valid, because only in the - * xwayland case it wouldn't be, but in that case we won't - * reach here, as the pid is initialized to 0. */ - assert(wl_client); - wl_client_get_credentials(wl_client, &pid, NULL, NULL); - } - return pid; -} - -WL_EXPORT bool -weston_desktop_surface_get_activated(struct weston_desktop_surface *surface) -{ - if (surface->implementation->get_activated == NULL) - return false; - return surface->implementation->get_activated(surface, - surface->implementation_data); -} - -WL_EXPORT bool -weston_desktop_surface_get_resizing(struct weston_desktop_surface *surface) -{ - if (surface->implementation->get_resizing == NULL) - return false; - return surface->implementation->get_resizing(surface, - surface->implementation_data); -} - -WL_EXPORT bool -weston_desktop_surface_get_maximized(struct weston_desktop_surface *surface) -{ - if (surface->implementation->get_maximized == NULL) - return false; - return surface->implementation->get_maximized(surface, - surface->implementation_data); -} - -WL_EXPORT bool -weston_desktop_surface_get_fullscreen(struct weston_desktop_surface *surface) -{ - if (surface->implementation->get_fullscreen == NULL) - return false; - return surface->implementation->get_fullscreen(surface, - surface->implementation_data); -} - -WL_EXPORT struct weston_geometry -weston_desktop_surface_get_geometry(struct weston_desktop_surface *surface) -{ - if (surface->has_geometry) - return surface->geometry; - return weston_surface_get_bounding_box(surface->surface); -} - -WL_EXPORT struct weston_size -weston_desktop_surface_get_max_size(struct weston_desktop_surface *surface) -{ - struct weston_size size = { 0, 0 }; - - if (surface->implementation->get_max_size == NULL) - return size; - return surface->implementation->get_max_size(surface, - surface->implementation_data); -} - -WL_EXPORT struct weston_size -weston_desktop_surface_get_min_size(struct weston_desktop_surface *surface) -{ - struct weston_size size = { 0, 0 }; - - if (surface->implementation->get_min_size == NULL) - return size; - return surface->implementation->get_min_size(surface, - surface->implementation_data); -} - -void -weston_desktop_surface_set_title(struct weston_desktop_surface *surface, - const char *title) -{ - char *tmp, *old; - - tmp = strdup(title); - if (tmp == NULL) - return; - - old = surface->title; - surface->title = tmp; - wl_signal_emit(&surface->metadata_signal, surface); - free(old); -} - -void -weston_desktop_surface_set_app_id(struct weston_desktop_surface *surface, - const char *app_id) -{ - char *tmp, *old; - - tmp = strdup(app_id); - if (tmp == NULL) - return; - - old = surface->app_id; - surface->app_id = tmp; - wl_signal_emit(&surface->metadata_signal, surface); - free(old); -} - -void -weston_desktop_surface_set_pid(struct weston_desktop_surface *surface, - pid_t pid) -{ - surface->pid = pid; -} - -void -weston_desktop_surface_set_geometry(struct weston_desktop_surface *surface, - struct weston_geometry geometry) -{ - surface->has_geometry = true; - surface->geometry = geometry; -} - -void -weston_desktop_surface_set_relative_to(struct weston_desktop_surface *surface, - struct weston_desktop_surface *parent, - int32_t x, int32_t y, bool use_geometry) -{ - struct weston_desktop_view *view, *parent_view; - struct wl_list *link, *tmp; - - assert(parent); - - surface->position.x = x; - surface->position.y = y; - surface->use_geometry = use_geometry; - - if (surface->parent == parent) - return; - - surface->parent = parent; - wl_list_remove(&surface->children_link); - wl_list_insert(surface->parent->children_list.prev, - &surface->children_link); - - link = surface->view_list.next; - tmp = link->next; - wl_list_for_each(parent_view, &parent->view_list, link) { - if (link == &surface->view_list) { - view = weston_desktop_surface_create_desktop_view(surface); - if (view == NULL) - return; - tmp = &surface->view_list; - } else { - view = wl_container_of(link, view, link); - wl_list_remove(&view->children_link); - } - - view->parent = parent_view; - wl_list_insert(parent_view->children_list.prev, - &view->children_link); - weston_desktop_view_propagate_layer(view); - - link = tmp; - tmp = link->next; - } - for (; link != &surface->view_list; link = tmp, tmp = link->next) { - view = wl_container_of(link, view, link); - weston_desktop_view_destroy(view); - } -} - -void -weston_desktop_surface_unset_relative_to(struct weston_desktop_surface *surface) -{ - struct weston_desktop_view *view, *tmp; - - if (surface->parent == NULL) - return; - - surface->parent = NULL; - wl_list_remove(&surface->children_link); - wl_list_init(&surface->children_link); - - wl_list_for_each_safe(view, tmp, &surface->view_list, link) - weston_desktop_view_destroy(view); -} - -void -weston_desktop_surface_popup_grab(struct weston_desktop_surface *surface, - struct weston_desktop_seat *seat, - uint32_t serial) -{ - struct wl_client *wl_client = - weston_desktop_client_get_client(surface->client); - if (weston_desktop_seat_popup_grab_start(seat, wl_client, serial)) - weston_desktop_seat_popup_grab_add_surface(seat, &surface->grab_link); - else - weston_desktop_surface_popup_dismiss(surface); -} - -void -weston_desktop_surface_popup_ungrab(struct weston_desktop_surface *surface, - struct weston_desktop_seat *seat) -{ - weston_desktop_seat_popup_grab_remove_surface(seat, &surface->grab_link); -} - -void -weston_desktop_surface_popup_dismiss(struct weston_desktop_surface *surface) -{ - struct weston_desktop_view *view, *tmp; - - wl_list_for_each_safe(view, tmp, &surface->view_list, link) - weston_desktop_view_destroy(view); - wl_list_remove(&surface->grab_link); - wl_list_init(&surface->grab_link); - weston_desktop_surface_close(surface); -} diff --git a/libweston-desktop/wl-shell.c b/libweston-desktop/wl-shell.c deleted file mode 100644 index 9efec89..0000000 --- a/libweston-desktop/wl-shell.c +++ /dev/null @@ -1,497 +0,0 @@ -/* - * Copyright © 2010-2012 Intel Corporation - * Copyright © 2011-2012 Collabora, Ltd. - * Copyright © 2013 Raspberry Pi Foundation - * Copyright © 2016 Quentin "Sardem FF7" Glidic - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include - -#include - -#include -#include - -#include -#include "internal.h" - -#define WD_WL_SHELL_PROTOCOL_VERSION 1 - -enum weston_desktop_wl_shell_surface_state { - NONE, - TOPLEVEL, - MAXIMIZED, - FULLSCREEN, - TRANSIENT, - POPUP, -}; - -struct weston_desktop_wl_shell_surface { - struct wl_resource *resource; - struct weston_desktop *desktop; - struct wl_display *display; - struct weston_desktop_surface *surface; - struct weston_desktop_surface *parent; - bool added; - struct weston_desktop_seat *popup_seat; - enum weston_desktop_wl_shell_surface_state state; - struct wl_listener wl_surface_resource_destroy_listener; -}; - -static void -weston_desktop_wl_shell_surface_set_size(struct weston_desktop_surface *dsurface, - void *user_data, - int32_t width, int32_t height) -{ - struct weston_desktop_wl_shell_surface *surface = user_data; - struct weston_surface *wsurface = - weston_desktop_surface_get_surface(surface->surface); - - if ((wsurface->width == width && wsurface->height == height) || - (width == 0 && height == 0)) - return; - - wl_shell_surface_send_configure(surface->resource, - WL_SHELL_SURFACE_RESIZE_NONE, - width, height); -} - -static void -weston_desktop_wl_shell_surface_maybe_ungrab(struct weston_desktop_wl_shell_surface *surface) -{ - if (surface->state != POPUP || - !weston_desktop_surface_get_grab(surface->surface)) - return; - - weston_desktop_surface_popup_ungrab(surface->surface, - surface->popup_seat); - surface->popup_seat = NULL; -} - -static void -weston_desktop_wl_shell_surface_committed(struct weston_desktop_surface *dsurface, - void *user_data, - int32_t sx, int32_t sy) -{ - struct weston_desktop_wl_shell_surface *surface = user_data; - struct weston_surface *wsurface = - weston_desktop_surface_get_surface(dsurface); - - if (wsurface->buffer_ref.buffer == NULL) - weston_desktop_wl_shell_surface_maybe_ungrab(surface); - - if (surface->added) - weston_desktop_api_committed(surface->desktop, surface->surface, - sx, sy); -} - -static void -weston_desktop_wl_shell_surface_ping(struct weston_desktop_surface *dsurface, - uint32_t serial, void *user_data) -{ - struct weston_desktop_wl_shell_surface *surface = user_data; - - wl_shell_surface_send_ping(surface->resource, serial); -} - -static void -weston_desktop_wl_shell_surface_close(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_wl_shell_surface *surface = user_data; - - if (surface->state == POPUP) - wl_shell_surface_send_popup_done(surface->resource); -} - -static bool -weston_desktop_wl_shell_surface_get_maximized(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_wl_shell_surface *surface = user_data; - - return surface->state == MAXIMIZED; -} - -static bool -weston_desktop_wl_shell_surface_get_fullscreen(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_wl_shell_surface *surface = user_data; - - return surface->state == FULLSCREEN; -} - -static void -weston_desktop_wl_shell_change_state(struct weston_desktop_wl_shell_surface *surface, - enum weston_desktop_wl_shell_surface_state state, - struct weston_desktop_surface *parent, - int32_t x, int32_t y) -{ - bool to_add = (parent == NULL); - - assert(state != NONE); - - if (to_add && surface->added) { - surface->state = state; - return; - } - - if (surface->state != state) { - if (surface->state == POPUP) - weston_desktop_wl_shell_surface_maybe_ungrab(surface); - - if (to_add) { - weston_desktop_surface_unset_relative_to(surface->surface); - weston_desktop_api_surface_added(surface->desktop, - surface->surface); - } else if (surface->added) { - weston_desktop_api_surface_removed(surface->desktop, - surface->surface); - } - - surface->state = state; - surface->added = to_add; - } - - if (parent != NULL) - weston_desktop_surface_set_relative_to(surface->surface, parent, - x, y, false); -} - -static void -weston_desktop_wl_shell_surface_destroy(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_wl_shell_surface *surface = user_data; - - wl_list_remove(&surface->wl_surface_resource_destroy_listener.link); - - weston_desktop_wl_shell_surface_maybe_ungrab(surface); - weston_desktop_surface_unset_relative_to(surface->surface); - if (surface->added) - weston_desktop_api_surface_removed(surface->desktop, - surface->surface); - - free(surface); -} - -static void -weston_desktop_wl_shell_surface_protocol_pong(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t serial) -{ - struct weston_desktop_surface *surface = wl_resource_get_user_data(resource); - - weston_desktop_client_pong(weston_desktop_surface_get_client(surface), serial); -} - -static void -weston_desktop_wl_shell_surface_protocol_move(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_seat *seat = - wl_resource_get_user_data(seat_resource); - struct weston_desktop_wl_shell_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - if (seat == NULL) - return; - - weston_desktop_api_move(surface->desktop, dsurface, seat, serial); -} - -static void -weston_desktop_wl_shell_surface_protocol_resize(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial, - enum wl_shell_surface_resize edges) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_seat *seat = wl_resource_get_user_data(seat_resource); - struct weston_desktop_wl_shell_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - enum weston_desktop_surface_edge surf_edges = - (enum weston_desktop_surface_edge) edges; - - if (seat == NULL) - return; - - weston_desktop_api_resize(surface->desktop, dsurface, seat, serial, surf_edges); -} - -static void -weston_desktop_wl_shell_surface_protocol_set_toplevel(struct wl_client *wl_client, - struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_wl_shell_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_wl_shell_change_state(surface, TOPLEVEL, NULL, 0, 0); - if (surface->parent == NULL) - return; - surface->parent = NULL; - weston_desktop_api_set_parent(surface->desktop, surface->surface, NULL); -} - -static void -weston_desktop_wl_shell_surface_protocol_set_transient(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *parent_resource, - int32_t x, int32_t y, - enum wl_shell_surface_transient flags) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_surface *wparent = - wl_resource_get_user_data(parent_resource); - struct weston_desktop_surface *parent; - struct weston_desktop_wl_shell_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - if (!weston_surface_is_desktop_surface(wparent)) - return; - - parent = weston_surface_get_desktop_surface(wparent); - if (flags & WL_SHELL_SURFACE_TRANSIENT_INACTIVE) { - weston_desktop_wl_shell_change_state(surface, TRANSIENT, parent, - x, y); - } else { - weston_desktop_wl_shell_change_state(surface, TOPLEVEL, NULL, - 0, 0); - surface->parent = parent; - weston_desktop_api_set_parent(surface->desktop, - surface->surface, parent); - } -} - -static void -weston_desktop_wl_shell_surface_protocol_set_fullscreen(struct wl_client *wl_client, - struct wl_resource *resource, - enum wl_shell_surface_fullscreen_method method, - uint32_t framerate, - struct wl_resource *output_resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_wl_shell_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - struct weston_output *output = NULL; - - if (output_resource != NULL) - output = weston_head_from_resource(output_resource)->output; - - weston_desktop_wl_shell_change_state(surface, FULLSCREEN, NULL, 0, 0); - weston_desktop_api_fullscreen_requested(surface->desktop, dsurface, - true, output); -} - -static void -weston_desktop_wl_shell_surface_protocol_set_popup(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial, - struct wl_resource *parent_resource, - int32_t x, int32_t y, - enum wl_shell_surface_transient flags) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_seat *wseat = wl_resource_get_user_data(seat_resource); - struct weston_desktop_seat *seat = weston_desktop_seat_from_seat(wseat); - struct weston_surface *parent = - wl_resource_get_user_data(parent_resource); - struct weston_desktop_surface *parent_surface; - struct weston_desktop_wl_shell_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - /* Check that if we have a valid wseat we also got a valid desktop seat */ - if (wseat != NULL && seat == NULL) { - wl_client_post_no_memory(wl_client); - return; - } - - if (!weston_surface_is_desktop_surface(parent)) - return; - - parent_surface = weston_surface_get_desktop_surface(parent); - - weston_desktop_wl_shell_change_state(surface, POPUP, - parent_surface, x, y); - weston_desktop_surface_popup_grab(surface->surface, seat, serial); - surface->popup_seat = seat; -} - -static void -weston_desktop_wl_shell_surface_protocol_set_maximized(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *output_resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_wl_shell_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_wl_shell_change_state(surface, MAXIMIZED, NULL, 0, 0); - weston_desktop_api_maximized_requested(surface->desktop, dsurface, true); -} - -static void -weston_desktop_wl_shell_surface_protocol_set_title(struct wl_client *wl_client, - struct wl_resource *resource, - const char *title) -{ - struct weston_desktop_surface *surface = - wl_resource_get_user_data(resource); - - weston_desktop_surface_set_title(surface, title); -} - -static void -weston_desktop_wl_shell_surface_protocol_set_class(struct wl_client *wl_client, - struct wl_resource *resource, - const char *class_) -{ - struct weston_desktop_surface *surface = - wl_resource_get_user_data(resource); - - weston_desktop_surface_set_app_id(surface, class_); -} - - -static const struct wl_shell_surface_interface weston_desktop_wl_shell_surface_implementation = { - .pong = weston_desktop_wl_shell_surface_protocol_pong, - .move = weston_desktop_wl_shell_surface_protocol_move, - .resize = weston_desktop_wl_shell_surface_protocol_resize, - .set_toplevel = weston_desktop_wl_shell_surface_protocol_set_toplevel, - .set_transient = weston_desktop_wl_shell_surface_protocol_set_transient, - .set_fullscreen = weston_desktop_wl_shell_surface_protocol_set_fullscreen, - .set_popup = weston_desktop_wl_shell_surface_protocol_set_popup, - .set_maximized = weston_desktop_wl_shell_surface_protocol_set_maximized, - .set_title = weston_desktop_wl_shell_surface_protocol_set_title, - .set_class = weston_desktop_wl_shell_surface_protocol_set_class, -}; - -static const struct weston_desktop_surface_implementation weston_desktop_wl_shell_surface_internal_implementation = { - .set_size = weston_desktop_wl_shell_surface_set_size, - .committed = weston_desktop_wl_shell_surface_committed, - .ping = weston_desktop_wl_shell_surface_ping, - .close = weston_desktop_wl_shell_surface_close, - - .get_maximized = weston_desktop_wl_shell_surface_get_maximized, - .get_fullscreen = weston_desktop_wl_shell_surface_get_fullscreen, - - .destroy = weston_desktop_wl_shell_surface_destroy, -}; - -static void -wl_surface_resource_destroyed(struct wl_listener *listener, - void *data) -{ - struct weston_desktop_wl_shell_surface *surface = - wl_container_of(listener, surface, - wl_surface_resource_destroy_listener); - - /* the wl_shell_surface spec says that wl_shell_surfaces are to be - * destroyed automatically when the wl_surface is destroyed. */ - weston_desktop_surface_destroy(surface->surface); -} - -static void -weston_desktop_wl_shell_protocol_get_shell_surface(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t id, - struct wl_resource *surface_resource) -{ - struct weston_desktop_client *client = wl_resource_get_user_data(resource); - struct weston_surface *wsurface = wl_resource_get_user_data(surface_resource); - struct weston_desktop_wl_shell_surface *surface; - - - if (weston_surface_set_role(wsurface, "wl_shell_surface", resource, WL_SHELL_ERROR_ROLE) < 0) - return; - - surface = zalloc(sizeof(struct weston_desktop_wl_shell_surface)); - if (surface == NULL) { - wl_client_post_no_memory(wl_client); - return; - } - - surface->desktop = weston_desktop_client_get_desktop(client); - surface->display = weston_desktop_get_display(surface->desktop); - - surface->surface = - weston_desktop_surface_create(surface->desktop, client, wsurface, - &weston_desktop_wl_shell_surface_internal_implementation, - surface); - if (surface->surface == NULL) { - free(surface); - return; - } - - surface->wl_surface_resource_destroy_listener.notify = - wl_surface_resource_destroyed; - wl_resource_add_destroy_listener(wsurface->resource, - &surface->wl_surface_resource_destroy_listener); - - surface->resource = - weston_desktop_surface_add_resource(surface->surface, - &wl_shell_surface_interface, - &weston_desktop_wl_shell_surface_implementation, - id, NULL); -} - - -static const struct wl_shell_interface weston_desktop_wl_shell_implementation = { - .get_shell_surface = weston_desktop_wl_shell_protocol_get_shell_surface, -}; - -static void -weston_desktop_wl_shell_bind(struct wl_client *client, void *data, - uint32_t version, uint32_t id) -{ - struct weston_desktop *desktop = data; - - weston_desktop_client_create(desktop, client, NULL, &wl_shell_interface, - &weston_desktop_wl_shell_implementation, - version, id); -} - -struct wl_global * -weston_desktop_wl_shell_create(struct weston_desktop *desktop, - struct wl_display *display) -{ - return wl_global_create(display, - &wl_shell_interface, - WD_WL_SHELL_PROTOCOL_VERSION, desktop, - weston_desktop_wl_shell_bind); -} diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c deleted file mode 100644 index 955fcca..0000000 --- a/libweston-desktop/xdg-shell-v6.c +++ /dev/null @@ -1,1444 +0,0 @@ -/* - * Copyright © 2010-2012 Intel Corporation - * Copyright © 2011-2012 Collabora, Ltd. - * Copyright © 2013 Raspberry Pi Foundation - * Copyright © 2016 Quentin "Sardem FF7" Glidic - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include - -#include -#include -#include "xdg-shell-unstable-v6-server-protocol.h" - -#include -#include "internal.h" - -#define WD_XDG_SHELL_PROTOCOL_VERSION 1 - -static const char *weston_desktop_xdg_toplevel_role = "xdg_toplevel"; -static const char *weston_desktop_xdg_popup_role = "xdg_popup"; - -struct weston_desktop_xdg_positioner { - struct weston_desktop *desktop; - struct weston_desktop_client *client; - struct wl_resource *resource; - - struct weston_size size; - struct weston_geometry anchor_rect; - enum zxdg_positioner_v6_anchor anchor; - enum zxdg_positioner_v6_gravity gravity; - enum zxdg_positioner_v6_constraint_adjustment constraint_adjustment; - struct weston_position offset; -}; - -enum weston_desktop_xdg_surface_role { - WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE, - WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL, - WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP, -}; - -struct weston_desktop_xdg_surface { - struct wl_resource *resource; - struct weston_desktop *desktop; - struct weston_surface *surface; - struct weston_desktop_surface *desktop_surface; - bool configured; - struct wl_event_source *configure_idle; - struct wl_list configure_list; /* weston_desktop_xdg_surface_configure::link */ - - bool has_next_geometry; - struct weston_geometry next_geometry; - - enum weston_desktop_xdg_surface_role role; -}; - -struct weston_desktop_xdg_surface_configure { - struct wl_list link; /* weston_desktop_xdg_surface::configure_list */ - uint32_t serial; -}; - -struct weston_desktop_xdg_toplevel_state { - bool maximized; - bool fullscreen; - bool resizing; - bool activated; -}; - -struct weston_desktop_xdg_toplevel_configure { - struct weston_desktop_xdg_surface_configure base; - struct weston_desktop_xdg_toplevel_state state; - struct weston_size size; -}; - -struct weston_desktop_xdg_toplevel { - struct weston_desktop_xdg_surface base; - - struct wl_resource *resource; - bool added; - struct { - struct weston_desktop_xdg_toplevel_state state; - struct weston_size size; - } pending; - struct { - struct weston_desktop_xdg_toplevel_state state; - struct weston_size size; - struct weston_size min_size, max_size; - } next; - struct { - struct weston_desktop_xdg_toplevel_state state; - struct weston_size min_size, max_size; - } current; -}; - -struct weston_desktop_xdg_popup { - struct weston_desktop_xdg_surface base; - - struct wl_resource *resource; - bool committed; - struct weston_desktop_xdg_surface *parent; - struct weston_desktop_seat *seat; - struct weston_geometry geometry; -}; - -#define weston_desktop_surface_role_biggest_size (sizeof(struct weston_desktop_xdg_toplevel)) -#define weston_desktop_surface_configure_biggest_size (sizeof(struct weston_desktop_xdg_toplevel)) - - -static struct weston_geometry -weston_desktop_xdg_positioner_get_geometry(struct weston_desktop_xdg_positioner *positioner, - struct weston_desktop_surface *dsurface, - struct weston_desktop_surface *parent) -{ - struct weston_geometry geometry = { - .x = positioner->offset.x, - .y = positioner->offset.y, - .width = positioner->size.width, - .height = positioner->size.height, - }; - - if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP) - geometry.y += positioner->anchor_rect.y; - else if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM) - geometry.y += positioner->anchor_rect.y + positioner->anchor_rect.height; - else - geometry.y += positioner->anchor_rect.y + positioner->anchor_rect.height / 2; - - if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT) - geometry.x += positioner->anchor_rect.x; - else if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT) - geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width; - else - geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width / 2; - - if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_TOP) - geometry.y -= geometry.height; - else if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_BOTTOM) - geometry.y = geometry.y; - else - geometry.y -= geometry.height / 2; - - if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_LEFT) - geometry.x -= geometry.width; - else if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_RIGHT) - geometry.x = geometry.x; - else - geometry.x -= geometry.width / 2; - - if (positioner->constraint_adjustment == ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_NONE) - return geometry; - - /* TODO: add compositor policy configuration and the code here */ - - return geometry; -} - -static void -weston_desktop_xdg_positioner_protocol_set_size(struct wl_client *wl_client, - struct wl_resource *resource, - int32_t width, int32_t height) -{ - struct weston_desktop_xdg_positioner *positioner = - wl_resource_get_user_data(resource); - - if (width < 1 || height < 1) { - wl_resource_post_error(resource, - ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, - "width and height must be positives and non-zero"); - return; - } - - positioner->size.width = width; - positioner->size.height = height; -} - -static void -weston_desktop_xdg_positioner_protocol_set_anchor_rect(struct wl_client *wl_client, - struct wl_resource *resource, - int32_t x, int32_t y, - int32_t width, int32_t height) -{ - struct weston_desktop_xdg_positioner *positioner = - wl_resource_get_user_data(resource); - - if (width < 1 || height < 1) { - wl_resource_post_error(resource, - ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, - "width and height must be positives and non-zero"); - return; - } - - positioner->anchor_rect.x = x; - positioner->anchor_rect.y = y; - positioner->anchor_rect.width = width; - positioner->anchor_rect.height = height; -} - -static void -weston_desktop_xdg_positioner_protocol_set_anchor(struct wl_client *wl_client, - struct wl_resource *resource, - enum zxdg_positioner_v6_anchor anchor) -{ - struct weston_desktop_xdg_positioner *positioner = - wl_resource_get_user_data(resource); - - if (((anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP ) && - (anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM)) || - ((anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT) && - (anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT))) { - wl_resource_post_error(resource, - ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, - "same-axis values are not allowed"); - return; - } - - positioner->anchor = anchor; -} - -static void -weston_desktop_xdg_positioner_protocol_set_gravity(struct wl_client *wl_client, - struct wl_resource *resource, - enum zxdg_positioner_v6_gravity gravity) -{ - struct weston_desktop_xdg_positioner *positioner = - wl_resource_get_user_data(resource); - - if (((gravity & ZXDG_POSITIONER_V6_GRAVITY_TOP) && - (gravity & ZXDG_POSITIONER_V6_GRAVITY_BOTTOM)) || - ((gravity & ZXDG_POSITIONER_V6_GRAVITY_LEFT) && - (gravity & ZXDG_POSITIONER_V6_GRAVITY_RIGHT))) { - wl_resource_post_error(resource, - ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, - "same-axis values are not allowed"); - return; - } - - positioner->gravity = gravity; -} - -static void -weston_desktop_xdg_positioner_protocol_set_constraint_adjustment(struct wl_client *wl_client, - struct wl_resource *resource, - enum zxdg_positioner_v6_constraint_adjustment constraint_adjustment) -{ - struct weston_desktop_xdg_positioner *positioner = - wl_resource_get_user_data(resource); - - positioner->constraint_adjustment = constraint_adjustment; -} - -static void -weston_desktop_xdg_positioner_protocol_set_offset(struct wl_client *wl_client, - struct wl_resource *resource, - int32_t x, int32_t y) -{ - struct weston_desktop_xdg_positioner *positioner = - wl_resource_get_user_data(resource); - - positioner->offset.x = x; - positioner->offset.y = y; -} - -static void -weston_desktop_xdg_positioner_destroy(struct wl_resource *resource) -{ - struct weston_desktop_xdg_positioner *positioner = - wl_resource_get_user_data(resource); - - free(positioner); -} - -static const struct zxdg_positioner_v6_interface weston_desktop_xdg_positioner_implementation = { - .destroy = weston_desktop_destroy_request, - .set_size = weston_desktop_xdg_positioner_protocol_set_size, - .set_anchor_rect = weston_desktop_xdg_positioner_protocol_set_anchor_rect, - .set_anchor = weston_desktop_xdg_positioner_protocol_set_anchor, - .set_gravity = weston_desktop_xdg_positioner_protocol_set_gravity, - .set_constraint_adjustment = weston_desktop_xdg_positioner_protocol_set_constraint_adjustment, - .set_offset = weston_desktop_xdg_positioner_protocol_set_offset, -}; - -static void -weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface); - -static void -weston_desktop_xdg_toplevel_ensure_added(struct weston_desktop_xdg_toplevel *toplevel) -{ - if (toplevel->added) - return; - - weston_desktop_api_surface_added(toplevel->base.desktop, - toplevel->base.desktop_surface); - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); - toplevel->added = true; -} - -static void -weston_desktop_xdg_toplevel_protocol_set_parent(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *parent_resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - struct weston_desktop_surface *parent = NULL; - - if (parent_resource != NULL) - parent = wl_resource_get_user_data(parent_resource); - - weston_desktop_xdg_toplevel_ensure_added(toplevel); - weston_desktop_api_set_parent(toplevel->base.desktop, dsurface, parent); -} - -static void -weston_desktop_xdg_toplevel_protocol_set_title(struct wl_client *wl_client, - struct wl_resource *resource, - const char *title) -{ - struct weston_desktop_surface *toplevel = - wl_resource_get_user_data(resource); - - weston_desktop_surface_set_title(toplevel, title); -} - -static void -weston_desktop_xdg_toplevel_protocol_set_app_id(struct wl_client *wl_client, - struct wl_resource *resource, - const char *app_id) -{ - struct weston_desktop_surface *toplevel = - wl_resource_get_user_data(resource); - - weston_desktop_surface_set_app_id(toplevel, app_id); -} - -static void -weston_desktop_xdg_toplevel_protocol_show_window_menu(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial, - int32_t x, int32_t y) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_seat *seat = - wl_resource_get_user_data(seat_resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - if (!toplevel->base.configured) { - wl_resource_post_error(toplevel->resource, - ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, - "Surface has not been configured yet"); - return; - } - - if (seat == NULL) - return; - - weston_desktop_api_show_window_menu(toplevel->base.desktop, - dsurface, seat, x, y); -} - -static void -weston_desktop_xdg_toplevel_protocol_move(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_seat *seat = - wl_resource_get_user_data(seat_resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - if (!toplevel->base.configured) { - wl_resource_post_error(toplevel->resource, - ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, - "Surface has not been configured yet"); - return; - } - - if (seat == NULL) - return; - - weston_desktop_api_move(toplevel->base.desktop, dsurface, seat, serial); -} - -static void -weston_desktop_xdg_toplevel_protocol_resize(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial, - enum zxdg_toplevel_v6_resize_edge edges) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_seat *seat = - wl_resource_get_user_data(seat_resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - enum weston_desktop_surface_edge surf_edges = - (enum weston_desktop_surface_edge) edges; - - if (!toplevel->base.configured) { - wl_resource_post_error(toplevel->resource, - ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, - "Surface has not been configured yet"); - return; - } - - if (seat == NULL) - return; - - weston_desktop_api_resize(toplevel->base.desktop, - dsurface, seat, serial, surf_edges); -} - -static void -weston_desktop_xdg_toplevel_ack_configure(struct weston_desktop_xdg_toplevel *toplevel, - struct weston_desktop_xdg_toplevel_configure *configure) -{ - toplevel->next.state = configure->state; - toplevel->next.size = configure->size; -} - -static void -weston_desktop_xdg_toplevel_protocol_set_min_size(struct wl_client *wl_client, - struct wl_resource *resource, - int32_t width, int32_t height) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - toplevel->next.min_size.width = width; - toplevel->next.min_size.height = height; -} - -static void -weston_desktop_xdg_toplevel_protocol_set_max_size(struct wl_client *wl_client, - struct wl_resource *resource, - int32_t width, int32_t height) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - toplevel->next.max_size.width = width; - toplevel->next.max_size.height = height; -} - -static void -weston_desktop_xdg_toplevel_protocol_set_maximized(struct wl_client *wl_client, - struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_xdg_toplevel_ensure_added(toplevel); - weston_desktop_api_maximized_requested(toplevel->base.desktop, dsurface, true); -} - -static void -weston_desktop_xdg_toplevel_protocol_unset_maximized(struct wl_client *wl_client, - struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_xdg_toplevel_ensure_added(toplevel); - weston_desktop_api_maximized_requested(toplevel->base.desktop, dsurface, false); -} - -static void -weston_desktop_xdg_toplevel_protocol_set_fullscreen(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *output_resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - struct weston_output *output = NULL; - - if (output_resource != NULL) - output = weston_head_from_resource(output_resource)->output; - - weston_desktop_xdg_toplevel_ensure_added(toplevel); - weston_desktop_api_fullscreen_requested(toplevel->base.desktop, dsurface, - true, output); -} - -static void -weston_desktop_xdg_toplevel_protocol_unset_fullscreen(struct wl_client *wl_client, - struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_xdg_toplevel_ensure_added(toplevel); - weston_desktop_api_fullscreen_requested(toplevel->base.desktop, dsurface, - false, NULL); -} - -static void -weston_desktop_xdg_toplevel_protocol_set_minimized(struct wl_client *wl_client, - struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_xdg_toplevel_ensure_added(toplevel); - weston_desktop_api_minimized_requested(toplevel->base.desktop, dsurface); -} - -static void -weston_desktop_xdg_toplevel_send_configure(struct weston_desktop_xdg_toplevel *toplevel, - struct weston_desktop_xdg_toplevel_configure *configure) -{ - uint32_t *s; - struct wl_array states; - - configure->state = toplevel->pending.state; - configure->size = toplevel->pending.size; - - wl_array_init(&states); - if (toplevel->pending.state.maximized) { - s = wl_array_add(&states, sizeof(uint32_t)); - *s = ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED; - } - if (toplevel->pending.state.fullscreen) { - s = wl_array_add(&states, sizeof(uint32_t)); - *s = ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN; - } - if (toplevel->pending.state.resizing) { - s = wl_array_add(&states, sizeof(uint32_t)); - *s = ZXDG_TOPLEVEL_V6_STATE_RESIZING; - } - if (toplevel->pending.state.activated) { - s = wl_array_add(&states, sizeof(uint32_t)); - *s = ZXDG_TOPLEVEL_V6_STATE_ACTIVATED; - } - - zxdg_toplevel_v6_send_configure(toplevel->resource, - toplevel->pending.size.width, - toplevel->pending.size.height, - &states); - - wl_array_release(&states); -}; - -static void -weston_desktop_xdg_toplevel_set_maximized(struct weston_desktop_surface *dsurface, - void *user_data, bool maximized) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - toplevel->pending.state.maximized = maximized; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); -} - -static void -weston_desktop_xdg_toplevel_set_fullscreen(struct weston_desktop_surface *dsurface, - void *user_data, bool fullscreen) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - toplevel->pending.state.fullscreen = fullscreen; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); -} - -static void -weston_desktop_xdg_toplevel_set_resizing(struct weston_desktop_surface *dsurface, - void *user_data, bool resizing) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - toplevel->pending.state.resizing = resizing; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); -} - -static void -weston_desktop_xdg_toplevel_set_activated(struct weston_desktop_surface *dsurface, - void *user_data, bool activated) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - toplevel->pending.state.activated = activated; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); -} - -static void -weston_desktop_xdg_toplevel_set_size(struct weston_desktop_surface *dsurface, - void *user_data, - int32_t width, int32_t height) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - toplevel->pending.size.width = width; - toplevel->pending.size.height = height; - - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); -} - -static void -weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplevel, - int32_t sx, int32_t sy) -{ - struct weston_surface *wsurface = - weston_desktop_surface_get_surface(toplevel->base.desktop_surface); - - if (!wsurface->buffer_ref.buffer && !toplevel->added) { - weston_desktop_xdg_toplevel_ensure_added(toplevel); - return; - } - if (!wsurface->buffer_ref.buffer) - return; - - struct weston_geometry geometry = - weston_desktop_surface_get_geometry(toplevel->base.desktop_surface); - - if ((toplevel->next.state.maximized || toplevel->next.state.fullscreen) && - (toplevel->next.size.width != geometry.width || - toplevel->next.size.height != geometry.height)) { - struct weston_desktop_client *client = - weston_desktop_surface_get_client(toplevel->base.desktop_surface); - struct wl_resource *client_resource = - weston_desktop_client_get_resource(client); - - wl_resource_post_error(client_resource, - ZXDG_SHELL_V6_ERROR_INVALID_SURFACE_STATE, - "xdg_surface buffer does not match the configured state"); - return; - } - - toplevel->current.state = toplevel->next.state; - toplevel->current.min_size = toplevel->next.min_size; - toplevel->current.max_size = toplevel->next.max_size; - - weston_desktop_api_committed(toplevel->base.desktop, - toplevel->base.desktop_surface, - sx, sy); -} - -static void -weston_desktop_xdg_toplevel_close(struct weston_desktop_xdg_toplevel *toplevel) -{ - zxdg_toplevel_v6_send_close(toplevel->resource); -} - -static bool -weston_desktop_xdg_toplevel_get_maximized(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - return toplevel->current.state.maximized; -} - -static bool -weston_desktop_xdg_toplevel_get_fullscreen(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - return toplevel->current.state.fullscreen; -} - -static bool -weston_desktop_xdg_toplevel_get_resizing(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - return toplevel->current.state.resizing; -} - -static bool -weston_desktop_xdg_toplevel_get_activated(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - return toplevel->current.state.activated; -} - -static void -weston_desktop_xdg_toplevel_destroy(struct weston_desktop_xdg_toplevel *toplevel) -{ - if (toplevel->added) - weston_desktop_api_surface_removed(toplevel->base.desktop, - toplevel->base.desktop_surface); -} - -static void -weston_desktop_xdg_toplevel_resource_destroy(struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - - if (dsurface != NULL) - weston_desktop_surface_resource_destroy(resource); -} - -static const struct zxdg_toplevel_v6_interface weston_desktop_xdg_toplevel_implementation = { - .destroy = weston_desktop_destroy_request, - .set_parent = weston_desktop_xdg_toplevel_protocol_set_parent, - .set_title = weston_desktop_xdg_toplevel_protocol_set_title, - .set_app_id = weston_desktop_xdg_toplevel_protocol_set_app_id, - .show_window_menu = weston_desktop_xdg_toplevel_protocol_show_window_menu, - .move = weston_desktop_xdg_toplevel_protocol_move, - .resize = weston_desktop_xdg_toplevel_protocol_resize, - .set_min_size = weston_desktop_xdg_toplevel_protocol_set_min_size, - .set_max_size = weston_desktop_xdg_toplevel_protocol_set_max_size, - .set_maximized = weston_desktop_xdg_toplevel_protocol_set_maximized, - .unset_maximized = weston_desktop_xdg_toplevel_protocol_unset_maximized, - .set_fullscreen = weston_desktop_xdg_toplevel_protocol_set_fullscreen, - .unset_fullscreen = weston_desktop_xdg_toplevel_protocol_unset_fullscreen, - .set_minimized = weston_desktop_xdg_toplevel_protocol_set_minimized, -}; - -static void -weston_desktop_xdg_popup_protocol_grab(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_popup *popup = - weston_desktop_surface_get_implementation_data(dsurface); - struct weston_seat *wseat = wl_resource_get_user_data(seat_resource); - struct weston_desktop_seat *seat = weston_desktop_seat_from_seat(wseat); - struct weston_desktop_surface *topmost; - bool parent_is_toplevel = - popup->parent->role == WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL; - - /* Check that if we have a valid wseat we also got a valid desktop seat */ - if (wseat != NULL && seat == NULL) { - wl_client_post_no_memory(wl_client); - return; - } - - if (popup->committed) { - wl_resource_post_error(popup->resource, - ZXDG_POPUP_V6_ERROR_INVALID_GRAB, - "xdg_popup already is mapped"); - return; - } - - /* If seat is NULL then get_topmost_surface will return NULL. In - * combination with setting parent_is_toplevel to TRUE here we will - * avoid posting an error, and we will instead gracefully fail the - * grab and dismiss the surface. - * FIXME: this is a hack because currently we cannot check the topmost - * parent with a destroyed weston_seat */ - if (seat == NULL) - parent_is_toplevel = true; - - topmost = weston_desktop_seat_popup_grab_get_topmost_surface(seat); - if ((topmost == NULL && !parent_is_toplevel) || - (topmost != NULL && topmost != popup->parent->desktop_surface)) { - struct weston_desktop_client *client = - weston_desktop_surface_get_client(dsurface); - struct wl_resource *client_resource = - weston_desktop_client_get_resource(client); - - wl_resource_post_error(client_resource, - ZXDG_SHELL_V6_ERROR_NOT_THE_TOPMOST_POPUP, - "xdg_popup was not created on the topmost popup"); - return; - } - - popup->seat = seat; - weston_desktop_surface_popup_grab(popup->base.desktop_surface, - popup->seat, serial); -} - -static void -weston_desktop_xdg_popup_send_configure(struct weston_desktop_xdg_popup *popup) -{ - zxdg_popup_v6_send_configure(popup->resource, - popup->geometry.x, - popup->geometry.y, - popup->geometry.width, - popup->geometry.height); -} - -static void -weston_desktop_xdg_popup_update_position(struct weston_desktop_surface *dsurface, - void *user_data); - -static void -weston_desktop_xdg_popup_committed(struct weston_desktop_xdg_popup *popup) -{ - struct weston_surface *wsurface = - weston_desktop_surface_get_surface (popup->base.desktop_surface); - struct weston_view *view; - - wl_list_for_each(view, &wsurface->views, surface_link) - weston_view_update_transform(view); - - if (!popup->committed) - weston_desktop_xdg_surface_schedule_configure(&popup->base); - popup->committed = true; - weston_desktop_xdg_popup_update_position(popup->base.desktop_surface, - popup); -} - -static void -weston_desktop_xdg_popup_update_position(struct weston_desktop_surface *dsurface, - void *user_data) -{ -} - -static void -weston_desktop_xdg_popup_close(struct weston_desktop_xdg_popup *popup) -{ - zxdg_popup_v6_send_popup_done(popup->resource); -} - -static void -weston_desktop_xdg_popup_destroy(struct weston_desktop_xdg_popup *popup) -{ - struct weston_desktop_surface *topmost; - struct weston_desktop_client *client = - weston_desktop_surface_get_client(popup->base.desktop_surface); - - if (!weston_desktop_surface_get_grab(popup->base.desktop_surface)) - return; - - topmost = weston_desktop_seat_popup_grab_get_topmost_surface(popup->seat); - if (topmost != popup->base.desktop_surface) { - struct wl_resource *client_resource = - weston_desktop_client_get_resource(client); - - wl_resource_post_error(client_resource, - ZXDG_SHELL_V6_ERROR_NOT_THE_TOPMOST_POPUP, - "xdg_popup was destroyed while it was not the topmost popup."); - } - - weston_desktop_surface_popup_ungrab(popup->base.desktop_surface, - popup->seat); -} - -static void -weston_desktop_xdg_popup_resource_destroy(struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - - if (dsurface != NULL) - weston_desktop_surface_resource_destroy(resource); -} - -static const struct zxdg_popup_v6_interface weston_desktop_xdg_popup_implementation = { - .destroy = weston_desktop_destroy_request, - .grab = weston_desktop_xdg_popup_protocol_grab, -}; - -static void -weston_desktop_xdg_surface_send_configure(void *user_data) -{ - struct weston_desktop_xdg_surface *surface = user_data; - struct weston_desktop_xdg_surface_configure *configure; - - surface->configure_idle = NULL; - - configure = zalloc(weston_desktop_surface_configure_biggest_size); - if (configure == NULL) { - struct weston_desktop_client *client = - weston_desktop_surface_get_client(surface->desktop_surface); - struct wl_client *wl_client = - weston_desktop_client_get_client(client); - wl_client_post_no_memory(wl_client); - return; - } - wl_list_insert(surface->configure_list.prev, &configure->link); - configure->serial = - wl_display_next_serial(weston_desktop_get_display(surface->desktop)); - - switch (surface->role) { - case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: - assert(0 && "not reached"); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: - weston_desktop_xdg_toplevel_send_configure((struct weston_desktop_xdg_toplevel *) surface, - (struct weston_desktop_xdg_toplevel_configure *) configure); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: - weston_desktop_xdg_popup_send_configure((struct weston_desktop_xdg_popup *) surface); - break; - } - - zxdg_surface_v6_send_configure(surface->resource, configure->serial); -} - -static bool -weston_desktop_xdg_toplevel_state_compare(struct weston_desktop_xdg_toplevel *toplevel) -{ - struct { - struct weston_desktop_xdg_toplevel_state state; - struct weston_size size; - } configured; - - if (!toplevel->base.configured) - return false; - - if (wl_list_empty(&toplevel->base.configure_list)) { - /* Last configure is actually the current state, just use it */ - configured.state = toplevel->current.state; - configured.size.width = toplevel->base.surface->width; - configured.size.height = toplevel->base.surface->height; - } else { - struct weston_desktop_xdg_toplevel_configure *configure = - wl_container_of(toplevel->base.configure_list.prev, - configure, base.link); - - configured.state = configure->state; - configured.size = configure->size; - } - - if (toplevel->pending.state.activated != configured.state.activated) - return false; - if (toplevel->pending.state.fullscreen != configured.state.fullscreen) - return false; - if (toplevel->pending.state.maximized != configured.state.maximized) - return false; - if (toplevel->pending.state.resizing != configured.state.resizing) - return false; - - if (toplevel->pending.size.width == configured.size.width && - toplevel->pending.size.height == configured.size.height) - return true; - - if (toplevel->pending.size.width == 0 && - toplevel->pending.size.height == 0) - return true; - - return false; -} - -static void -weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface) -{ - struct wl_display *display = weston_desktop_get_display(surface->desktop); - struct wl_event_loop *loop = wl_display_get_event_loop(display); - bool pending_same = false; - - switch (surface->role) { - case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: - assert(0 && "not reached"); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: - pending_same = weston_desktop_xdg_toplevel_state_compare((struct weston_desktop_xdg_toplevel *) surface); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: - break; - } - - if (surface->configure_idle != NULL) { - if (!pending_same) - return; - - wl_event_source_remove(surface->configure_idle); - surface->configure_idle = NULL; - } else { - if (pending_same) - return; - - surface->configure_idle = - wl_event_loop_add_idle(loop, - weston_desktop_xdg_surface_send_configure, - surface); - } -} - -static void -weston_desktop_xdg_surface_protocol_get_toplevel(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t id) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_surface *wsurface = - weston_desktop_surface_get_surface(dsurface); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - if (weston_surface_set_role(wsurface, weston_desktop_xdg_toplevel_role, - resource, ZXDG_SHELL_V6_ERROR_ROLE) < 0) - return; - - toplevel->resource = - weston_desktop_surface_add_resource(toplevel->base.desktop_surface, - &zxdg_toplevel_v6_interface, - &weston_desktop_xdg_toplevel_implementation, - id, weston_desktop_xdg_toplevel_resource_destroy); - if (toplevel->resource == NULL) - return; - - toplevel->base.role = WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL; -} - -static void -weston_desktop_xdg_surface_protocol_get_popup(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t id, - struct wl_resource *parent_resource, - struct wl_resource *positioner_resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_surface *wsurface = - weston_desktop_surface_get_surface(dsurface); - struct weston_desktop_xdg_popup *popup = - weston_desktop_surface_get_implementation_data(dsurface); - struct weston_desktop_surface *parent_surface = - wl_resource_get_user_data(parent_resource); - struct weston_desktop_xdg_surface *parent = - weston_desktop_surface_get_implementation_data(parent_surface); - struct weston_desktop_xdg_positioner *positioner = - wl_resource_get_user_data(positioner_resource); - - /* Checking whether the size and anchor rect both have a positive size - * is enough to verify both have been correctly set */ - if (positioner->size.width == 0 || positioner->anchor_rect.width == 0) { - wl_resource_post_error(resource, - ZXDG_SHELL_V6_ERROR_INVALID_POSITIONER, - "positioner object is not complete"); - return; - } - - if (weston_surface_set_role(wsurface, weston_desktop_xdg_popup_role, - resource, ZXDG_SHELL_V6_ERROR_ROLE) < 0) - return; - - popup->resource = - weston_desktop_surface_add_resource(popup->base.desktop_surface, - &zxdg_popup_v6_interface, - &weston_desktop_xdg_popup_implementation, - id, weston_desktop_xdg_popup_resource_destroy); - if (popup->resource == NULL) - return; - - popup->base.role = WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP; - popup->parent = parent; - - popup->geometry = - weston_desktop_xdg_positioner_get_geometry(positioner, - dsurface, - parent_surface); - - weston_desktop_surface_set_relative_to(popup->base.desktop_surface, - parent_surface, - popup->geometry.x, - popup->geometry.y, - true); -} - -static bool -weston_desktop_xdg_surface_check_role(struct weston_desktop_xdg_surface *surface) -{ - struct weston_surface *wsurface = - weston_desktop_surface_get_surface(surface->desktop_surface); - const char *role; - - role = weston_surface_get_role(wsurface); - if (role != NULL && - (role == weston_desktop_xdg_toplevel_role || - role == weston_desktop_xdg_popup_role)) - return true; - - wl_resource_post_error(surface->resource, - ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, - "xdg_surface must have a role"); - return false; -} - -static void -weston_desktop_xdg_surface_protocol_set_window_geometry(struct wl_client *wl_client, - struct wl_resource *resource, - int32_t x, int32_t y, - int32_t width, int32_t height) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - if (!weston_desktop_xdg_surface_check_role(surface)) - return; - - surface->has_next_geometry = true; - surface->next_geometry.x = x; - surface->next_geometry.y = y; - surface->next_geometry.width = width; - surface->next_geometry.height = height; -} - -static void -weston_desktop_xdg_surface_protocol_ack_configure(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t serial) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - struct weston_desktop_xdg_surface_configure *configure, *temp; - bool found = false; - - if (!weston_desktop_xdg_surface_check_role(surface)) - return; - - wl_list_for_each_safe(configure, temp, &surface->configure_list, link) { - if (configure->serial < serial) { - wl_list_remove(&configure->link); - free(configure); - } else if (configure->serial == serial) { - wl_list_remove(&configure->link); - found = true; - break; - } else { - break; - } - } - if (!found) { - struct weston_desktop_client *client = - weston_desktop_surface_get_client(dsurface); - struct wl_resource *client_resource = - weston_desktop_client_get_resource(client); - wl_resource_post_error(client_resource, - ZXDG_SHELL_V6_ERROR_INVALID_SURFACE_STATE, - "Wrong configure serial: %u", serial); - return; - } - - surface->configured = true; - - switch (surface->role) { - case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: - assert(0 && "not reached"); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: - weston_desktop_xdg_toplevel_ack_configure((struct weston_desktop_xdg_toplevel *) surface, - (struct weston_desktop_xdg_toplevel_configure *) configure); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: - break; - } - - free(configure); -} - -static void -weston_desktop_xdg_surface_ping(struct weston_desktop_surface *dsurface, - uint32_t serial, void *user_data) -{ - struct weston_desktop_client *client = - weston_desktop_surface_get_client(dsurface); - - zxdg_shell_v6_send_ping(weston_desktop_client_get_resource(client), - serial); -} - -static void -weston_desktop_xdg_surface_committed(struct weston_desktop_surface *dsurface, - void *user_data, - int32_t sx, int32_t sy) -{ - struct weston_desktop_xdg_surface *surface = user_data; - struct weston_surface *wsurface = - weston_desktop_surface_get_surface (dsurface); - - if (wsurface->buffer_ref.buffer && !surface->configured) { - wl_resource_post_error(surface->resource, - ZXDG_SURFACE_V6_ERROR_UNCONFIGURED_BUFFER, - "xdg_surface has never been configured"); - return; - } - - if (surface->has_next_geometry) { - surface->has_next_geometry = false; - weston_desktop_surface_set_geometry(surface->desktop_surface, - surface->next_geometry); - } - - switch (surface->role) { - case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: - wl_resource_post_error(surface->resource, - ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, - "xdg_surface must have a role"); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: - weston_desktop_xdg_toplevel_committed((struct weston_desktop_xdg_toplevel *) surface, sx, sy); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: - weston_desktop_xdg_popup_committed((struct weston_desktop_xdg_popup *) surface); - break; - } -} - -static void -weston_desktop_xdg_surface_close(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_surface *surface = user_data; - - switch (surface->role) { - case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: - assert(0 && "not reached"); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: - weston_desktop_xdg_toplevel_close((struct weston_desktop_xdg_toplevel *) surface); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: - weston_desktop_xdg_popup_close((struct weston_desktop_xdg_popup *) surface); - break; - } -} - -static void -weston_desktop_xdg_surface_destroy(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_surface *surface = user_data; - struct weston_desktop_xdg_surface_configure *configure, *temp; - - switch (surface->role) { - case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: - weston_desktop_xdg_toplevel_destroy((struct weston_desktop_xdg_toplevel *) surface); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: - weston_desktop_xdg_popup_destroy((struct weston_desktop_xdg_popup *) surface); - break; - } - - if (surface->configure_idle != NULL) - wl_event_source_remove(surface->configure_idle); - - wl_list_for_each_safe(configure, temp, &surface->configure_list, link) - free(configure); - - free(surface); -} - -static const struct zxdg_surface_v6_interface weston_desktop_xdg_surface_implementation = { - .destroy = weston_desktop_destroy_request, - .get_toplevel = weston_desktop_xdg_surface_protocol_get_toplevel, - .get_popup = weston_desktop_xdg_surface_protocol_get_popup, - .set_window_geometry = weston_desktop_xdg_surface_protocol_set_window_geometry, - .ack_configure = weston_desktop_xdg_surface_protocol_ack_configure, -}; - -static const struct weston_desktop_surface_implementation weston_desktop_xdg_surface_internal_implementation = { - /* These are used for toplevel only */ - .set_maximized = weston_desktop_xdg_toplevel_set_maximized, - .set_fullscreen = weston_desktop_xdg_toplevel_set_fullscreen, - .set_resizing = weston_desktop_xdg_toplevel_set_resizing, - .set_activated = weston_desktop_xdg_toplevel_set_activated, - .set_size = weston_desktop_xdg_toplevel_set_size, - - .get_maximized = weston_desktop_xdg_toplevel_get_maximized, - .get_fullscreen = weston_desktop_xdg_toplevel_get_fullscreen, - .get_resizing = weston_desktop_xdg_toplevel_get_resizing, - .get_activated = weston_desktop_xdg_toplevel_get_activated, - - /* These are used for popup only */ - .update_position = weston_desktop_xdg_popup_update_position, - - /* Common API */ - .committed = weston_desktop_xdg_surface_committed, - .ping = weston_desktop_xdg_surface_ping, - .close = weston_desktop_xdg_surface_close, - - .destroy = weston_desktop_xdg_surface_destroy, -}; - -static void -weston_desktop_xdg_shell_protocol_create_positioner(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t id) -{ - struct weston_desktop_client *client = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_positioner *positioner; - - positioner = zalloc(sizeof(struct weston_desktop_xdg_positioner)); - if (positioner == NULL) { - wl_client_post_no_memory(wl_client); - return; - } - - positioner->client = client; - positioner->desktop = weston_desktop_client_get_desktop(positioner->client); - - positioner->resource = - wl_resource_create(wl_client, - &zxdg_positioner_v6_interface, - wl_resource_get_version(resource), id); - if (positioner->resource == NULL) { - wl_client_post_no_memory(wl_client); - free(positioner); - return; - } - wl_resource_set_implementation(positioner->resource, - &weston_desktop_xdg_positioner_implementation, - positioner, weston_desktop_xdg_positioner_destroy); -} - -static void -weston_desktop_xdg_surface_resource_destroy(struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - - if (dsurface != NULL) - weston_desktop_surface_resource_destroy(resource); -} - -static void -weston_desktop_xdg_shell_protocol_get_xdg_surface(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t id, - struct wl_resource *surface_resource) -{ - struct weston_desktop_client *client = - wl_resource_get_user_data(resource); - struct weston_surface *wsurface = - wl_resource_get_user_data(surface_resource); - struct weston_desktop_xdg_surface *surface; - - surface = zalloc(weston_desktop_surface_role_biggest_size); - if (surface == NULL) { - wl_client_post_no_memory(wl_client); - return; - } - - surface->desktop = weston_desktop_client_get_desktop(client); - surface->surface = wsurface; - wl_list_init(&surface->configure_list); - - surface->desktop_surface = - weston_desktop_surface_create(surface->desktop, client, - surface->surface, - &weston_desktop_xdg_surface_internal_implementation, - surface); - if (surface->desktop_surface == NULL) { - free(surface); - return; - } - - surface->resource = - weston_desktop_surface_add_resource(surface->desktop_surface, - &zxdg_surface_v6_interface, - &weston_desktop_xdg_surface_implementation, - id, weston_desktop_xdg_surface_resource_destroy); - if (surface->resource == NULL) - return; - - if (wsurface->buffer_ref.buffer != NULL) { - wl_resource_post_error(surface->resource, - ZXDG_SURFACE_V6_ERROR_UNCONFIGURED_BUFFER, - "xdg_surface must not have a buffer at creation"); - return; - } -} - -static void -weston_desktop_xdg_shell_protocol_pong(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t serial) -{ - struct weston_desktop_client *client = - wl_resource_get_user_data(resource); - - weston_desktop_client_pong(client, serial); -} - -static const struct zxdg_shell_v6_interface weston_desktop_xdg_shell_implementation = { - .destroy = weston_desktop_destroy_request, - .create_positioner = weston_desktop_xdg_shell_protocol_create_positioner, - .get_xdg_surface = weston_desktop_xdg_shell_protocol_get_xdg_surface, - .pong = weston_desktop_xdg_shell_protocol_pong, -}; - -static void -weston_desktop_xdg_shell_bind(struct wl_client *client, void *data, - uint32_t version, uint32_t id) -{ - struct weston_desktop *desktop = data; - - weston_desktop_client_create(desktop, client, NULL, - &zxdg_shell_v6_interface, - &weston_desktop_xdg_shell_implementation, - version, id); -} - -struct wl_global * -weston_desktop_xdg_shell_v6_create(struct weston_desktop *desktop, struct wl_display *display) -{ - return wl_global_create(display, &zxdg_shell_v6_interface, - WD_XDG_SHELL_PROTOCOL_VERSION, desktop, - weston_desktop_xdg_shell_bind); -} diff --git a/libweston-desktop/xdg-shell.c b/libweston-desktop/xdg-shell.c deleted file mode 100644 index 4a9eb97..0000000 --- a/libweston-desktop/xdg-shell.c +++ /dev/null @@ -1,1498 +0,0 @@ -/* - * Copyright © 2010-2012 Intel Corporation - * Copyright © 2011-2012 Collabora, Ltd. - * Copyright © 2013 Raspberry Pi Foundation - * Copyright © 2016 Quentin "Sardem FF7" Glidic - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include - -#include -#include -#include "xdg-shell-server-protocol.h" - -#include -#include "internal.h" - -/************************************************************************************ - * WARNING: This file implements the stable xdg shell protocol. - * Any changes to this file may also need to be added to the xdg-shell-v6.c file which - * implements the older unstable xdg shell v6 protocol. - ************************************************************************************/ - -#define WD_XDG_SHELL_PROTOCOL_VERSION 1 - -static const char *weston_desktop_xdg_toplevel_role = "xdg_toplevel"; -static const char *weston_desktop_xdg_popup_role = "xdg_popup"; - -struct weston_desktop_xdg_positioner { - struct weston_desktop *desktop; - struct weston_desktop_client *client; - struct wl_resource *resource; - - struct weston_size size; - struct weston_geometry anchor_rect; - enum xdg_positioner_anchor anchor; - enum xdg_positioner_gravity gravity; - enum xdg_positioner_constraint_adjustment constraint_adjustment; - struct weston_position offset; -}; - -enum weston_desktop_xdg_surface_role { - WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE, - WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL, - WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP, -}; - -struct weston_desktop_xdg_surface { - struct wl_resource *resource; - struct weston_desktop *desktop; - struct weston_surface *surface; - struct weston_desktop_surface *desktop_surface; - bool configured; - struct wl_event_source *configure_idle; - struct wl_list configure_list; /* weston_desktop_xdg_surface_configure::link */ - - bool has_next_geometry; - struct weston_geometry next_geometry; - - enum weston_desktop_xdg_surface_role role; -}; - -struct weston_desktop_xdg_surface_configure { - struct wl_list link; /* weston_desktop_xdg_surface::configure_list */ - uint32_t serial; -}; - -struct weston_desktop_xdg_toplevel_state { - bool maximized; - bool fullscreen; - bool resizing; - bool activated; -}; - -struct weston_desktop_xdg_toplevel_configure { - struct weston_desktop_xdg_surface_configure base; - struct weston_desktop_xdg_toplevel_state state; - struct weston_size size; -}; - -struct weston_desktop_xdg_toplevel { - struct weston_desktop_xdg_surface base; - - struct wl_resource *resource; - bool added; - struct { - struct weston_desktop_xdg_toplevel_state state; - struct weston_size size; - } pending; - struct { - struct weston_desktop_xdg_toplevel_state state; - struct weston_size size; - struct weston_size min_size, max_size; - } next; - struct { - struct weston_desktop_xdg_toplevel_state state; - struct weston_size min_size, max_size; - } current; -}; - -struct weston_desktop_xdg_popup { - struct weston_desktop_xdg_surface base; - - struct wl_resource *resource; - bool committed; - struct weston_desktop_xdg_surface *parent; - struct weston_desktop_seat *seat; - struct weston_geometry geometry; -}; - -#define weston_desktop_surface_role_biggest_size (sizeof(struct weston_desktop_xdg_toplevel)) -#define weston_desktop_surface_configure_biggest_size (sizeof(struct weston_desktop_xdg_toplevel)) - - -static struct weston_geometry -weston_desktop_xdg_positioner_get_geometry(struct weston_desktop_xdg_positioner *positioner, - struct weston_desktop_surface *dsurface, - struct weston_desktop_surface *parent) -{ - struct weston_geometry geometry = { - .x = positioner->offset.x, - .y = positioner->offset.y, - .width = positioner->size.width, - .height = positioner->size.height, - }; - - switch (positioner->anchor) { - case XDG_POSITIONER_ANCHOR_TOP: - case XDG_POSITIONER_ANCHOR_TOP_LEFT: - case XDG_POSITIONER_ANCHOR_TOP_RIGHT: - geometry.y += positioner->anchor_rect.y; - break; - case XDG_POSITIONER_ANCHOR_BOTTOM: - case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT: - case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT: - geometry.y += positioner->anchor_rect.y + positioner->anchor_rect.height; - break; - default: - geometry.y += positioner->anchor_rect.y + positioner->anchor_rect.height / 2; - } - - switch (positioner->anchor) { - case XDG_POSITIONER_ANCHOR_LEFT: - case XDG_POSITIONER_ANCHOR_TOP_LEFT: - case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT: - geometry.x += positioner->anchor_rect.x; - break; - case XDG_POSITIONER_ANCHOR_RIGHT: - case XDG_POSITIONER_ANCHOR_TOP_RIGHT: - case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT: - geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width; - break; - default: - geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width / 2; - } - - switch (positioner->gravity) { - case XDG_POSITIONER_GRAVITY_TOP: - case XDG_POSITIONER_GRAVITY_TOP_LEFT: - case XDG_POSITIONER_GRAVITY_TOP_RIGHT: - geometry.y -= geometry.height; - break; - case XDG_POSITIONER_GRAVITY_BOTTOM: - case XDG_POSITIONER_GRAVITY_BOTTOM_LEFT: - case XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT: - geometry.y = geometry.y; - break; - default: - geometry.y -= geometry.height / 2; - } - - switch (positioner->gravity) { - case XDG_POSITIONER_GRAVITY_LEFT: - case XDG_POSITIONER_GRAVITY_TOP_LEFT: - case XDG_POSITIONER_GRAVITY_BOTTOM_LEFT: - geometry.x -= geometry.width; - break; - case XDG_POSITIONER_GRAVITY_RIGHT: - case XDG_POSITIONER_GRAVITY_TOP_RIGHT: - case XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT: - geometry.x = geometry.x; - break; - default: - geometry.x -= geometry.width / 2; - } - - if (positioner->constraint_adjustment == XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE) - return geometry; - - /* TODO: add compositor policy configuration and the code here */ - - return geometry; -} - -static void -weston_desktop_xdg_positioner_protocol_set_size(struct wl_client *wl_client, - struct wl_resource *resource, - int32_t width, int32_t height) -{ - struct weston_desktop_xdg_positioner *positioner = - wl_resource_get_user_data(resource); - - if (width < 1 || height < 1) { - wl_resource_post_error(resource, - XDG_POSITIONER_ERROR_INVALID_INPUT, - "width and height must be positives and non-zero"); - return; - } - - positioner->size.width = width; - positioner->size.height = height; -} - -static void -weston_desktop_xdg_positioner_protocol_set_anchor_rect(struct wl_client *wl_client, - struct wl_resource *resource, - int32_t x, int32_t y, - int32_t width, int32_t height) -{ - struct weston_desktop_xdg_positioner *positioner = - wl_resource_get_user_data(resource); - - if (width < 0 || height < 0) { - wl_resource_post_error(resource, - XDG_POSITIONER_ERROR_INVALID_INPUT, - "width and height must be non-negative"); - return; - } - - positioner->anchor_rect.x = x; - positioner->anchor_rect.y = y; - positioner->anchor_rect.width = width; - positioner->anchor_rect.height = height; -} - -static void -weston_desktop_xdg_positioner_protocol_set_anchor(struct wl_client *wl_client, - struct wl_resource *resource, - enum xdg_positioner_anchor anchor) -{ - struct weston_desktop_xdg_positioner *positioner = - wl_resource_get_user_data(resource); - - positioner->anchor = anchor; -} - -static void -weston_desktop_xdg_positioner_protocol_set_gravity(struct wl_client *wl_client, - struct wl_resource *resource, - enum xdg_positioner_gravity gravity) -{ - struct weston_desktop_xdg_positioner *positioner = - wl_resource_get_user_data(resource); - - positioner->gravity = gravity; -} - -static void -weston_desktop_xdg_positioner_protocol_set_constraint_adjustment(struct wl_client *wl_client, - struct wl_resource *resource, - enum xdg_positioner_constraint_adjustment constraint_adjustment) -{ - struct weston_desktop_xdg_positioner *positioner = - wl_resource_get_user_data(resource); - - positioner->constraint_adjustment = constraint_adjustment; -} - -static void -weston_desktop_xdg_positioner_protocol_set_offset(struct wl_client *wl_client, - struct wl_resource *resource, - int32_t x, int32_t y) -{ - struct weston_desktop_xdg_positioner *positioner = - wl_resource_get_user_data(resource); - - positioner->offset.x = x; - positioner->offset.y = y; -} - -static void -weston_desktop_xdg_positioner_destroy(struct wl_resource *resource) -{ - struct weston_desktop_xdg_positioner *positioner = - wl_resource_get_user_data(resource); - - free(positioner); -} - -static const struct xdg_positioner_interface weston_desktop_xdg_positioner_implementation = { - .destroy = weston_desktop_destroy_request, - .set_size = weston_desktop_xdg_positioner_protocol_set_size, - .set_anchor_rect = weston_desktop_xdg_positioner_protocol_set_anchor_rect, - .set_anchor = weston_desktop_xdg_positioner_protocol_set_anchor, - .set_gravity = weston_desktop_xdg_positioner_protocol_set_gravity, - .set_constraint_adjustment = weston_desktop_xdg_positioner_protocol_set_constraint_adjustment, - .set_offset = weston_desktop_xdg_positioner_protocol_set_offset, -}; - -static void -weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface); - -static void -weston_desktop_xdg_toplevel_ensure_added(struct weston_desktop_xdg_toplevel *toplevel) -{ - if (toplevel->added) - return; - - weston_desktop_api_surface_added(toplevel->base.desktop, - toplevel->base.desktop_surface); - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); - toplevel->added = true; -} - -static void -weston_desktop_xdg_toplevel_protocol_set_parent(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *parent_resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - struct weston_desktop_surface *parent = NULL; - - if (parent_resource != NULL) - parent = wl_resource_get_user_data(parent_resource); - - weston_desktop_xdg_toplevel_ensure_added(toplevel); - weston_desktop_api_set_parent(toplevel->base.desktop, dsurface, parent); -} - -static void -weston_desktop_xdg_toplevel_protocol_set_title(struct wl_client *wl_client, - struct wl_resource *resource, - const char *title) -{ - struct weston_desktop_surface *toplevel = - wl_resource_get_user_data(resource); - - weston_desktop_surface_set_title(toplevel, title); -} - -static void -weston_desktop_xdg_toplevel_protocol_set_app_id(struct wl_client *wl_client, - struct wl_resource *resource, - const char *app_id) -{ - struct weston_desktop_surface *toplevel = - wl_resource_get_user_data(resource); - - weston_desktop_surface_set_app_id(toplevel, app_id); -} - -static void -weston_desktop_xdg_toplevel_protocol_show_window_menu(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial, - int32_t x, int32_t y) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_seat *seat = - wl_resource_get_user_data(seat_resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - if (!toplevel->base.configured) { - wl_resource_post_error(toplevel->resource, - XDG_SURFACE_ERROR_NOT_CONSTRUCTED, - "Surface has not been configured yet"); - return; - } - - if (seat == NULL) - return; - - weston_desktop_api_show_window_menu(toplevel->base.desktop, - dsurface, seat, x, y); -} - -static void -weston_desktop_xdg_toplevel_protocol_move(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_seat *seat = - wl_resource_get_user_data(seat_resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - if (!toplevel->base.configured) { - wl_resource_post_error(toplevel->resource, - XDG_SURFACE_ERROR_NOT_CONSTRUCTED, - "Surface has not been configured yet"); - return; - } - - if (seat == NULL) - return; - - weston_desktop_api_move(toplevel->base.desktop, dsurface, seat, serial); -} - -static void -weston_desktop_xdg_toplevel_protocol_resize(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial, - enum xdg_toplevel_resize_edge edges) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_seat *seat = - wl_resource_get_user_data(seat_resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - enum weston_desktop_surface_edge surf_edges = - (enum weston_desktop_surface_edge) edges; - - if (!toplevel->base.configured) { - wl_resource_post_error(toplevel->resource, - XDG_SURFACE_ERROR_NOT_CONSTRUCTED, - "Surface has not been configured yet"); - return; - } - - if (seat == NULL) - return; - - weston_desktop_api_resize(toplevel->base.desktop, - dsurface, seat, serial, surf_edges); -} - -static void -weston_desktop_xdg_toplevel_ack_configure(struct weston_desktop_xdg_toplevel *toplevel, - struct weston_desktop_xdg_toplevel_configure *configure) -{ - toplevel->next.state = configure->state; - toplevel->next.size = configure->size; -} - -static void -weston_desktop_xdg_toplevel_protocol_set_min_size(struct wl_client *wl_client, - struct wl_resource *resource, - int32_t width, int32_t height) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - toplevel->next.min_size.width = width; - toplevel->next.min_size.height = height; -} - -static void -weston_desktop_xdg_toplevel_protocol_set_max_size(struct wl_client *wl_client, - struct wl_resource *resource, - int32_t width, int32_t height) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - toplevel->next.max_size.width = width; - toplevel->next.max_size.height = height; -} - -static void -weston_desktop_xdg_toplevel_protocol_set_maximized(struct wl_client *wl_client, - struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_xdg_toplevel_ensure_added(toplevel); - weston_desktop_api_maximized_requested(toplevel->base.desktop, dsurface, true); -} - -static void -weston_desktop_xdg_toplevel_protocol_unset_maximized(struct wl_client *wl_client, - struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_xdg_toplevel_ensure_added(toplevel); - weston_desktop_api_maximized_requested(toplevel->base.desktop, dsurface, false); -} - -static void -weston_desktop_xdg_toplevel_protocol_set_fullscreen(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *output_resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - struct weston_output *output = NULL; - - if (output_resource != NULL) - output = weston_head_from_resource(output_resource)->output; - - weston_desktop_xdg_toplevel_ensure_added(toplevel); - weston_desktop_api_fullscreen_requested(toplevel->base.desktop, dsurface, - true, output); -} - -static void -weston_desktop_xdg_toplevel_protocol_unset_fullscreen(struct wl_client *wl_client, - struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_xdg_toplevel_ensure_added(toplevel); - weston_desktop_api_fullscreen_requested(toplevel->base.desktop, dsurface, - false, NULL); -} - -static void -weston_desktop_xdg_toplevel_protocol_set_minimized(struct wl_client *wl_client, - struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_xdg_toplevel_ensure_added(toplevel); - weston_desktop_api_minimized_requested(toplevel->base.desktop, dsurface); -} - -static void -weston_desktop_xdg_toplevel_send_configure(struct weston_desktop_xdg_toplevel *toplevel, - struct weston_desktop_xdg_toplevel_configure *configure) -{ - uint32_t *s; - struct wl_array states; - - configure->state = toplevel->pending.state; - configure->size = toplevel->pending.size; - - wl_array_init(&states); - if (toplevel->pending.state.maximized) { - s = wl_array_add(&states, sizeof(uint32_t)); - *s = XDG_TOPLEVEL_STATE_MAXIMIZED; - } - if (toplevel->pending.state.fullscreen) { - s = wl_array_add(&states, sizeof(uint32_t)); - *s = XDG_TOPLEVEL_STATE_FULLSCREEN; - } - if (toplevel->pending.state.resizing) { - s = wl_array_add(&states, sizeof(uint32_t)); - *s = XDG_TOPLEVEL_STATE_RESIZING; - } - if (toplevel->pending.state.activated) { - s = wl_array_add(&states, sizeof(uint32_t)); - *s = XDG_TOPLEVEL_STATE_ACTIVATED; - } - - xdg_toplevel_send_configure(toplevel->resource, - toplevel->pending.size.width, - toplevel->pending.size.height, - &states); - - wl_array_release(&states); -}; - -static void -weston_desktop_xdg_toplevel_set_maximized(struct weston_desktop_surface *dsurface, - void *user_data, bool maximized) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - toplevel->pending.state.maximized = maximized; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); -} - -static void -weston_desktop_xdg_toplevel_set_fullscreen(struct weston_desktop_surface *dsurface, - void *user_data, bool fullscreen) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - toplevel->pending.state.fullscreen = fullscreen; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); -} - -static void -weston_desktop_xdg_toplevel_set_resizing(struct weston_desktop_surface *dsurface, - void *user_data, bool resizing) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - toplevel->pending.state.resizing = resizing; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); -} - -static void -weston_desktop_xdg_toplevel_set_activated(struct weston_desktop_surface *dsurface, - void *user_data, bool activated) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - toplevel->pending.state.activated = activated; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); -} - -static void -weston_desktop_xdg_toplevel_set_size(struct weston_desktop_surface *dsurface, - void *user_data, - int32_t width, int32_t height) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - toplevel->pending.size.width = width; - toplevel->pending.size.height = height; - - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); -} - -static void -weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplevel, - int32_t sx, int32_t sy) -{ - struct weston_surface *wsurface = - weston_desktop_surface_get_surface(toplevel->base.desktop_surface); - - if (!wsurface->buffer_ref.buffer && !toplevel->added) { - weston_desktop_xdg_toplevel_ensure_added(toplevel); - return; - } - if (!wsurface->buffer_ref.buffer) - return; - - struct weston_geometry geometry = - weston_desktop_surface_get_geometry(toplevel->base.desktop_surface); - - if (toplevel->next.state.maximized && - (toplevel->next.size.width != geometry.width || - toplevel->next.size.height != geometry.height)) { - struct weston_desktop_client *client = - weston_desktop_surface_get_client(toplevel->base.desktop_surface); - struct wl_resource *client_resource = - weston_desktop_client_get_resource(client); - - wl_resource_post_error(client_resource, - XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, - "xdg_surface buffer (%" PRIi32 " x %" PRIi32 ") " - "does not match the configured maximized state (%" PRIi32 " x %" PRIi32 ")", - geometry.width, geometry.height, - toplevel->next.size.width, - toplevel->next.size.height); - return; - } - - if (toplevel->next.state.fullscreen && - (toplevel->next.size.width < geometry.width || - toplevel->next.size.height < geometry.height)) { - struct weston_desktop_client *client = - weston_desktop_surface_get_client(toplevel->base.desktop_surface); - struct wl_resource *client_resource = - weston_desktop_client_get_resource(client); - - wl_resource_post_error(client_resource, - XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, - "xdg_surface buffer (%" PRIi32 " x %" PRIi32 ") " - "is larger than the configured fullscreen state (%" PRIi32 " x %" PRIi32 ")", - geometry.width, geometry.height, - toplevel->next.size.width, - toplevel->next.size.height); - return; - } - - toplevel->current.state = toplevel->next.state; - toplevel->current.min_size = toplevel->next.min_size; - toplevel->current.max_size = toplevel->next.max_size; - - weston_desktop_api_committed(toplevel->base.desktop, - toplevel->base.desktop_surface, - sx, sy); -} - -static void -weston_desktop_xdg_toplevel_close(struct weston_desktop_xdg_toplevel *toplevel) -{ - xdg_toplevel_send_close(toplevel->resource); -} - -static bool -weston_desktop_xdg_toplevel_get_maximized(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - return toplevel->current.state.maximized; -} - -static bool -weston_desktop_xdg_toplevel_get_fullscreen(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - return toplevel->current.state.fullscreen; -} - -static bool -weston_desktop_xdg_toplevel_get_resizing(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - return toplevel->current.state.resizing; -} - -static bool -weston_desktop_xdg_toplevel_get_activated(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_toplevel *toplevel = user_data; - - return toplevel->current.state.activated; -} - -static void -weston_desktop_xdg_toplevel_destroy(struct weston_desktop_xdg_toplevel *toplevel) -{ - if (toplevel->added) - weston_desktop_api_surface_removed(toplevel->base.desktop, - toplevel->base.desktop_surface); -} - -static void -weston_desktop_xdg_toplevel_resource_destroy(struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - - if (dsurface != NULL) - weston_desktop_surface_resource_destroy(resource); -} - -static const struct xdg_toplevel_interface weston_desktop_xdg_toplevel_implementation = { - .destroy = weston_desktop_destroy_request, - .set_parent = weston_desktop_xdg_toplevel_protocol_set_parent, - .set_title = weston_desktop_xdg_toplevel_protocol_set_title, - .set_app_id = weston_desktop_xdg_toplevel_protocol_set_app_id, - .show_window_menu = weston_desktop_xdg_toplevel_protocol_show_window_menu, - .move = weston_desktop_xdg_toplevel_protocol_move, - .resize = weston_desktop_xdg_toplevel_protocol_resize, - .set_min_size = weston_desktop_xdg_toplevel_protocol_set_min_size, - .set_max_size = weston_desktop_xdg_toplevel_protocol_set_max_size, - .set_maximized = weston_desktop_xdg_toplevel_protocol_set_maximized, - .unset_maximized = weston_desktop_xdg_toplevel_protocol_unset_maximized, - .set_fullscreen = weston_desktop_xdg_toplevel_protocol_set_fullscreen, - .unset_fullscreen = weston_desktop_xdg_toplevel_protocol_unset_fullscreen, - .set_minimized = weston_desktop_xdg_toplevel_protocol_set_minimized, -}; - -static void -weston_desktop_xdg_popup_protocol_grab(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_popup *popup = - weston_desktop_surface_get_implementation_data(dsurface); - struct weston_seat *wseat = wl_resource_get_user_data(seat_resource); - struct weston_desktop_seat *seat = weston_desktop_seat_from_seat(wseat); - struct weston_desktop_surface *topmost; - bool parent_is_toplevel = - popup->parent->role == WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL; - - /* Check that if we have a valid wseat we also got a valid desktop seat */ - if (wseat != NULL && seat == NULL) { - wl_client_post_no_memory(wl_client); - return; - } - - if (popup->committed) { - wl_resource_post_error(popup->resource, - XDG_POPUP_ERROR_INVALID_GRAB, - "xdg_popup already is mapped"); - return; - } - - /* If seat is NULL then get_topmost_surface will return NULL. In - * combination with setting parent_is_toplevel to TRUE here we will - * avoid posting an error, and we will instead gracefully fail the - * grab and dismiss the surface. - * FIXME: this is a hack because currently we cannot check the topmost - * parent with a destroyed weston_seat */ - if (seat == NULL) - parent_is_toplevel = true; - - topmost = weston_desktop_seat_popup_grab_get_topmost_surface(seat); - if ((topmost == NULL && !parent_is_toplevel) || - (topmost != NULL && topmost != popup->parent->desktop_surface)) { - struct weston_desktop_client *client = - weston_desktop_surface_get_client(dsurface); - struct wl_resource *client_resource = - weston_desktop_client_get_resource(client); - - wl_resource_post_error(client_resource, - XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP, - "xdg_popup was not created on the topmost popup"); - return; - } - - popup->seat = seat; - weston_desktop_surface_popup_grab(popup->base.desktop_surface, - popup->seat, serial); -} - -static void -weston_desktop_xdg_popup_send_configure(struct weston_desktop_xdg_popup *popup) -{ - xdg_popup_send_configure(popup->resource, - popup->geometry.x, - popup->geometry.y, - popup->geometry.width, - popup->geometry.height); -} - -static void -weston_desktop_xdg_popup_update_position(struct weston_desktop_surface *dsurface, - void *user_data); - -static void -weston_desktop_xdg_popup_committed(struct weston_desktop_xdg_popup *popup) -{ - struct weston_surface *wsurface = - weston_desktop_surface_get_surface (popup->base.desktop_surface); - struct weston_view *view; - - wl_list_for_each(view, &wsurface->views, surface_link) - weston_view_update_transform(view); - - if (!popup->committed) - weston_desktop_xdg_surface_schedule_configure(&popup->base); - popup->committed = true; - weston_desktop_xdg_popup_update_position(popup->base.desktop_surface, - popup); -} - -static void -weston_desktop_xdg_popup_update_position(struct weston_desktop_surface *dsurface, - void *user_data) -{ -} - -static void -weston_desktop_xdg_popup_close(struct weston_desktop_xdg_popup *popup) -{ - xdg_popup_send_popup_done(popup->resource); -} - -static void -weston_desktop_xdg_popup_destroy(struct weston_desktop_xdg_popup *popup) -{ - struct weston_desktop_surface *topmost; - struct weston_desktop_client *client = - weston_desktop_surface_get_client(popup->base.desktop_surface); - - if (!weston_desktop_surface_get_grab(popup->base.desktop_surface)) - return; - - topmost = weston_desktop_seat_popup_grab_get_topmost_surface(popup->seat); - if (topmost != popup->base.desktop_surface) { - struct wl_resource *client_resource = - weston_desktop_client_get_resource(client); - - wl_resource_post_error(client_resource, - XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP, - "xdg_popup was destroyed while it was not the topmost popup."); - } - - weston_desktop_surface_popup_ungrab(popup->base.desktop_surface, - popup->seat); -} - -static void -weston_desktop_xdg_popup_resource_destroy(struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - - if (dsurface != NULL) - weston_desktop_surface_resource_destroy(resource); -} - -static const struct xdg_popup_interface weston_desktop_xdg_popup_implementation = { - .destroy = weston_desktop_destroy_request, - .grab = weston_desktop_xdg_popup_protocol_grab, -}; - -static void -weston_desktop_xdg_surface_send_configure(void *user_data) -{ - struct weston_desktop_xdg_surface *surface = user_data; - struct weston_desktop_xdg_surface_configure *configure; - - surface->configure_idle = NULL; - - configure = zalloc(weston_desktop_surface_configure_biggest_size); - if (configure == NULL) { - struct weston_desktop_client *client = - weston_desktop_surface_get_client(surface->desktop_surface); - struct wl_client *wl_client = - weston_desktop_client_get_client(client); - wl_client_post_no_memory(wl_client); - return; - } - wl_list_insert(surface->configure_list.prev, &configure->link); - configure->serial = - wl_display_next_serial(weston_desktop_get_display(surface->desktop)); - - switch (surface->role) { - case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: - assert(0 && "not reached"); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: - weston_desktop_xdg_toplevel_send_configure((struct weston_desktop_xdg_toplevel *) surface, - (struct weston_desktop_xdg_toplevel_configure *) configure); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: - weston_desktop_xdg_popup_send_configure((struct weston_desktop_xdg_popup *) surface); - break; - } - - xdg_surface_send_configure(surface->resource, configure->serial); -} - -static bool -weston_desktop_xdg_toplevel_state_compare(struct weston_desktop_xdg_toplevel *toplevel) -{ - struct { - struct weston_desktop_xdg_toplevel_state state; - struct weston_size size; - } configured; - - if (!toplevel->base.configured) - return false; - - if (wl_list_empty(&toplevel->base.configure_list)) { - /* Last configure is actually the current state, just use it */ - configured.state = toplevel->current.state; - configured.size.width = toplevel->base.surface->width; - configured.size.height = toplevel->base.surface->height; - } else { - struct weston_desktop_xdg_toplevel_configure *configure = - wl_container_of(toplevel->base.configure_list.prev, - configure, base.link); - - configured.state = configure->state; - configured.size = configure->size; - } - - if (toplevel->pending.state.activated != configured.state.activated) - return false; - if (toplevel->pending.state.fullscreen != configured.state.fullscreen) - return false; - if (toplevel->pending.state.maximized != configured.state.maximized) - return false; - if (toplevel->pending.state.resizing != configured.state.resizing) - return false; - - if (toplevel->pending.size.width == configured.size.width && - toplevel->pending.size.height == configured.size.height) - return true; - - if (toplevel->pending.size.width == 0 && - toplevel->pending.size.height == 0) - return true; - - return false; -} - -static void -weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface) -{ - struct wl_display *display = weston_desktop_get_display(surface->desktop); - struct wl_event_loop *loop = wl_display_get_event_loop(display); - bool pending_same = false; - - switch (surface->role) { - case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: - assert(0 && "not reached"); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: - pending_same = weston_desktop_xdg_toplevel_state_compare((struct weston_desktop_xdg_toplevel *) surface); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: - break; - } - - if (surface->configure_idle != NULL) { - if (!pending_same) - return; - - wl_event_source_remove(surface->configure_idle); - surface->configure_idle = NULL; - } else { - if (pending_same) - return; - - surface->configure_idle = - wl_event_loop_add_idle(loop, - weston_desktop_xdg_surface_send_configure, - surface); - } -} - -static void -weston_desktop_xdg_surface_protocol_get_toplevel(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t id) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_surface *wsurface = - weston_desktop_surface_get_surface(dsurface); - struct weston_desktop_xdg_toplevel *toplevel = - weston_desktop_surface_get_implementation_data(dsurface); - - if (weston_surface_set_role(wsurface, weston_desktop_xdg_toplevel_role, - resource, XDG_WM_BASE_ERROR_ROLE) < 0) - return; - - toplevel->resource = - weston_desktop_surface_add_resource(toplevel->base.desktop_surface, - &xdg_toplevel_interface, - &weston_desktop_xdg_toplevel_implementation, - id, weston_desktop_xdg_toplevel_resource_destroy); - if (toplevel->resource == NULL) - return; - - toplevel->base.role = WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL; -} - -static void -weston_desktop_xdg_surface_protocol_get_popup(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t id, - struct wl_resource *parent_resource, - struct wl_resource *positioner_resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_surface *wsurface = - weston_desktop_surface_get_surface(dsurface); - struct weston_desktop_xdg_popup *popup = - weston_desktop_surface_get_implementation_data(dsurface); - struct weston_desktop_surface *parent_surface; - struct weston_desktop_xdg_surface *parent; - struct weston_desktop_xdg_positioner *positioner = - wl_resource_get_user_data(positioner_resource); - - /* Popup parents are allowed to be non-null, but only if a parent is - * specified 'using some other protocol' before committing. Since we - * don't support such a protocol yet, clients cannot legitimately - * create a popup with a non-null parent. */ - if (!parent_resource) { - wl_resource_post_error(resource, - XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT, - "popup parent must be non-null"); - return; - } - - parent_surface = wl_resource_get_user_data(parent_resource); - parent = weston_desktop_surface_get_implementation_data(parent_surface); - - /* Checking whether the size and anchor rect both have a positive size - * is enough to verify both have been correctly set */ - if (positioner->size.width == 0 || positioner->anchor_rect.width == 0 || - positioner->anchor_rect.height == 0) { - wl_resource_post_error(resource, - XDG_WM_BASE_ERROR_INVALID_POSITIONER, - "positioner object is not complete"); - return; - } - - if (weston_surface_set_role(wsurface, weston_desktop_xdg_popup_role, - resource, XDG_WM_BASE_ERROR_ROLE) < 0) - return; - - popup->resource = - weston_desktop_surface_add_resource(popup->base.desktop_surface, - &xdg_popup_interface, - &weston_desktop_xdg_popup_implementation, - id, weston_desktop_xdg_popup_resource_destroy); - if (popup->resource == NULL) - return; - - popup->base.role = WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP; - popup->parent = parent; - - popup->geometry = - weston_desktop_xdg_positioner_get_geometry(positioner, - dsurface, - parent_surface); - - weston_desktop_surface_set_relative_to(popup->base.desktop_surface, - parent_surface, - popup->geometry.x, - popup->geometry.y, - true); -} - -static bool -weston_desktop_xdg_surface_check_role(struct weston_desktop_xdg_surface *surface) -{ - struct weston_surface *wsurface = - weston_desktop_surface_get_surface(surface->desktop_surface); - const char *role; - - role = weston_surface_get_role(wsurface); - if (role != NULL && - (role == weston_desktop_xdg_toplevel_role || - role == weston_desktop_xdg_popup_role)) - return true; - - wl_resource_post_error(surface->resource, - XDG_SURFACE_ERROR_NOT_CONSTRUCTED, - "xdg_surface must have a role"); - return false; -} - -static void -weston_desktop_xdg_surface_protocol_set_window_geometry(struct wl_client *wl_client, - struct wl_resource *resource, - int32_t x, int32_t y, - int32_t width, int32_t height) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - if (!weston_desktop_xdg_surface_check_role(surface)) - return; - - surface->has_next_geometry = true; - surface->next_geometry.x = x; - surface->next_geometry.y = y; - surface->next_geometry.width = width; - surface->next_geometry.height = height; -} - -static void -weston_desktop_xdg_surface_protocol_ack_configure(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t serial) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - struct weston_desktop_xdg_surface_configure *configure, *temp; - bool found = false; - - if (!weston_desktop_xdg_surface_check_role(surface)) - return; - - wl_list_for_each_safe(configure, temp, &surface->configure_list, link) { - if (configure->serial < serial) { - wl_list_remove(&configure->link); - free(configure); - } else if (configure->serial == serial) { - wl_list_remove(&configure->link); - found = true; - break; - } else { - break; - } - } - if (!found) { - struct weston_desktop_client *client = - weston_desktop_surface_get_client(dsurface); - struct wl_resource *client_resource = - weston_desktop_client_get_resource(client); - wl_resource_post_error(client_resource, - XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, - "Wrong configure serial: %u", serial); - return; - } - - surface->configured = true; - - switch (surface->role) { - case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: - assert(0 && "not reached"); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: - weston_desktop_xdg_toplevel_ack_configure((struct weston_desktop_xdg_toplevel *) surface, - (struct weston_desktop_xdg_toplevel_configure *) configure); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: - break; - } - - free(configure); -} - -static void -weston_desktop_xdg_surface_ping(struct weston_desktop_surface *dsurface, - uint32_t serial, void *user_data) -{ - struct weston_desktop_client *client = - weston_desktop_surface_get_client(dsurface); - - xdg_wm_base_send_ping(weston_desktop_client_get_resource(client), - serial); -} - -static void -weston_desktop_xdg_surface_committed(struct weston_desktop_surface *dsurface, - void *user_data, - int32_t sx, int32_t sy) -{ - struct weston_desktop_xdg_surface *surface = user_data; - struct weston_surface *wsurface = - weston_desktop_surface_get_surface (dsurface); - - if (wsurface->buffer_ref.buffer && !surface->configured) { - wl_resource_post_error(surface->resource, - XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER, - "xdg_surface has never been configured"); - return; - } - - if (surface->has_next_geometry) { - surface->has_next_geometry = false; - weston_desktop_surface_set_geometry(surface->desktop_surface, - surface->next_geometry); - } - - switch (surface->role) { - case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: - wl_resource_post_error(surface->resource, - XDG_SURFACE_ERROR_NOT_CONSTRUCTED, - "xdg_surface must have a role"); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: - weston_desktop_xdg_toplevel_committed((struct weston_desktop_xdg_toplevel *) surface, sx, sy); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: - weston_desktop_xdg_popup_committed((struct weston_desktop_xdg_popup *) surface); - break; - } -} - -static void -weston_desktop_xdg_surface_close(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_surface *surface = user_data; - - switch (surface->role) { - case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: - assert(0 && "not reached"); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: - weston_desktop_xdg_toplevel_close((struct weston_desktop_xdg_toplevel *) surface); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: - weston_desktop_xdg_popup_close((struct weston_desktop_xdg_popup *) surface); - break; - } -} - -static void -weston_desktop_xdg_surface_destroy(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_surface *surface = user_data; - struct weston_desktop_xdg_surface_configure *configure, *temp; - - switch (surface->role) { - case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: - weston_desktop_xdg_toplevel_destroy((struct weston_desktop_xdg_toplevel *) surface); - break; - case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: - weston_desktop_xdg_popup_destroy((struct weston_desktop_xdg_popup *) surface); - break; - } - - if (surface->configure_idle != NULL) - wl_event_source_remove(surface->configure_idle); - - wl_list_for_each_safe(configure, temp, &surface->configure_list, link) - free(configure); - - free(surface); -} - -static const struct xdg_surface_interface weston_desktop_xdg_surface_implementation = { - .destroy = weston_desktop_destroy_request, - .get_toplevel = weston_desktop_xdg_surface_protocol_get_toplevel, - .get_popup = weston_desktop_xdg_surface_protocol_get_popup, - .set_window_geometry = weston_desktop_xdg_surface_protocol_set_window_geometry, - .ack_configure = weston_desktop_xdg_surface_protocol_ack_configure, -}; - -static const struct weston_desktop_surface_implementation weston_desktop_xdg_surface_internal_implementation = { - /* These are used for toplevel only */ - .set_maximized = weston_desktop_xdg_toplevel_set_maximized, - .set_fullscreen = weston_desktop_xdg_toplevel_set_fullscreen, - .set_resizing = weston_desktop_xdg_toplevel_set_resizing, - .set_activated = weston_desktop_xdg_toplevel_set_activated, - .set_size = weston_desktop_xdg_toplevel_set_size, - - .get_maximized = weston_desktop_xdg_toplevel_get_maximized, - .get_fullscreen = weston_desktop_xdg_toplevel_get_fullscreen, - .get_resizing = weston_desktop_xdg_toplevel_get_resizing, - .get_activated = weston_desktop_xdg_toplevel_get_activated, - - /* These are used for popup only */ - .update_position = weston_desktop_xdg_popup_update_position, - - /* Common API */ - .committed = weston_desktop_xdg_surface_committed, - .ping = weston_desktop_xdg_surface_ping, - .close = weston_desktop_xdg_surface_close, - - .destroy = weston_desktop_xdg_surface_destroy, -}; - -static void -weston_desktop_xdg_shell_protocol_create_positioner(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t id) -{ - struct weston_desktop_client *client = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_positioner *positioner; - - positioner = zalloc(sizeof(struct weston_desktop_xdg_positioner)); - if (positioner == NULL) { - wl_client_post_no_memory(wl_client); - return; - } - - positioner->client = client; - positioner->desktop = weston_desktop_client_get_desktop(positioner->client); - - positioner->resource = - wl_resource_create(wl_client, - &xdg_positioner_interface, - wl_resource_get_version(resource), id); - if (positioner->resource == NULL) { - wl_client_post_no_memory(wl_client); - free(positioner); - return; - } - wl_resource_set_implementation(positioner->resource, - &weston_desktop_xdg_positioner_implementation, - positioner, weston_desktop_xdg_positioner_destroy); -} - -static void -weston_desktop_xdg_surface_resource_destroy(struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - - if (dsurface != NULL) - weston_desktop_surface_resource_destroy(resource); -} - -static void -weston_desktop_xdg_shell_protocol_get_xdg_surface(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t id, - struct wl_resource *surface_resource) -{ - struct weston_desktop_client *client = - wl_resource_get_user_data(resource); - struct weston_surface *wsurface = - wl_resource_get_user_data(surface_resource); - struct weston_desktop_xdg_surface *surface; - - surface = zalloc(weston_desktop_surface_role_biggest_size); - if (surface == NULL) { - wl_client_post_no_memory(wl_client); - return; - } - - surface->desktop = weston_desktop_client_get_desktop(client); - surface->surface = wsurface; - wl_list_init(&surface->configure_list); - - surface->desktop_surface = - weston_desktop_surface_create(surface->desktop, client, - surface->surface, - &weston_desktop_xdg_surface_internal_implementation, - surface); - if (surface->desktop_surface == NULL) { - free(surface); - return; - } - - surface->resource = - weston_desktop_surface_add_resource(surface->desktop_surface, - &xdg_surface_interface, - &weston_desktop_xdg_surface_implementation, - id, weston_desktop_xdg_surface_resource_destroy); - if (surface->resource == NULL) - return; - - if (wsurface->buffer_ref.buffer != NULL) { - wl_resource_post_error(surface->resource, - XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER, - "xdg_surface must not have a buffer at creation"); - return; - } -} - -static void -weston_desktop_xdg_shell_protocol_pong(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t serial) -{ - struct weston_desktop_client *client = - wl_resource_get_user_data(resource); - - weston_desktop_client_pong(client, serial); -} - -static const struct xdg_wm_base_interface weston_desktop_xdg_shell_implementation = { - .destroy = weston_desktop_destroy_request, - .create_positioner = weston_desktop_xdg_shell_protocol_create_positioner, - .get_xdg_surface = weston_desktop_xdg_shell_protocol_get_xdg_surface, - .pong = weston_desktop_xdg_shell_protocol_pong, -}; - -static void -weston_desktop_xdg_shell_bind(struct wl_client *client, void *data, - uint32_t version, uint32_t id) -{ - struct weston_desktop *desktop = data; - - weston_desktop_client_create(desktop, client, NULL, - &xdg_wm_base_interface, - &weston_desktop_xdg_shell_implementation, - version, id); -} - -struct wl_global * -weston_desktop_xdg_wm_base_create(struct weston_desktop *desktop, - struct wl_display *display) -{ - return wl_global_create(display, &xdg_wm_base_interface, - WD_XDG_SHELL_PROTOCOL_VERSION, desktop, - weston_desktop_xdg_shell_bind); -} diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c deleted file mode 100644 index 711c8a3..0000000 --- a/libweston-desktop/xwayland.c +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Copyright © 2010-2012 Intel Corporation - * Copyright © 2011-2012 Collabora, Ltd. - * Copyright © 2013 Raspberry Pi Foundation - * Copyright © 2016 Quentin "Sardem FF7" Glidic - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include - -#include - -#include -#include - -#include -#include "internal.h" -#include "xwayland/xwayland-internal-interface.h" - -enum weston_desktop_xwayland_surface_state { - NONE, - TOPLEVEL, - MAXIMIZED, - FULLSCREEN, - TRANSIENT, - XWAYLAND, -}; - -struct weston_desktop_xwayland { - struct weston_desktop *desktop; - struct weston_desktop_client *client; - struct weston_layer layer; -}; - -struct weston_desktop_xwayland_surface { - struct weston_desktop_xwayland *xwayland; - struct weston_desktop *desktop; - struct weston_desktop_surface *surface; - struct wl_listener resource_destroy_listener; - struct weston_view *view; - const struct weston_xwayland_client_interface *client_interface; - struct weston_geometry next_geometry; - bool has_next_geometry; - bool committed; - bool added; - enum weston_desktop_xwayland_surface_state state; -}; - -static void -weston_desktop_xwayland_surface_change_state(struct weston_desktop_xwayland_surface *surface, - enum weston_desktop_xwayland_surface_state state, - struct weston_desktop_surface *parent, - int32_t x, int32_t y) -{ - struct weston_surface *wsurface; - bool to_add = (parent == NULL && state != XWAYLAND); - - assert(state != NONE); - assert(!parent || state == TRANSIENT); - - if (to_add && surface->added) { - surface->state = state; - return; - } - - wsurface = weston_desktop_surface_get_surface(surface->surface); - - if (surface->state != state) { - if (surface->state == XWAYLAND) { - assert(!surface->added); - - weston_desktop_surface_unlink_view(surface->view); - weston_view_destroy(surface->view); - surface->view = NULL; - weston_surface_unmap(wsurface); - } - - if (to_add) { - weston_desktop_surface_unset_relative_to(surface->surface); - weston_desktop_api_surface_added(surface->desktop, - surface->surface); - surface->added = true; - if (surface->state == NONE && surface->committed) - /* We had a race, and wl_surface.commit() was - * faster, just fake a commit to map the - * surface */ - weston_desktop_api_committed(surface->desktop, - surface->surface, - 0, 0); - - } else if (surface->added) { - weston_desktop_api_surface_removed(surface->desktop, - surface->surface); - surface->added = false; - } - - if (state == XWAYLAND) { - assert(!surface->added); - - surface->view = - weston_desktop_surface_create_view(surface->surface); - weston_layer_entry_insert(&surface->xwayland->layer.view_list, - &surface->view->layer_link); - surface->view->is_mapped = true; - wsurface->is_mapped = true; - } - - surface->state = state; - } - - if (parent != NULL) - weston_desktop_surface_set_relative_to(surface->surface, parent, - x, y, false); -} - -static void -weston_desktop_xwayland_surface_committed(struct weston_desktop_surface *dsurface, - void *user_data, - int32_t sx, int32_t sy) -{ - struct weston_desktop_xwayland_surface *surface = user_data; - struct weston_geometry oldgeom; - - assert(dsurface == surface->surface); - surface->committed = true; - -#ifdef WM_DEBUG - weston_log("%s: xwayland surface %p\n", __func__, surface); -#endif - - if (surface->has_next_geometry) { - oldgeom = weston_desktop_surface_get_geometry(surface->surface); - sx -= surface->next_geometry.x - oldgeom.x; - sy -= surface->next_geometry.y - oldgeom.x; - - surface->has_next_geometry = false; - weston_desktop_surface_set_geometry(surface->surface, - surface->next_geometry); - } - - if (surface->added) - weston_desktop_api_committed(surface->desktop, surface->surface, - sx, sy); -} - -static void -weston_desktop_xwayland_surface_set_size(struct weston_desktop_surface *dsurface, - void *user_data, - int32_t width, int32_t height) -{ - struct weston_desktop_xwayland_surface *surface = user_data; - struct weston_surface *wsurface = - weston_desktop_surface_get_surface(surface->surface); - - surface->client_interface->send_configure(wsurface, width, height); -} - -static void -weston_desktop_xwayland_surface_destroy(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xwayland_surface *surface = user_data; - - wl_list_remove(&surface->resource_destroy_listener.link); - - weston_desktop_surface_unset_relative_to(surface->surface); - if (surface->added) - weston_desktop_api_surface_removed(surface->desktop, - surface->surface); - else if (surface->state == XWAYLAND) - weston_desktop_surface_unlink_view(surface->view); - - free(surface); -} - -static bool -weston_desktop_xwayland_surface_get_maximized(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xwayland_surface *surface = user_data; - - return surface->state == MAXIMIZED; -} - -static bool -weston_desktop_xwayland_surface_get_fullscreen(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xwayland_surface *surface = user_data; - - return surface->state == FULLSCREEN; -} - -static const struct weston_desktop_surface_implementation weston_desktop_xwayland_surface_internal_implementation = { - .committed = weston_desktop_xwayland_surface_committed, - .set_size = weston_desktop_xwayland_surface_set_size, - - .get_maximized = weston_desktop_xwayland_surface_get_maximized, - .get_fullscreen = weston_desktop_xwayland_surface_get_fullscreen, - - .destroy = weston_desktop_xwayland_surface_destroy, -}; - -static void -weston_destop_xwayland_resource_destroyed(struct wl_listener *listener, - void *data) -{ - struct weston_desktop_xwayland_surface *surface = - wl_container_of(listener, surface, resource_destroy_listener); - - weston_desktop_surface_destroy(surface->surface); -} - -static struct weston_desktop_xwayland_surface * -create_surface(struct weston_desktop_xwayland *xwayland, - struct weston_surface *wsurface, - const struct weston_xwayland_client_interface *client_interface) -{ - struct weston_desktop_xwayland_surface *surface; - - surface = zalloc(sizeof(struct weston_desktop_xwayland_surface)); - if (surface == NULL) - return NULL; - - surface->xwayland = xwayland; - surface->desktop = xwayland->desktop; - surface->client_interface = client_interface; - - surface->surface = - weston_desktop_surface_create(surface->desktop, - xwayland->client, wsurface, - &weston_desktop_xwayland_surface_internal_implementation, - surface); - if (surface->surface == NULL) { - free(surface); - return NULL; - } - - surface->resource_destroy_listener.notify = - weston_destop_xwayland_resource_destroyed; - wl_resource_add_destroy_listener(wsurface->resource, - &surface->resource_destroy_listener); - - weston_desktop_surface_set_pid(surface->surface, 0); - - return surface; -} - -static void -set_toplevel(struct weston_desktop_xwayland_surface *surface) -{ - weston_desktop_xwayland_surface_change_state(surface, TOPLEVEL, NULL, - 0, 0); -} - -static void -set_toplevel_with_position(struct weston_desktop_xwayland_surface *surface, - int32_t x, int32_t y) -{ - weston_desktop_xwayland_surface_change_state(surface, TOPLEVEL, NULL, - 0, 0); - weston_desktop_api_set_xwayland_position(surface->desktop, - surface->surface, x, y); -} - -static void -set_parent(struct weston_desktop_xwayland_surface *surface, - struct weston_surface *wparent) -{ - struct weston_desktop_surface *parent; - - if (!weston_surface_is_desktop_surface(wparent)) - return; - - parent = weston_surface_get_desktop_surface(wparent); - weston_desktop_api_set_parent(surface->desktop, surface->surface, parent); -} - -static void -set_transient(struct weston_desktop_xwayland_surface *surface, - struct weston_surface *wparent, int x, int y) -{ - struct weston_desktop_surface *parent; - - if (!weston_surface_is_desktop_surface(wparent)) - return; - - parent = weston_surface_get_desktop_surface(wparent); - weston_desktop_xwayland_surface_change_state(surface, TRANSIENT, parent, - x, y); -} - -static void -set_fullscreen(struct weston_desktop_xwayland_surface *surface, - struct weston_output *output) -{ - weston_desktop_xwayland_surface_change_state(surface, FULLSCREEN, NULL, - 0, 0); - weston_desktop_api_fullscreen_requested(surface->desktop, - surface->surface, true, output); -} - -static void -set_xwayland(struct weston_desktop_xwayland_surface *surface, int x, int y) -{ - weston_desktop_xwayland_surface_change_state(surface, XWAYLAND, NULL, - x, y); - weston_view_set_position(surface->view, x, y); -} - -static int -move(struct weston_desktop_xwayland_surface *surface, - struct weston_pointer *pointer) -{ - if (surface->state == TOPLEVEL || - surface->state == MAXIMIZED || - surface->state == FULLSCREEN) - weston_desktop_api_move(surface->desktop, surface->surface, - pointer->seat, pointer->grab_serial); - return 0; -} - -static int -resize(struct weston_desktop_xwayland_surface *surface, - struct weston_pointer *pointer, uint32_t edges) -{ - if (surface->state == TOPLEVEL || - surface->state == MAXIMIZED || - surface->state == FULLSCREEN) - weston_desktop_api_resize(surface->desktop, surface->surface, - pointer->seat, pointer->grab_serial, - edges); - return 0; -} - -static void -set_title(struct weston_desktop_xwayland_surface *surface, const char *title) -{ - weston_desktop_surface_set_title(surface->surface, title); -} - -static void -set_window_geometry(struct weston_desktop_xwayland_surface *surface, - int32_t x, int32_t y, int32_t width, int32_t height) -{ - surface->has_next_geometry = true; - surface->next_geometry.x = x; - surface->next_geometry.y = y; - surface->next_geometry.width = width; - surface->next_geometry.height = height; -} - -static void -set_maximized(struct weston_desktop_xwayland_surface *surface) -{ - weston_desktop_xwayland_surface_change_state(surface, MAXIMIZED, NULL, - 0, 0); - weston_desktop_api_maximized_requested(surface->desktop, - surface->surface, true); -} - -static void -set_pid(struct weston_desktop_xwayland_surface *surface, pid_t pid) -{ - weston_desktop_surface_set_pid(surface->surface, pid); -} - -static const struct weston_desktop_xwayland_interface weston_desktop_xwayland_interface = { - .create_surface = create_surface, - .set_toplevel = set_toplevel, - .set_toplevel_with_position = set_toplevel_with_position, - .set_parent = set_parent, - .set_transient = set_transient, - .set_fullscreen = set_fullscreen, - .set_xwayland = set_xwayland, - .move = move, - .resize = resize, - .set_title = set_title, - .set_window_geometry = set_window_geometry, - .set_maximized = set_maximized, - .set_pid = set_pid, -}; - -void -weston_desktop_xwayland_init(struct weston_desktop *desktop) -{ - struct weston_compositor *compositor = weston_desktop_get_compositor(desktop); - struct weston_desktop_xwayland *xwayland; - - xwayland = zalloc(sizeof(struct weston_desktop_xwayland)); - if (xwayland == NULL) - return; - - xwayland->desktop = desktop; - xwayland->client = weston_desktop_client_create(desktop, NULL, NULL, NULL, NULL, 0, 0); - - weston_layer_init(&xwayland->layer, compositor); - /* We put this layer on top of regular shell surfaces, but hopefully - * below any UI the shell would add */ - weston_layer_set_position(&xwayland->layer, - WESTON_LAYER_POSITION_NORMAL + 1); - - compositor->xwayland = xwayland; - compositor->xwayland_interface = &weston_desktop_xwayland_interface; -} diff --git a/libweston/animation.c b/libweston/animation.c deleted file mode 100644 index 9ca7774..0000000 --- a/libweston/animation.c +++ /dev/null @@ -1,533 +0,0 @@ -/* - * Copyright © 2011 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include "libweston-internal.h" -#include "shared/helpers.h" -#include "shared/timespec-util.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, const struct timespec *time) -{ - 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 (timespec_sub_to_msec(time, &spring->timestamp) > 1000) { - weston_log("unexpectedly large timestamp jump " - "(from %" PRId64 " to %" PRId64 ")\n", - timespec_to_msec(&spring->timestamp), - timespec_to_msec(time)); - timespec_add_msec(&spring->timestamp, time, -1000); - } - - step = 0.01; - while (4 < timespec_sub_to_msec(time, &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; - } - - timespec_add_msec(&spring->timestamp, &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_view_animation_frame_func_t)(struct weston_view_animation *animation); - -struct weston_view_animation { - struct weston_view *view; - struct weston_animation animation; - struct weston_spring spring; - struct weston_transform transform; - struct wl_listener listener; - float start, stop; - weston_view_animation_frame_func_t frame; - weston_view_animation_frame_func_t reset; - weston_view_animation_done_func_t done; - void *data; - void *private; -}; - -WL_EXPORT void -weston_view_animation_destroy(struct weston_view_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_view_geometry_dirty(animation->view); - if (animation->done) - animation->done(animation, animation->data); - free(animation); -} - -static void -handle_animation_view_destroy(struct wl_listener *listener, void *data) -{ - struct weston_view_animation *animation = - container_of(listener, - struct weston_view_animation, listener); - - weston_view_animation_destroy(animation); -} - -static void -weston_view_animation_frame(struct weston_animation *base, - struct weston_output *output, - const struct timespec *time) -{ - struct weston_view_animation *animation = - container_of(base, - struct weston_view_animation, animation); - struct weston_compositor *compositor = - animation->view->surface->compositor; - - if (base->frame_counter <= 1) - animation->spring.timestamp = *time; - - weston_spring_update(&animation->spring, time); - - if (weston_spring_done(&animation->spring)) { - weston_view_schedule_repaint(animation->view); - weston_view_animation_destroy(animation); - return; - } - - if (animation->frame) - animation->frame(animation); - - weston_view_geometry_dirty(animation->view); - weston_view_schedule_repaint(animation->view); - - /* The view's output_mask will be zero if its position is - * offscreen. Animations should always run but as they are also - * run off the repaint cycle, if there's nothing to repaint - * the animation stops running. Therefore if we catch this situation - * and schedule a repaint on all outputs it will be avoided. - */ - if (animation->view->output_mask == 0) - weston_compositor_schedule_repaint(compositor); -} - -static void -idle_animation_destroy(void *data) -{ - struct weston_view_animation *animation = data; - - weston_view_animation_destroy(animation); -} - -static struct weston_view_animation * -weston_view_animation_create(struct weston_view *view, - float start, float stop, - weston_view_animation_frame_func_t frame, - weston_view_animation_frame_func_t reset, - weston_view_animation_done_func_t done, - void *data, - void *private) -{ - struct weston_view_animation *animation; - struct weston_compositor *ec = view->surface->compositor; - struct wl_event_loop *loop; - - animation = malloc(sizeof *animation); - if (!animation) - return NULL; - - animation->view = view; - animation->frame = frame; - animation->reset = reset; - animation->done = done; - animation->data = data; - animation->start = start; - animation->stop = stop; - animation->private = private; - - weston_matrix_init(&animation->transform.matrix); - wl_list_insert(&view->geometry.transformation_list, - &animation->transform.link); - - animation->animation.frame = weston_view_animation_frame; - - animation->listener.notify = handle_animation_view_destroy; - wl_signal_add(&view->destroy_signal, &animation->listener); - - if (view->output) { - wl_list_insert(&view->output->animation_list, - &animation->animation.link); - } else { - wl_list_init(&animation->animation.link); - loop = wl_display_get_event_loop(ec->wl_display); - wl_event_loop_add_idle(loop, idle_animation_destroy, animation); - } - - return animation; -} - -static void -weston_view_animation_run(struct weston_view_animation *animation) -{ - struct timespec zero_time = { 0 }; - - animation->animation.frame_counter = 0; - weston_view_animation_frame(&animation->animation, NULL, &zero_time); -} - -static void -reset_alpha(struct weston_view_animation *animation) -{ - struct weston_view *view = animation->view; - - view->alpha = animation->stop; -} - -static void -zoom_frame(struct weston_view_animation *animation) -{ - struct weston_view *es = animation->view; - 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->surface->width, - -0.5f * es->surface->height, 0); - weston_matrix_scale(&animation->transform.matrix, scale, scale, scale); - weston_matrix_translate(&animation->transform.matrix, - 0.5f * es->surface->width, - 0.5f * es->surface->height, 0); - - es->alpha = animation->spring.current; - if (es->alpha > 1.0) - es->alpha = 1.0; -} - -WL_EXPORT struct weston_view_animation * -weston_zoom_run(struct weston_view *view, float start, float stop, - weston_view_animation_done_func_t done, void *data) -{ - struct weston_view_animation *zoom; - - zoom = weston_view_animation_create(view, start, stop, - zoom_frame, reset_alpha, - done, data, NULL); - - if (zoom == NULL) - return NULL; - - weston_spring_init(&zoom->spring, 300.0, start, stop); - zoom->spring.friction = 1400; - zoom->spring.previous = start - (stop - start) * 0.03; - - weston_view_animation_run(zoom); - - return zoom; -} - -static void -fade_frame(struct weston_view_animation *animation) -{ - if (animation->spring.current > 0.999) - animation->view->alpha = 1; - else if (animation->spring.current < 0.001 ) - animation->view->alpha = 0; - else - animation->view->alpha = animation->spring.current; -} - -WL_EXPORT struct weston_view_animation * -weston_fade_run(struct weston_view *view, - float start, float end, float k, - weston_view_animation_done_func_t done, void *data) -{ - struct weston_view_animation *fade; - - fade = weston_view_animation_create(view, start, end, - fade_frame, reset_alpha, - done, data, NULL); - - if (fade == NULL) - return NULL; - - weston_spring_init(&fade->spring, 1000.0, start, end); - fade->spring.friction = 4000; - fade->spring.previous = start - (end - start) * 0.1; - - view->alpha = start; - - weston_view_animation_run(fade); - - return fade; -} - -WL_EXPORT void -weston_fade_update(struct weston_view_animation *fade, float target) -{ - fade->spring.target = target; - fade->stop = target; -} - -static void -stable_fade_frame(struct weston_view_animation *animation) -{ - struct weston_view *back_view; - - if (animation->spring.current > 0.999) - animation->view->alpha = 1; - else if (animation->spring.current < 0.001 ) - animation->view->alpha = 0; - else - animation->view->alpha = animation->spring.current; - - back_view = (struct weston_view *) animation->private; - back_view->alpha = - (animation->spring.target - animation->view->alpha) / - (1.0 - animation->view->alpha); - weston_view_geometry_dirty(back_view); -} - -WL_EXPORT struct weston_view_animation * -weston_stable_fade_run(struct weston_view *front_view, float start, - struct weston_view *back_view, float end, - weston_view_animation_done_func_t done, void *data) -{ - struct weston_view_animation *fade; - - fade = weston_view_animation_create(front_view, 0, 0, - stable_fade_frame, NULL, - done, data, back_view); - - if (fade == NULL) - return NULL; - - weston_spring_init(&fade->spring, 400, start, end); - fade->spring.friction = 1150; - - front_view->alpha = start; - back_view->alpha = end; - - weston_view_animation_run(fade); - - return fade; -} - -static void -slide_frame(struct weston_view_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_view_animation * -weston_slide_run(struct weston_view *view, float start, float stop, - weston_view_animation_done_func_t done, void *data) -{ - struct weston_view_animation *animation; - - animation = weston_view_animation_create(view, start, stop, - slide_frame, NULL, done, - data, NULL); - if (!animation) - return NULL; - - weston_spring_init(&animation->spring, 400.0, 0.0, 1.0); - animation->spring.friction = 600; - animation->spring.clip = WESTON_SPRING_BOUNCE; - - weston_view_animation_run(animation); - - return animation; -} - -struct weston_move_animation { - int dx; - int dy; - bool reverse; - bool scale; - weston_view_animation_done_func_t done; -}; - -static void -move_frame(struct weston_view_animation *animation) -{ - struct weston_move_animation *move = animation->private; - float scale; - float progress = animation->spring.current; - - if (move->reverse) - progress = 1.0 - progress; - - scale = animation->start + - (animation->stop - animation->start) * - progress; - weston_matrix_init(&animation->transform.matrix); - if (move->scale) - weston_matrix_scale(&animation->transform.matrix, scale, scale, - 1.0f); - weston_matrix_translate(&animation->transform.matrix, - move->dx * progress, move->dy * progress, - 0); -} - -static void -move_done(struct weston_view_animation *animation, void *data) -{ - struct weston_move_animation *move = animation->private; - - if (move->done) - move->done(animation, data); - - free(move); -} - -static struct weston_view_animation * -weston_move_scale_run_internal(struct weston_view *view, int dx, int dy, - float start, float end, bool reverse, bool scale, - weston_view_animation_done_func_t done, - void *data) -{ - struct weston_move_animation *move; - struct weston_view_animation *animation; - - move = malloc(sizeof(*move)); - if (!move) - return NULL; - move->dx = dx; - move->dy = dy; - move->reverse = reverse; - move->scale = scale; - move->done = done; - - animation = weston_view_animation_create(view, start, end, move_frame, - NULL, move_done, data, move); - - if (animation == NULL){ - free(move); - return NULL; - } - - weston_spring_init(&animation->spring, 400.0, 0.0, 1.0); - animation->spring.friction = 1150; - - weston_view_animation_run(animation); - - return animation; -} - -WL_EXPORT struct weston_view_animation * -weston_move_scale_run(struct weston_view *view, int dx, int dy, - float start, float end, bool reverse, - weston_view_animation_done_func_t done, void *data) -{ - return weston_move_scale_run_internal(view, dx, dy, start, end, reverse, - true, done, data); -} - -WL_EXPORT struct weston_view_animation * -weston_move_run(struct weston_view *view, int dx, int dy, - float start, float end, bool reverse, - weston_view_animation_done_func_t done, void *data) -{ - return weston_move_scale_run_internal(view, dx, dy, start, end, reverse, - false, done, data); -} diff --git a/libweston/backend-drm/auth/wayland_drm_auth.h b/libweston/backend-drm/auth/wayland_drm_auth.h deleted file mode 100755 index 773a2ea..0000000 --- a/libweston/backend-drm/auth/wayland_drm_auth.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2021 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef WAYLAND_DRM_AUTH_H -#define WAYLAND_DRM_AUTH_H -#include -#include "libweston/libweston.h" - -#undef LOG_TAG -#undef LOG_DOMAIN -#define LOG_TAG "DRMAUTH" -#define LOG_DOMAIN 0xD001400 - -#define FILENAME (strrchr(__FILE__, '/') ? (strrchr(__FILE__, '/') + 1) : __FILE__) - -#ifndef DRMAUTH_LOGD -#define DRMAUTH_LOGD(format, ...) \ - do { \ - HILOG_DEBUG("[%s@%s:%d] " format "\n", __FUNCTION__, FILENAME, __LINE__, ##__VA_ARGS__); \ - } while (0) -#endif - -#ifndef DRMAUTH_LOGI -#define DRMAUTH_LOGI(format, ...) \ - do { \ - HILOG_INFO("[%s@%s:%d] " format "\n", __FUNCTION__, FILENAME, __LINE__, ##__VA_ARGS__); \ - } while (0) -#endif - -#ifndef DRMAUTH_LOGW -#define DRMAUTH_LOGW(format, ...) \ - do { \ - HILOG_WARN("[%s@%s:%d] " format "\n", __FUNCTION__, FILENAME, __LINE__, ##__VA_ARGS__); \ - } while (0) -#endif - -#ifndef DRMAUTH_LOGE -#define DRMAUTH_LOGE(format, ...) \ - do { \ - HILOG_ERROR("[%s@%s:%d] " format "\n", __FUNCTION__, FILENAME, __LINE__, ##__VA_ARGS__); \ - } while (0) -#endif - - -#ifndef CHK_RETURN -#define CHK_RETURN(val, ret, ...) \ - do { \ - if (val) { \ - __VA_ARGS__; \ - return (ret); \ - } \ - } while (0) -#endif - -#ifndef CHK_RETURN_NO_VALUE -#define CHK_RETURN_NO_VALUE(val, ret, ...) \ - do { \ - if (val) { \ - __VA_ARGS__; \ - return; \ - } \ - } while (0) -#endif - -#endif // WAYLAND_DRM_AUTH_H diff --git a/libweston/backend-drm/auth/wayland_drm_auth_server.c b/libweston/backend-drm/auth/wayland_drm_auth_server.c deleted file mode 100755 index 91c9120..0000000 --- a/libweston/backend-drm/auth/wayland_drm_auth_server.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2021 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "wayland_drm_auth_server.h" -#include -#include -#include -#include "xf86drm.h" -#include "wayland_drm_auth.h" -#include "drm-auth-server-protocol.h" - -typedef struct { - int maserFd; - struct wl_global *drmAuthGlobal; -} DrmAuthMng; - -DrmAuthMng *g_drmAuthMng = NULL; - -static void DrmAuthenticate(struct wl_client *client, struct wl_resource *resource, uint32_t magic) -{ - int ret; - DrmAuthMng *drmAuthMng = wl_resource_get_user_data(resource); - DRMAUTH_LOGD("DrmAuthenticate magic %x", magic); - CHK_RETURN_NO_VALUE((drmAuthMng == NULL), DRMAUTH_LOGE("can not get user data")); - ret = drmAuthMagic(drmAuthMng->maserFd, magic); - if (ret) { - DRMAUTH_LOGE("drm authenticate failed errno : %d", errno); - wl_resource_post_event(resource, WL_DRM_AUTH_STATUS, WL_DRM_AUTH_STATUS_FAILED); - return; - } - wl_resource_post_event(resource, WL_DRM_AUTH_STATUS, WL_DRM_AUTH_STATUS_SUCCESS); -} - -static const struct wl_drm_auth_interface g_drmAuthInterface = { DrmAuthenticate }; - -static void BindDrmAuth(struct wl_client *client, void *data, uint32_t version, uint32_t id) -{ - struct wl_resource *resource; - DRMAUTH_LOGD("BindDrmAuth"); - resource = wl_resource_create(client, &wl_drm_auth_interface, 1, id); - CHK_RETURN_NO_VALUE((resource == NULL), DRMAUTH_LOGE("create resource failed"); wl_client_post_no_memory(client)); - wl_resource_set_implementation(resource, &g_drmAuthInterface, data, NULL); -} - -void DeInitWaylandDrmAuthService() -{ - DRMAUTH_LOGD("DeInitWaylandDrmAuthService"); - if (g_drmAuthMng->drmAuthGlobal != NULL) { - wl_global_destroy(g_drmAuthMng->drmAuthGlobal); - } - if (g_drmAuthMng != NULL) { - free(g_drmAuthMng); - g_drmAuthMng = NULL; - } -} - -int InitWaylandDrmAuthService(struct wl_display *display, int drmMasterFd) -{ - if (g_drmAuthMng != NULL) { - DRMAUTH_LOGI("drm auth service has inited will do nothing"); - } - g_drmAuthMng = calloc(1, sizeof(DrmAuthMng)); - CHK_RETURN((g_drmAuthMng == NULL), -1, DRMAUTH_LOGE("calloc DrmAuthMng Failed errno : %d", errno)); - g_drmAuthMng->maserFd = drmMasterFd; - g_drmAuthMng->drmAuthGlobal = wl_global_create(display, &wl_drm_auth_interface, 1, g_drmAuthMng, BindDrmAuth); - CHK_RETURN((g_drmAuthMng == NULL), -1, DRMAUTH_LOGE("drm auth global create failed"); - DeInitWaylandDrmAuthService()); - return 0; -} diff --git a/libweston/backend-drm/auth/wayland_drm_auth_server.h b/libweston/backend-drm/auth/wayland_drm_auth_server.h deleted file mode 100755 index 01507db..0000000 --- a/libweston/backend-drm/auth/wayland_drm_auth_server.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2021 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef WAYLAND_DRM_AUTH_SERVER_H -#define WAYLAND_DRM_AUTH_SERVER_H - -#include "wayland-server.h" - -/* * - * @brief init the wayland drm authenticate service - * - * it will create drm auth global to the wayland server, and handle the client authenticate resquest - * - * @param display Indicates the pointer of wayland display - * - * @param drmMasterFd Indicates the file descriptor of drm mast - * - * @return Returns 0 if the operation is successful; returns an error code defined in {@link DispErrCode} - * otherwise. - * @since 1.0 - * @version 1.0 - */ -extern int InitWaylandDrmAuthService(struct wl_display *display, int drmMasterFd); - -/* * - * @brief deinit the wayland drm authenticate service - * - * it will destroy the drm auth global and free the memory of drm auth service - * - * otherwise. - * @since 1.0 - * @version 1.0 - */ -extern void DeInitWaylandDrmAuthService(); - -#endif // WAYLAND_DRM_AUTH_SERVER_H \ No newline at end of file diff --git a/libweston/backend-drm/drm-gbm.c b/libweston/backend-drm/drm-gbm.c deleted file mode 100644 index 30609e3..0000000 --- a/libweston/backend-drm/drm-gbm.c +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2011 Intel Corporation - * Copyright © 2017, 2018 Collabora, Ltd. - * Copyright © 2017, 2018 General Electric Company - * Copyright (c) 2018 DisplayLink (UK) Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "drm-internal.h" -#include "pixman-renderer.h" -#include "pixel-formats.h" -#include "renderer-gl/gl-renderer.h" -#include "shared/weston-egl-ext.h" -#include "linux-dmabuf.h" -#include "linux-explicit-synchronization.h" - -struct gl_renderer_interface *gl_renderer; - -static struct gbm_device * -create_gbm_device(int fd) -{ - struct gbm_device *gbm; - - gl_renderer = weston_load_module("gl-renderer.so", - "gl_renderer_interface"); - if (!gl_renderer) - return NULL; - - /* GBM will load a dri driver, but even though they need symbols from - * libglapi, in some version of Mesa they are not linked to it. Since - * only the gl-renderer module links to it, the call above won't make - * these symbols globally available, and loading the DRI driver fails. - * Workaround this by dlopen()'ing libglapi with RTLD_GLOBAL. */ - dlopen("libglapi.so.0", RTLD_LAZY | RTLD_GLOBAL); - - gbm = gbm_create_device(fd); - - return gbm; -} - -/* When initializing EGL, if the preferred buffer format isn't available - * we may be able to substitute an ARGB format for an XRGB one. - * - * This returns 0 if substitution isn't possible, but 0 might be a - * legitimate format for other EGL platforms, so the caller is - * responsible for checking for 0 before calling gl_renderer->create(). - * - * This works around https://bugs.freedesktop.org/show_bug.cgi?id=89689 - * but it's entirely possible we'll see this again on other implementations. - */ -static uint32_t -fallback_format_for(uint32_t format) -{ - const struct pixel_format_info *pf; - - pf = pixel_format_get_info_by_opaque_substitute(format); - if (!pf) - return 0; - - return pf->format; -} - -static int -drm_backend_create_gl_renderer(struct drm_backend *b) -{ - uint32_t format[3] = { - b->gbm_format, - fallback_format_for(b->gbm_format), - 0, - }; - struct gl_renderer_display_options options = { - .egl_platform = EGL_PLATFORM_GBM_KHR, - .egl_native_display = b->gbm, - .egl_surface_type = EGL_WINDOW_BIT, - .drm_formats = format, - .drm_formats_count = 2, - }; - - if (format[1]) - options.drm_formats_count = 3; - - if (gl_renderer->display_create(b->compositor, &options) < 0) - return -1; - - return 0; -} - -int -init_egl(struct drm_backend *b) -{ - b->gbm = create_gbm_device(b->drm.fd); - - if (!b->gbm) - return -1; - - if (drm_backend_create_gl_renderer(b) < 0) { - gbm_device_destroy(b->gbm); - return -1; - } - - return 0; -} - -static void drm_output_fini_cursor_egl(struct drm_output *output) -{ - unsigned int i; - - for (i = 0; i < ARRAY_LENGTH(output->gbm_cursor_fb); i++) { - drm_fb_unref(output->gbm_cursor_fb[i]); - output->gbm_cursor_fb[i] = NULL; - } -} - -static int -drm_output_init_cursor_egl(struct drm_output *output, struct drm_backend *b) -{ - unsigned int i; - - /* No point creating cursors if we don't have a plane for them. */ - if (!output->cursor_plane) - return 0; - - for (i = 0; i < ARRAY_LENGTH(output->gbm_cursor_fb); i++) { - struct gbm_bo *bo; - - bo = gbm_bo_create(b->gbm, b->cursor_width, b->cursor_height, - GBM_FORMAT_ARGB8888, - GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE); - if (!bo) - goto err; - - output->gbm_cursor_fb[i] = - drm_fb_get_from_bo(bo, b, false, BUFFER_CURSOR); - if (!output->gbm_cursor_fb[i]) { - gbm_bo_destroy(bo); - goto err; - } - output->gbm_cursor_handle[i] = gbm_bo_get_handle(bo).s32; - } - - return 0; - -err: - weston_log("cursor buffers unavailable, using gl cursors\n"); - b->cursors_are_broken = true; - drm_output_fini_cursor_egl(output); - return -1; -} - -/* Init output state that depends on gl or gbm */ -int -drm_output_init_egl(struct drm_output *output, struct drm_backend *b) -{ - uint32_t format[2] = { - output->gbm_format, - fallback_format_for(output->gbm_format), - }; - struct gl_renderer_output_options options = { - .drm_formats = format, - .drm_formats_count = 1, - }; - struct weston_mode *mode = output->base.current_mode; - struct drm_plane *plane = output->scanout_plane; - unsigned int i; - - assert(output->gbm_surface == NULL); - - for (i = 0; i < plane->count_formats; i++) { - if (plane->formats[i].format == output->gbm_format) - break; - } - - if (i == plane->count_formats) { - weston_log("format 0x%x not supported by output %s\n", - output->gbm_format, output->base.name); - return -1; - } - -#ifdef HAVE_GBM_MODIFIERS - if (plane->formats[i].count_modifiers > 0) { - output->gbm_surface = - gbm_surface_create_with_modifiers(b->gbm, - mode->width, - mode->height, - output->gbm_format, - plane->formats[i].modifiers, - plane->formats[i].count_modifiers); - } - - /* If allocating with modifiers fails, try again without. This can - * happen when the KMS display device supports modifiers but the - * GBM driver does not, e.g. the old i915 Mesa driver. */ - if (!output->gbm_surface) -#endif - { - output->gbm_surface = - gbm_surface_create(b->gbm, mode->width, mode->height, - output->gbm_format, - output->gbm_bo_flags); - } - - if (!output->gbm_surface) { - weston_log("failed to create gbm surface\n"); - return -1; - } - - if (options.drm_formats[1]) - options.drm_formats_count = 2; - options.window_for_legacy = (EGLNativeWindowType) output->gbm_surface; - options.window_for_platform = output->gbm_surface; - if (gl_renderer->output_window_create(&output->base, &options) < 0) { - weston_log("failed to create gl renderer output state\n"); - gbm_surface_destroy(output->gbm_surface); - output->gbm_surface = NULL; - return -1; - } - - drm_output_init_cursor_egl(output, b); - - return 0; -} - -void -drm_output_fini_egl(struct drm_output *output) -{ - struct drm_backend *b = to_drm_backend(output->base.compositor); - - /* Destroying the GBM surface will destroy all our GBM buffers, - * regardless of refcount. Ensure we destroy them here. */ - if (!b->shutting_down && - output->scanout_plane->state_cur->fb && - output->scanout_plane->state_cur->fb->type == BUFFER_GBM_SURFACE) { - drm_plane_reset_state(output->scanout_plane); - } - - gl_renderer->output_destroy(&output->base); - gbm_surface_destroy(output->gbm_surface); - output->gbm_surface = NULL; - drm_output_fini_cursor_egl(output); -} - -struct drm_fb * -drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage) -{ - struct drm_output *output = state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); - struct gbm_bo *bo; - struct drm_fb *ret; - - output->base.compositor->renderer->repaint_output(&output->base, - damage); - - bo = gbm_surface_lock_front_buffer(output->gbm_surface); - if (!bo) { - weston_log("failed to lock front buffer: %s\n", - strerror(errno)); - return NULL; - } - - /* The renderer always produces an opaque image. */ - ret = drm_fb_get_from_bo(bo, b, true, BUFFER_GBM_SURFACE); - if (!ret) { - weston_log("failed to get drm_fb for bo\n"); - gbm_surface_release_buffer(output->gbm_surface, bo); - return NULL; - } - ret->gbm_surface = output->gbm_surface; - - return ret; -} - -static void -switch_to_gl_renderer(struct drm_backend *b) -{ - struct drm_output *output; - bool dmabuf_support_inited; - bool linux_explicit_sync_inited; - - if (!b->use_pixman) - return; - - dmabuf_support_inited = !!b->compositor->renderer->import_dmabuf; - linux_explicit_sync_inited = - b->compositor->capabilities & WESTON_CAP_EXPLICIT_SYNC; - - weston_log("Switching to GL renderer\n"); - - b->gbm = create_gbm_device(b->drm.fd); - if (!b->gbm) { - weston_log("Failed to create gbm device. " - "Aborting renderer switch\n"); - return; - } - - wl_list_for_each(output, &b->compositor->output_list, base.link) - pixman_renderer_output_destroy(&output->base); - - b->compositor->renderer->destroy(b->compositor); - - if (drm_backend_create_gl_renderer(b) < 0) { - gbm_device_destroy(b->gbm); - weston_log("Failed to create GL renderer. Quitting.\n"); - /* FIXME: we need a function to shutdown cleanly */ - assert(0); - } - - wl_list_for_each(output, &b->compositor->output_list, base.link) - drm_output_init_egl(output, b); - - b->use_pixman = 0; - - if (!dmabuf_support_inited && b->compositor->renderer->import_dmabuf) { - if (linux_dmabuf_setup(b->compositor) < 0) - weston_log("Error: initializing dmabuf " - "support failed.\n"); - } - - if (!linux_explicit_sync_inited && - (b->compositor->capabilities & WESTON_CAP_EXPLICIT_SYNC)) { - if (linux_explicit_synchronization_setup(b->compositor) < 0) - weston_log("Error: initializing explicit " - " synchronization support failed.\n"); - } -} - -void -renderer_switch_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data) -{ - struct drm_backend *b = - to_drm_backend(keyboard->seat->compositor); - - switch_to_gl_renderer(b); -} - diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h deleted file mode 100755 index dc55605..0000000 --- a/libweston/backend-drm/drm-internal.h +++ /dev/null @@ -1,831 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2011 Intel Corporation - * Copyright © 2017, 2018 Collabora, Ltd. - * Copyright © 2017, 2018 General Electric Company - * Copyright (c) 2018 DisplayLink (UK) Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#include -#include -#include - -#ifdef BUILD_DRM_GBM -#include -#endif -#include - -#include -#include -#include -#include "shared/helpers.h" -#include "libinput-seat.h" -#include "backend.h" -#include "libweston-internal.h" - -#ifndef DRM_CLIENT_CAP_ASPECT_RATIO -#define DRM_CLIENT_CAP_ASPECT_RATIO 4 -#endif - -#ifndef GBM_BO_USE_CURSOR -#define GBM_BO_USE_CURSOR GBM_BO_USE_CURSOR_64X64 -#endif - -#ifndef GBM_BO_USE_LINEAR -#define GBM_BO_USE_LINEAR (1 << 4) -#endif - -#ifndef DRM_PLANE_ZPOS_INVALID_PLANE -#define DRM_PLANE_ZPOS_INVALID_PLANE 0xffffffffffffffffULL -#endif - -/** - * A small wrapper to print information into the 'drm-backend' debug scope. - * - * The following conventions are used to print variables: - * - * - fixed uint32_t values, including Weston object IDs such as weston_output - * IDs, DRM object IDs such as CRTCs or properties, and GBM/DRM formats: - * "%lu (0x%lx)" (unsigned long) value, (unsigned long) value - * - * - fixed uint64_t values, such as DRM property values (including object IDs - * when used as a value): - * "%llu (0x%llx)" (unsigned long long) value, (unsigned long long) value - * - * - non-fixed-width signed int: - * "%d" value - * - * - non-fixed-width unsigned int: - * "%u (0x%x)" value, value - * - * - non-fixed-width unsigned long: - * "%lu (0x%lx)" value, value - * - * Either the integer or hexadecimal forms may be omitted if it is known that - * one representation is not useful (e.g. width/height in hex are rarely what - * you want). - * - * This is to avoid implicit widening or narrowing when we use fixed-size - * types: uint32_t can be resolved by either unsigned int or unsigned long - * on a 32-bit system but only unsigned int on a 64-bit system, with uint64_t - * being unsigned long long on a 32-bit system and unsigned long on a 64-bit - * system. To avoid confusing side effects, we explicitly cast to the widest - * possible type and use a matching format specifier. - */ -// OHOS hilog -//#define drm_debug(b, ...) \ -// weston_log_scope_printf((b)->debug, __VA_ARGS__) -#define drm_debug(b, fmt, ...) (HILOG_INFO(LOG_CORE, fmt, ##__VA_ARGS__)) - -#define MAX_CLONED_CONNECTORS 4 - -#ifndef DRM_MODE_PICTURE_ASPECT_64_27 -#define DRM_MODE_PICTURE_ASPECT_64_27 3 -#define DRM_MODE_FLAG_PIC_AR_64_27 \ - (DRM_MODE_PICTURE_ASPECT_64_27<<19) -#endif -#ifndef DRM_MODE_PICTURE_ASPECT_256_135 -#define DRM_MODE_PICTURE_ASPECT_256_135 4 -#define DRM_MODE_FLAG_PIC_AR_256_135 \ - (DRM_MODE_PICTURE_ASPECT_256_135<<19) -#endif - - -/** - * Represents the values of an enum-type KMS property - */ -struct drm_property_enum_info { - const char *name; /**< name as string (static, not freed) */ - bool valid; /**< true if value is supported; ignore if false */ - uint64_t value; /**< raw value */ -}; - -/** - * Holds information on a DRM property, including its ID and the enum - * values it holds. - * - * DRM properties are allocated dynamically, and maintained as DRM objects - * within the normal object ID space; they thus do not have a stable ID - * to refer to. This includes enum values, which must be referred to by - * integer values, but these are not stable. - * - * drm_property_info allows a cache to be maintained where Weston can use - * enum values internally to refer to properties, with the mapping to DRM - * ID values being maintained internally. - */ -struct drm_property_info { - const char *name; /**< name as string (static, not freed) */ - uint32_t prop_id; /**< KMS property object ID */ - uint32_t flags; - unsigned int num_enum_values; /**< number of enum values */ - struct drm_property_enum_info *enum_values; /**< array of enum values */ - unsigned int num_range_values; - uint64_t range_values[2]; -}; - -/** - * List of properties attached to DRM planes - */ -enum wdrm_plane_property { - WDRM_PLANE_TYPE = 0, - WDRM_PLANE_SRC_X, - WDRM_PLANE_SRC_Y, - WDRM_PLANE_SRC_W, - WDRM_PLANE_SRC_H, - WDRM_PLANE_CRTC_X, - WDRM_PLANE_CRTC_Y, - WDRM_PLANE_CRTC_W, - WDRM_PLANE_CRTC_H, - WDRM_PLANE_FB_ID, - WDRM_PLANE_CRTC_ID, - WDRM_PLANE_IN_FORMATS, - WDRM_PLANE_IN_FENCE_FD, - WDRM_PLANE_FB_DAMAGE_CLIPS, - WDRM_PLANE_ZPOS, - WDRM_PLANE__COUNT -}; - -/** - * Possible values for the WDRM_PLANE_TYPE property. - */ -enum wdrm_plane_type { - WDRM_PLANE_TYPE_PRIMARY = 0, - WDRM_PLANE_TYPE_CURSOR, - WDRM_PLANE_TYPE_OVERLAY, - WDRM_PLANE_TYPE__COUNT -}; - -/** - * List of properties attached to a DRM connector - */ -enum wdrm_connector_property { - WDRM_CONNECTOR_EDID = 0, - WDRM_CONNECTOR_DPMS, - WDRM_CONNECTOR_CRTC_ID, - WDRM_CONNECTOR_NON_DESKTOP, - WDRM_CONNECTOR_CONTENT_PROTECTION, - WDRM_CONNECTOR_HDCP_CONTENT_TYPE, - WDRM_CONNECTOR_PANEL_ORIENTATION, - WDRM_CONNECTOR__COUNT -}; - -enum wdrm_content_protection_state { - WDRM_CONTENT_PROTECTION_UNDESIRED = 0, - WDRM_CONTENT_PROTECTION_DESIRED, - WDRM_CONTENT_PROTECTION_ENABLED, - WDRM_CONTENT_PROTECTION__COUNT -}; - -enum wdrm_hdcp_content_type { - WDRM_HDCP_CONTENT_TYPE0 = 0, - WDRM_HDCP_CONTENT_TYPE1, - WDRM_HDCP_CONTENT_TYPE__COUNT -}; - -enum wdrm_dpms_state { - WDRM_DPMS_STATE_OFF = 0, - WDRM_DPMS_STATE_ON, - WDRM_DPMS_STATE_STANDBY, /* unused */ - WDRM_DPMS_STATE_SUSPEND, /* unused */ - WDRM_DPMS_STATE__COUNT -}; - -enum wdrm_panel_orientation { - WDRM_PANEL_ORIENTATION_NORMAL = 0, - WDRM_PANEL_ORIENTATION_UPSIDE_DOWN, - WDRM_PANEL_ORIENTATION_LEFT_SIDE_UP, - WDRM_PANEL_ORIENTATION_RIGHT_SIDE_UP, - WDRM_PANEL_ORIENTATION__COUNT -}; - -/** - * List of properties attached to DRM CRTCs - */ -enum wdrm_crtc_property { - WDRM_CRTC_MODE_ID = 0, - WDRM_CRTC_ACTIVE, - WDRM_CRTC__COUNT -}; - -struct drm_backend { - struct weston_backend base; - struct weston_compositor *compositor; - - 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; - dev_t devnum; - } drm; - struct gbm_device *gbm; - struct wl_listener session_listener; - uint32_t gbm_format; - - /* we need these parameters in order to not fail drmModeAddFB2() - * due to out of bounds dimensions, and then mistakenly set - * sprites_are_broken: - */ - int min_width, max_width; - int min_height, max_height; - - struct wl_list plane_list; - - void *repaint_data; - - bool state_invalid; - - /* CRTC IDs not used by any enabled output. */ - struct wl_array unused_crtcs; - - bool sprites_are_broken; - bool cursors_are_broken; - - bool universal_planes; - bool atomic_modeset; - - bool use_pixman; - bool use_pixman_shadow; - - struct udev_input input; - - int32_t cursor_width; - int32_t cursor_height; - - uint32_t pageflip_timeout; - - bool shutting_down; - - bool aspect_ratio_supported; - - bool fb_modifiers; - - struct weston_log_scope *debug; -}; - -struct drm_mode { - struct weston_mode base; - drmModeModeInfo mode_info; - uint32_t blob_id; -}; - -enum drm_fb_type { - BUFFER_INVALID = 0, /**< never used */ - BUFFER_CLIENT, /**< directly sourced from client */ - BUFFER_DMABUF, /**< imported from linux_dmabuf client */ - BUFFER_PIXMAN_DUMB, /**< internal Pixman rendering */ - BUFFER_GBM_SURFACE, /**< internal EGL rendering */ - BUFFER_CURSOR, /**< internal cursor buffer */ -}; - -struct drm_fb { - enum drm_fb_type type; - - int refcnt; - - uint32_t fb_id, size; - uint32_t handles[4]; - uint32_t strides[4]; - uint32_t offsets[4]; - int num_planes; - const struct pixel_format_info *format; - uint64_t modifier; - int width, height; - int fd; - struct weston_buffer_reference buffer_ref; - struct weston_buffer_release_reference buffer_release_ref; - - /* Used by gbm fbs */ - struct gbm_bo *bo; - struct gbm_surface *gbm_surface; - - /* 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]; -}; - -/** - * Pending state holds one or more drm_output_state structures, collected from - * performing repaint. This pending state is transient, and only lives between - * beginning a repaint group and flushing the results: after flush, each - * output state will complete and be retired separately. - */ -struct drm_pending_state { - struct drm_backend *backend; - struct wl_list output_list; -}; - -/* - * Output state holds the dynamic state for one Weston output, i.e. a KMS CRTC, - * plus >= 1 each of encoder/connector/plane. Since everything but the planes - * is currently statically assigned per-output, we mainly use this to track - * plane state. - * - * pending_state is set when the output state is owned by a pending_state, - * i.e. when it is being constructed and has not yet been applied. When the - * output state has been applied, the owning pending_state is freed. - */ -struct drm_output_state { - struct drm_pending_state *pending_state; - struct drm_output *output; - struct wl_list link; - enum dpms_enum dpms; - enum weston_hdcp_protection protection; - struct wl_list plane_list; -}; - -/** - * An instance of this class is created each time we believe we have a plane - * suitable to be used by a view as a direct scan-out. The list is initalized - * and populated locally. - */ -struct drm_plane_zpos { - struct drm_plane *plane; - struct wl_list link; /**< :candidate_plane_zpos_list */ -}; - -/** - * Plane state holds the dynamic state for a plane: where it is positioned, - * and which buffer it is currently displaying. - * - * The plane state is owned by an output state, except when setting an initial - * state. See drm_output_state for notes on state object lifetime. - */ -struct drm_plane_state { - struct drm_plane *plane; - struct drm_output *output; - struct drm_output_state *output_state; - - struct drm_fb *fb; - - struct weston_view *ev; /**< maintained for drm_assign_planes only */ - - int32_t src_x, src_y; - uint32_t src_w, src_h; - int32_t dest_x, dest_y; - uint32_t dest_w, dest_h; - - uint64_t zpos; - - bool complete; - - /* We don't own the fd, so we shouldn't close it */ - int in_fence_fd; - - uint32_t damage_blob_id; /* damage to kernel */ - - struct wl_list link; /* drm_output_state::plane_list */ -}; - -/** - * A plane represents one buffer, positioned within a CRTC, and stacked - * relative to other planes on the same CRTC. - * - * Each CRTC has a 'primary plane', which use used to display the classic - * framebuffer contents, as accessed through the legacy drmModeSetCrtc - * call (which combines setting the CRTC's actual physical mode, and the - * properties of the primary plane). - * - * The cursor plane also has its own alternate legacy API. - * - * Other planes are used opportunistically to display content we do not - * wish to blit into the primary plane. These non-primary/cursor planes - * are referred to as 'sprites'. - */ -struct drm_plane { - struct weston_plane base; - - struct drm_backend *backend; - - enum wdrm_plane_type type; - - uint32_t possible_crtcs; - uint32_t plane_id; - uint32_t count_formats; - - struct drm_property_info props[WDRM_PLANE__COUNT]; - - /* The last state submitted to the kernel for this plane. */ - struct drm_plane_state *state_cur; - - uint64_t zpos_min; - uint64_t zpos_max; - - struct wl_list link; - - struct { - uint32_t format; - uint32_t count_modifiers; - uint64_t *modifiers; - } formats[]; -}; - -struct drm_head { - struct weston_head base; - struct drm_backend *backend; - - drmModeConnector *connector; - uint32_t connector_id; - struct drm_edid edid; - - /* Holds the properties for the connector */ - struct drm_property_info props_conn[WDRM_CONNECTOR__COUNT]; - - struct backlight *backlight; - - drmModeModeInfo inherited_mode; /**< Original mode on the connector */ - uint32_t inherited_crtc_id; /**< Original CRTC assignment */ -}; - -struct drm_output { - struct weston_output base; - struct drm_backend *backend; - - uint32_t crtc_id; /* object ID to pass to DRM functions */ - int pipe; /* index of CRTC in resource array / bitmasks */ - - /* Holds the properties for the CRTC */ - struct drm_property_info props_crtc[WDRM_CRTC__COUNT]; - - bool page_flip_pending; - bool atomic_complete_pending; - bool destroy_pending; - bool disable_pending; - bool dpms_off_pending; - - uint32_t gbm_cursor_handle[2]; - struct drm_fb *gbm_cursor_fb[2]; - struct drm_plane *cursor_plane; - struct weston_view *cursor_view; - int current_cursor; - - struct gbm_surface *gbm_surface; - uint32_t gbm_format; - uint32_t gbm_bo_flags; - - /* Plane being displayed directly on the CRTC */ - struct drm_plane *scanout_plane; - - /* The last state submitted to the kernel for this CRTC. */ - struct drm_output_state *state_cur; - /* The previously-submitted state, where the hardware has not - * yet acknowledged completion of state_cur. */ - struct drm_output_state *state_last; - - 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; - - struct wl_event_source *pageflip_timer; - - bool virtual; - - submit_frame_cb virtual_submit_frame; -}; - -static inline struct drm_head * -to_drm_head(struct weston_head *base) -{ - return container_of(base, struct drm_head, base); -} - -static inline struct drm_output * -to_drm_output(struct weston_output *base) -{ - return container_of(base, struct drm_output, base); -} - -static inline struct drm_backend * -to_drm_backend(struct weston_compositor *base) -{ - return container_of(base->backend, struct drm_backend, base); -} - -static inline struct drm_mode * -to_drm_mode(struct weston_mode *base) -{ - return container_of(base, struct drm_mode, base); -} - -static inline const char * -drm_output_get_plane_type_name(struct drm_plane *p) -{ - switch (p->type) { - case WDRM_PLANE_TYPE_PRIMARY: - return "primary"; - case WDRM_PLANE_TYPE_CURSOR: - return "cursor"; - case WDRM_PLANE_TYPE_OVERLAY: - return "overlay"; - default: - assert(0); - // OHOS build - return ""; - break; - } -} - -struct drm_output * -drm_output_find_by_crtc(struct drm_backend *b, uint32_t crtc_id); - -struct drm_head * -drm_head_find_by_connector(struct drm_backend *backend, uint32_t connector_id); - -static inline bool -drm_view_transform_supported(struct weston_view *ev, struct weston_output *output) -{ - struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; - - /* This will incorrectly disallow cases where the combination of - * buffer and view transformations match the output transform. - * Fixing this requires a full analysis of the transformation - * chain. */ - if (ev->transform.enabled && - ev->transform.matrix.type >= WESTON_MATRIX_TRANSFORM_ROTATE) - return false; - - if (viewport->buffer.transform != output->transform) - return false; - - return true; -} - -int -drm_mode_ensure_blob(struct drm_backend *backend, struct drm_mode *mode); - -struct drm_mode * -drm_output_choose_mode(struct drm_output *output, - struct weston_mode *target_mode); -void -update_head_from_connector(struct drm_head *head, - drmModeObjectProperties *props); - -void -drm_mode_list_destroy(struct drm_backend *backend, struct wl_list *mode_list); - -void -drm_output_print_modes(struct drm_output *output); - -int -drm_output_set_mode(struct weston_output *base, - enum weston_drm_backend_output_mode mode, - const char *modeline); - -void -drm_property_info_populate(struct drm_backend *b, - const struct drm_property_info *src, - struct drm_property_info *info, - unsigned int num_infos, - drmModeObjectProperties *props); -uint64_t -drm_property_get_value(struct drm_property_info *info, - const drmModeObjectProperties *props, - uint64_t def); -uint64_t * -drm_property_get_range_values(struct drm_property_info *info, - const drmModeObjectProperties *props); -int -drm_plane_populate_formats(struct drm_plane *plane, const drmModePlane *kplane, - const drmModeObjectProperties *props, - const bool use_modifiers); -void -drm_property_info_free(struct drm_property_info *info, int num_props); - -extern struct drm_property_enum_info plane_type_enums[]; -extern const struct drm_property_info plane_props[]; -extern struct drm_property_enum_info dpms_state_enums[]; -extern struct drm_property_enum_info content_protection_enums[]; -extern struct drm_property_enum_info hdcp_content_type_enums[]; -extern const struct drm_property_info connector_props[]; -extern const struct drm_property_info crtc_props[]; - -int -init_kms_caps(struct drm_backend *b); - -int -drm_pending_state_test(struct drm_pending_state *pending_state); -int -drm_pending_state_apply(struct drm_pending_state *pending_state); -int -drm_pending_state_apply_sync(struct drm_pending_state *pending_state); - -void -drm_output_set_gamma(struct weston_output *output_base, - uint16_t size, uint16_t *r, uint16_t *g, uint16_t *b); - -void -drm_output_update_msc(struct drm_output *output, unsigned int seq); -void -drm_output_update_complete(struct drm_output *output, uint32_t flags, - unsigned int sec, unsigned int usec); -int -on_drm_input(int fd, uint32_t mask, void *data); - -struct drm_fb * -drm_fb_ref(struct drm_fb *fb); -void -drm_fb_unref(struct drm_fb *fb); - -struct drm_fb * -drm_fb_create_dumb(struct drm_backend *b, int width, int height, - uint32_t format); -struct drm_fb * -drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, - bool is_opaque, enum drm_fb_type type); - -#ifdef BUILD_DRM_GBM -extern struct drm_fb * -drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev); -extern bool -drm_can_scanout_dmabuf(struct weston_compositor *ec, - struct linux_dmabuf_buffer *dmabuf); -#else -static inline struct drm_fb * -drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev) -{ - return NULL; -} -static inline bool -drm_can_scanout_dmabuf(struct weston_compositor *ec, - struct linux_dmabuf_buffer *dmabuf) -{ - return false; -} -#endif - -struct drm_pending_state * -drm_pending_state_alloc(struct drm_backend *backend); -void -drm_pending_state_free(struct drm_pending_state *pending_state); -struct drm_output_state * -drm_pending_state_get_output(struct drm_pending_state *pending_state, - struct drm_output *output); - - -/** - * Mode for drm_output_state_duplicate. - */ -enum drm_output_state_duplicate_mode { - DRM_OUTPUT_STATE_CLEAR_PLANES, /**< reset all planes to off */ - DRM_OUTPUT_STATE_PRESERVE_PLANES, /**< preserve plane state */ -}; - -struct drm_output_state * -drm_output_state_alloc(struct drm_output *output, - struct drm_pending_state *pending_state); -struct drm_output_state * -drm_output_state_duplicate(struct drm_output_state *src, - struct drm_pending_state *pending_state, - enum drm_output_state_duplicate_mode plane_mode); -void -drm_output_state_free(struct drm_output_state *state); -struct drm_plane_state * -drm_output_state_get_plane(struct drm_output_state *state_output, - struct drm_plane *plane); -struct drm_plane_state * -drm_output_state_get_existing_plane(struct drm_output_state *state_output, - struct drm_plane *plane); - - - -struct drm_plane_state * -drm_plane_state_alloc(struct drm_output_state *state_output, - struct drm_plane *plane); -struct drm_plane_state * -drm_plane_state_duplicate(struct drm_output_state *state_output, - struct drm_plane_state *src); -void -drm_plane_state_free(struct drm_plane_state *state, bool force); -void -drm_plane_state_put_back(struct drm_plane_state *state); -bool -drm_plane_state_coords_for_view(struct drm_plane_state *state, - struct weston_view *ev, uint64_t zpos); -void -drm_plane_reset_state(struct drm_plane *plane); - -void -drm_assign_planes(struct weston_output *output_base, void *repaint_data); - -bool -drm_plane_is_available(struct drm_plane *plane, struct drm_output *output); - -void -drm_output_render(struct drm_output_state *state, pixman_region32_t *damage); - -int -parse_gbm_format(const char *s, uint32_t default_value, uint32_t *gbm_format); - -extern struct gl_renderer_interface *gl_renderer; - -#ifdef BUILD_DRM_VIRTUAL -extern int -drm_backend_init_virtual_output_api(struct weston_compositor *compositor); -#else -inline static int -drm_backend_init_virtual_output_api(struct weston_compositor *compositor) -{ - return 0; -} -#endif - -#ifdef BUILD_DRM_GBM -int -init_egl(struct drm_backend *b); - -int -drm_output_init_egl(struct drm_output *output, struct drm_backend *b); - -void -drm_output_fini_egl(struct drm_output *output); - -struct drm_fb * -drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage); - -void -renderer_switch_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data); -#else -inline static int -init_egl(struct drm_backend *b) -{ - weston_log("Compiled without GBM/EGL support\n"); - return -1; -} - -inline static int -drm_output_init_egl(struct drm_output *output, struct drm_backend *b) -{ - return -1; -} - -inline static void -drm_output_fini_egl(struct drm_output *output) -{ -} - -inline static struct drm_fb * -drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage) -{ - return NULL; -} - -inline static void -renderer_switch_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data) -{ - weston_log("Compiled without GBM/EGL support\n"); -} -#endif diff --git a/libweston/backend-drm/drm-virtual.c b/libweston/backend-drm/drm-virtual.c deleted file mode 100644 index ebebbbd..0000000 --- a/libweston/backend-drm/drm-virtual.c +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2011 Intel Corporation - * Copyright © 2017, 2018 Collabora, Ltd. - * Copyright © 2017, 2018 General Electric Company - * Copyright (c) 2018 DisplayLink (UK) Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include - -#include "drm-internal.h" -#include "renderer-gl/gl-renderer.h" - -/** - * Create a drm_plane for virtual output - * - * Call drm_virtual_plane_destroy to clean up the plane. - * - * @param b DRM compositor backend - * @param output Output to create internal plane for - */ -static struct drm_plane * -drm_virtual_plane_create(struct drm_backend *b, struct drm_output *output) -{ - struct drm_plane *plane; - - /* num of formats is one */ - plane = zalloc(sizeof(*plane) + sizeof(plane->formats[0])); - if (!plane) { - weston_log("%s: out of memory\n", __func__); - return NULL; - } - - plane->type = WDRM_PLANE_TYPE_PRIMARY; - plane->backend = b; - plane->state_cur = drm_plane_state_alloc(NULL, plane); - plane->state_cur->complete = true; - plane->formats[0].format = output->gbm_format; - plane->count_formats = 1; - if ((output->gbm_bo_flags & GBM_BO_USE_LINEAR) && b->fb_modifiers) { - uint64_t *modifiers = zalloc(sizeof *modifiers); - if (modifiers) { - *modifiers = DRM_FORMAT_MOD_LINEAR; - plane->formats[0].modifiers = modifiers; - plane->formats[0].count_modifiers = 1; - } - } - - weston_plane_init(&plane->base, b->compositor, 0, 0); - wl_list_insert(&b->plane_list, &plane->link); - - return plane; -} - -/** - * Destroy one DRM plane - * - * @param plane Plane to deallocate (will be freed) - */ -static void -drm_virtual_plane_destroy(struct drm_plane *plane) -{ - drm_plane_state_free(plane->state_cur, true); - weston_plane_release(&plane->base); - wl_list_remove(&plane->link); - if (plane->formats[0].modifiers) - free(plane->formats[0].modifiers); - free(plane); -} - -static int -drm_virtual_output_start_repaint_loop(struct weston_output *output_base) -{ - weston_output_finish_frame(output_base, NULL, - WP_PRESENTATION_FEEDBACK_INVALID); - - return 0; -} - -static int -drm_virtual_output_submit_frame(struct drm_output *output, - struct drm_fb *fb) -{ - struct drm_backend *b = to_drm_backend(output->base.compositor); - int fd, ret; - - assert(fb->num_planes == 1); - ret = drmPrimeHandleToFD(b->drm.fd, fb->handles[0], DRM_CLOEXEC, &fd); - if (ret) { - weston_log("drmPrimeHandleFD failed, errno=%d\n", errno); - return -1; - } - - drm_fb_ref(fb); - ret = output->virtual_submit_frame(&output->base, fd, fb->strides[0], - fb); - if (ret < 0) { - drm_fb_unref(fb); - close(fd); - } - return ret; -} - -static int -drm_virtual_output_repaint(struct weston_output *output_base, - pixman_region32_t *damage, - void *repaint_data) -{ - struct drm_pending_state *pending_state = repaint_data; - struct drm_output_state *state = NULL; - struct drm_output *output = to_drm_output(output_base); - struct drm_plane *scanout_plane = output->scanout_plane; - struct drm_plane_state *scanout_state; - - assert(output->virtual); - - if (output->disable_pending || output->destroy_pending) - goto err; - - /* Drop frame if there isn't free buffers */ - if (!gbm_surface_has_free_buffers(output->gbm_surface)) { - weston_log("%s: Drop frame!!\n", __func__); - return -1; - } - - assert(!output->state_last); - - /* If planes have been disabled in the core, we might not have - * hit assign_planes at all, so might not have valid output state - * here. */ - state = drm_pending_state_get_output(pending_state, output); - if (!state) - state = drm_output_state_duplicate(output->state_cur, - pending_state, - DRM_OUTPUT_STATE_CLEAR_PLANES); - - drm_output_render(state, damage); - scanout_state = drm_output_state_get_plane(state, scanout_plane); - if (!scanout_state || !scanout_state->fb) - goto err; - - if (drm_virtual_output_submit_frame(output, scanout_state->fb) < 0) - goto err; - - return 0; - -err: - drm_output_state_free(state); - return -1; -} - -static void -drm_virtual_output_deinit(struct weston_output *base) -{ - struct drm_output *output = to_drm_output(base); - - drm_output_fini_egl(output); - - drm_virtual_plane_destroy(output->scanout_plane); -} - -static void -drm_virtual_output_destroy(struct weston_output *base) -{ - struct drm_output *output = to_drm_output(base); - - assert(output->virtual); - - if (output->base.enabled) - drm_virtual_output_deinit(&output->base); - - weston_output_release(&output->base); - - drm_output_state_free(output->state_cur); - - free(output); -} - -static int -drm_virtual_output_enable(struct weston_output *output_base) -{ - struct drm_output *output = to_drm_output(output_base); - struct drm_backend *b = to_drm_backend(output_base->compositor); - - assert(output->virtual); - - if (b->use_pixman) { - weston_log("Not support pixman renderer on Virtual output\n"); - goto err; - } - - if (!output->virtual_submit_frame) { - weston_log("The virtual_submit_frame hook is not set\n"); - goto err; - } - - output->scanout_plane = drm_virtual_plane_create(b, output); - if (!output->scanout_plane) { - weston_log("Failed to find primary plane for output %s\n", - output->base.name); - return -1; - } - - if (drm_output_init_egl(output, b) < 0) { - weston_log("Failed to init output gl state\n"); - goto err; - } - - output->base.start_repaint_loop = drm_virtual_output_start_repaint_loop; - output->base.repaint = drm_virtual_output_repaint; - output->base.assign_planes = drm_assign_planes; - output->base.set_dpms = NULL; - output->base.switch_mode = NULL; - output->base.gamma_size = 0; - output->base.set_gamma = NULL; - - weston_compositor_stack_plane(b->compositor, - &output->scanout_plane->base, - &b->compositor->primary_plane); - - return 0; -err: - return -1; -} - -static int -drm_virtual_output_disable(struct weston_output *base) -{ - struct drm_output *output = to_drm_output(base); - - assert(output->virtual); - - if (output->base.enabled) - drm_virtual_output_deinit(&output->base); - - return 0; -} - -static struct weston_output * -drm_virtual_output_create(struct weston_compositor *c, char *name) -{ - struct drm_output *output; - - output = zalloc(sizeof *output); - if (!output) - return NULL; - - output->virtual = true; - output->gbm_bo_flags = GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING; - - weston_output_init(&output->base, c, name); - - output->base.enable = drm_virtual_output_enable; - output->base.destroy = drm_virtual_output_destroy; - output->base.disable = drm_virtual_output_disable; - output->base.attach_head = NULL; - - output->state_cur = drm_output_state_alloc(output, NULL); - - weston_compositor_add_pending_output(&output->base, c); - - return &output->base; -} - -static uint32_t -drm_virtual_output_set_gbm_format(struct weston_output *base, - const char *gbm_format) -{ - struct drm_output *output = to_drm_output(base); - struct drm_backend *b = to_drm_backend(base->compositor); - - if (parse_gbm_format(gbm_format, b->gbm_format, &output->gbm_format) == -1) - output->gbm_format = b->gbm_format; - - return output->gbm_format; -} - -static void -drm_virtual_output_set_submit_frame_cb(struct weston_output *output_base, - submit_frame_cb cb) -{ - struct drm_output *output = to_drm_output(output_base); - - output->virtual_submit_frame = cb; -} - -static int -drm_virtual_output_get_fence_fd(struct weston_output *output_base) -{ - return gl_renderer->create_fence_fd(output_base); -} - -static void -drm_virtual_output_buffer_released(struct drm_fb *fb) -{ - drm_fb_unref(fb); -} - -static void -drm_virtual_output_finish_frame(struct weston_output *output_base, - struct timespec *stamp, - uint32_t presented_flags) -{ - struct drm_output *output = to_drm_output(output_base); - struct drm_plane_state *ps; - - wl_list_for_each(ps, &output->state_cur->plane_list, link) - ps->complete = true; - - drm_output_state_free(output->state_last); - output->state_last = NULL; - - weston_output_finish_frame(&output->base, stamp, presented_flags); - - /* 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 const struct weston_drm_virtual_output_api virt_api = { - drm_virtual_output_create, - drm_virtual_output_set_gbm_format, - drm_virtual_output_set_submit_frame_cb, - drm_virtual_output_get_fence_fd, - drm_virtual_output_buffer_released, - drm_virtual_output_finish_frame -}; - -int drm_backend_init_virtual_output_api(struct weston_compositor *compositor) -{ - return weston_plugin_api_register(compositor, - WESTON_DRM_VIRTUAL_OUTPUT_API_NAME, - &virt_api, sizeof(virt_api)); -} diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c deleted file mode 100755 index 8e10181..0000000 --- a/libweston/backend-drm/drm.c +++ /dev/null @@ -1,3086 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2011 Intel Corporation - * Copyright © 2017, 2018 Collabora, Ltd. - * Copyright © 2017, 2018 General Electric Company - * Copyright (c) 2018 DisplayLink (UK) Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include "drm-internal.h" -#include "shared/helpers.h" -#include "shared/timespec-util.h" -#include "shared/string-helpers.h" -#include "pixman-renderer.h" -#include "pixel-formats.h" -#include "libbacklight.h" -#include "libinput-seat.h" -#include "launcher-util.h" -#include "vaapi-recorder.h" -#include "presentation-time-server-protocol.h" -#include "linux-dmabuf.h" -#include "linux-dmabuf-unstable-v1-server-protocol.h" -#include "linux-explicit-synchronization.h" -#include "wayland_drm_auth_server.h" // OHOS drm auth - -static const char default_seat[] = "seat0"; - -static void -drm_backend_create_faked_zpos(struct drm_backend *b) -{ - struct drm_plane *plane; - uint64_t zpos = 0ULL; - uint64_t zpos_min_primary; - uint64_t zpos_min_overlay; - uint64_t zpos_min_cursor; - - zpos_min_primary = zpos; - wl_list_for_each(plane, &b->plane_list, link) { - /* if the property is there, bail out sooner */ - if (plane->props[WDRM_PLANE_ZPOS].prop_id != 0) - return; - - if (plane->type != WDRM_PLANE_TYPE_PRIMARY) - continue; - zpos++; - } - - zpos_min_overlay = zpos; - wl_list_for_each(plane, &b->plane_list, link) { - if (plane->type != WDRM_PLANE_TYPE_OVERLAY) - continue; - zpos++; - } - - zpos_min_cursor = zpos; - wl_list_for_each(plane, &b->plane_list, link) { - if (plane->type != WDRM_PLANE_TYPE_CURSOR) - continue; - zpos++; - } - - drm_debug(b, "[drm-backend] zpos property not found. " - "Using invented immutable zpos values:\n"); - /* assume that invented zpos values are immutable */ - wl_list_for_each(plane, &b->plane_list, link) { - if (plane->type == WDRM_PLANE_TYPE_PRIMARY) { - plane->zpos_min = zpos_min_primary; - plane->zpos_max = zpos_min_primary; - } else if (plane->type == WDRM_PLANE_TYPE_OVERLAY) { - plane->zpos_min = zpos_min_overlay; - plane->zpos_max = zpos_min_overlay; - } else if (plane->type == WDRM_PLANE_TYPE_CURSOR) { - plane->zpos_min = zpos_min_cursor; - plane->zpos_max = zpos_min_cursor; - } - drm_debug(b, "\t[plane] %s plane %d, zpos_min %"PRIu64", " - "zpos_max %"PRIu64"\n", - drm_output_get_plane_type_name(plane), - plane->plane_id, plane->zpos_min, plane->zpos_max); - } -} - -static void -wl_array_remove_uint32(struct wl_array *array, uint32_t elm) -{ - uint32_t *pos, *end; - - end = (uint32_t *) ((char *) array->data + array->size); - - wl_array_for_each(pos, array) { - if (*pos != elm) - continue; - - array->size -= sizeof(*pos); - if (pos + 1 == end) - break; - - memmove(pos, pos + 1, (char *) end - (char *) (pos + 1)); - break; - } -} - -static int -pageflip_timeout(void *data) { - /* - * Our timer just went off, that means we're not receiving drm - * page flip events anymore for that output. Let's gracefully exit - * weston with a return value so devs can debug what's going on. - */ - struct drm_output *output = data; - struct weston_compositor *compositor = output->base.compositor; - - weston_log("Pageflip timeout reached on output %s, your " - "driver is probably buggy! Exiting.\n", - output->base.name); - weston_compositor_exit_with_code(compositor, EXIT_FAILURE); - - return 0; -} - -/* Creates the pageflip timer. Note that it isn't armed by default */ -static int -drm_output_pageflip_timer_create(struct drm_output *output) -{ - struct wl_event_loop *loop = NULL; - struct weston_compositor *ec = output->base.compositor; - - loop = wl_display_get_event_loop(ec->wl_display); - assert(loop); - output->pageflip_timer = wl_event_loop_add_timer(loop, - pageflip_timeout, - output); - - if (output->pageflip_timer == NULL) { - weston_log("creating drm pageflip timer failed: %s\n", - strerror(errno)); - return -1; - } - - return 0; -} - -static void -drm_output_destroy(struct weston_output *output_base); - -/** - * Returns true if the plane can be used on the given output for its current - * repaint cycle. - */ -bool -drm_plane_is_available(struct drm_plane *plane, struct drm_output *output) -{ - assert(plane->state_cur); - - if (output->virtual) - return false; - - /* The plane still has a request not yet completed by the kernel. */ - if (!plane->state_cur->complete) - return false; - - /* The plane is still active on another output. */ - if (plane->state_cur->output && plane->state_cur->output != output) - return false; - - /* Check whether the plane can be used with this CRTC; possible_crtcs - * is a bitmask of CRTC indices (pipe), rather than CRTC object ID. */ - return !!(plane->possible_crtcs & (1 << output->pipe)); -} - -struct drm_output * -drm_output_find_by_crtc(struct drm_backend *b, uint32_t crtc_id) -{ - struct drm_output *output; - - wl_list_for_each(output, &b->compositor->output_list, base.link) { - if (output->crtc_id == crtc_id) - return output; - } - - return NULL; -} - -struct drm_head * -drm_head_find_by_connector(struct drm_backend *backend, uint32_t connector_id) -{ - struct weston_head *base; - struct drm_head *head; - - wl_list_for_each(base, - &backend->compositor->head_list, compositor_link) { - head = to_drm_head(base); - if (head->connector_id == connector_id) - return head; - } - - return NULL; -} - -/** - * Get output state to disable output - * - * Returns a pointer to an output_state object which can be used to disable - * an output (e.g. DPMS off). - * - * @param pending_state The pending state object owning this update - * @param output The output to disable - * @returns A drm_output_state to disable the output - */ -static struct drm_output_state * -drm_output_get_disable_state(struct drm_pending_state *pending_state, - struct drm_output *output) -{ - struct drm_output_state *output_state; - - output_state = drm_output_state_duplicate(output->state_cur, - pending_state, - DRM_OUTPUT_STATE_CLEAR_PLANES); - output_state->dpms = WESTON_DPMS_OFF; - - output_state->protection = WESTON_HDCP_DISABLE; - - return output_state; -} - - -/** - * Mark a drm_output_state (the output's last state) as complete. This handles - * any post-completion actions such as updating the repaint timer, disabling the - * output, and finally freeing the state. - */ -void -drm_output_update_complete(struct drm_output *output, uint32_t flags, - unsigned int sec, unsigned int usec) -{ - struct drm_backend *b = to_drm_backend(output->base.compositor); - struct drm_plane_state *ps; - struct timespec ts; - - /* Stop the pageflip timer instead of rearming it here */ - if (output->pageflip_timer) - wl_event_source_timer_update(output->pageflip_timer, 0); - - wl_list_for_each(ps, &output->state_cur->plane_list, link) - ps->complete = true; - - drm_output_state_free(output->state_last); - output->state_last = NULL; - - if (output->destroy_pending) { - output->destroy_pending = false; - output->disable_pending = false; - output->dpms_off_pending = false; - drm_output_destroy(&output->base); - return; - } else if (output->disable_pending) { - output->disable_pending = false; - output->dpms_off_pending = false; - weston_output_disable(&output->base); - return; - } else if (output->dpms_off_pending) { - struct drm_pending_state *pending = drm_pending_state_alloc(b); - output->dpms_off_pending = false; - drm_output_get_disable_state(pending, output); - drm_pending_state_apply_sync(pending); - } - if (output->state_cur->dpms == WESTON_DPMS_OFF && - output->base.repaint_status != REPAINT_AWAITING_COMPLETION) { - /* DPMS can happen to us either in the middle of a repaint - * cycle (when we have painted fresh content, only to throw it - * away for DPMS off), or at any other random point. If the - * latter is true, then we cannot go through finish_frame, - * because the repaint machinery does not expect this. */ - return; - } - - ts.tv_sec = sec; - ts.tv_nsec = usec * 1000; - weston_output_finish_frame(&output->base, &ts, flags); - - /* 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 struct drm_fb * -drm_output_render_pixman(struct drm_output_state *state, - pixman_region32_t *damage) -{ - struct drm_output *output = state->output; - struct weston_compositor *ec = output->base.compositor; - - output->current_image ^= 1; - - pixman_renderer_output_set_buffer(&output->base, - output->image[output->current_image]); - pixman_renderer_output_set_hw_extra_damage(&output->base, - &output->previous_damage); - - ec->renderer->repaint_output(&output->base, damage); - - pixman_region32_copy(&output->previous_damage, damage); - - return drm_fb_ref(output->dumb[output->current_image]); -} - -void -drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) -{ - struct drm_output *output = state->output; - struct weston_compositor *c = output->base.compositor; - struct drm_plane_state *scanout_state; - struct drm_plane *scanout_plane = output->scanout_plane; - struct drm_property_info *damage_info = - &scanout_plane->props[WDRM_PLANE_FB_DAMAGE_CLIPS]; - struct drm_backend *b = to_drm_backend(c); - struct drm_fb *fb; - pixman_region32_t scanout_damage; - pixman_box32_t *rects; - int n_rects; - - /* If we already have a client buffer promoted to scanout, then we don't - * want to render. */ - scanout_state = drm_output_state_get_plane(state, - output->scanout_plane); - if (scanout_state->fb) - return; - - /* - * If we don't have any damage on the primary plane, and we already - * have a renderer buffer active, we can reuse it; else we pass - * the damaged region into the renderer to re-render the affected - * area. - */ - if (!pixman_region32_not_empty(damage) && - scanout_plane->state_cur->fb && - (scanout_plane->state_cur->fb->type == BUFFER_GBM_SURFACE || - scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB)) { - fb = drm_fb_ref(scanout_plane->state_cur->fb); - } else if (b->use_pixman) { - fb = drm_output_render_pixman(state, damage); - } else { - fb = drm_output_render_gl(state, damage); - } - - if (!fb) { - drm_plane_state_put_back(scanout_state); - return; - } - - scanout_state->fb = fb; - scanout_state->output = output; - - scanout_state->src_x = 0; - scanout_state->src_y = 0; - scanout_state->src_w = fb->width << 16; - scanout_state->src_h = fb->height << 16; - - scanout_state->dest_x = 0; - scanout_state->dest_y = 0; - scanout_state->dest_w = output->base.current_mode->width; - scanout_state->dest_h = output->base.current_mode->height; - - pixman_region32_subtract(&c->primary_plane.damage, - &c->primary_plane.damage, damage); - - /* Don't bother calculating plane damage if the plane doesn't support it */ - if (damage_info->prop_id == 0) - return; - - pixman_region32_init(&scanout_damage); - pixman_region32_copy(&scanout_damage, damage); - - if (output->base.zoom.active) { - weston_matrix_transform_region(&scanout_damage, - &output->base.matrix, - &scanout_damage); - } else { - pixman_region32_translate(&scanout_damage, - -output->base.x, -output->base.y); - weston_transformed_region(output->base.width, - output->base.height, - output->base.transform, - output->base.current_scale, - &scanout_damage, - &scanout_damage); - } - - assert(scanout_state->damage_blob_id == 0); - - rects = pixman_region32_rectangles(&scanout_damage, &n_rects); - - /* - * If this function fails, the blob id should still be 0. - * This tells the kernel there is no damage information, which means - * that it will consider the whole plane damaged. While this may - * affect efficiency, it should still produce correct results. - */ - drmModeCreatePropertyBlob(b->drm.fd, rects, - sizeof(*rects) * n_rects, - &scanout_state->damage_blob_id); - - pixman_region32_fini(&scanout_damage); -} - -static int -drm_output_repaint(struct weston_output *output_base, - pixman_region32_t *damage, - void *repaint_data) -{ - struct drm_pending_state *pending_state = repaint_data; - struct drm_output *output = to_drm_output(output_base); - struct drm_output_state *state = NULL; - struct drm_plane_state *scanout_state; - - assert(!output->virtual); - - if (output->disable_pending || output->destroy_pending) - goto err; - - assert(!output->state_last); - - /* If planes have been disabled in the core, we might not have - * hit assign_planes at all, so might not have valid output state - * here. */ - state = drm_pending_state_get_output(pending_state, output); - if (!state) - state = drm_output_state_duplicate(output->state_cur, - pending_state, - DRM_OUTPUT_STATE_CLEAR_PLANES); - state->dpms = WESTON_DPMS_ON; - - if (output_base->allow_protection) - state->protection = output_base->desired_protection; - else - state->protection = WESTON_HDCP_DISABLE; - - drm_output_render(state, damage); - scanout_state = drm_output_state_get_plane(state, - output->scanout_plane); - if (!scanout_state || !scanout_state->fb) - goto err; - - return 0; - -err: - drm_output_state_free(state); - return -1; -} - -/* Determine the type of vblank synchronization to use for the output. - * - * The pipe parameter indicates which CRTC is in use. Knowing this, we - * can determine which vblank sequence type to use for it. Traditional - * cards had only two CRTCs, with CRTC 0 using no special flags, and - * CRTC 1 using DRM_VBLANK_SECONDARY. The first bit of the pipe - * parameter indicates this. - * - * Bits 1-5 of the pipe parameter are 5 bit wide pipe number between - * 0-31. If this is non-zero it indicates we're dealing with a - * multi-gpu situation and we need to calculate the vblank sync - * using DRM_BLANK_HIGH_CRTC_MASK. - */ -static unsigned int -drm_waitvblank_pipe(struct drm_output *output) -{ - if (output->pipe > 1) - return (output->pipe << DRM_VBLANK_HIGH_CRTC_SHIFT) & - DRM_VBLANK_HIGH_CRTC_MASK; - else if (output->pipe > 0) - return DRM_VBLANK_SECONDARY; - else - return 0; -} - -static int -drm_output_start_repaint_loop(struct weston_output *output_base) -{ - struct drm_output *output = to_drm_output(output_base); - struct drm_pending_state *pending_state; - struct drm_plane *scanout_plane = output->scanout_plane; - struct drm_backend *backend = - to_drm_backend(output_base->compositor); - struct timespec ts, tnow; - struct timespec vbl2now; - int64_t refresh_nsec; - int ret; - drmVBlank vbl = { - .request.type = DRM_VBLANK_RELATIVE, - .request.sequence = 0, - .request.signal = 0, - }; - - if (output->disable_pending || output->destroy_pending) - return 0; - - if (!output->scanout_plane->state_cur->fb) { - /* We can't page flip if there's no mode set */ - goto finish_frame; - } - - /* Need to smash all state in from scratch; current timings might not - * be what we want, page flip might not work, etc. - */ - if (backend->state_invalid) - goto finish_frame; - - assert(scanout_plane->state_cur->output == output); - - /* Try to get current msc and timestamp via instant query */ - vbl.request.type |= drm_waitvblank_pipe(output); - ret = drmWaitVBlank(backend->drm.fd, &vbl); - - /* Error ret or zero timestamp means failure to get valid timestamp */ - if ((ret == 0) && (vbl.reply.tval_sec > 0 || vbl.reply.tval_usec > 0)) { - ts.tv_sec = vbl.reply.tval_sec; - ts.tv_nsec = vbl.reply.tval_usec * 1000; - - /* Valid timestamp for most recent vblank - not stale? - * Stale ts could happen on Linux 3.17+, so make sure it - * is not older than 1 refresh duration since now. - */ - weston_compositor_read_presentation_clock(backend->compositor, - &tnow); - timespec_sub(&vbl2now, &tnow, &ts); - refresh_nsec = - millihz_to_nsec(output->base.current_mode->refresh); - if (timespec_to_nsec(&vbl2now) < refresh_nsec) { - drm_output_update_msc(output, vbl.reply.sequence); - weston_output_finish_frame(output_base, &ts, - WP_PRESENTATION_FEEDBACK_INVALID); - return 0; - } - } - - /* Immediate query didn't provide valid timestamp. - * Use pageflip fallback. - */ - - assert(!output->page_flip_pending); - assert(!output->state_last); - - pending_state = drm_pending_state_alloc(backend); - drm_output_state_duplicate(output->state_cur, pending_state, - DRM_OUTPUT_STATE_PRESERVE_PLANES); - - ret = drm_pending_state_apply(pending_state); - if (ret != 0) { - weston_log("applying repaint-start state failed: %s\n", - strerror(errno)); - if (ret == -EACCES) - return -1; - goto finish_frame; - } - - return 0; - -finish_frame: - /* if we cannot page-flip, immediately finish frame */ - weston_output_finish_frame(output_base, NULL, - WP_PRESENTATION_FEEDBACK_INVALID); - return 0; -} - -/** - * Begin a new repaint cycle - * - * Called by the core compositor at the beginning of a repaint cycle. Creates - * a new pending_state structure to own any output state created by individual - * output repaint functions until the repaint is flushed or cancelled. - */ -static void * -drm_repaint_begin(struct weston_compositor *compositor) -{ - struct drm_backend *b = to_drm_backend(compositor); - struct drm_pending_state *ret; - - ret = drm_pending_state_alloc(b); - b->repaint_data = ret; - - // OHOS remove logger - // if (weston_log_scope_is_enabled(b->debug)) { - // char *dbg = weston_compositor_print_scene_graph(compositor); - // drm_debug(b, "[repaint] Beginning repaint; pending_state %p\n", - // ret); - // drm_debug(b, "%s", dbg); - // free(dbg); - // } - - return ret; -} - -/** - * Flush a repaint set - * - * Called by the core compositor when a repaint cycle has been completed - * and should be flushed. Frees the pending state, transitioning ownership - * of the output state from the pending state, to the update itself. When - * the update completes (see drm_output_update_complete), the output - * state will be freed. - */ -static int -drm_repaint_flush(struct weston_compositor *compositor, void *repaint_data) -{ - struct drm_backend *b = to_drm_backend(compositor); - struct drm_pending_state *pending_state = repaint_data; - int ret; - - ret = drm_pending_state_apply(pending_state); - if (ret != 0) - weston_log("repaint-flush failed: %s\n", strerror(errno)); - - drm_debug(b, "[repaint] flushed pending_state %p\n", pending_state); - b->repaint_data = NULL; - - return (ret == -EACCES) ? -1 : 0; -} - -/** - * Cancel a repaint set - * - * Called by the core compositor when a repaint has finished, so the data - * held across the repaint cycle should be discarded. - */ -static void -drm_repaint_cancel(struct weston_compositor *compositor, void *repaint_data) -{ - struct drm_backend *b = to_drm_backend(compositor); - struct drm_pending_state *pending_state = repaint_data; - - drm_pending_state_free(pending_state); - drm_debug(b, "[repaint] cancel pending_state %p\n", pending_state); - b->repaint_data = NULL; -} - -static int -drm_output_init_pixman(struct drm_output *output, struct drm_backend *b); -static void -drm_output_fini_pixman(struct drm_output *output); - -static int -drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mode) -{ - struct drm_output *output = to_drm_output(output_base); - struct drm_backend *b = to_drm_backend(output_base->compositor); - struct drm_mode *drm_mode = drm_output_choose_mode(output, mode); - - if (!drm_mode) { - weston_log("%s: invalid resolution %dx%d\n", - output_base->name, 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; - - /* XXX: This drops our current buffer too early, before we've started - * displaying it. Ideally this should be much more atomic and - * integrated with a full repaint cycle, rather than doing a - * sledgehammer modeswitch first, and only later showing new - * content. - */ - b->state_invalid = true; - - if (b->use_pixman) { - drm_output_fini_pixman(output); - if (drm_output_init_pixman(output, b) < 0) { - weston_log("failed to init output pixman state with " - "new mode\n"); - return -1; - } - } else { - drm_output_fini_egl(output); - if (drm_output_init_egl(output, b) < 0) { - weston_log("failed to init output egl state with " - "new mode"); - return -1; - } - } - - return 0; -} - -static int -init_pixman(struct drm_backend *b) -{ - return pixman_renderer_init(b->compositor); -} - -/** - * Create a drm_plane for a hardware plane - * - * Creates one drm_plane structure for a hardware plane, and initialises its - * properties and formats. - * - * In the absence of universal plane support, where KMS does not explicitly - * expose the primary and cursor planes to userspace, this may also create - * an 'internal' plane for internal management. - * - * This function does not add the plane to the list of usable planes in Weston - * itself; the caller is responsible for this. - * - * Call drm_plane_destroy to clean up the plane. - * - * @sa drm_output_find_special_plane - * @param b DRM compositor backend - * @param kplane DRM plane to create, or NULL if creating internal plane - * @param output Output to create internal plane for, or NULL - * @param type Type to use when creating internal plane, or invalid - * @param format Format to use for internal planes, or 0 - */ -static struct drm_plane * -drm_plane_create(struct drm_backend *b, const drmModePlane *kplane, - struct drm_output *output, enum wdrm_plane_type type, - uint32_t format) -{ - struct drm_plane *plane; - drmModeObjectProperties *props; - uint64_t *zpos_range_values; - uint32_t num_formats = (kplane) ? kplane->count_formats : 1; - - plane = zalloc(sizeof(*plane) + - (sizeof(plane->formats[0]) * num_formats)); - if (!plane) { - weston_log("%s: out of memory\n", __func__); - return NULL; - } - - plane->backend = b; - plane->count_formats = num_formats; - plane->state_cur = drm_plane_state_alloc(NULL, plane); - plane->state_cur->complete = true; - - if (kplane) { - plane->possible_crtcs = kplane->possible_crtcs; - plane->plane_id = kplane->plane_id; - - props = drmModeObjectGetProperties(b->drm.fd, kplane->plane_id, - DRM_MODE_OBJECT_PLANE); - if (!props) { - weston_log("couldn't get plane properties\n"); - goto err; - } - drm_property_info_populate(b, plane_props, plane->props, - WDRM_PLANE__COUNT, props); - plane->type = - drm_property_get_value(&plane->props[WDRM_PLANE_TYPE], - props, - WDRM_PLANE_TYPE__COUNT); - - zpos_range_values = - drm_property_get_range_values(&plane->props[WDRM_PLANE_ZPOS], - props); - - if (zpos_range_values) { - plane->zpos_min = zpos_range_values[0]; - plane->zpos_max = zpos_range_values[1]; - } else { - plane->zpos_min = DRM_PLANE_ZPOS_INVALID_PLANE; - plane->zpos_max = DRM_PLANE_ZPOS_INVALID_PLANE; - } - - if (drm_plane_populate_formats(plane, kplane, props, - b->fb_modifiers) < 0) { - drmModeFreeObjectProperties(props); - goto err; - } - - drmModeFreeObjectProperties(props); - } - else { - plane->possible_crtcs = (1 << output->pipe); - plane->plane_id = 0; - plane->count_formats = 1; - plane->formats[0].format = format; - plane->type = type; - plane->zpos_max = DRM_PLANE_ZPOS_INVALID_PLANE; - plane->zpos_min = DRM_PLANE_ZPOS_INVALID_PLANE; - } - - if (plane->type == WDRM_PLANE_TYPE__COUNT) - goto err_props; - - /* With universal planes, everything is a DRM plane; without - * universal planes, the only DRM planes are overlay planes. - * Everything else is a fake plane. */ - if (b->universal_planes) { - assert(kplane); - } else { - if (kplane) - assert(plane->type == WDRM_PLANE_TYPE_OVERLAY); - else - assert(plane->type != WDRM_PLANE_TYPE_OVERLAY && - output); - } - - weston_plane_init(&plane->base, b->compositor, 0, 0); - wl_list_insert(&b->plane_list, &plane->link); - - return plane; - -err_props: - drm_property_info_free(plane->props, WDRM_PLANE__COUNT); -err: - drm_plane_state_free(plane->state_cur, true); - free(plane); - return NULL; -} - -/** - * Find, or create, a special-purpose plane - * - * Primary and cursor planes are a special case, in that before universal - * planes, they are driven by non-plane API calls. Without universal plane - * support, the only way to configure a primary plane is via drmModeSetCrtc, - * and the only way to configure a cursor plane is drmModeSetCursor2. - * - * Although they may actually be regular planes in the hardware, without - * universal plane support, these planes are not actually exposed to - * userspace in the regular plane list. - * - * However, for ease of internal tracking, we want to manage all planes - * through the same drm_plane structures. Therefore, when we are running - * without universal plane support, we create fake drm_plane structures - * to track these planes. - * - * @param b DRM backend - * @param output Output to use for plane - * @param type Type of plane - */ -static struct drm_plane * -drm_output_find_special_plane(struct drm_backend *b, struct drm_output *output, - enum wdrm_plane_type type) -{ - struct drm_plane *plane; - - if (!b->universal_planes) { - uint32_t format; - - switch (type) { - case WDRM_PLANE_TYPE_CURSOR: - format = DRM_FORMAT_ARGB8888; - break; - case WDRM_PLANE_TYPE_PRIMARY: - /* We don't know what formats the primary plane supports - * before universal planes, so we just assume that the - * GBM format works; however, this isn't set until after - * the output is created. */ - format = 0; - break; - default: - assert(!"invalid type in drm_output_find_special_plane"); - break; - } - - return drm_plane_create(b, NULL, output, type, format); - } - - wl_list_for_each(plane, &b->plane_list, link) { - struct drm_output *tmp; - bool found_elsewhere = false; - - if (plane->type != type) - continue; - if (!drm_plane_is_available(plane, output)) - continue; - - /* On some platforms, primary/cursor planes can roam - * between different CRTCs, so make sure we don't claim the - * same plane for two outputs. */ - wl_list_for_each(tmp, &b->compositor->output_list, - base.link) { - if (tmp->cursor_plane == plane || - tmp->scanout_plane == plane) { - found_elsewhere = true; - break; - } - } - - if (found_elsewhere) - continue; - - plane->possible_crtcs = (1 << output->pipe); - return plane; - } - - return NULL; -} - -/** - * Destroy one DRM plane - * - * Destroy a DRM plane, removing it from screen and releasing its retained - * buffers in the process. The counterpart to drm_plane_create. - * - * @param plane Plane to deallocate (will be freed) - */ -static void -drm_plane_destroy(struct drm_plane *plane) -{ - if (plane->type == WDRM_PLANE_TYPE_OVERLAY) - drmModeSetPlane(plane->backend->drm.fd, plane->plane_id, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - drm_plane_state_free(plane->state_cur, true); - drm_property_info_free(plane->props, WDRM_PLANE__COUNT); - weston_plane_release(&plane->base); - wl_list_remove(&plane->link); - free(plane); -} - -/** - * Initialise sprites (overlay planes) - * - * Walk the list of provided DRM planes, and add overlay planes. - * - * Call destroy_sprites to free these planes. - * - * @param b DRM compositor backend - */ -static void -create_sprites(struct drm_backend *b) -{ - drmModePlaneRes *kplane_res; - drmModePlane *kplane; - struct drm_plane *drm_plane; - uint32_t i; - kplane_res = drmModeGetPlaneResources(b->drm.fd); - if (!kplane_res) { - weston_log("failed to get plane resources: %s\n", - strerror(errno)); - return; - } - - for (i = 0; i < kplane_res->count_planes; i++) { - kplane = drmModeGetPlane(b->drm.fd, kplane_res->planes[i]); - if (!kplane) - continue; - - drm_plane = drm_plane_create(b, kplane, NULL, - WDRM_PLANE_TYPE__COUNT, 0); - drmModeFreePlane(kplane); - if (!drm_plane) - continue; - - if (drm_plane->type == WDRM_PLANE_TYPE_OVERLAY) - weston_compositor_stack_plane(b->compositor, - &drm_plane->base, - &b->compositor->primary_plane); - } - - drmModeFreePlaneResources(kplane_res); -} - -/** - * Clean up sprites (overlay planes) - * - * The counterpart to create_sprites. - * - * @param b DRM compositor backend - */ -static void -destroy_sprites(struct drm_backend *b) -{ - struct drm_plane *plane, *next; - - wl_list_for_each_safe(plane, next, &b->plane_list, link) - drm_plane_destroy(plane); -} - -/* returns a value between 0-255 range, where higher is brighter */ -static uint32_t -drm_get_backlight(struct drm_head *head) -{ - long brightness, max_brightness, norm; - - brightness = backlight_get_brightness(head->backlight); - max_brightness = backlight_get_max_brightness(head->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 = to_drm_output(output_base); - struct drm_head *head; - long max_brightness, new_brightness; - - if (value > 255) - return; - - wl_list_for_each(head, &output->base.head_list, base.output_link) { - if (!head->backlight) - return; - - max_brightness = backlight_get_max_brightness(head->backlight); - - /* get denormalized value */ - new_brightness = (value * max_brightness) / 255; - - backlight_set_brightness(head->backlight, new_brightness); - } -} - -static void -drm_output_init_backlight(struct drm_output *output) -{ - struct weston_head *base; - struct drm_head *head; - - output->base.set_backlight = NULL; - - wl_list_for_each(base, &output->base.head_list, output_link) { - head = to_drm_head(base); - - if (head->backlight) { - weston_log("Initialized backlight for head '%s', device %s\n", - head->base.name, head->backlight->path); - - if (!output->base.set_backlight) { - output->base.set_backlight = drm_set_backlight; - output->base.backlight_current = - drm_get_backlight(head); - } - } - } -} - -/** - * Power output on or off - * - * The DPMS/power level of an output is used to switch it on or off. This - * is DRM's hook for doing so, which can called either as part of repaint, - * or independently of the repaint loop. - * - * If we are called as part of repaint, we simply set the relevant bit in - * state and return. - * - * This function is never called on a virtual output. - */ -static void -drm_set_dpms(struct weston_output *output_base, enum dpms_enum level) -{ - struct drm_output *output = to_drm_output(output_base); - struct drm_backend *b = to_drm_backend(output_base->compositor); - struct drm_pending_state *pending_state = b->repaint_data; - struct drm_output_state *state; - int ret; - - assert(!output->virtual); - - if (output->state_cur->dpms == level) - return; - - /* If we're being called during the repaint loop, then this is - * simple: discard any previously-generated state, and create a new - * state where we disable everything. When we come to flush, this - * will be applied. - * - * However, we need to be careful: we can be called whilst another - * output is in its repaint cycle (pending_state exists), but our - * output still has an incomplete state application outstanding. - * In that case, we need to wait until that completes. */ - if (pending_state && !output->state_last) { - /* The repaint loop already sets DPMS on; we don't need to - * explicitly set it on here, as it will already happen - * whilst applying the repaint state. */ - if (level == WESTON_DPMS_ON) - return; - - state = drm_pending_state_get_output(pending_state, output); - if (state) - drm_output_state_free(state); - state = drm_output_get_disable_state(pending_state, output); - return; - } - - /* As we throw everything away when disabling, just send us back through - * a repaint cycle. */ - if (level == WESTON_DPMS_ON) { - if (output->dpms_off_pending) - output->dpms_off_pending = false; - weston_output_schedule_repaint(output_base); - return; - } - - /* If we've already got a request in the pipeline, then we need to - * park our DPMS request until that request has quiesced. */ - if (output->state_last) { - output->dpms_off_pending = true; - return; - } - - pending_state = drm_pending_state_alloc(b); - drm_output_get_disable_state(pending_state, output); - ret = drm_pending_state_apply_sync(pending_state); - if (ret != 0) - weston_log("drm_set_dpms: couldn't disable output?\n"); -} - -static const char * const connector_type_names[] = { - [DRM_MODE_CONNECTOR_Unknown] = "Unknown", - [DRM_MODE_CONNECTOR_VGA] = "VGA", - [DRM_MODE_CONNECTOR_DVII] = "DVI-I", - [DRM_MODE_CONNECTOR_DVID] = "DVI-D", - [DRM_MODE_CONNECTOR_DVIA] = "DVI-A", - [DRM_MODE_CONNECTOR_Composite] = "Composite", - [DRM_MODE_CONNECTOR_SVIDEO] = "SVIDEO", - [DRM_MODE_CONNECTOR_LVDS] = "LVDS", - [DRM_MODE_CONNECTOR_Component] = "Component", - [DRM_MODE_CONNECTOR_9PinDIN] = "DIN", - [DRM_MODE_CONNECTOR_DisplayPort] = "DP", - [DRM_MODE_CONNECTOR_HDMIA] = "HDMI-A", - [DRM_MODE_CONNECTOR_HDMIB] = "HDMI-B", - [DRM_MODE_CONNECTOR_TV] = "TV", - [DRM_MODE_CONNECTOR_eDP] = "eDP", - [DRM_MODE_CONNECTOR_VIRTUAL] = "Virtual", - [DRM_MODE_CONNECTOR_DSI] = "DSI", - [DRM_MODE_CONNECTOR_DPI] = "DPI", -}; - -/** Create a name given a DRM connector - * - * \param con The DRM connector whose type and id form the name. - * \return A newly allocate string, or NULL on error. Must be free()'d - * after use. - * - * The name does not identify the DRM display device. - */ -static char * -make_connector_name(const drmModeConnector *con) -{ - char *name; - const char *type_name = NULL; - int ret; - - if (con->connector_type < ARRAY_LENGTH(connector_type_names)) - type_name = connector_type_names[con->connector_type]; - - if (!type_name) - type_name = "UNNAMED"; - - ret = asprintf(&name, "%s-%d", type_name, con->connector_type_id); - if (ret < 0) - return NULL; - - return name; -} - -static int -drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) -{ - int w = output->base.current_mode->width; - int h = output->base.current_mode->height; - uint32_t format = output->gbm_format; - uint32_t pixman_format; - unsigned int i; - b->use_pixman_shadow = false; // OHOS - const struct pixman_renderer_output_options options = { - .use_shadow = b->use_pixman_shadow, - }; - - switch (format) { - case DRM_FORMAT_XRGB8888: - pixman_format = PIXMAN_x8r8g8b8; - break; - case DRM_FORMAT_RGB565: - pixman_format = PIXMAN_r5g6b5; - break; - default: - weston_log("Unsupported pixman format 0x%x\n", format); - return -1; - } - - /* FIXME error checking */ - for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { - output->dumb[i] = drm_fb_create_dumb(b, w, h, format); - if (!output->dumb[i]) - goto err; - - output->image[i] = - pixman_image_create_bits(pixman_format, w, h, - output->dumb[i]->map, - output->dumb[i]->strides[0]); - if (!output->image[i]) - goto err; - } - - if (pixman_renderer_output_create(&output->base, &options) < 0) - goto err; - - weston_log("DRM: output %s %s shadow framebuffer.\n", output->base.name, - b->use_pixman_shadow ? "uses" : "does not use"); - - 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_unref(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) -{ - struct drm_backend *b = to_drm_backend(output->base.compositor); - unsigned int i; - - /* Destroying the Pixman surface will destroy all our buffers, - * regardless of refcount. Ensure we destroy them here. */ - if (!b->shutting_down && - output->scanout_plane->state_cur->fb && - output->scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB) { - drm_plane_reset_state(output->scanout_plane); - } - - pixman_renderer_output_destroy(&output->base); - pixman_region32_fini(&output->previous_damage); - - for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { - pixman_image_unref(output->image[i]); - drm_fb_unref(output->dumb[i]); - output->dumb[i] = NULL; - output->image[i] = NULL; - } -} - -static void -setup_output_seat_constraint(struct drm_backend *b, - struct weston_output *output, - const char *s) -{ - if (strcmp(s, "") != 0) { - struct weston_pointer *pointer; - struct udev_seat *seat; - - seat = udev_seat_get_named(&b->input, s); - if (!seat) - return; - - seat->base.output = output; - - pointer = weston_seat_get_pointer(&seat->base); - if (pointer) - weston_pointer_clamp(pointer, - &pointer->x, - &pointer->y); - } -} - -static int -drm_output_attach_head(struct weston_output *output_base, - struct weston_head *head_base) -{ - struct drm_backend *b = to_drm_backend(output_base->compositor); - - if (wl_list_length(&output_base->head_list) >= MAX_CLONED_CONNECTORS) - return -1; - - if (!output_base->enabled) - return 0; - - /* XXX: ensure the configuration will work. - * This is actually impossible without major infrastructure - * work. */ - - /* Need to go through modeset to add connectors. */ - /* XXX: Ideally we'd do this per-output, not globally. */ - /* XXX: Doing it globally, what guarantees another output's update - * will not clear the flag before this output is updated? - */ - b->state_invalid = true; - - weston_output_schedule_repaint(output_base); - - return 0; -} - -static void -drm_output_detach_head(struct weston_output *output_base, - struct weston_head *head_base) -{ - struct drm_backend *b = to_drm_backend(output_base->compositor); - - if (!output_base->enabled) - return; - - /* Need to go through modeset to drop connectors that should no longer - * be driven. */ - /* XXX: Ideally we'd do this per-output, not globally. */ - b->state_invalid = true; - - weston_output_schedule_repaint(output_base); -} - -int -parse_gbm_format(const char *s, uint32_t default_value, uint32_t *gbm_format) -{ - const struct pixel_format_info *pinfo; - - if (s == NULL) { - *gbm_format = default_value; - - return 0; - } - - pinfo = pixel_format_get_info_by_drm_name(s); - if (!pinfo) { - weston_log("fatal: unrecognized pixel format: %s\n", s); - - return -1; - } - - /* GBM formats and DRM formats are identical. */ - *gbm_format = pinfo->format; - - return 0; -} - -static int -drm_head_read_current_setup(struct drm_head *head, struct drm_backend *backend) -{ - int drm_fd = backend->drm.fd; - drmModeEncoder *encoder; - drmModeCrtc *crtc; - - /* Get the current mode on the crtc that's currently driving - * this connector. */ - encoder = drmModeGetEncoder(drm_fd, head->connector->encoder_id); - if (encoder != NULL) { - head->inherited_crtc_id = encoder->crtc_id; - - crtc = drmModeGetCrtc(drm_fd, encoder->crtc_id); - drmModeFreeEncoder(encoder); - - if (crtc == NULL) - return -1; - if (crtc->mode_valid) - head->inherited_mode = crtc->mode; - drmModeFreeCrtc(crtc); - } - - return 0; -} - -static void -drm_output_set_gbm_format(struct weston_output *base, - const char *gbm_format) -{ - struct drm_output *output = to_drm_output(base); - struct drm_backend *b = to_drm_backend(base->compositor); - - if (parse_gbm_format(gbm_format, b->gbm_format, &output->gbm_format) == -1) - output->gbm_format = b->gbm_format; -} - -static void -drm_output_set_seat(struct weston_output *base, - const char *seat) -{ - struct drm_output *output = to_drm_output(base); - struct drm_backend *b = to_drm_backend(base->compositor); - - setup_output_seat_constraint(b, &output->base, - seat ? seat : ""); -} - -static int -drm_output_init_gamma_size(struct drm_output *output) -{ - struct drm_backend *backend = to_drm_backend(output->base.compositor); - drmModeCrtc *crtc; - - assert(output->base.compositor); - assert(output->crtc_id != 0); - crtc = drmModeGetCrtc(backend->drm.fd, output->crtc_id); - if (!crtc) - return -1; - - output->base.gamma_size = crtc->gamma_size; - - drmModeFreeCrtc(crtc); - - return 0; -} - -static uint32_t -drm_head_get_possible_crtcs_mask(struct drm_head *head) -{ - uint32_t possible_crtcs = 0; - drmModeEncoder *encoder; - int i; - - for (i = 0; i < head->connector->count_encoders; i++) { - encoder = drmModeGetEncoder(head->backend->drm.fd, - head->connector->encoders[i]); - if (!encoder) - continue; - - possible_crtcs |= encoder->possible_crtcs; - drmModeFreeEncoder(encoder); - } - - return possible_crtcs; -} - -static int -drm_crtc_get_index(drmModeRes *resources, uint32_t crtc_id) -{ - int i; - - for (i = 0; i < resources->count_crtcs; i++) { - if (resources->crtcs[i] == crtc_id) - return i; - } - - assert(0 && "unknown crtc id"); - return -1; -} - -/** Pick a CRTC that might be able to drive all attached connectors - * - * @param output The output whose attached heads to include. - * @param resources The DRM KMS resources. - * @return CRTC index, or -1 on failure or not found. - */ -static int -drm_output_pick_crtc(struct drm_output *output, drmModeRes *resources) -{ - struct drm_backend *backend; - struct weston_head *base; - struct drm_head *head; - uint32_t possible_crtcs = 0xffffffff; - int existing_crtc[32]; - unsigned j, n = 0; - uint32_t crtc_id; - int best_crtc_index = -1; - int fallback_crtc_index = -1; - int i; - bool match; - - backend = to_drm_backend(output->base.compositor); - - /* This algorithm ignores drmModeEncoder::possible_clones restriction, - * because it is more often set wrong than not in the kernel. */ - - /* Accumulate a mask of possible crtcs and find existing routings. */ - wl_list_for_each(base, &output->base.head_list, output_link) { - head = to_drm_head(base); - - possible_crtcs &= drm_head_get_possible_crtcs_mask(head); - - crtc_id = head->inherited_crtc_id; - if (crtc_id > 0 && n < ARRAY_LENGTH(existing_crtc)) - existing_crtc[n++] = drm_crtc_get_index(resources, - crtc_id); - } - - /* Find a crtc that could drive each connector individually at least, - * and prefer existing routings. */ - for (i = 0; i < resources->count_crtcs; i++) { - crtc_id = resources->crtcs[i]; - - /* Could the crtc not drive each connector? */ - if (!(possible_crtcs & (1 << i))) - continue; - - /* Is the crtc already in use? */ - if (drm_output_find_by_crtc(backend, crtc_id)) - continue; - - /* Try to preserve the existing CRTC -> connector routing; - * it makes initialisation faster, and also since we have a - * very dumb picking algorithm, may preserve a better - * choice. */ - for (j = 0; j < n; j++) { - if (existing_crtc[j] == i) - return i; - } - - /* Check if any other head had existing routing to this CRTC. - * If they did, this is not the best CRTC as it might be needed - * for another output we haven't enabled yet. */ - match = false; - wl_list_for_each(base, &backend->compositor->head_list, - compositor_link) { - head = to_drm_head(base); - - if (head->base.output == &output->base) - continue; - - if (weston_head_is_enabled(&head->base)) - continue; - - if (head->inherited_crtc_id == crtc_id) { - match = true; - break; - } - } - if (!match) - best_crtc_index = i; - - fallback_crtc_index = i; - } - - if (best_crtc_index != -1) - return best_crtc_index; - - if (fallback_crtc_index != -1) - return fallback_crtc_index; - - /* Likely possible_crtcs was empty due to asking for clones, - * but since the DRM documentation says the kernel lies, let's - * pick one crtc anyway. Trial and error is the only way to - * be sure if something doesn't work. */ - - /* First pick any existing assignment. */ - for (j = 0; j < n; j++) { - crtc_id = resources->crtcs[existing_crtc[j]]; - if (!drm_output_find_by_crtc(backend, crtc_id)) - return existing_crtc[j]; - } - - /* Otherwise pick any available crtc. */ - for (i = 0; i < resources->count_crtcs; i++) { - crtc_id = resources->crtcs[i]; - - if (!drm_output_find_by_crtc(backend, crtc_id)) - return i; - } - - return -1; -} - -/** Allocate a CRTC for the output - * - * @param output The output with no allocated CRTC. - * @param resources DRM KMS resources. - * @return 0 on success, -1 on failure. - * - * Finds a free CRTC that might drive the attached connectors, reserves the CRTC - * for the output, and loads the CRTC properties. - * - * Populates the cursor and scanout planes. - * - * On failure, the output remains without a CRTC. - */ -static int -drm_output_init_crtc(struct drm_output *output, drmModeRes *resources) -{ - struct drm_backend *b = to_drm_backend(output->base.compositor); - drmModeObjectPropertiesPtr props; - int i; - - assert(output->crtc_id == 0); - - i = drm_output_pick_crtc(output, resources); - if (i < 0) { - weston_log("Output '%s': No available CRTCs.\n", - output->base.name); - return -1; - } - - output->crtc_id = resources->crtcs[i]; - output->pipe = i; - - props = drmModeObjectGetProperties(b->drm.fd, output->crtc_id, - DRM_MODE_OBJECT_CRTC); - if (!props) { - weston_log("failed to get CRTC properties\n"); - goto err_crtc; - } - drm_property_info_populate(b, crtc_props, output->props_crtc, - WDRM_CRTC__COUNT, props); - drmModeFreeObjectProperties(props); - - output->scanout_plane = - drm_output_find_special_plane(b, output, - WDRM_PLANE_TYPE_PRIMARY); - if (!output->scanout_plane) { - weston_log("Failed to find primary plane for output %s\n", - output->base.name); - goto err_crtc; - } - - /* Without universal planes, we can't discover which formats are - * supported by the primary plane; we just hope that the GBM format - * works. */ - if (!b->universal_planes) - output->scanout_plane->formats[0].format = output->gbm_format; - - /* Failing to find a cursor plane is not fatal, as we'll fall back - * to software cursor. */ - output->cursor_plane = - drm_output_find_special_plane(b, output, - WDRM_PLANE_TYPE_CURSOR); - - wl_array_remove_uint32(&b->unused_crtcs, output->crtc_id); - - return 0; - -err_crtc: - output->crtc_id = 0; - output->pipe = 0; - - return -1; -} - -/** Free the CRTC from the output - * - * @param output The output whose CRTC to deallocate. - * - * The CRTC reserved for the given output becomes free to use again. - */ -static void -drm_output_fini_crtc(struct drm_output *output) -{ - struct drm_backend *b = to_drm_backend(output->base.compositor); - uint32_t *unused; - - /* If the compositor is already shutting down, the planes have already - * been destroyed. */ - if (!b->shutting_down) { - if (!b->universal_planes) { - /* Without universal planes, our special planes are - * pseudo-planes allocated at output creation, freed at - * output destruction, and not usable by other outputs. - */ - if (output->cursor_plane) - drm_plane_destroy(output->cursor_plane); - if (output->scanout_plane) - drm_plane_destroy(output->scanout_plane); - } else { - /* With universal planes, the 'special' planes are - * allocated at startup, freed at shutdown, and live on - * the plane list in between. We want the planes to - * continue to exist and be freed up for other outputs. - */ - if (output->cursor_plane) - drm_plane_reset_state(output->cursor_plane); - if (output->scanout_plane) - drm_plane_reset_state(output->scanout_plane); - } - } - - drm_property_info_free(output->props_crtc, WDRM_CRTC__COUNT); - - assert(output->crtc_id != 0); - - unused = wl_array_add(&b->unused_crtcs, sizeof(*unused)); - *unused = output->crtc_id; - - /* Force resetting unused CRTCs */ - b->state_invalid = true; - - output->crtc_id = 0; - output->cursor_plane = NULL; - output->scanout_plane = NULL; -} - -static int -drm_output_enable(struct weston_output *base) -{ - struct drm_output *output = to_drm_output(base); - struct drm_backend *b = to_drm_backend(base->compositor); - drmModeRes *resources; - int ret; - - assert(!output->virtual); - - resources = drmModeGetResources(b->drm.fd); - if (!resources) { - weston_log("drmModeGetResources failed\n"); - return -1; - } - ret = drm_output_init_crtc(output, resources); - drmModeFreeResources(resources); - if (ret < 0) - return -1; - - if (drm_output_init_gamma_size(output) < 0) - goto err; - - if (b->pageflip_timeout) - drm_output_pageflip_timer_create(output); - - if (b->use_pixman) { - if (drm_output_init_pixman(output, b) < 0) { - weston_log("Failed to init output pixman state\n"); - goto err; - } - } else if (drm_output_init_egl(output, b) < 0) { - weston_log("Failed to init output gl state\n"); - goto err; - } - - drm_output_init_backlight(output); - - output->base.start_repaint_loop = drm_output_start_repaint_loop; - output->base.repaint = drm_output_repaint; - output->base.assign_planes = drm_assign_planes; - output->base.set_dpms = drm_set_dpms; - output->base.switch_mode = drm_output_switch_mode; - output->base.set_gamma = drm_output_set_gamma; - - if (output->cursor_plane) - weston_compositor_stack_plane(b->compositor, - &output->cursor_plane->base, - NULL); - else - b->cursors_are_broken = true; - - weston_compositor_stack_plane(b->compositor, - &output->scanout_plane->base, - &b->compositor->primary_plane); - - weston_log("Output %s (crtc %d) video modes:\n", - output->base.name, output->crtc_id); - drm_output_print_modes(output); - - return 0; - -err: - drm_output_fini_crtc(output); - - return -1; -} - -static void -drm_output_deinit(struct weston_output *base) -{ - struct drm_output *output = to_drm_output(base); - struct drm_backend *b = to_drm_backend(base->compositor); - - if (b->use_pixman) - drm_output_fini_pixman(output); - else - drm_output_fini_egl(output); - - /* Since our planes are no longer in use anywhere, remove their base - * weston_plane's link from the plane stacking list, unless we're - * shutting down, in which case the plane has already been - * destroyed. */ - if (!b->shutting_down) { - wl_list_remove(&output->scanout_plane->base.link); - wl_list_init(&output->scanout_plane->base.link); - - if (output->cursor_plane) { - wl_list_remove(&output->cursor_plane->base.link); - wl_list_init(&output->cursor_plane->base.link); - /* Turn off hardware cursor */ - drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); - } - } - - drm_output_fini_crtc(output); -} - -static void -drm_head_destroy(struct drm_head *head); - -static void -drm_output_destroy(struct weston_output *base) -{ - struct drm_output *output = to_drm_output(base); - struct drm_backend *b = to_drm_backend(base->compositor); - - assert(!output->virtual); - - if (output->page_flip_pending || output->atomic_complete_pending) { - output->destroy_pending = true; - weston_log("destroy output while page flip pending\n"); - return; - } - - if (output->base.enabled) - drm_output_deinit(&output->base); - - drm_mode_list_destroy(b, &output->base.mode_list); - - if (output->pageflip_timer) - wl_event_source_remove(output->pageflip_timer); - - weston_output_release(&output->base); - - assert(!output->state_last); - drm_output_state_free(output->state_cur); - - free(output); -} - -static int -drm_output_disable(struct weston_output *base) -{ - struct drm_output *output = to_drm_output(base); - - assert(!output->virtual); - - if (output->page_flip_pending || output->atomic_complete_pending) { - output->disable_pending = true; - return -1; - } - - weston_log("Disabling output %s\n", output->base.name); - - if (output->base.enabled) - drm_output_deinit(&output->base); - - output->disable_pending = false; - - return 0; -} - -/** - * Update the list of unused connectors and CRTCs - * - * This keeps the unused_crtc arrays up to date. - * - * @param b Weston backend structure - * @param resources DRM resources for this device - */ -static void -drm_backend_update_unused_outputs(struct drm_backend *b, drmModeRes *resources) -{ - int i; - - wl_array_release(&b->unused_crtcs); - wl_array_init(&b->unused_crtcs); - - for (i = 0; i < resources->count_crtcs; i++) { - struct drm_output *output; - uint32_t *crtc_id; - - output = drm_output_find_by_crtc(b, resources->crtcs[i]); - if (output && output->base.enabled) - continue; - - crtc_id = wl_array_add(&b->unused_crtcs, sizeof(*crtc_id)); - *crtc_id = resources->crtcs[i]; - } -} - -/* - * This function converts the protection status from drm values to - * weston_hdcp_protection status. The drm values as read from the connector - * properties "Content Protection" and "HDCP Content Type" need to be converted - * to appropriate weston values, that can be sent to a client application. - */ -static int -get_weston_protection_from_drm(enum wdrm_content_protection_state protection, - enum wdrm_hdcp_content_type type, - enum weston_hdcp_protection *weston_protection) - -{ - if (protection >= WDRM_CONTENT_PROTECTION__COUNT) - return -1; - if (protection == WDRM_CONTENT_PROTECTION_DESIRED || - protection == WDRM_CONTENT_PROTECTION_UNDESIRED) { - *weston_protection = WESTON_HDCP_DISABLE; - return 0; - } - if (type >= WDRM_HDCP_CONTENT_TYPE__COUNT) - return -1; - if (type == WDRM_HDCP_CONTENT_TYPE0) { - *weston_protection = WESTON_HDCP_ENABLE_TYPE_0; - return 0; - } - if (type == WDRM_HDCP_CONTENT_TYPE1) { - *weston_protection = WESTON_HDCP_ENABLE_TYPE_1; - return 0; - } - return -1; -} - -/** - * Get current content-protection status for a given head. - * - * @param head drm_head, whose protection is to be retrieved - * @param props drm property object of the connector, related to the head - * @return protection status in case of success, -1 otherwise - */ -static enum weston_hdcp_protection -drm_head_get_current_protection(struct drm_head *head, - drmModeObjectProperties *props) -{ - struct drm_property_info *info; - enum wdrm_content_protection_state protection; - enum wdrm_hdcp_content_type type; - enum weston_hdcp_protection weston_hdcp = WESTON_HDCP_DISABLE; - - info = &head->props_conn[WDRM_CONNECTOR_CONTENT_PROTECTION]; - protection = drm_property_get_value(info, props, - WDRM_CONTENT_PROTECTION__COUNT); - - if (protection == WDRM_CONTENT_PROTECTION__COUNT) - return WESTON_HDCP_DISABLE; - - info = &head->props_conn[WDRM_CONNECTOR_HDCP_CONTENT_TYPE]; - type = drm_property_get_value(info, props, - WDRM_HDCP_CONTENT_TYPE__COUNT); - - /* - * In case of platforms supporting HDCP1.4, only property - * 'Content Protection' is exposed and not the 'HDCP Content Type' - * for such cases HDCP Type 0 should be considered as the content-type. - */ - - if (type == WDRM_HDCP_CONTENT_TYPE__COUNT) - type = WDRM_HDCP_CONTENT_TYPE0; - - if (get_weston_protection_from_drm(protection, type, - &weston_hdcp) == -1) { - weston_log("Invalid drm protection:%d type:%d, for head:%s connector-id:%d\n", - protection, type, head->base.name, - head->connector_id); - return WESTON_HDCP_DISABLE; - } - - return weston_hdcp; -} - -/** Replace connector data and monitor information - * - * @param head The head to update. - * @param connector The connector data to be owned by the head, must match - * the head's connector ID. - * @return 0 on success, -1 on failure. - * - * Takes ownership of @c connector on success, not on failure. - * - * May schedule a heads changed call. - */ -static int -drm_head_assign_connector_info(struct drm_head *head, - drmModeConnector *connector) -{ - drmModeObjectProperties *props; - - assert(connector); - assert(head->connector_id == connector->connector_id); - - props = drmModeObjectGetProperties(head->backend->drm.fd, - head->connector_id, - DRM_MODE_OBJECT_CONNECTOR); - if (!props) { - weston_log("Error: failed to get connector '%s' properties\n", - head->base.name); - return -1; - } - - if (head->connector) - drmModeFreeConnector(head->connector); - head->connector = connector; - - drm_property_info_populate(head->backend, connector_props, - head->props_conn, - WDRM_CONNECTOR__COUNT, props); - update_head_from_connector(head, props); - - weston_head_set_content_protection_status(&head->base, - drm_head_get_current_protection(head, props)); - drmModeFreeObjectProperties(props); - - return 0; -} - -static void -drm_head_log_info(struct drm_head *head, const char *msg) -{ - if (head->base.connected) { - weston_log("DRM: head '%s' %s, connector %d is connected, " - "EDID make '%s', model '%s', serial '%s'\n", - head->base.name, msg, head->connector_id, - head->base.make, head->base.model, - head->base.serial_number ?: ""); - } else { - weston_log("DRM: head '%s' %s, connector %d is disconnected.\n", - head->base.name, msg, head->connector_id); - } -} - -/** Update connector and monitor information - * - * @param head The head to update. - * - * Re-reads the DRM property lists for the connector and updates monitor - * information and connection status. This may schedule a heads changed call - * to the user. - */ -static void -drm_head_update_info(struct drm_head *head) -{ - drmModeConnector *connector; - - connector = drmModeGetConnector(head->backend->drm.fd, - head->connector_id); - if (!connector) { - weston_log("DRM: getting connector info for '%s' failed.\n", - head->base.name); - return; - } - - if (drm_head_assign_connector_info(head, connector) < 0) - drmModeFreeConnector(connector); - - if (head->base.device_changed) - drm_head_log_info(head, "updated"); -} - -/** - * Create a Weston head for a connector - * - * Given a DRM connector, create a matching drm_head structure and add it - * to Weston's head list. - * - * @param backend Weston backend structure - * @param connector_id DRM connector ID for the head - * @param drm_device udev device pointer - * @returns The new head, or NULL on failure. - */ -static struct drm_head * -drm_head_create(struct drm_backend *backend, uint32_t connector_id, - struct udev_device *drm_device) -{ - struct drm_head *head; - drmModeConnector *connector; - char *name; - - head = zalloc(sizeof *head); - if (!head) - return NULL; - - connector = drmModeGetConnector(backend->drm.fd, connector_id); - if (!connector) - goto err_alloc; - - name = make_connector_name(connector); - if (!name) - goto err_alloc; - - weston_head_init(&head->base, name); - free(name); - - head->connector_id = connector_id; - head->backend = backend; - - head->backlight = backlight_init(drm_device, connector->connector_type); - - if (drm_head_assign_connector_info(head, connector) < 0) - goto err_init; - - if (head->connector->connector_type == DRM_MODE_CONNECTOR_LVDS || - head->connector->connector_type == DRM_MODE_CONNECTOR_eDP) - weston_head_set_internal(&head->base); - - if (drm_head_read_current_setup(head, backend) < 0) { - weston_log("Failed to retrieve current mode from connector %d.\n", - head->connector_id); - /* Not fatal. */ - } - - weston_compositor_add_head(backend->compositor, &head->base); - drm_head_log_info(head, "found"); - - return head; - -err_init: - weston_head_release(&head->base); - -err_alloc: - if (connector) - drmModeFreeConnector(connector); - - free(head); - - return NULL; -} - -static void -drm_head_destroy(struct drm_head *head) -{ - weston_head_release(&head->base); - - drm_property_info_free(head->props_conn, WDRM_CONNECTOR__COUNT); - drmModeFreeConnector(head->connector); - - if (head->backlight) - backlight_destroy(head->backlight); - - free(head); -} - -/** - * Create a Weston output structure - * - * Create an "empty" drm_output. This is the implementation of - * weston_backend::create_output. - * - * Creating an output is usually followed by drm_output_attach_head() - * and drm_output_enable() to make use of it. - * - * @param compositor The compositor instance. - * @param name Name for the new output. - * @returns The output, or NULL on failure. - */ -static struct weston_output * -drm_output_create(struct weston_compositor *compositor, const char *name) -{ - struct drm_backend *b = to_drm_backend(compositor); - struct drm_output *output; - - output = zalloc(sizeof *output); - if (output == NULL) - return NULL; - - output->backend = b; -#ifdef BUILD_DRM_GBM - output->gbm_bo_flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING; -#endif - - weston_output_init(&output->base, compositor, name); - - output->base.enable = drm_output_enable; - output->base.destroy = drm_output_destroy; - output->base.disable = drm_output_disable; - output->base.attach_head = drm_output_attach_head; - output->base.detach_head = drm_output_detach_head; - - output->destroy_pending = false; - output->disable_pending = false; - - output->state_cur = drm_output_state_alloc(output, NULL); - - weston_compositor_add_pending_output(&output->base, b->compositor); - - return &output->base; -} - -static int -drm_backend_create_heads(struct drm_backend *b, struct udev_device *drm_device) -{ - struct drm_head *head; - drmModeRes *resources; - int i; - - resources = drmModeGetResources(b->drm.fd); - if (!resources) { - weston_log("drmModeGetResources failed\n"); - return -1; - } - - b->min_width = resources->min_width; - b->max_width = resources->max_width; - b->min_height = resources->min_height; - b->max_height = resources->max_height; - - for (i = 0; i < resources->count_connectors; i++) { - uint32_t connector_id = resources->connectors[i]; - - head = drm_head_create(b, connector_id, drm_device); - if (!head) { - weston_log("DRM: failed to create head for connector %d.\n", - connector_id); - } - } - - drm_backend_update_unused_outputs(b, resources); - - drmModeFreeResources(resources); - - return 0; -} - -static void -drm_backend_update_heads(struct drm_backend *b, struct udev_device *drm_device) -{ - drmModeRes *resources; - struct weston_head *base, *next; - struct drm_head *head; - int i; - - resources = drmModeGetResources(b->drm.fd); - if (!resources) { - weston_log("drmModeGetResources failed\n"); - return; - } - - /* collect new connectors that have appeared, e.g. MST */ - for (i = 0; i < resources->count_connectors; i++) { - uint32_t connector_id = resources->connectors[i]; - - head = drm_head_find_by_connector(b, connector_id); - if (head) { - drm_head_update_info(head); - } else { - head = drm_head_create(b, connector_id, drm_device); - if (!head) - weston_log("DRM: failed to create head for hot-added connector %d.\n", - connector_id); - } - } - - /* Remove connectors that have disappeared. */ - wl_list_for_each_safe(base, next, - &b->compositor->head_list, compositor_link) { - bool removed = true; - - head = to_drm_head(base); - - for (i = 0; i < resources->count_connectors; i++) { - if (resources->connectors[i] == head->connector_id) { - removed = false; - break; - } - } - - if (!removed) - continue; - - weston_log("DRM: head '%s' (connector %d) disappeared.\n", - head->base.name, head->connector_id); - drm_head_destroy(head); - } - - drm_backend_update_unused_outputs(b, resources); - - drmModeFreeResources(resources); -} - -static enum wdrm_connector_property -drm_head_find_property_by_id(struct drm_head *head, uint32_t property_id) -{ - int i; - enum wdrm_connector_property prop = WDRM_CONNECTOR__COUNT; - - if (!head || !property_id) - return WDRM_CONNECTOR__COUNT; - - for (i = 0; i < WDRM_CONNECTOR__COUNT; i++) - if (head->props_conn[i].prop_id == property_id) { - prop = (enum wdrm_connector_property) i; - break; - } - return prop; -} - -static void -drm_backend_update_conn_props(struct drm_backend *b, - uint32_t connector_id, - uint32_t property_id) -{ - struct drm_head *head; - enum wdrm_connector_property conn_prop; - drmModeObjectProperties *props; - - head = drm_head_find_by_connector(b, connector_id); - if (!head) { - weston_log("DRM: failed to find head for connector id: %d.\n", - connector_id); - return; - } - - conn_prop = drm_head_find_property_by_id(head, property_id); - if (conn_prop >= WDRM_CONNECTOR__COUNT) - return; - - props = drmModeObjectGetProperties(b->drm.fd, - connector_id, - DRM_MODE_OBJECT_CONNECTOR); - if (!props) { - weston_log("Error: failed to get connector '%s' properties\n", - head->base.name); - return; - } - if (conn_prop == WDRM_CONNECTOR_CONTENT_PROTECTION) { - weston_head_set_content_protection_status(&head->base, - drm_head_get_current_protection(head, props)); - } - drmModeFreeObjectProperties(props); -} - -static int -udev_event_is_hotplug(struct drm_backend *b, struct udev_device *device) -{ - const char *sysnum; - const char *val; - - sysnum = udev_device_get_sysnum(device); - if (!sysnum || atoi(sysnum) != b->drm.id) - return 0; - - val = udev_device_get_property_value(device, "HOTPLUG"); - if (!val) - return 0; - - return strcmp(val, "1") == 0; -} - -static int -udev_event_is_conn_prop_change(struct drm_backend *b, - struct udev_device *device, - uint32_t *connector_id, - uint32_t *property_id) - -{ - const char *val; - int id; - - val = udev_device_get_property_value(device, "CONNECTOR"); - if (!val || !safe_strtoint(val, &id)) - return 0; - else - *connector_id = id; - - val = udev_device_get_property_value(device, "PROPERTY"); - if (!val || !safe_strtoint(val, &id)) - return 0; - else - *property_id = id; - - return 1; -} - -static int -udev_drm_event(int fd, uint32_t mask, void *data) -{ - struct drm_backend *b = data; - struct udev_device *event; - uint32_t conn_id, prop_id; - - event = udev_monitor_receive_device(b->udev_monitor); - - if (udev_event_is_hotplug(b, event)) { - if (udev_event_is_conn_prop_change(b, event, &conn_id, &prop_id)) - drm_backend_update_conn_props(b, conn_id, prop_id); - else - drm_backend_update_heads(b, event); - } - - udev_device_unref(event); - - return 1; -} - -static void -drm_destroy(struct weston_compositor *ec) -{ - struct drm_backend *b = to_drm_backend(ec); - struct weston_head *base, *next; - - udev_input_destroy(&b->input); - - wl_event_source_remove(b->udev_drm_source); - wl_event_source_remove(b->drm_source); - - b->shutting_down = true; - - destroy_sprites(b); - -// OHOS remove logger -// weston_log_scope_destroy(b->debug); - b->debug = NULL; - weston_compositor_shutdown(ec); - - wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) - drm_head_destroy(to_drm_head(base)); - -#ifdef BUILD_DRM_GBM - if (b->gbm) - gbm_device_destroy(b->gbm); -#endif - - udev_monitor_unref(b->udev_monitor); - udev_unref(b->udev); - - weston_launcher_destroy(ec->launcher); - - wl_array_release(&b->unused_crtcs); - - DeInitWaylandDrmAuthService(); // OHOS drm auth - close(b->drm.fd); - free(b->drm.filename); - free(b); -} - -static void -session_notify(struct wl_listener *listener, void *data) -{ - struct weston_compositor *compositor = data; - struct drm_backend *b = to_drm_backend(compositor); - struct drm_plane *plane; - struct drm_output *output; - - if (compositor->session_active) { - weston_log("activating session\n"); - weston_compositor_wake(compositor); - weston_compositor_damage_all(compositor); - b->state_invalid = true; - udev_input_enable(&b->input); - } else { - weston_log("deactivating session\n"); - udev_input_disable(&b->input); - - 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 attempts at repainting. When we switch - * back, we schedule a repaint, which will process - * pending frame callbacks. */ - - wl_list_for_each(output, &compositor->output_list, base.link) { - output->base.repaint_needed = false; - if (output->cursor_plane) - drmModeSetCursor(b->drm.fd, output->crtc_id, - 0, 0, 0); - } - - output = container_of(compositor->output_list.next, - struct drm_output, base.link); - - wl_list_for_each(plane, &b->plane_list, link) { - if (plane->type != WDRM_PLANE_TYPE_OVERLAY) - continue; - - drmModeSetPlane(b->drm.fd, - plane->plane_id, - output->crtc_id, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0); - } - } -} - - -/** - * Handle KMS GPU being added/removed - * - * If the device being added/removed is the KMS device, we activate/deactivate - * the compositor session. - * - * @param compositor The compositor instance. - * @param device The device being added/removed. - * @param added Whether the device is being added (or removed) - */ -static void -drm_device_changed(struct weston_compositor *compositor, - dev_t device, bool added) -{ - struct drm_backend *b = to_drm_backend(compositor); - - if (b->drm.fd < 0 || b->drm.devnum != device || - compositor->session_active == added) - return; - - compositor->session_active = added; - wl_signal_emit(&compositor->session_signal, compositor); -} - -/** - * Determines whether or not a device is capable of modesetting. If successful, - * sets b->drm.fd and b->drm.filename to the opened device. - */ -static bool -drm_device_is_kms(struct drm_backend *b, struct udev_device *device) -{ - const char *filename = udev_device_get_devnode(device); - const char *sysnum = udev_device_get_sysnum(device); - dev_t devnum = udev_device_get_devnum(device); - drmModeRes *res; - int id = -1, fd; - - if (!filename) - return false; - - fd = weston_launcher_open(b->compositor->launcher, filename, O_RDWR | O_CLOEXEC); // OHOS - if (fd < 0) - return false; - - res = drmModeGetResources(fd); - if (!res) - goto out_fd; - - if (res->count_crtcs <= 0 || res->count_connectors <= 0 || - res->count_encoders <= 0) - goto out_res; - - if (sysnum) - id = atoi(sysnum); - if (!sysnum || id < 0) { - weston_log("couldn't get sysnum for device %s\n", filename); - goto out_res; - } - - /* We can be called successfully on multiple devices; if we have, - * clean up old entries. */ - if (b->drm.fd >= 0) - weston_launcher_close(b->compositor->launcher, b->drm.fd); - free(b->drm.filename); - - b->drm.fd = fd; - b->drm.id = id; - b->drm.filename = strdup(filename); - b->drm.devnum = devnum; - - drmModeFreeResources(res); - - return true; - -out_res: - drmModeFreeResources(res); -out_fd: - weston_launcher_close(b->compositor->launcher, fd); - return false; -} - -/* - * 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. - * Devices are also vetted to make sure they are are capable of modesetting, - * rather than pure render nodes (GPU with no display), or pure - * memory-allocation devices (VGEM). - */ -static struct udev_device* -find_primary_gpu(struct drm_backend *b, 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(b->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)) { - bool is_boot_vga = false; - - path = udev_list_entry_get_name(entry); - device = udev_device_new_from_syspath(b->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")) - is_boot_vga = true; - } - - /* If we already have a modesetting-capable device, and this - * device isn't our boot-VGA device, we aren't going to use - * it. */ - if (!is_boot_vga && drm_device) { - udev_device_unref(device); - continue; - } - - /* Make sure this device is actually capable of modesetting; - * if this call succeeds, b->drm.{fd,filename} will be set, - * and any old values freed. */ - if (!drm_device_is_kms(b, device)) { - udev_device_unref(device); - continue; - } - - /* There can only be one boot_vga device, and we try to use it - * at all costs. */ - if (is_boot_vga) { - if (drm_device) - udev_device_unref(drm_device); - drm_device = device; - break; - } - - /* Per the (!is_boot_vga && drm_device) test above, we only - * trump existing saved devices with boot-VGA devices, so if - * we end up here, this must be the first device we've seen. */ - assert(!drm_device); - drm_device = device; - } - - /* If we're returning a device to use, we must have an open FD for - * it. */ - assert(!!drm_device == (b->drm.fd >= 0)); - - udev_enumerate_unref(e); - return drm_device; -} - -static struct udev_device * -open_specific_drm_device(struct drm_backend *b, const char *name) -{ - struct udev_device *device; - - device = udev_device_new_from_subsystem_sysname(b->udev, "drm", name); - if (!device) { - weston_log("ERROR: could not open DRM device '%s'\n", name); - return NULL; - } - - if (!drm_device_is_kms(b, device)) { - udev_device_unref(device); - weston_log("ERROR: DRM device '%s' is not a KMS device.\n", name); - return NULL; - } - - /* If we're returning a device to use, we must have an open FD for - * it. */ - assert(b->drm.fd >= 0); - - return device; -} - -static void -planes_binding(struct weston_keyboard *keyboard, const struct timespec *time, - uint32_t key, void *data) -{ - struct drm_backend *b = data; - - switch (key) { - case KEY_C: - b->cursors_are_broken ^= true; - break; - case KEY_V: - /* We don't support overlay-plane usage with legacy KMS. */ - if (b->atomic_modeset) - b->sprites_are_broken ^= true; - break; - default: - break; - } -} - -#ifdef BUILD_VAAPI_RECORDER -static void -recorder_destroy(struct drm_output *output) -{ - vaapi_recorder_destroy(output->recorder); - output->recorder = NULL; - - weston_output_disable_planes_decr(&output->base); - - wl_list_remove(&output->recorder_frame_listener.link); - weston_log("[libva recorder] done\n"); -} - -static void -recorder_frame_notify(struct wl_listener *listener, void *data) -{ - struct drm_output *output; - struct drm_backend *b; - int fd, ret; - - output = container_of(listener, struct drm_output, - recorder_frame_listener); - b = to_drm_backend(output->base.compositor); - - if (!output->recorder) - return; - - ret = drmPrimeHandleToFD(b->drm.fd, - output->scanout_plane->state_cur->fb->handles[0], - DRM_CLOEXEC, &fd); - if (ret) { - weston_log("[libva recorder] " - "failed to create prime fd for front buffer\n"); - return; - } - - ret = vaapi_recorder_frame(output->recorder, fd, - output->scanout_plane->state_cur->fb->strides[0]); - if (ret < 0) { - weston_log("[libva recorder] aborted: %s\n", strerror(errno)); - recorder_destroy(output); - } -} - -static void * -create_recorder(struct drm_backend *b, int width, int height, - const char *filename) -{ - int fd; - drm_magic_t magic; - - fd = open(b->drm.filename, O_RDWR | O_CLOEXEC); - if (fd < 0) - return NULL; - - drmGetMagic(fd, &magic); - drmAuthMagic(b->drm.fd, magic); - - return vaapi_recorder_create(fd, width, height, filename); -} - -static void -recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time, - uint32_t key, void *data) -{ - struct drm_backend *b = data; - struct drm_output *output; - int width, height; - - output = container_of(b->compositor->output_list.next, - struct drm_output, base.link); - - if (!output->recorder) { - if (output->gbm_format != DRM_FORMAT_XRGB8888) { - weston_log("failed to start vaapi recorder: " - "output format not supported\n"); - return; - } - - width = output->base.current_mode->width; - height = output->base.current_mode->height; - - output->recorder = - create_recorder(b, width, height, "capture.h264"); - if (!output->recorder) { - weston_log("failed to create vaapi recorder\n"); - return; - } - - weston_output_disable_planes_incr(&output->base); - - 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 { - recorder_destroy(output); - } -} -#else -static void -recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time, - uint32_t key, void *data) -{ - weston_log("Compiled without libva support\n"); -} -#endif - - -static const struct weston_drm_output_api api = { - drm_output_set_mode, - drm_output_set_gbm_format, - drm_output_set_seat, -}; - -static struct drm_backend * -drm_backend_create(struct weston_compositor *compositor, - struct weston_drm_backend_config *config) -{ - struct drm_backend *b; - struct udev_device *drm_device; - struct wl_event_loop *loop; - const char *seat_id = default_seat; - const char *session_seat; - int ret; - - session_seat = getenv("XDG_SEAT"); - if (session_seat) - seat_id = session_seat; - - if (config->seat_id) - seat_id = config->seat_id; - - weston_log("initializing drm backend\n"); - - b = zalloc(sizeof *b); - if (b == NULL) - return NULL; - - b->state_invalid = true; - b->drm.fd = -1; - wl_array_init(&b->unused_crtcs); - - b->compositor = compositor; - b->use_pixman = config->use_pixman; - b->pageflip_timeout = config->pageflip_timeout; - b->use_pixman_shadow = config->use_pixman_shadow; -// OHOS remove logger -// b->debug = weston_compositor_add_log_scope(compositor, "drm-backend", -// "Debug messages from DRM/KMS backend\n", -// NULL, NULL, NULL); - - compositor->backend = &b->base; - compositor->require_input = !config->continue_without_input; - - if (parse_gbm_format(config->gbm_format, DRM_FORMAT_XRGB8888, &b->gbm_format) < 0) - goto err_compositor; - - /* Check if we run drm-backend using weston-launch */ - compositor->launcher = weston_launcher_connect(compositor, config->tty, - seat_id, true); - if (compositor->launcher == NULL) { - weston_log("fatal: drm backend should be run using " - "weston-launch binary, or your system should " - "provide the logind D-Bus API.\n"); - goto err_compositor; - } - - b->udev = udev_new(); - if (b->udev == NULL) { - weston_log("failed to initialize udev context\n"); - goto err_launcher; - } - - b->session_listener.notify = session_notify; - wl_signal_add(&compositor->session_signal, &b->session_listener); - - if (config->specific_device) - drm_device = open_specific_drm_device(b, config->specific_device); - else - drm_device = find_primary_gpu(b, seat_id); - if (drm_device == NULL) { - weston_log("no drm device found\n"); - goto err_udev; - } - // OHOS drm auth: init the wayland drm auth service - InitWaylandDrmAuthService(compositor->wl_display, b->drm.fd); - - if (init_kms_caps(b) < 0) { - weston_log("failed to initialize kms\n"); - goto err_udev_dev; - } - - if (b->use_pixman) { - if (init_pixman(b) < 0) { - weston_log("failed to initialize pixman renderer\n"); - goto err_udev_dev; - } - } else { - if (init_egl(b) < 0) { - weston_log("failed to initialize egl\n"); - goto err_udev_dev; - } - } - - b->base.destroy = drm_destroy; - b->base.repaint_begin = drm_repaint_begin; - b->base.repaint_flush = drm_repaint_flush; - b->base.repaint_cancel = drm_repaint_cancel; - b->base.create_output = drm_output_create; - b->base.device_changed = drm_device_changed; - b->base.can_scanout_dmabuf = drm_can_scanout_dmabuf; - - weston_setup_vt_switch_bindings(compositor); - - wl_list_init(&b->plane_list); - create_sprites(b); - - if (udev_input_init(&b->input, - compositor, b->udev, seat_id, - config->configure_device) < 0) { - weston_log("failed to create input devices\n"); -// OHOS build -// goto err_sprite; - } - - if (drm_backend_create_heads(b, drm_device) < 0) { - weston_log("Failed to create heads for %s\n", b->drm.filename); - goto err_udev_input; - } - - /* 'compute' faked zpos values in case HW doesn't expose any */ - drm_backend_create_faked_zpos(b); - - /* A this point we have some idea of whether or not we have a working - * cursor plane. */ - if (!b->cursors_are_broken) - compositor->capabilities |= WESTON_CAP_CURSOR_PLANE; - - loop = wl_display_get_event_loop(compositor->wl_display); - b->drm_source = - wl_event_loop_add_fd(loop, b->drm.fd, - WL_EVENT_READABLE, on_drm_input, b); - - b->udev_monitor = udev_monitor_new_from_netlink(b->udev, "udev"); - if (b->udev_monitor == NULL) { - weston_log("failed to initialize udev monitor\n"); - goto err_drm_source; - } - udev_monitor_filter_add_match_subsystem_devtype(b->udev_monitor, - "drm", NULL); - b->udev_drm_source = - wl_event_loop_add_fd(loop, - udev_monitor_get_fd(b->udev_monitor), - WL_EVENT_READABLE, udev_drm_event, b); - - if (udev_monitor_enable_receiving(b->udev_monitor) < 0) { - weston_log("failed to enable udev-monitor receiving\n"); - goto err_udev_monitor; - } - - udev_device_unref(drm_device); - -// OHOS remove debugger - // weston_compositor_add_debug_binding(compositor, KEY_O, - // planes_binding, b); - // weston_compositor_add_debug_binding(compositor, KEY_C, - // planes_binding, b); - // weston_compositor_add_debug_binding(compositor, KEY_V, - // planes_binding, b); - // weston_compositor_add_debug_binding(compositor, KEY_Q, - // recorder_binding, b); - // weston_compositor_add_debug_binding(compositor, KEY_W, - // renderer_switch_binding, b); - - - if (compositor->renderer->import_dmabuf) { - if (linux_dmabuf_setup(compositor) < 0) - weston_log("Error: initializing dmabuf " - "support failed.\n"); - if (weston_direct_display_setup(compositor) < 0) - weston_log("Error: initializing direct-display " - "support failed.\n"); - } - - if (compositor->capabilities & WESTON_CAP_EXPLICIT_SYNC) { - if (linux_explicit_synchronization_setup(compositor) < 0) - weston_log("Error: initializing explicit " - " synchronization support failed.\n"); - } - - if (b->atomic_modeset) - if (weston_compositor_enable_content_protection(compositor) < 0) - weston_log("Error: initializing content-protection " - "support failed.\n"); - - ret = weston_plugin_api_register(compositor, WESTON_DRM_OUTPUT_API_NAME, - &api, sizeof(api)); - - if (ret < 0) { - weston_log("Failed to register output API.\n"); - goto err_udev_monitor; - } - - ret = drm_backend_init_virtual_output_api(compositor); - if (ret < 0) { - weston_log("Failed to register virtual output API.\n"); - goto err_udev_monitor; - } - - return b; - -err_udev_monitor: - wl_event_source_remove(b->udev_drm_source); - udev_monitor_unref(b->udev_monitor); -err_drm_source: - wl_event_source_remove(b->drm_source); -err_udev_input: - udev_input_destroy(&b->input); -// OHOS build -// err_sprite: -#ifdef BUILD_DRM_GBM - if (b->gbm) - gbm_device_destroy(b->gbm); -#endif - destroy_sprites(b); -err_udev_dev: - udev_device_unref(drm_device); -err_udev: - udev_unref(b->udev); -err_launcher: - weston_launcher_destroy(compositor->launcher); -err_compositor: - weston_compositor_shutdown(compositor); - free(b); - return NULL; -} - -static void -config_init_to_defaults(struct weston_drm_backend_config *config) -{ - // OHOS - // config->use_pixman_shadow = true; - config->use_pixman_shadow = false; -} - -WL_EXPORT int -weston_backend_init(struct weston_compositor *compositor, - struct weston_backend_config *config_base) -{ - struct drm_backend *b; - struct weston_drm_backend_config config = {{ 0, }}; - - if (config_base == NULL || - config_base->struct_version != WESTON_DRM_BACKEND_CONFIG_VERSION || - config_base->struct_size > sizeof(struct weston_drm_backend_config)) { - weston_log("drm backend config structure is invalid\n"); - return -1; - } - - config_init_to_defaults(&config); - memcpy(&config, config_base, config_base->struct_size); - - b = drm_backend_create(compositor, &config); - if (b == NULL) - return -1; - - return 0; -} diff --git a/libweston/backend-drm/fb.c b/libweston/backend-drm/fb.c deleted file mode 100644 index e7349c4..0000000 --- a/libweston/backend-drm/fb.c +++ /dev/null @@ -1,582 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2011 Intel Corporation - * Copyright © 2017, 2018 Collabora, Ltd. - * Copyright © 2017, 2018 General Electric Company - * Copyright (c) 2018 DisplayLink (UK) Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include "shared/helpers.h" -#include "drm-internal.h" -#include "linux-dmabuf.h" - -static void -drm_fb_destroy(struct drm_fb *fb) -{ - if (fb->fb_id != 0) - drmModeRmFB(fb->fd, fb->fb_id); - weston_buffer_reference(&fb->buffer_ref, NULL); - weston_buffer_release_reference(&fb->buffer_release_ref, NULL); - free(fb); -} - -static void -drm_fb_destroy_dumb(struct drm_fb *fb) -{ - struct drm_mode_destroy_dumb destroy_arg; - - assert(fb->type == BUFFER_PIXMAN_DUMB); - - if (fb->map && fb->size > 0) - munmap(fb->map, fb->size); - - memset(&destroy_arg, 0, sizeof(destroy_arg)); - destroy_arg.handle = fb->handles[0]; - drmIoctl(fb->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg); - - drm_fb_destroy(fb); -} - -static int -drm_fb_addfb(struct drm_backend *b, struct drm_fb *fb) -{ - int ret = -EINVAL; - uint64_t mods[4] = { }; - size_t i; - - /* If we have a modifier set, we must only use the WithModifiers - * entrypoint; we cannot import it through legacy ioctls. */ - if (b->fb_modifiers && fb->modifier != DRM_FORMAT_MOD_INVALID) { - /* KMS demands that if a modifier is set, it must be the same - * for all planes. */ - for (i = 0; i < ARRAY_LENGTH(mods) && fb->handles[i]; i++) - mods[i] = fb->modifier; - ret = drmModeAddFB2WithModifiers(fb->fd, fb->width, fb->height, - fb->format->format, - fb->handles, fb->strides, - fb->offsets, mods, &fb->fb_id, - DRM_MODE_FB_MODIFIERS); - return ret; - } - - ret = drmModeAddFB2(fb->fd, fb->width, fb->height, fb->format->format, - fb->handles, fb->strides, fb->offsets, &fb->fb_id, - 0); - if (ret == 0) - return 0; - - /* Legacy AddFB can't always infer the format from depth/bpp alone, so - * check if our format is one of the lucky ones. */ - if (!fb->format->depth || !fb->format->bpp) - return ret; - - /* Cannot fall back to AddFB for multi-planar formats either. */ - if (fb->handles[1] || fb->handles[2] || fb->handles[3]) - return ret; - - ret = drmModeAddFB(fb->fd, fb->width, fb->height, - fb->format->depth, fb->format->bpp, - fb->strides[0], fb->handles[0], &fb->fb_id); - return ret; -} - -struct drm_fb * -drm_fb_create_dumb(struct drm_backend *b, int width, int height, - uint32_t format) -{ - 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; - fb->refcnt = 1; - - fb->format = pixel_format_get_info(format); - if (!fb->format) { - weston_log("failed to look up format 0x%lx\n", - (unsigned long) format); - goto err_fb; - } - - if (!fb->format->depth || !fb->format->bpp) { - weston_log("format 0x%lx is not compatible with dumb buffers\n", - (unsigned long) format); - goto err_fb; - } - - memset(&create_arg, 0, sizeof create_arg); - create_arg.bpp = fb->format->bpp; - create_arg.width = width; - create_arg.height = height; - - ret = drmIoctl(b->drm.fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_arg); - if (ret) - goto err_fb; - - fb->type = BUFFER_PIXMAN_DUMB; - fb->modifier = DRM_FORMAT_MOD_INVALID; - fb->handles[0] = create_arg.handle; - fb->strides[0] = create_arg.pitch; - fb->num_planes = 1; - fb->size = create_arg.size; - fb->width = width; - fb->height = height; - fb->fd = b->drm.fd; - - if (drm_fb_addfb(b, fb) != 0) { - weston_log("failed to create kms fb: %s\n", strerror(errno)); - goto err_bo; - } - - memset(&map_arg, 0, sizeof map_arg); - map_arg.handle = fb->handles[0]; - ret = drmIoctl(fb->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_arg); - if (ret) - goto err_add_fb; - - fb->map = mmap(NULL, fb->size, PROT_WRITE, - MAP_SHARED, b->drm.fd, map_arg.offset); - if (fb->map == MAP_FAILED) - goto err_add_fb; - - return fb; - -err_add_fb: - drmModeRmFB(b->drm.fd, fb->fb_id); -err_bo: - memset(&destroy_arg, 0, sizeof(destroy_arg)); - destroy_arg.handle = create_arg.handle; - drmIoctl(b->drm.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg); -err_fb: - free(fb); - return NULL; -} - -struct drm_fb * -drm_fb_ref(struct drm_fb *fb) -{ - fb->refcnt++; - return fb; -} - -#ifdef BUILD_DRM_GBM -static void -drm_fb_destroy_gbm(struct gbm_bo *bo, void *data) -{ - struct drm_fb *fb = data; - - assert(fb->type == BUFFER_GBM_SURFACE || fb->type == BUFFER_CLIENT || - fb->type == BUFFER_CURSOR); - drm_fb_destroy(fb); -} - -static void -drm_fb_destroy_dmabuf(struct drm_fb *fb) -{ - /* We deliberately do not close the GEM handles here; GBM manages - * their lifetime through the BO. */ - if (fb->bo) - gbm_bo_destroy(fb->bo); - drm_fb_destroy(fb); -} - -static struct drm_fb * -drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, - struct drm_backend *backend, bool is_opaque) -{ - struct drm_fb *fb; - struct gbm_import_fd_data import_legacy = { - .width = dmabuf->attributes.width, - .height = dmabuf->attributes.height, - .format = dmabuf->attributes.format, - .stride = dmabuf->attributes.stride[0], - .fd = dmabuf->attributes.fd[0], - }; -#ifdef HAVE_GBM_FD_IMPORT - struct gbm_import_fd_modifier_data import_mod = { - .width = dmabuf->attributes.width, - .height = dmabuf->attributes.height, - .format = dmabuf->attributes.format, - .num_fds = dmabuf->attributes.n_planes, - .modifier = dmabuf->attributes.modifier[0], - }; -#endif /* HAVE_GBM_FD_IMPORT */ - - int i; - - /* XXX: TODO: - * - * Currently the buffer is rejected if any dmabuf attribute - * flag is set. This keeps us from passing an inverted / - * interlaced / bottom-first buffer (or any other type that may - * be added in the future) through to an overlay. Ultimately, - * these types of buffers should be handled through buffer - * transforms and not as spot-checks requiring specific - * knowledge. */ - if (dmabuf->attributes.flags) - return NULL; - - fb = zalloc(sizeof *fb); - if (fb == NULL) - return NULL; - - fb->refcnt = 1; - fb->type = BUFFER_DMABUF; - -#ifdef HAVE_GBM_FD_IMPORT - static_assert(ARRAY_LENGTH(import_mod.fds) == - ARRAY_LENGTH(dmabuf->attributes.fd), - "GBM and linux_dmabuf FD size must match"); - static_assert(sizeof(import_mod.fds) == sizeof(dmabuf->attributes.fd), - "GBM and linux_dmabuf FD size must match"); - memcpy(import_mod.fds, dmabuf->attributes.fd, sizeof(import_mod.fds)); - - static_assert(ARRAY_LENGTH(import_mod.strides) == - ARRAY_LENGTH(dmabuf->attributes.stride), - "GBM and linux_dmabuf stride size must match"); - static_assert(sizeof(import_mod.strides) == - sizeof(dmabuf->attributes.stride), - "GBM and linux_dmabuf stride size must match"); - memcpy(import_mod.strides, dmabuf->attributes.stride, - sizeof(import_mod.strides)); - - static_assert(ARRAY_LENGTH(import_mod.offsets) == - ARRAY_LENGTH(dmabuf->attributes.offset), - "GBM and linux_dmabuf offset size must match"); - static_assert(sizeof(import_mod.offsets) == - sizeof(dmabuf->attributes.offset), - "GBM and linux_dmabuf offset size must match"); - memcpy(import_mod.offsets, dmabuf->attributes.offset, - sizeof(import_mod.offsets)); -#endif /* NOT HAVE_GBM_FD_IMPORT */ - - /* The legacy FD-import path does not allow us to supply modifiers, - * multiple planes, or buffer offsets. */ - if (dmabuf->attributes.modifier[0] != DRM_FORMAT_MOD_INVALID || - dmabuf->attributes.n_planes > 1 || - dmabuf->attributes.offset[0] > 0) { -#ifdef HAVE_GBM_FD_IMPORT - fb->bo = gbm_bo_import(backend->gbm, GBM_BO_IMPORT_FD_MODIFIER, - &import_mod, - GBM_BO_USE_SCANOUT); -#else /* NOT HAVE_GBM_FD_IMPORT */ - drm_debug(backend, "\t\t\t[dmabuf] Unsupported use of modifiers.\n"); - goto err_free; -#endif /* NOT HAVE_GBM_FD_IMPORT */ - } else { - fb->bo = gbm_bo_import(backend->gbm, GBM_BO_IMPORT_FD, - &import_legacy, - GBM_BO_USE_SCANOUT); - } - - if (!fb->bo) - goto err_free; - - fb->width = dmabuf->attributes.width; - fb->height = dmabuf->attributes.height; - fb->modifier = dmabuf->attributes.modifier[0]; - fb->size = 0; - fb->fd = backend->drm.fd; - - static_assert(ARRAY_LENGTH(fb->strides) == - ARRAY_LENGTH(dmabuf->attributes.stride), - "drm_fb and dmabuf stride size must match"); - static_assert(sizeof(fb->strides) == sizeof(dmabuf->attributes.stride), - "drm_fb and dmabuf stride size must match"); - memcpy(fb->strides, dmabuf->attributes.stride, sizeof(fb->strides)); - static_assert(ARRAY_LENGTH(fb->offsets) == - ARRAY_LENGTH(dmabuf->attributes.offset), - "drm_fb and dmabuf offset size must match"); - static_assert(sizeof(fb->offsets) == sizeof(dmabuf->attributes.offset), - "drm_fb and dmabuf offset size must match"); - memcpy(fb->offsets, dmabuf->attributes.offset, sizeof(fb->offsets)); - - fb->format = pixel_format_get_info(dmabuf->attributes.format); - if (!fb->format) { - weston_log("couldn't look up format info for 0x%lx\n", - (unsigned long) dmabuf->attributes.format); - goto err_free; - } - - if (is_opaque) - fb->format = pixel_format_get_opaque_substitute(fb->format); - - if (backend->min_width > fb->width || - fb->width > backend->max_width || - backend->min_height > fb->height || - fb->height > backend->max_height) { - weston_log("bo geometry out of bounds\n"); - goto err_free; - } - -#ifdef HAVE_GBM_MODIFIERS - fb->num_planes = dmabuf->attributes.n_planes; - for (i = 0; i < dmabuf->attributes.n_planes; i++) { - union gbm_bo_handle handle; - - handle = gbm_bo_get_handle_for_plane(fb->bo, i); - if (handle.s32 == -1) - goto err_free; - fb->handles[i] = handle.u32; - } -#else /* NOT HAVE_GBM_MODIFIERS */ - { - union gbm_bo_handle handle; - - fb->num_planes = 1; - - handle = gbm_bo_get_handle(fb->bo); - - if (handle.s32 == -1) - goto err_free; - fb->handles[0] = handle.u32; - } -#endif /* NOT HAVE_GBM_MODIFIERS */ - - - if (drm_fb_addfb(backend, fb) != 0) - goto err_free; - - return fb; - -err_free: - drm_fb_destroy_dmabuf(fb); - return NULL; -} - -struct drm_fb * -drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, - bool is_opaque, enum drm_fb_type type) -{ - struct drm_fb *fb = gbm_bo_get_user_data(bo); -#ifdef HAVE_GBM_MODIFIERS - int i; -#endif - - if (fb) { - assert(fb->type == type); - return drm_fb_ref(fb); - } - - fb = zalloc(sizeof *fb); - if (fb == NULL) - return NULL; - - fb->type = type; - fb->refcnt = 1; - fb->bo = bo; - fb->fd = backend->drm.fd; - - fb->width = gbm_bo_get_width(bo); - fb->height = gbm_bo_get_height(bo); - fb->format = pixel_format_get_info(gbm_bo_get_format(bo)); - fb->size = 0; - -#ifdef HAVE_GBM_MODIFIERS - fb->modifier = gbm_bo_get_modifier(bo); - fb->num_planes = gbm_bo_get_plane_count(bo); - for (i = 0; i < fb->num_planes; i++) { - fb->strides[i] = gbm_bo_get_stride_for_plane(bo, i); - fb->handles[i] = gbm_bo_get_handle_for_plane(bo, i).u32; - fb->offsets[i] = gbm_bo_get_offset(bo, i); - } -#else - fb->num_planes = 1; - fb->strides[0] = gbm_bo_get_stride(bo); - fb->handles[0] = gbm_bo_get_handle(bo).u32; - fb->modifier = DRM_FORMAT_MOD_INVALID; -#endif - - if (!fb->format) { - weston_log("couldn't look up format 0x%lx\n", - (unsigned long) gbm_bo_get_format(bo)); - goto err_free; - } - - /* We can scanout an ARGB buffer if the surface's opaque region covers - * the whole output, but we have to use XRGB as the KMS format code. */ - if (is_opaque) - fb->format = pixel_format_get_opaque_substitute(fb->format); - - if (backend->min_width > fb->width || - fb->width > backend->max_width || - backend->min_height > fb->height || - fb->height > backend->max_height) { - weston_log("bo geometry out of bounds\n"); - goto err_free; - } - - if (drm_fb_addfb(backend, fb) != 0) { - if (type == BUFFER_GBM_SURFACE) - weston_log("failed to create kms fb: %s\n", - strerror(errno)); - goto err_free; - } - - gbm_bo_set_user_data(bo, fb, drm_fb_destroy_gbm); - - return fb; - -err_free: - free(fb); - return NULL; -} - -static void -drm_fb_set_buffer(struct drm_fb *fb, struct weston_buffer *buffer, - struct weston_buffer_release *buffer_release) -{ - assert(fb->buffer_ref.buffer == NULL); - assert(fb->type == BUFFER_CLIENT || fb->type == BUFFER_DMABUF); - weston_buffer_reference(&fb->buffer_ref, buffer); - weston_buffer_release_reference(&fb->buffer_release_ref, - buffer_release); -} -#endif - -void -drm_fb_unref(struct drm_fb *fb) -{ - if (!fb) - return; - - assert(fb->refcnt > 0); - if (--fb->refcnt > 0) - return; - - switch (fb->type) { - case BUFFER_PIXMAN_DUMB: - drm_fb_destroy_dumb(fb); - break; -#ifdef BUILD_DRM_GBM - case BUFFER_CURSOR: - case BUFFER_CLIENT: - gbm_bo_destroy(fb->bo); - break; - case BUFFER_GBM_SURFACE: - gbm_surface_release_buffer(fb->gbm_surface, fb->bo); - break; - case BUFFER_DMABUF: - drm_fb_destroy_dmabuf(fb); - break; -#endif - default: - assert(NULL); - break; - } -} - -#ifdef BUILD_DRM_GBM -bool -drm_can_scanout_dmabuf(struct weston_compositor *ec, - struct linux_dmabuf_buffer *dmabuf) -{ - struct drm_fb *fb; - struct drm_backend *b = to_drm_backend(ec); - bool ret = false; - - fb = drm_fb_get_from_dmabuf(dmabuf, b, true); - if (fb) - ret = true; - - drm_fb_unref(fb); - drm_debug(b, "[dmabuf] dmabuf %p, import test %s\n", dmabuf, - ret ? "succeeded" : "failed"); - return ret; -} - -struct drm_fb * -drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev) -{ - struct drm_output *output = state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); - struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; - bool is_opaque = weston_view_is_opaque(ev, &ev->transform.boundingbox); - struct linux_dmabuf_buffer *dmabuf; - struct drm_fb *fb; - - if (ev->alpha != 1.0f) - return NULL; - - if (!drm_view_transform_supported(ev, &output->base)) - return NULL; - - if (ev->surface->protection_mode == WESTON_SURFACE_PROTECTION_MODE_ENFORCED && - ev->surface->desired_protection > output->base.current_protection) - return NULL; - - if (!buffer) - return NULL; - - if (wl_shm_buffer_get(buffer->resource)) - return NULL; - - /* GBM is used for dmabuf import as well as from client wl_buffer. */ - if (!b->gbm) - return NULL; - - dmabuf = linux_dmabuf_buffer_get(buffer->resource); - if (dmabuf) { - fb = drm_fb_get_from_dmabuf(dmabuf, b, is_opaque); - if (!fb) - return NULL; - } else { - struct gbm_bo *bo; - - bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER, - buffer->resource, GBM_BO_USE_SCANOUT); - if (!bo) - return NULL; - - fb = drm_fb_get_from_bo(bo, b, is_opaque, BUFFER_CLIENT); - if (!fb) { - gbm_bo_destroy(bo); - return NULL; - } - } - - drm_debug(b, "\t\t\t[view] view %p format: %s\n", - ev, fb->format->drm_format_name); - drm_fb_set_buffer(fb, buffer, - ev->surface->buffer_release_ref.buffer_release); - return fb; -} -#endif diff --git a/libweston/backend-drm/kms.c b/libweston/backend-drm/kms.c deleted file mode 100755 index c14b222..0000000 --- a/libweston/backend-drm/kms.c +++ /dev/null @@ -1,1507 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2011 Intel Corporation - * Copyright © 2017, 2018 Collabora, Ltd. - * Copyright © 2017, 2018 General Electric Company - * Copyright (c) 2018 DisplayLink (UK) Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include - -#include -#include -#include - -#include -#include -#include "shared/helpers.h" -#include "drm-internal.h" -#include "pixel-formats.h" -#include "presentation-time-server-protocol.h" - -#ifndef DRM_FORMAT_MOD_LINEAR -#define DRM_FORMAT_MOD_LINEAR 0 -#endif - -struct drm_property_enum_info plane_type_enums[] = { - [WDRM_PLANE_TYPE_PRIMARY] = { - .name = "Primary", - }, - [WDRM_PLANE_TYPE_OVERLAY] = { - .name = "Overlay", - }, - [WDRM_PLANE_TYPE_CURSOR] = { - .name = "Cursor", - }, -}; - -const struct drm_property_info plane_props[] = { - [WDRM_PLANE_TYPE] = { - .name = "type", - .enum_values = plane_type_enums, - .num_enum_values = WDRM_PLANE_TYPE__COUNT, - }, - [WDRM_PLANE_SRC_X] = { .name = "SRC_X", }, - [WDRM_PLANE_SRC_Y] = { .name = "SRC_Y", }, - [WDRM_PLANE_SRC_W] = { .name = "SRC_W", }, - [WDRM_PLANE_SRC_H] = { .name = "SRC_H", }, - [WDRM_PLANE_CRTC_X] = { .name = "CRTC_X", }, - [WDRM_PLANE_CRTC_Y] = { .name = "CRTC_Y", }, - [WDRM_PLANE_CRTC_W] = { .name = "CRTC_W", }, - [WDRM_PLANE_CRTC_H] = { .name = "CRTC_H", }, - [WDRM_PLANE_FB_ID] = { .name = "FB_ID", }, - [WDRM_PLANE_CRTC_ID] = { .name = "CRTC_ID", }, - [WDRM_PLANE_IN_FORMATS] = { .name = "IN_FORMATS" }, - [WDRM_PLANE_IN_FENCE_FD] = { .name = "IN_FENCE_FD" }, - [WDRM_PLANE_FB_DAMAGE_CLIPS] = { .name = "FB_DAMAGE_CLIPS" }, - [WDRM_PLANE_ZPOS] = { .name = "zpos" }, -}; - -struct drm_property_enum_info dpms_state_enums[] = { - [WDRM_DPMS_STATE_OFF] = { - .name = "Off", - }, - [WDRM_DPMS_STATE_ON] = { - .name = "On", - }, - [WDRM_DPMS_STATE_STANDBY] = { - .name = "Standby", - }, - [WDRM_DPMS_STATE_SUSPEND] = { - .name = "Suspend", - }, -}; - -struct drm_property_enum_info content_protection_enums[] = { - [WDRM_CONTENT_PROTECTION_UNDESIRED] = { - .name = "Undesired", - }, - [WDRM_CONTENT_PROTECTION_DESIRED] = { - .name = "Desired", - }, - [WDRM_CONTENT_PROTECTION_ENABLED] = { - .name = "Enabled", - }, -}; - -struct drm_property_enum_info hdcp_content_type_enums[] = { - [WDRM_HDCP_CONTENT_TYPE0] = { - .name = "HDCP Type0", - }, - [WDRM_HDCP_CONTENT_TYPE1] = { - .name = "HDCP Type1", - }, -}; - -struct drm_property_enum_info panel_orientation_enums[] = { - [WDRM_PANEL_ORIENTATION_NORMAL] = { .name = "Normal", }, - [WDRM_PANEL_ORIENTATION_UPSIDE_DOWN] = { .name = "Upside Down", }, - [WDRM_PANEL_ORIENTATION_LEFT_SIDE_UP] = { .name = "Left Side Up", }, - [WDRM_PANEL_ORIENTATION_RIGHT_SIDE_UP] = { .name = "Right Side Up", }, -}; - -const struct drm_property_info connector_props[] = { - [WDRM_CONNECTOR_EDID] = { .name = "EDID" }, - [WDRM_CONNECTOR_DPMS] = { - .name = "DPMS", - .enum_values = dpms_state_enums, - .num_enum_values = WDRM_DPMS_STATE__COUNT, - }, - [WDRM_CONNECTOR_CRTC_ID] = { .name = "CRTC_ID", }, - [WDRM_CONNECTOR_NON_DESKTOP] = { .name = "non-desktop", }, - [WDRM_CONNECTOR_CONTENT_PROTECTION] = { - .name = "Content Protection", - .enum_values = content_protection_enums, - .num_enum_values = WDRM_CONTENT_PROTECTION__COUNT, - }, - [WDRM_CONNECTOR_HDCP_CONTENT_TYPE] = { - .name = "HDCP Content Type", - .enum_values = hdcp_content_type_enums, - .num_enum_values = WDRM_HDCP_CONTENT_TYPE__COUNT, - }, - [WDRM_CONNECTOR_PANEL_ORIENTATION] = { - .name = "panel orientation", - .enum_values = panel_orientation_enums, - .num_enum_values = WDRM_PANEL_ORIENTATION__COUNT, - }, -}; - -const struct drm_property_info crtc_props[] = { - [WDRM_CRTC_MODE_ID] = { .name = "MODE_ID", }, - [WDRM_CRTC_ACTIVE] = { .name = "ACTIVE", }, -}; - - -/** - * Mode for drm_pending_state_apply and co. - */ -enum drm_state_apply_mode { - DRM_STATE_APPLY_SYNC, /**< state fully processed */ - DRM_STATE_APPLY_ASYNC, /**< state pending event delivery */ - DRM_STATE_TEST_ONLY, /**< test if the state can be applied */ -}; - -/** - * Get the current value of a KMS property - * - * Given a drmModeObjectGetProperties return, as well as the drm_property_info - * for the target property, return the current value of that property, - * with an optional default. If the property is a KMS enum type, the return - * value will be translated into the appropriate internal enum. - * - * If the property is not present, the default value will be returned. - * - * @param info Internal structure for property to look up - * @param props Raw KMS properties for the target object - * @param def Value to return if property is not found - */ -uint64_t -drm_property_get_value(struct drm_property_info *info, - const drmModeObjectProperties *props, - uint64_t def) -{ - unsigned int i; - - if (info->prop_id == 0) - return def; - - for (i = 0; i < props->count_props; i++) { - unsigned int j; - - if (props->props[i] != info->prop_id) - continue; - - /* Simple (non-enum) types can return the value directly */ - if (info->num_enum_values == 0) - return props->prop_values[i]; - - /* Map from raw value to enum value */ - for (j = 0; j < info->num_enum_values; j++) { - if (!info->enum_values[j].valid) - continue; - if (info->enum_values[j].value != props->prop_values[i]) - continue; - - return j; - } - - /* We don't have a mapping for this enum; return default. */ - break; - } - - return def; -} - -/** - * Get the current range values of a KMS property - * - * Given a drmModeObjectGetProperties return, as well as the drm_property_info - * for the target property, return the current range values of that property, - * - * If the property is not present, or there's no it is not a prop range then - * NULL will be returned. - * - * @param info Internal structure for property to look up - * @param props Raw KMS properties for the target object - */ -uint64_t * -drm_property_get_range_values(struct drm_property_info *info, - const drmModeObjectProperties *props) -{ - unsigned int i; - - if (info->prop_id == 0) - return NULL; - - for (i = 0; i < props->count_props; i++) { - - if (props->props[i] != info->prop_id) - continue; - - if (!(info->flags & DRM_MODE_PROP_RANGE) && - !(info->flags & DRM_MODE_PROP_SIGNED_RANGE)) - continue; - - return info->range_values; - } - - return NULL; -} - -/** - * Cache DRM property values - * - * Update a per-object array of drm_property_info structures, given the - * DRM properties of the object. - * - * Call this every time an object newly appears (note that only connectors - * can be hotplugged), the first time it is seen, or when its status changes - * in a way which invalidates the potential property values (currently, the - * only case for this is connector hotplug). - * - * This updates the property IDs and enum values within the drm_property_info - * array. - * - * DRM property enum values are dynamic at runtime; the user must query the - * property to find out the desired runtime value for a requested string - * name. Using the 'type' field on planes as an example, there is no single - * hardcoded constant for primary plane types; instead, the property must be - * queried at runtime to find the value associated with the string "Primary". - * - * This helper queries and caches the enum values, to allow us to use a set - * of compile-time-constant enums portably across various implementations. - * The values given in enum_names are searched for, and stored in the - * same-indexed field of the map array. - * - * @param b DRM backend object - * @param src DRM property info array to source from - * @param info DRM property info array to copy into - * @param num_infos Number of entries in the source array - * @param props DRM object properties for the object - */ -void -drm_property_info_populate(struct drm_backend *b, - const struct drm_property_info *src, - struct drm_property_info *info, - unsigned int num_infos, - drmModeObjectProperties *props) -{ - drmModePropertyRes *prop; - unsigned i, j; - - for (i = 0; i < num_infos; i++) { - unsigned int j; - - info[i].name = src[i].name; - info[i].prop_id = 0; - info[i].num_enum_values = src[i].num_enum_values; - - if (src[i].num_enum_values == 0) - continue; - - info[i].enum_values = - malloc(src[i].num_enum_values * - sizeof(*info[i].enum_values)); - assert(info[i].enum_values); - for (j = 0; j < info[i].num_enum_values; j++) { - info[i].enum_values[j].name = src[i].enum_values[j].name; - info[i].enum_values[j].valid = false; - } - } - - for (i = 0; i < props->count_props; i++) { - unsigned int k; - - prop = drmModeGetProperty(b->drm.fd, props->props[i]); - if (!prop) - continue; - - for (j = 0; j < num_infos; j++) { - if (!strcmp(prop->name, info[j].name)) - break; - } - - /* We don't know/care about this property. */ - if (j == num_infos) { -#ifdef DEBUG - weston_log("DRM debug: unrecognized property %u '%s'\n", - prop->prop_id, prop->name); -#endif - drmModeFreeProperty(prop); - continue; - } - - if (info[j].num_enum_values == 0 && - (prop->flags & DRM_MODE_PROP_ENUM)) { - weston_log("DRM: expected property %s to not be an" - " enum, but it is; ignoring\n", prop->name); - drmModeFreeProperty(prop); - continue; - } - - info[j].prop_id = props->props[i]; - info[j].flags = prop->flags; - - if (prop->flags & DRM_MODE_PROP_RANGE || - prop->flags & DRM_MODE_PROP_SIGNED_RANGE) { - info[j].num_range_values = prop->count_values; - for (int i = 0; i < prop->count_values; i++) - info[j].range_values[i] = prop->values[i]; - } - - - if (info[j].num_enum_values == 0) { - drmModeFreeProperty(prop); - continue; - } - - if (!(prop->flags & DRM_MODE_PROP_ENUM)) { - weston_log("DRM: expected property %s to be an enum," - " but it is not; ignoring\n", prop->name); - drmModeFreeProperty(prop); - info[j].prop_id = 0; - continue; - } - - for (k = 0; k < info[j].num_enum_values; k++) { - int l; - - for (l = 0; l < prop->count_enums; l++) { - if (!strcmp(prop->enums[l].name, - info[j].enum_values[k].name)) - break; - } - - if (l == prop->count_enums) - continue; - - info[j].enum_values[k].valid = true; - info[j].enum_values[k].value = prop->enums[l].value; - } - - drmModeFreeProperty(prop); - } - -#ifdef DEBUG - for (i = 0; i < num_infos; i++) { - if (info[i].prop_id == 0) - weston_log("DRM warning: property '%s' missing\n", - info[i].name); - } -#endif -} - -/** - * Free DRM property information - * - * Frees all memory associated with a DRM property info array and zeroes - * it out, leaving it usable for a further drm_property_info_update() or - * drm_property_info_free(). - * - * @param info DRM property info array - * @param num_props Number of entries in array to free - */ -void -drm_property_info_free(struct drm_property_info *info, int num_props) -{ - int i; - - for (i = 0; i < num_props; i++) - free(info[i].enum_values); - - memset(info, 0, sizeof(*info) * num_props); -} - -static inline uint32_t * -formats_ptr(struct drm_format_modifier_blob *blob) -{ - return (uint32_t *)(((char *)blob) + blob->formats_offset); -} - -static inline struct drm_format_modifier * -modifiers_ptr(struct drm_format_modifier_blob *blob) -{ - return (struct drm_format_modifier *) - (((char *)blob) + blob->modifiers_offset); -} - -/** - * Populates the plane's formats array, using either the IN_FORMATS blob - * property (if available), or the plane's format list if not. - */ -int -drm_plane_populate_formats(struct drm_plane *plane, const drmModePlane *kplane, - const drmModeObjectProperties *props, - const bool use_modifiers) -{ - unsigned i; - drmModePropertyBlobRes *blob; - struct drm_format_modifier_blob *fmt_mod_blob; - struct drm_format_modifier *blob_modifiers; - uint32_t *blob_formats; - uint32_t blob_id; - - if (!use_modifiers) - goto fallback; - - blob_id = drm_property_get_value(&plane->props[WDRM_PLANE_IN_FORMATS], - props, - 0); - if (blob_id == 0) - goto fallback; - - blob = drmModeGetPropertyBlob(plane->backend->drm.fd, blob_id); - if (!blob) - goto fallback; - - fmt_mod_blob = blob->data; - blob_formats = formats_ptr(fmt_mod_blob); - blob_modifiers = modifiers_ptr(fmt_mod_blob); - - if (plane->count_formats != fmt_mod_blob->count_formats) { - weston_log("DRM backend: format count differs between " - "plane (%d) and IN_FORMATS (%d)\n", - plane->count_formats, - fmt_mod_blob->count_formats); - weston_log("This represents a kernel bug; Weston is " - "unable to continue.\n"); - abort(); - } - - for (i = 0; i < fmt_mod_blob->count_formats; i++) { - uint32_t count_modifiers = 0; - uint64_t *modifiers = NULL; - unsigned j; - - for (j = 0; j < fmt_mod_blob->count_modifiers; j++) { - struct drm_format_modifier *mod = &blob_modifiers[j]; - - if ((i < mod->offset) || (i > mod->offset + 63)) - continue; - if (!(mod->formats & (1 << (i - mod->offset)))) - continue; - - modifiers = realloc(modifiers, - (count_modifiers + 1) * - sizeof(modifiers[0])); - assert(modifiers); - modifiers[count_modifiers++] = mod->modifier; - } - - if (count_modifiers == 0) { - modifiers = malloc(sizeof(*modifiers)); - *modifiers = DRM_FORMAT_MOD_LINEAR; - count_modifiers = 1; - } - - plane->formats[i].format = blob_formats[i]; - plane->formats[i].modifiers = modifiers; - plane->formats[i].count_modifiers = count_modifiers; - } - - drmModeFreePropertyBlob(blob); - - return 0; - -fallback: - /* No IN_FORMATS blob available, so just use the old. */ - assert(plane->count_formats == kplane->count_formats); - for (i = 0; i < kplane->count_formats; i++) { - plane->formats[i].format = kplane->formats[i]; - plane->formats[i].modifiers = malloc(sizeof(uint64_t)); - plane->formats[i].modifiers[0] = DRM_FORMAT_MOD_LINEAR; - plane->formats[i].count_modifiers = 1; - } - - return 0; -} - -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 = to_drm_output(output_base); - struct drm_backend *backend = - to_drm_backend(output->base.compositor); - - /* check */ - if (output_base->gamma_size != size) - return; - - rc = drmModeCrtcSetGamma(backend->drm.fd, - output->crtc_id, - size, r, g, b); - if (rc) - weston_log("set gamma failed: %s\n", strerror(errno)); -} - -/** - * Mark an output state as current on the output, i.e. it has been - * submitted to the kernel. The mode argument determines whether this - * update will be applied synchronously (e.g. when calling drmModeSetCrtc), - * or asynchronously (in which case we wait for events to complete). - */ -static void -drm_output_assign_state(struct drm_output_state *state, - enum drm_state_apply_mode mode) -{ - struct drm_output *output = state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); - struct drm_plane_state *plane_state; - struct drm_head *head; - - assert(!output->state_last); - - if (mode == DRM_STATE_APPLY_ASYNC) - output->state_last = output->state_cur; - else - drm_output_state_free(output->state_cur); - - wl_list_remove(&state->link); - wl_list_init(&state->link); - state->pending_state = NULL; - - output->state_cur = state; - - if (b->atomic_modeset && mode == DRM_STATE_APPLY_ASYNC) { - drm_debug(b, "\t[CRTC:%u] setting pending flip\n", output->crtc_id); - output->atomic_complete_pending = true; - } - - if (b->atomic_modeset && - state->protection == WESTON_HDCP_DISABLE) - wl_list_for_each(head, &output->base.head_list, base.output_link) - weston_head_set_content_protection_status(&head->base, - WESTON_HDCP_DISABLE); - - /* Replace state_cur on each affected plane with the new state, being - * careful to dispose of orphaned (but only orphaned) previous state. - * If the previous state is not orphaned (still has an output_state - * attached), it will be disposed of by freeing the output_state. */ - wl_list_for_each(plane_state, &state->plane_list, link) { - struct drm_plane *plane = plane_state->plane; - - if (plane->state_cur && !plane->state_cur->output_state) - drm_plane_state_free(plane->state_cur, true); - plane->state_cur = plane_state; - - if (mode != DRM_STATE_APPLY_ASYNC) { - plane_state->complete = true; - continue; - } - - if (b->atomic_modeset) - continue; - - assert(plane->type != WDRM_PLANE_TYPE_OVERLAY); - if (plane->type == WDRM_PLANE_TYPE_PRIMARY) - output->page_flip_pending = true; - } -} - -static void -drm_output_set_cursor(struct drm_output_state *output_state) -{ - struct drm_output *output = output_state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); - struct drm_plane *plane = output->cursor_plane; - struct drm_plane_state *state; - uint32_t handle; - - if (!plane) - return; - - state = drm_output_state_get_existing_plane(output_state, plane); - if (!state) - return; - - if (!state->fb) { - pixman_region32_fini(&plane->base.damage); - pixman_region32_init(&plane->base.damage); - drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); - return; - } - - assert(state->fb == output->gbm_cursor_fb[output->current_cursor]); - assert(!plane->state_cur->output || plane->state_cur->output == output); - - handle = output->gbm_cursor_handle[output->current_cursor]; - if (plane->state_cur->fb != state->fb) { - if (drmModeSetCursor(b->drm.fd, output->crtc_id, handle, - b->cursor_width, b->cursor_height)) { - weston_log("failed to set cursor: %s\n", - strerror(errno)); - goto err; - } - } - - pixman_region32_fini(&plane->base.damage); - pixman_region32_init(&plane->base.damage); - - if (drmModeMoveCursor(b->drm.fd, output->crtc_id, - state->dest_x, state->dest_y)) { - weston_log("failed to move cursor: %s\n", strerror(errno)); - goto err; - } - - return; - -err: - b->cursors_are_broken = true; - drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); -} - -static int -drm_output_apply_state_legacy(struct drm_output_state *state) -{ - struct drm_output *output = state->output; - struct drm_backend *backend = to_drm_backend(output->base.compositor); - struct drm_plane *scanout_plane = output->scanout_plane; - struct drm_property_info *dpms_prop; - struct drm_plane_state *scanout_state; - struct drm_mode *mode; - struct drm_head *head; - const struct pixel_format_info *pinfo = NULL; - uint32_t connectors[MAX_CLONED_CONNECTORS]; - int n_conn = 0; - struct timespec now; - int ret = 0; - - wl_list_for_each(head, &output->base.head_list, base.output_link) { - assert(n_conn < MAX_CLONED_CONNECTORS); - connectors[n_conn++] = head->connector_id; - } - - /* If disable_planes is set then assign_planes() wasn't - * called for this render, so we could still have a stale - * cursor plane set up. - */ - if (output->base.disable_planes) { - output->cursor_view = NULL; - if (output->cursor_plane) { - output->cursor_plane->base.x = INT32_MIN; - output->cursor_plane->base.y = INT32_MIN; - } - } - - if (state->dpms != WESTON_DPMS_ON) { - if (output->cursor_plane) { - ret = drmModeSetCursor(backend->drm.fd, output->crtc_id, - 0, 0, 0); - if (ret) - weston_log("drmModeSetCursor failed disable: %s\n", - strerror(errno)); - } - - ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, 0, 0, 0, - NULL, 0, NULL); - if (ret) - weston_log("drmModeSetCrtc failed disabling: %s\n", - strerror(errno)); - - drm_output_assign_state(state, DRM_STATE_APPLY_SYNC); - weston_compositor_read_presentation_clock(output->base.compositor, &now); - drm_output_update_complete(output, - WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION, - now.tv_sec, now.tv_nsec / 1000); - - return 0; - } - - scanout_state = - drm_output_state_get_existing_plane(state, scanout_plane); - - /* The legacy SetCrtc API doesn't allow us to do scaling, and the - * legacy PageFlip API doesn't allow us to do clipping either. */ - assert(scanout_state->src_x == 0); - assert(scanout_state->src_y == 0); - assert(scanout_state->src_w == - (unsigned) (output->base.current_mode->width << 16)); - assert(scanout_state->src_h == - (unsigned) (output->base.current_mode->height << 16)); - assert(scanout_state->dest_x == 0); - assert(scanout_state->dest_y == 0); - assert(scanout_state->dest_w == scanout_state->src_w >> 16); - assert(scanout_state->dest_h == scanout_state->src_h >> 16); - /* The legacy SetCrtc API doesn't support fences */ - assert(scanout_state->in_fence_fd == -1); - - mode = to_drm_mode(output->base.current_mode); - if (backend->state_invalid || - !scanout_plane->state_cur->fb || - scanout_plane->state_cur->fb->strides[0] != - scanout_state->fb->strides[0]) { - - ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, - scanout_state->fb->fb_id, - 0, 0, - connectors, n_conn, - &mode->mode_info); - if (ret) { - weston_log("set mode failed: %s\n", strerror(errno)); - goto err; - } - } - - pinfo = scanout_state->fb->format; - drm_debug(backend, "\t[CRTC:%u, PLANE:%u] FORMAT: %s\n", - output->crtc_id, scanout_state->plane->plane_id, - pinfo ? pinfo->drm_format_name : "UNKNOWN"); - - if (drmModePageFlip(backend->drm.fd, output->crtc_id, - scanout_state->fb->fb_id, - DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { - weston_log("queueing pageflip failed: %s\n", strerror(errno)); - goto err; - } - - assert(!output->page_flip_pending); - - if (output->pageflip_timer) - wl_event_source_timer_update(output->pageflip_timer, - backend->pageflip_timeout); - - drm_output_set_cursor(state); - - if (state->dpms != output->state_cur->dpms) { - wl_list_for_each(head, &output->base.head_list, base.output_link) { - dpms_prop = &head->props_conn[WDRM_CONNECTOR_DPMS]; - if (dpms_prop->prop_id == 0) - continue; - - ret = drmModeConnectorSetProperty(backend->drm.fd, - head->connector_id, - dpms_prop->prop_id, - state->dpms); - if (ret) { - weston_log("DRM: DPMS: failed property set for %s\n", - head->base.name); - } - } - } - - drm_output_assign_state(state, DRM_STATE_APPLY_ASYNC); - - return 0; - -err: - output->cursor_view = NULL; - drm_output_state_free(state); - return -1; -} - -static int -crtc_add_prop(drmModeAtomicReq *req, struct drm_output *output, - enum wdrm_crtc_property prop, uint64_t val) -{ - struct drm_property_info *info = &output->props_crtc[prop]; - int ret; - - if (info->prop_id == 0) - return -1; - - ret = drmModeAtomicAddProperty(req, output->crtc_id, info->prop_id, - val); - drm_debug(output->backend, "\t\t\t[CRTC:%lu] %lu (%s) -> %llu (0x%llx)\n", - (unsigned long) output->crtc_id, - (unsigned long) info->prop_id, info->name, - (unsigned long long) val, (unsigned long long) val); - return (ret <= 0) ? -1 : 0; -} - -static int -connector_add_prop(drmModeAtomicReq *req, struct drm_head *head, - enum wdrm_connector_property prop, uint64_t val) -{ - struct drm_property_info *info = &head->props_conn[prop]; - int ret; - - if (info->prop_id == 0) - return -1; - - ret = drmModeAtomicAddProperty(req, head->connector_id, - info->prop_id, val); - drm_debug(head->backend, "\t\t\t[CONN:%lu] %lu (%s) -> %llu (0x%llx)\n", - (unsigned long) head->connector_id, - (unsigned long) info->prop_id, info->name, - (unsigned long long) val, (unsigned long long) val); - return (ret <= 0) ? -1 : 0; -} - -static int -plane_add_prop(drmModeAtomicReq *req, struct drm_plane *plane, - enum wdrm_plane_property prop, uint64_t val) -{ - struct drm_property_info *info = &plane->props[prop]; - int ret; - - if (info->prop_id == 0) - return -1; - - ret = drmModeAtomicAddProperty(req, plane->plane_id, info->prop_id, - val); - drm_debug(plane->backend, "\t\t\t[PLANE:%lu] %lu (%s) -> %llu (0x%llx)\n", - (unsigned long) plane->plane_id, - (unsigned long) info->prop_id, info->name, - (unsigned long long) val, (unsigned long long) val); - return (ret <= 0) ? -1 : 0; -} - -static bool -drm_head_has_prop(struct drm_head *head, - enum wdrm_connector_property prop) -{ - if (head && head->props_conn[prop].prop_id != 0) - return true; - - return false; -} - -/* - * This function converts the protection requests from weston_hdcp_protection - * corresponding drm values. These values can be set in "Content Protection" - * & "HDCP Content Type" connector properties. - */ -static void -get_drm_protection_from_weston(enum weston_hdcp_protection weston_protection, - enum wdrm_content_protection_state *drm_protection, - enum wdrm_hdcp_content_type *drm_cp_type) -{ - - switch (weston_protection) { - case WESTON_HDCP_DISABLE: - *drm_protection = WDRM_CONTENT_PROTECTION_UNDESIRED; - *drm_cp_type = WDRM_HDCP_CONTENT_TYPE0; - break; - case WESTON_HDCP_ENABLE_TYPE_0: - *drm_protection = WDRM_CONTENT_PROTECTION_DESIRED; - *drm_cp_type = WDRM_HDCP_CONTENT_TYPE0; - break; - case WESTON_HDCP_ENABLE_TYPE_1: - *drm_protection = WDRM_CONTENT_PROTECTION_DESIRED; - *drm_cp_type = WDRM_HDCP_CONTENT_TYPE1; - break; - default: - assert(0 && "bad weston_hdcp_protection"); - } -} - -static void -drm_head_set_hdcp_property(struct drm_head *head, - enum weston_hdcp_protection protection, - drmModeAtomicReq *req) -{ - int ret; - enum wdrm_content_protection_state drm_protection; - enum wdrm_hdcp_content_type drm_cp_type; - struct drm_property_enum_info *enum_info; - uint64_t prop_val; - - get_drm_protection_from_weston(protection, &drm_protection, - &drm_cp_type); - - if (!drm_head_has_prop(head, WDRM_CONNECTOR_CONTENT_PROTECTION)) - return; - - /* - * Content-type property is not exposed for platforms not supporting - * HDCP2.2, therefore, type-1 cannot be supported. The type-0 content - * still can be supported if the content-protection property is exposed. - */ - if (!drm_head_has_prop(head, WDRM_CONNECTOR_HDCP_CONTENT_TYPE) && - drm_cp_type != WDRM_HDCP_CONTENT_TYPE0) - return; - - enum_info = head->props_conn[WDRM_CONNECTOR_CONTENT_PROTECTION].enum_values; - prop_val = enum_info[drm_protection].value; - ret = connector_add_prop(req, head, WDRM_CONNECTOR_CONTENT_PROTECTION, - prop_val); - assert(ret == 0); - - if (!drm_head_has_prop(head, WDRM_CONNECTOR_HDCP_CONTENT_TYPE)) - return; - - enum_info = head->props_conn[WDRM_CONNECTOR_HDCP_CONTENT_TYPE].enum_values; - prop_val = enum_info[drm_cp_type].value; - ret = connector_add_prop(req, head, WDRM_CONNECTOR_HDCP_CONTENT_TYPE, - prop_val); - assert(ret == 0); -} - -static int -drm_output_apply_state_atomic(struct drm_output_state *state, - drmModeAtomicReq *req, - uint32_t *flags) -{ - struct drm_output *output = state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); - struct drm_plane_state *plane_state; - struct drm_mode *current_mode = to_drm_mode(output->base.current_mode); - struct drm_head *head; - int ret = 0; - - drm_debug(b, "\t\t[atomic] %s output %lu (%s) state\n", - (*flags & DRM_MODE_ATOMIC_TEST_ONLY) ? "testing" : "applying", - (unsigned long) output->base.id, output->base.name); - - if (state->dpms != output->state_cur->dpms) { - drm_debug(b, "\t\t\t[atomic] DPMS state differs, modeset OK\n"); - *flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; - } - - if (state->dpms == WESTON_DPMS_ON) { - ret = drm_mode_ensure_blob(b, current_mode); - if (ret != 0) - return ret; - - ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID, - current_mode->blob_id); - ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 1); - - /* No need for the DPMS property, since it is implicit in - * routing and CRTC activity. */ - wl_list_for_each(head, &output->base.head_list, base.output_link) { - ret |= connector_add_prop(req, head, WDRM_CONNECTOR_CRTC_ID, - output->crtc_id); - } - } else { - ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID, 0); - ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 0); - - /* No need for the DPMS property, since it is implicit in - * routing and CRTC activity. */ - wl_list_for_each(head, &output->base.head_list, base.output_link) - ret |= connector_add_prop(req, head, WDRM_CONNECTOR_CRTC_ID, 0); - } - - wl_list_for_each(head, &output->base.head_list, base.output_link) - drm_head_set_hdcp_property(head, state->protection, req); - - if (ret != 0) { - weston_log("couldn't set atomic CRTC/connector state\n"); - return ret; - } - - wl_list_for_each(plane_state, &state->plane_list, link) { - struct drm_plane *plane = plane_state->plane; - const struct pixel_format_info *pinfo = NULL; - - ret |= plane_add_prop(req, plane, WDRM_PLANE_FB_ID, - plane_state->fb ? plane_state->fb->fb_id : 0); - ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_ID, - plane_state->fb ? output->crtc_id : 0); - ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_X, - plane_state->src_x); - ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_Y, - plane_state->src_y); - ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_W, - plane_state->src_w); - ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_H, - plane_state->src_h); - ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_X, - plane_state->dest_x); - ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_Y, - plane_state->dest_y); - ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_W, - plane_state->dest_w); - ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_H, - plane_state->dest_h); - if (plane->props[WDRM_PLANE_FB_DAMAGE_CLIPS].prop_id != 0) - ret |= plane_add_prop(req, plane, WDRM_PLANE_FB_DAMAGE_CLIPS, - plane_state->damage_blob_id); - - if (plane_state->fb && plane_state->fb->format) - pinfo = plane_state->fb->format; - - drm_debug(plane->backend, "\t\t\t[PLANE:%lu] FORMAT: %s\n", - (unsigned long) plane->plane_id, - pinfo ? pinfo->drm_format_name : "UNKNOWN"); - - if (plane_state->in_fence_fd >= 0) { - ret |= plane_add_prop(req, plane, - WDRM_PLANE_IN_FENCE_FD, - plane_state->in_fence_fd); - } - - /* do note, that 'invented' zpos values are set as immutable */ - if (plane_state->zpos != DRM_PLANE_ZPOS_INVALID_PLANE && - plane_state->plane->zpos_min != plane_state->plane->zpos_max) - ret |= plane_add_prop(req, plane, - WDRM_PLANE_ZPOS, - plane_state->zpos); - - if (ret != 0) { - weston_log("couldn't set plane state\n"); - return ret; - } - } - - return 0; -} - -/** - * Helper function used only by drm_pending_state_apply, with the same - * guarantees and constraints as that function. - */ -static int -drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, - enum drm_state_apply_mode mode) -{ - struct drm_backend *b = pending_state->backend; - struct drm_output_state *output_state, *tmp; - struct drm_plane *plane; - drmModeAtomicReq *req = drmModeAtomicAlloc(); - uint32_t flags; - int ret = 0; - - if (!req) - return -1; - - switch (mode) { - case DRM_STATE_APPLY_SYNC: - flags = 0; - break; - case DRM_STATE_APPLY_ASYNC: - flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; - break; - case DRM_STATE_TEST_ONLY: - flags = DRM_MODE_ATOMIC_TEST_ONLY; - break; - } - - if (b->state_invalid) { - struct weston_head *head_base; - struct drm_head *head; - uint32_t *unused; - int err; - - drm_debug(b, "\t\t[atomic] previous state invalid; " - "starting with fresh state\n"); - - /* If we need to reset all our state (e.g. because we've - * just started, or just been VT-switched in), explicitly - * disable all the CRTCs and connectors we aren't using. */ - wl_list_for_each(head_base, - &b->compositor->head_list, compositor_link) { - struct drm_property_info *info; - - if (weston_head_is_enabled(head_base)) - continue; - - head = to_drm_head(head_base); - - drm_debug(b, "\t\t[atomic] disabling inactive head %s\n", - head_base->name); - - info = &head->props_conn[WDRM_CONNECTOR_CRTC_ID]; - err = drmModeAtomicAddProperty(req, head->connector_id, - info->prop_id, 0); - drm_debug(b, "\t\t\t[CONN:%lu] %lu (%s) -> 0\n", - (unsigned long) head->connector_id, - (unsigned long) info->prop_id, - info->name); - if (err <= 0) - ret = -1; - } - - wl_array_for_each(unused, &b->unused_crtcs) { - struct drm_property_info infos[WDRM_CRTC__COUNT]; - struct drm_property_info *info; - drmModeObjectProperties *props; - uint64_t active; - - memset(infos, 0, sizeof(infos)); - - /* We can't emit a disable on a CRTC that's already - * off, as the kernel will refuse to generate an event - * for an off->off state and fail the commit. - */ - props = drmModeObjectGetProperties(b->drm.fd, - *unused, - DRM_MODE_OBJECT_CRTC); - if (!props) { - ret = -1; - continue; - } - - drm_property_info_populate(b, crtc_props, infos, - WDRM_CRTC__COUNT, - props); - - info = &infos[WDRM_CRTC_ACTIVE]; - active = drm_property_get_value(info, props, 0); - drmModeFreeObjectProperties(props); - if (active == 0) { - drm_property_info_free(infos, WDRM_CRTC__COUNT); - continue; - } - - drm_debug(b, "\t\t[atomic] disabling unused CRTC %lu\n", - (unsigned long) *unused); - - drm_debug(b, "\t\t\t[CRTC:%lu] %lu (%s) -> 0\n", - (unsigned long) *unused, - (unsigned long) info->prop_id, info->name); - err = drmModeAtomicAddProperty(req, *unused, - info->prop_id, 0); - if (err <= 0) - ret = -1; - - info = &infos[WDRM_CRTC_MODE_ID]; - drm_debug(b, "\t\t\t[CRTC:%lu] %lu (%s) -> 0\n", - (unsigned long) *unused, - (unsigned long) info->prop_id, info->name); - err = drmModeAtomicAddProperty(req, *unused, - info->prop_id, 0); - if (err <= 0) - ret = -1; - - drm_property_info_free(infos, WDRM_CRTC__COUNT); - } - - /* Disable all the planes; planes which are being used will - * override this state in the output-state application. */ - // OHOS: now can not disable planes , commit will failed - // wl_list_for_each(plane, &b->plane_list, link) { - // drm_debug(b, "\t\t[atomic] starting with plane %lu disabled\n", - // (unsigned long) plane->plane_id); - // plane_add_prop(req, plane, WDRM_PLANE_CRTC_ID, 0); - // plane_add_prop(req, plane, WDRM_PLANE_FB_ID, 0); - // } - - flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; - } - - wl_list_for_each(output_state, &pending_state->output_list, link) { - if (output_state->output->virtual) - continue; - if (mode == DRM_STATE_APPLY_SYNC) - assert(output_state->dpms == WESTON_DPMS_OFF); - ret |= drm_output_apply_state_atomic(output_state, req, &flags); - } - - if (ret != 0) { - weston_log("atomic: couldn't compile atomic state\n"); - goto out; - } - - ret = drmModeAtomicCommit(b->drm.fd, req, flags, b); - drm_debug(b, "[atomic] drmModeAtomicCommit\n"); - - /* Test commits do not take ownership of the state; return - * without freeing here. */ - if (mode == DRM_STATE_TEST_ONLY) { - drmModeAtomicFree(req); - return ret; - } - - if (ret != 0) { - weston_log("atomic: couldn't commit new state: %s\n", - strerror(errno)); - goto out; - } - - wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, - link) - drm_output_assign_state(output_state, mode); - - b->state_invalid = false; - - assert(wl_list_empty(&pending_state->output_list)); - -out: - drmModeAtomicFree(req); - drm_pending_state_free(pending_state); - return ret; -} - -/** - * Tests a pending state, to see if the kernel will accept the update as - * constructed. - * - * Using atomic modesetting, the kernel performs the same checks as it would - * on a real commit, returning success or failure without actually modifying - * the running state. It does not return -EBUSY if there are pending updates - * in flight, so states may be tested at any point, however this means a - * state which passed testing may fail on a real commit if the timing is not - * respected (e.g. committing before the previous commit has completed). - * - * Without atomic modesetting, we have no way to check, so we optimistically - * claim it will work. - * - * Unlike drm_pending_state_apply() and drm_pending_state_apply_sync(), this - * function does _not_ take ownership of pending_state, nor does it clear - * state_invalid. - */ -int -drm_pending_state_test(struct drm_pending_state *pending_state) -{ - struct drm_backend *b = pending_state->backend; - - if (b->atomic_modeset) - return drm_pending_state_apply_atomic(pending_state, - DRM_STATE_TEST_ONLY); - - /* We have no way to test state before application on the legacy - * modesetting API, so just claim it succeeded. */ - return 0; -} - -/** - * Applies all of a pending_state asynchronously: the primary entry point for - * applying KMS state to a device. Updates the state for all outputs in the - * pending_state, as well as disabling any unclaimed outputs. - * - * Unconditionally takes ownership of pending_state, and clears state_invalid. - */ -int -drm_pending_state_apply(struct drm_pending_state *pending_state) -{ - struct drm_backend *b = pending_state->backend; - struct drm_output_state *output_state, *tmp; - uint32_t *unused; - - if (b->atomic_modeset) - return drm_pending_state_apply_atomic(pending_state, - DRM_STATE_APPLY_ASYNC); - - if (b->state_invalid) { - /* If we need to reset all our state (e.g. because we've - * just started, or just been VT-switched in), explicitly - * disable all the CRTCs we aren't using. This also disables - * all connectors on these CRTCs, so we don't need to do that - * separately with the pre-atomic API. */ - wl_array_for_each(unused, &b->unused_crtcs) - drmModeSetCrtc(b->drm.fd, *unused, 0, 0, 0, NULL, 0, - NULL); - } - - wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, - link) { - struct drm_output *output = output_state->output; - int ret; - - if (output->virtual) { - drm_output_assign_state(output_state, - DRM_STATE_APPLY_ASYNC); - continue; - } - - ret = drm_output_apply_state_legacy(output_state); - if (ret != 0) { - weston_log("Couldn't apply state for output %s\n", - output->base.name); - } - } - - b->state_invalid = false; - - assert(wl_list_empty(&pending_state->output_list)); - - drm_pending_state_free(pending_state); - - return 0; -} - -/** - * The synchronous version of drm_pending_state_apply. May only be used to - * disable outputs. Does so synchronously: the request is guaranteed to have - * completed on return, and the output will not be touched afterwards. - * - * Unconditionally takes ownership of pending_state, and clears state_invalid. - */ -int -drm_pending_state_apply_sync(struct drm_pending_state *pending_state) -{ - struct drm_backend *b = pending_state->backend; - struct drm_output_state *output_state, *tmp; - uint32_t *unused; - - if (b->atomic_modeset) - return drm_pending_state_apply_atomic(pending_state, - DRM_STATE_APPLY_SYNC); - - if (b->state_invalid) { - /* If we need to reset all our state (e.g. because we've - * just started, or just been VT-switched in), explicitly - * disable all the CRTCs we aren't using. This also disables - * all connectors on these CRTCs, so we don't need to do that - * separately with the pre-atomic API. */ - wl_array_for_each(unused, &b->unused_crtcs) - drmModeSetCrtc(b->drm.fd, *unused, 0, 0, 0, NULL, 0, - NULL); - } - - wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, - link) { - int ret; - - assert(output_state->dpms == WESTON_DPMS_OFF); - ret = drm_output_apply_state_legacy(output_state); - if (ret != 0) { - weston_log("Couldn't apply state for output %s\n", - output_state->output->base.name); - } - } - - b->state_invalid = false; - - assert(wl_list_empty(&pending_state->output_list)); - - drm_pending_state_free(pending_state); - - return 0; -} - -void -drm_output_update_msc(struct drm_output *output, unsigned int seq) -{ - uint64_t msc_hi = output->base.msc >> 32; - - if (seq < (output->base.msc & 0xffffffff)) - msc_hi++; - - output->base.msc = (msc_hi << 32) + seq; -} - -static void -page_flip_handler(int fd, unsigned int frame, - unsigned int sec, unsigned int usec, void *data) -{ - struct drm_output *output = data; - struct drm_backend *b = to_drm_backend(output->base.compositor); - uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_VSYNC | - WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | - WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; - - drm_output_update_msc(output, frame); - - assert(!b->atomic_modeset); - assert(output->page_flip_pending); - output->page_flip_pending = false; - - drm_output_update_complete(output, flags, sec, usec); -} - -static void -atomic_flip_handler(int fd, unsigned int frame, unsigned int sec, - unsigned int usec, unsigned int crtc_id, void *data) -{ - struct drm_backend *b = data; - struct drm_output *output = drm_output_find_by_crtc(b, crtc_id); - uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_VSYNC | - WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | - WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; - - /* During the initial modeset, we can disable CRTCs which we don't - * actually handle during normal operation; this will give us events - * for unknown outputs. Ignore them. */ - if (!output || !output->base.enabled) - return; - - drm_output_update_msc(output, frame); - - drm_debug(b, "[atomic][CRTC:%u] flip processing started\n", crtc_id); - assert(b->atomic_modeset); - assert(output->atomic_complete_pending); - output->atomic_complete_pending = false; - - drm_output_update_complete(output, flags, sec, usec); - drm_debug(b, "[atomic][CRTC:%u] flip processing completed\n", crtc_id); -} - -int -on_drm_input(int fd, uint32_t mask, void *data) -{ - struct drm_backend *b = data; - drmEventContext evctx; - - memset(&evctx, 0, sizeof evctx); - evctx.version = 3; - if (b->atomic_modeset) - evctx.page_flip_handler2 = atomic_flip_handler; - else - evctx.page_flip_handler = page_flip_handler; - drmHandleEvent(fd, &evctx); - - return 1; -} - -int -init_kms_caps(struct drm_backend *b) -{ - uint64_t cap; - int ret; - clockid_t clk_id; - - weston_log("using %s\n", b->drm.filename); - - ret = drmGetCap(b->drm.fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap); - if (ret == 0 && cap == 1) - clk_id = CLOCK_MONOTONIC; - else - clk_id = CLOCK_REALTIME; - - if (weston_compositor_set_presentation_clock(b->compositor, clk_id) < 0) { - weston_log("Error: failed to set presentation clock %d.\n", - clk_id); - return -1; - } - - ret = drmGetCap(b->drm.fd, DRM_CAP_CURSOR_WIDTH, &cap); - if (ret == 0) - b->cursor_width = cap; - else - b->cursor_width = 64; - - ret = drmGetCap(b->drm.fd, DRM_CAP_CURSOR_HEIGHT, &cap); - if (ret == 0) - b->cursor_height = cap; - else - b->cursor_height = 64; - - if (!getenv("WESTON_DISABLE_UNIVERSAL_PLANES")) { - ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); - b->universal_planes = (ret == 0); - } - - if (b->universal_planes && !getenv("WESTON_DISABLE_ATOMIC")) { - ret = drmGetCap(b->drm.fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap); - if (ret != 0) - cap = 0; - ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_ATOMIC, 1); - b->atomic_modeset = ((ret == 0) && (cap == 1)); - } - weston_log("DRM: %s atomic modesetting\n", - b->atomic_modeset ? "supports" : "does not support"); - - if (!getenv("WESTON_DISABLE_GBM_MODIFIERS")) { - ret = drmGetCap(b->drm.fd, DRM_CAP_ADDFB2_MODIFIERS, &cap); - if (ret == 0) - b->fb_modifiers = cap; - } - weston_log("DRM: %s GBM modifiers\n", - b->fb_modifiers ? "supports" : "does not support"); - - /* - * KMS support for hardware planes cannot properly synchronize - * without nuclear page flip. Without nuclear/atomic, hw plane - * and cursor plane updates would either tear or cause extra - * waits for vblanks which means dropping the compositor framerate - * to a fraction. For cursors, it's not so bad, so they are - * enabled. - */ - if (!b->atomic_modeset || getenv("WESTON_FORCE_RENDERER")) - b->sprites_are_broken = true; - - ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_ASPECT_RATIO, 1); - b->aspect_ratio_supported = (ret == 0); - weston_log("DRM: %s picture aspect ratio\n", - b->aspect_ratio_supported ? "supports" : "does not support"); - - return 0; -} diff --git a/libweston/backend-drm/libbacklight.c b/libweston/backend-drm/libbacklight.c deleted file mode 100644 index 4bbc6db..0000000 --- a/libweston/backend-drm/libbacklight.c +++ /dev/null @@ -1,318 +0,0 @@ -/* - * 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 -#include - -#include "shared/string-helpers.h" - -static long backlight_get(struct backlight *backlight, char *node) -{ - char buffer[100]; - char *path; - int fd, value; - long 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; - } - - if (!safe_strtoint(buffer, &value)) { - ret = -1; - goto out; - } - - 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) { - free(backlight_path); - 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/libweston/backend-drm/libbacklight.h b/libweston/backend-drm/libbacklight.h deleted file mode 100644 index 2007824..0000000 --- a/libweston/backend-drm/libbacklight.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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 - */ -#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 subsystem 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/libweston/backend-drm/meson.build b/libweston/backend-drm/meson.build deleted file mode 100644 index 484c270..0000000 --- a/libweston/backend-drm/meson.build +++ /dev/null @@ -1,93 +0,0 @@ -if not get_option('backend-drm') - subdir_done() -endif - -lib_backlight = static_library( - 'backlight', - 'libbacklight.c', - dependencies: [ - dep_libdrm_headers, - dependency('libudev') - ], - include_directories: common_inc, - install: false -) -dep_backlight = declare_dependency( - link_with: lib_backlight, - include_directories: include_directories('.') -) - -config_h.set('BUILD_DRM_COMPOSITOR', '1') - -srcs_drm = [ - 'drm.c', - 'fb.c', - 'modes.c', - 'kms.c', - 'state-helpers.c', - 'state-propose.c', - linux_dmabuf_unstable_v1_protocol_c, - linux_dmabuf_unstable_v1_server_protocol_h, - presentation_time_server_protocol_h, -] - -deps_drm = [ - dep_libdl, - dep_libweston_private, - dep_session_helper, - dep_libdrm, - dep_libinput_backend, - dependency('libudev', version: '>= 136'), - dep_backlight -] - -if get_option('renderer-gl') - dep_gbm = dependency('gbm', required: false) - if not dep_gbm.found() - error('drm-backend with GL renderer requires gbm which was not found. Or, you can use \'-Drenderer-gl=false\'.') - endif - if dep_gbm.version().version_compare('>= 17.1') - config_h.set('HAVE_GBM_MODIFIERS', '1') - endif - if dep_gbm.version().version_compare('>= 17.2') - config_h.set('HAVE_GBM_FD_IMPORT', '1') - endif - deps_drm += dep_gbm - srcs_drm += 'drm-gbm.c' - config_h.set('BUILD_DRM_GBM', '1') -endif - -if get_option('backend-drm-screencast-vaapi') - foreach name : [ 'libva', 'libva-drm' ] - d = dependency(name, version: '>= 0.34.0', required: false) - if not d.found() - error('VA-API recorder requires @0@ >= 0.34.0 which was not found. Or, you can use \'-Dbackend-drm-screencast-vaapi=false\'.'.format(name)) - endif - deps_drm += d - endforeach - - srcs_drm += 'vaapi-recorder.c' - deps_drm += dependency('threads') - config_h.set('BUILD_VAAPI_RECORDER', '1') -endif - -if get_option('remoting') or get_option('pipewire') - if not get_option('renderer-gl') - error('DRM virtual requires renderer-gl.') - endif - srcs_drm += 'drm-virtual.c' - config_h.set('BUILD_DRM_VIRTUAL', '1') -endif - -plugin_drm = shared_library( - 'drm-backend', - srcs_drm, - include_directories: common_inc, - dependencies: deps_drm, - name_prefix: '', - install: true, - install_dir: dir_module_libweston -) -env_modmap += 'drm-backend.so=@0@;'.format(plugin_drm.full_path()) - -install_headers(backend_drm_h, subdir: dir_include_libweston_install) diff --git a/libweston/backend-drm/modes.c b/libweston/backend-drm/modes.c deleted file mode 100755 index 4f16cf9..0000000 --- a/libweston/backend-drm/modes.c +++ /dev/null @@ -1,812 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2011 Intel Corporation - * Copyright © 2017, 2018 Collabora, Ltd. - * Copyright © 2017, 2018 General Electric Company - * Copyright (c) 2018 DisplayLink (UK) Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include - -#include "drm-internal.h" - -static const char *const aspect_ratio_as_string[] = { - [WESTON_MODE_PIC_AR_NONE] = "", - [WESTON_MODE_PIC_AR_4_3] = " 4:3", - [WESTON_MODE_PIC_AR_16_9] = " 16:9", - [WESTON_MODE_PIC_AR_64_27] = " 64:27", - [WESTON_MODE_PIC_AR_256_135] = " 256:135", -}; - -/* - * Get the aspect-ratio from drmModeModeInfo mode flags. - * - * @param drm_mode_flags- flags from drmModeModeInfo structure. - * @returns aspect-ratio as encoded in enum 'weston_mode_aspect_ratio'. - */ -static enum weston_mode_aspect_ratio -drm_to_weston_mode_aspect_ratio(uint32_t drm_mode_flags) -{ - switch (drm_mode_flags & DRM_MODE_FLAG_PIC_AR_MASK) { - case DRM_MODE_FLAG_PIC_AR_4_3: - return WESTON_MODE_PIC_AR_4_3; - case DRM_MODE_FLAG_PIC_AR_16_9: - return WESTON_MODE_PIC_AR_16_9; - case DRM_MODE_FLAG_PIC_AR_64_27: - return WESTON_MODE_PIC_AR_64_27; - case DRM_MODE_FLAG_PIC_AR_256_135: - return WESTON_MODE_PIC_AR_256_135; - case DRM_MODE_FLAG_PIC_AR_NONE: - default: - return WESTON_MODE_PIC_AR_NONE; - } -} - -static const char * -aspect_ratio_to_string(enum weston_mode_aspect_ratio ratio) -{ - if (ratio < 0 || ratio >= ARRAY_LENGTH(aspect_ratio_as_string) || - !aspect_ratio_as_string[ratio]) - return " (unknown aspect ratio)"; - - return aspect_ratio_as_string[ratio]; -} - -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; - } -} - -int -drm_mode_ensure_blob(struct drm_backend *backend, struct drm_mode *mode) -{ - int ret; - - if (mode->blob_id) - return 0; - - ret = drmModeCreatePropertyBlob(backend->drm.fd, - &mode->mode_info, - sizeof(mode->mode_info), - &mode->blob_id); - if (ret != 0) - weston_log("failed to create mode property blob: %s\n", - strerror(errno)); - - drm_debug(backend, "\t\t\t[atomic] created new mode blob %lu for %s\n", - (unsigned long) mode->blob_id, mode->mode_info.name); - - return ret; -} - -static bool -check_non_desktop(struct drm_head *head, drmModeObjectPropertiesPtr props) -{ - struct drm_property_info *non_desktop_info = - &head->props_conn[WDRM_CONNECTOR_NON_DESKTOP]; - - return drm_property_get_value(non_desktop_info, props, 0); -} - -static uint32_t -get_panel_orientation(struct drm_head *head, drmModeObjectPropertiesPtr props) -{ - struct drm_property_info *orientation = - &head->props_conn[WDRM_CONNECTOR_PANEL_ORIENTATION]; - uint64_t kms_val = - drm_property_get_value(orientation, props, - WDRM_PANEL_ORIENTATION_NORMAL); - - switch (kms_val) { - case WDRM_PANEL_ORIENTATION_NORMAL: - return WL_OUTPUT_TRANSFORM_NORMAL; - case WDRM_PANEL_ORIENTATION_UPSIDE_DOWN: - return WL_OUTPUT_TRANSFORM_180; - case WDRM_PANEL_ORIENTATION_LEFT_SIDE_UP: - return WL_OUTPUT_TRANSFORM_90; - case WDRM_PANEL_ORIENTATION_RIGHT_SIDE_UP: - return WL_OUTPUT_TRANSFORM_270; - default: - assert(!"unknown property value in get_panel_orientation"); - // OHOS build - return WL_OUTPUT_TRANSFORM_NORMAL; - } -} - -static int -parse_modeline(const char *s, drmModeModeInfo *mode) -{ - char hsync[16]; - char vsync[16]; - float fclock; - - memset(mode, 0, sizeof *mode); - - 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 (strcasecmp(hsync, "+hsync") == 0) - mode->flags |= DRM_MODE_FLAG_PHSYNC; - else if (strcasecmp(hsync, "-hsync") == 0) - mode->flags |= DRM_MODE_FLAG_NHSYNC; - else - return -1; - - if (strcasecmp(vsync, "+vsync") == 0) - mode->flags |= DRM_MODE_FLAG_PVSYNC; - else if (strcasecmp(vsync, "-vsync") == 0) - mode->flags |= DRM_MODE_FLAG_NVSYNC; - else - return -1; - - snprintf(mode->name, sizeof mode->name, "%dx%d@%.3f", - mode->hdisplay, mode->vdisplay, fclock); - - return 0; -} - -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); - - /* guarantee our new string is null-terminated */ - text[12] = '\0'; - - /* 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; -} - -/** Parse monitor make, model and serial from EDID - * - * \param head The head whose \c drm_edid to fill in. - * \param props The DRM connector properties to get the EDID from. - * \param[out] make The monitor make (PNP ID). - * \param[out] model The monitor model (name). - * \param[out] serial_number The monitor serial number. - * - * Each of \c *make, \c *model and \c *serial_number are set only if the - * information is found in the EDID. The pointers they are set to must not - * be free()'d explicitly, instead they get implicitly freed when the - * \c drm_head is destroyed. - */ -static void -find_and_parse_output_edid(struct drm_head *head, - drmModeObjectPropertiesPtr props, - const char **make, - const char **model, - const char **serial_number) -{ - drmModePropertyBlobPtr edid_blob = NULL; - uint32_t blob_id; - int rc; - - blob_id = - drm_property_get_value(&head->props_conn[WDRM_CONNECTOR_EDID], - props, 0); - if (!blob_id) - return; - - edid_blob = drmModeGetPropertyBlob(head->backend->drm.fd, blob_id); - if (!edid_blob) - return; - - rc = edid_parse(&head->edid, - edid_blob->data, - edid_blob->length); - if (!rc) { - if (head->edid.pnp_id[0] != '\0') - *make = head->edid.pnp_id; - if (head->edid.monitor_name[0] != '\0') - *model = head->edid.monitor_name; - if (head->edid.serial_number[0] != '\0') - *serial_number = head->edid.serial_number; - } - drmModeFreePropertyBlob(edid_blob); -} - -static uint32_t -drm_refresh_rate_mHz(const drmModeModeInfo *info) -{ - uint64_t refresh; - - /* 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; - - return refresh; -} - -/** - * Add a mode to output's mode list - * - * Copy the supplied DRM mode into a Weston mode structure, and add it to the - * output's mode list. - * - * @param output DRM output to add mode to - * @param info DRM mode structure to add - * @returns Newly-allocated Weston/DRM mode structure - */ -static struct drm_mode * -drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info) -{ - struct drm_mode *mode; - - mode = malloc(sizeof *mode); - if (mode == NULL) - return NULL; - - mode->base.flags = 0; - mode->base.width = info->hdisplay; - mode->base.height = info->vdisplay; - - mode->base.refresh = drm_refresh_rate_mHz(info); - mode->mode_info = *info; - mode->blob_id = 0; - - if (info->type & DRM_MODE_TYPE_PREFERRED) - mode->base.flags |= WL_OUTPUT_MODE_PREFERRED; - - mode->base.aspect_ratio = drm_to_weston_mode_aspect_ratio(info->flags); - - wl_list_insert(output->base.mode_list.prev, &mode->base.link); - - return mode; -} - -/** - * Destroys a mode, and removes it from the list. - */ -static void -drm_output_destroy_mode(struct drm_backend *backend, struct drm_mode *mode) -{ - if (mode->blob_id) - drmModeDestroyPropertyBlob(backend->drm.fd, mode->blob_id); - wl_list_remove(&mode->base.link); - free(mode); -} - -/** Destroy a list of drm_modes - * - * @param backend The backend for releasing mode property blobs. - * @param mode_list The list linked by drm_mode::base.link. - */ -void -drm_mode_list_destroy(struct drm_backend *backend, struct wl_list *mode_list) -{ - struct drm_mode *mode, *next; - - wl_list_for_each_safe(mode, next, mode_list, base.link) - drm_output_destroy_mode(backend, mode); -} - -void -drm_output_print_modes(struct drm_output *output) -{ - struct weston_mode *m; - struct drm_mode *dm; - const char *aspect_ratio; - - wl_list_for_each(m, &output->base.mode_list, link) { - dm = to_drm_mode(m); - - aspect_ratio = aspect_ratio_to_string(m->aspect_ratio); - weston_log_continue(STAMP_SPACE "%dx%d@%.1f%s%s%s, %.1f MHz\n", - m->width, m->height, m->refresh / 1000.0, - aspect_ratio, - m->flags & WL_OUTPUT_MODE_PREFERRED ? - ", preferred" : "", - m->flags & WL_OUTPUT_MODE_CURRENT ? - ", current" : "", - dm->mode_info.clock / 1000.0); - } -} - - -/** - * Find the closest-matching mode for a given target - * - * Given a target mode, find the most suitable mode amongst the output's - * current mode list to use, preferring the current mode if possible, to - * avoid an expensive mode switch. - * - * @param output DRM output - * @param target_mode Mode to attempt to match - * @returns Pointer to a mode from the output's mode list - */ -struct drm_mode * -drm_output_choose_mode(struct drm_output *output, - struct weston_mode *target_mode) -{ - struct drm_mode *tmp_mode = NULL, *mode_fall_back = NULL, *mode; - enum weston_mode_aspect_ratio src_aspect = WESTON_MODE_PIC_AR_NONE; - enum weston_mode_aspect_ratio target_aspect = WESTON_MODE_PIC_AR_NONE; - struct drm_backend *b; - - b = to_drm_backend(output->base.compositor); - target_aspect = target_mode->aspect_ratio; - src_aspect = output->base.current_mode->aspect_ratio; - 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)) { - if (!b->aspect_ratio_supported || src_aspect == target_aspect) - return to_drm_mode(output->base.current_mode); - } - - wl_list_for_each(mode, &output->base.mode_list, base.link) { - - src_aspect = mode->base.aspect_ratio; - if (mode->mode_info.hdisplay == target_mode->width && - mode->mode_info.vdisplay == target_mode->height) { - if (mode->base.refresh == target_mode->refresh || - target_mode->refresh == 0) { - if (!b->aspect_ratio_supported || - src_aspect == target_aspect) - return mode; - else if (!mode_fall_back) - mode_fall_back = mode; - } else if (!tmp_mode) { - tmp_mode = mode; - } - } - } - - if (mode_fall_back) - return mode_fall_back; - - return tmp_mode; -} - -void -update_head_from_connector(struct drm_head *head, - drmModeObjectProperties *props) -{ - const char *make = "unknown"; - const char *model = "unknown"; - const char *serial_number = "unknown"; - - find_and_parse_output_edid(head, props, &make, &model, &serial_number); - weston_head_set_monitor_strings(&head->base, make, model, serial_number); - weston_head_set_non_desktop(&head->base, - check_non_desktop(head, props)); - weston_head_set_subpixel(&head->base, - drm_subpixel_to_wayland(head->connector->subpixel)); - - weston_head_set_physical_size(&head->base, head->connector->mmWidth, - head->connector->mmHeight); - - weston_head_set_transform(&head->base, - get_panel_orientation(head, props)); - - /* Unknown connection status is assumed disconnected. */ - weston_head_set_connection_status(&head->base, - head->connector->connection == DRM_MODE_CONNECTED); -} - -/** - * Choose suitable mode for an output - * - * Find the most suitable mode to use for initial setup (or reconfiguration on - * hotplug etc) for a DRM output. - * - * @param backend the DRM backend - * @param output DRM output to choose mode for - * @param mode Strategy and preference to use when choosing mode - * @param modeline Manually-entered mode (may be NULL) - * @param current_mode Mode currently being displayed on this output - * @returns A mode from the output's mode list, or NULL if none available - */ -static struct drm_mode * -drm_output_choose_initial_mode(struct drm_backend *backend, - struct drm_output *output, - enum weston_drm_backend_output_mode mode, - const char *modeline, - const drmModeModeInfo *current_mode) -{ - struct drm_mode *preferred = NULL; - struct drm_mode *current = NULL; - struct drm_mode *configured = NULL; - struct drm_mode *config_fall_back = NULL; - struct drm_mode *best = NULL; - struct drm_mode *drm_mode; - drmModeModeInfo drm_modeline; - int32_t width = 0; - int32_t height = 0; - uint32_t refresh = 0; - uint32_t aspect_width = 0; - uint32_t aspect_height = 0; - enum weston_mode_aspect_ratio aspect_ratio = WESTON_MODE_PIC_AR_NONE; - int n; - - if (mode == WESTON_DRM_BACKEND_OUTPUT_PREFERRED && modeline) { - n = sscanf(modeline, "%dx%d@%d %u:%u", &width, &height, - &refresh, &aspect_width, &aspect_height); - if (backend->aspect_ratio_supported && n == 5) { - if (aspect_width == 4 && aspect_height == 3) - aspect_ratio = WESTON_MODE_PIC_AR_4_3; - else if (aspect_width == 16 && aspect_height == 9) - aspect_ratio = WESTON_MODE_PIC_AR_16_9; - else if (aspect_width == 64 && aspect_height == 27) - aspect_ratio = WESTON_MODE_PIC_AR_64_27; - else if (aspect_width == 256 && aspect_height == 135) - aspect_ratio = WESTON_MODE_PIC_AR_256_135; - else - weston_log("Invalid modeline \"%s\" for output %s\n", - modeline, output->base.name); - } - if (n != 2 && n != 3 && n != 5) { - width = -1; - - if (parse_modeline(modeline, &drm_modeline) == 0) { - configured = drm_output_add_mode(output, &drm_modeline); - if (!configured) - return NULL; - } else { - weston_log("Invalid modeline \"%s\" for output %s\n", - modeline, output->base.name); - } - } - } - - wl_list_for_each_reverse(drm_mode, &output->base.mode_list, base.link) { - if (width == drm_mode->base.width && - height == drm_mode->base.height && - (refresh == 0 || refresh == drm_mode->mode_info.vrefresh)) { - if (!backend->aspect_ratio_supported || - aspect_ratio == drm_mode->base.aspect_ratio) - configured = drm_mode; - else - config_fall_back = drm_mode; - } - - if (memcmp(current_mode, &drm_mode->mode_info, - sizeof *current_mode) == 0) - current = drm_mode; - - if (drm_mode->base.flags & WL_OUTPUT_MODE_PREFERRED) - preferred = drm_mode; - - best = drm_mode; - } - - if (current == NULL && current_mode->clock != 0) { - current = drm_output_add_mode(output, current_mode); - if (!current) - return NULL; - } - - if (mode == WESTON_DRM_BACKEND_OUTPUT_CURRENT) - configured = current; - - if (configured) - return configured; - - if (config_fall_back) - return config_fall_back; - - if (preferred) - return preferred; - - if (current) - return current; - - if (best) - return best; - - weston_log("no available modes for %s\n", output->base.name); - return NULL; -} - -static uint32_t -u32distance(uint32_t a, uint32_t b) -{ - if (a < b) - return b - a; - else - return a - b; -} - -/** Choose equivalent mode - * - * If the two modes are not equivalent, return NULL. - * Otherwise return the mode that is more likely to work in place of both. - * - * None of the fuzzy matching criteria in this function have any justification. - * - * typedef struct _drmModeModeInfo { - * uint32_t clock; - * uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew; - * uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan; - * - * uint32_t vrefresh; - * - * uint32_t flags; - * uint32_t type; - * char name[DRM_DISPLAY_MODE_LEN]; - * } drmModeModeInfo, *drmModeModeInfoPtr; - */ -static const drmModeModeInfo * -drm_mode_pick_equivalent(const drmModeModeInfo *a, const drmModeModeInfo *b) -{ - uint32_t refresh_a, refresh_b; - - if (a->hdisplay != b->hdisplay || a->vdisplay != b->vdisplay) - return NULL; - - if (a->flags != b->flags) - return NULL; - - /* kHz */ - if (u32distance(a->clock, b->clock) > 500) - return NULL; - - refresh_a = drm_refresh_rate_mHz(a); - refresh_b = drm_refresh_rate_mHz(b); - if (u32distance(refresh_a, refresh_b) > 50) - return NULL; - - if ((a->type ^ b->type) & DRM_MODE_TYPE_PREFERRED) { - if (a->type & DRM_MODE_TYPE_PREFERRED) - return a; - else - return b; - } - - return a; -} - -/* If the given mode info is not already in the list, add it. - * If it is in the list, either keep the existing or replace it, - * depending on which one is "better". - */ -static int -drm_output_try_add_mode(struct drm_output *output, const drmModeModeInfo *info) -{ - struct weston_mode *base; - struct drm_mode *mode = NULL; - struct drm_backend *backend; - const drmModeModeInfo *chosen = NULL; - - assert(info); - - wl_list_for_each(base, &output->base.mode_list, link) { - mode = to_drm_mode(base); - chosen = drm_mode_pick_equivalent(&mode->mode_info, info); - if (chosen) - break; - } - - if (chosen == info) { - assert(mode); - backend = to_drm_backend(output->base.compositor); - drm_output_destroy_mode(backend, mode); - chosen = NULL; - } - - if (!chosen) { - mode = drm_output_add_mode(output, info); - if (!mode) - return -1; - } - /* else { the equivalent mode is already in the list } */ - - return 0; -} - -/** Rewrite the output's mode list - * - * @param output The output. - * @return 0 on success, -1 on failure. - * - * Destroy all existing modes in the list, and reconstruct a new list from - * scratch, based on the currently attached heads. - * - * On failure the output's mode list may contain some modes. - */ -static int -drm_output_update_modelist_from_heads(struct drm_output *output) -{ - struct drm_backend *backend = to_drm_backend(output->base.compositor); - struct weston_head *head_base; - struct drm_head *head; - int i; - int ret; - - assert(!output->base.enabled); - - drm_mode_list_destroy(backend, &output->base.mode_list); - - wl_list_for_each(head_base, &output->base.head_list, output_link) { - head = to_drm_head(head_base); - for (i = 0; i < head->connector->count_modes; i++) { - ret = drm_output_try_add_mode(output, - &head->connector->modes[i]); - if (ret < 0) - return -1; - } - } - - return 0; -} - -int -drm_output_set_mode(struct weston_output *base, - enum weston_drm_backend_output_mode mode, - const char *modeline) -{ - struct drm_output *output = to_drm_output(base); - struct drm_backend *b = to_drm_backend(base->compositor); - struct drm_head *head = to_drm_head(weston_output_get_first_head(base)); - - struct drm_mode *current; - - if (output->virtual) - return -1; - - if (drm_output_update_modelist_from_heads(output) < 0) - return -1; - - current = drm_output_choose_initial_mode(b, output, mode, modeline, - &head->inherited_mode); - if (!current) - return -1; - - output->base.current_mode = ¤t->base; - output->base.current_mode->flags |= WL_OUTPUT_MODE_CURRENT; - - /* Set native_ fields, so weston_output_mode_switch_to_native() works */ - output->base.native_mode = output->base.current_mode; - output->base.native_scale = output->base.current_scale; - - return 0; -} diff --git a/libweston/backend-drm/state-helpers.c b/libweston/backend-drm/state-helpers.c deleted file mode 100644 index 0ee663c..0000000 --- a/libweston/backend-drm/state-helpers.c +++ /dev/null @@ -1,473 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2011 Intel Corporation - * Copyright © 2017, 2018 Collabora, Ltd. - * Copyright © 2017, 2018 General Electric Company - * Copyright (c) 2018 DisplayLink (UK) Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include - -#include "drm-internal.h" - -/** - * Allocate a new, empty, plane state. - */ -struct drm_plane_state * -drm_plane_state_alloc(struct drm_output_state *state_output, - struct drm_plane *plane) -{ - struct drm_plane_state *state = zalloc(sizeof(*state)); - - assert(state); - state->output_state = state_output; - state->plane = plane; - state->in_fence_fd = -1; - state->zpos = DRM_PLANE_ZPOS_INVALID_PLANE; - - /* Here we only add the plane state to the desired link, and not - * set the member. Having an output pointer set means that the - * plane will be displayed on the output; this won't be the case - * when we go to disable a plane. In this case, it must be part of - * the commit (and thus the output state), but the member must be - * NULL, as it will not be on any output when the state takes - * effect. - */ - if (state_output) - wl_list_insert(&state_output->plane_list, &state->link); - else - wl_list_init(&state->link); - - return state; -} - -/** - * Free an existing plane state. As a special case, the state will not - * normally be freed if it is the current state; see drm_plane_set_state. - */ -void -drm_plane_state_free(struct drm_plane_state *state, bool force) -{ - if (!state) - return; - - wl_list_remove(&state->link); - wl_list_init(&state->link); - state->output_state = NULL; - state->in_fence_fd = -1; - state->zpos = DRM_PLANE_ZPOS_INVALID_PLANE; - - /* Once the damage blob has been submitted, it is refcounted internally - * by the kernel, which means we can safely discard it. - */ - if (state->damage_blob_id != 0) { - drmModeDestroyPropertyBlob(state->plane->backend->drm.fd, - state->damage_blob_id); - state->damage_blob_id = 0; - } - - if (force || state != state->plane->state_cur) { - drm_fb_unref(state->fb); - free(state); - } -} - -/** - * Duplicate an existing plane state into a new plane state, storing it within - * the given output state. If the output state already contains a plane state - * for the drm_plane referenced by 'src', that plane state is freed first. - */ -struct drm_plane_state * -drm_plane_state_duplicate(struct drm_output_state *state_output, - struct drm_plane_state *src) -{ - struct drm_plane_state *dst = zalloc(sizeof(*dst)); - struct drm_plane_state *old, *tmp; - - assert(src); - assert(dst); - *dst = *src; - /* We don't want to copy this, because damage is transient, and only - * lasts for the duration of a single repaint. - */ - dst->damage_blob_id = 0; - wl_list_init(&dst->link); - - wl_list_for_each_safe(old, tmp, &state_output->plane_list, link) { - /* Duplicating a plane state into the same output state, so - * it can replace itself with an identical copy of itself, - * makes no sense. */ - assert(old != src); - if (old->plane == dst->plane) - drm_plane_state_free(old, false); - } - - wl_list_insert(&state_output->plane_list, &dst->link); - if (src->fb) - dst->fb = drm_fb_ref(src->fb); - dst->output_state = state_output; - dst->complete = false; - - return dst; -} - -/** - * Remove a plane state from an output state; if the plane was previously - * enabled, then replace it with a disabling state. This ensures that the - * output state was untouched from it was before the plane state was - * modified by the caller of this function. - * - * This is required as drm_output_state_get_plane may either allocate a - * new plane state, in which case this function will just perform a matching - * drm_plane_state_free, or it may instead repurpose an existing disabling - * state (if the plane was previously active), in which case this function - * will reset it. - */ -void -drm_plane_state_put_back(struct drm_plane_state *state) -{ - struct drm_output_state *state_output; - struct drm_plane *plane; - - if (!state) - return; - - state_output = state->output_state; - plane = state->plane; - drm_plane_state_free(state, false); - - /* Plane was previously disabled; no need to keep this temporary - * state around. */ - if (!plane->state_cur->fb) - return; - - (void) drm_plane_state_alloc(state_output, plane); -} - -/** - * Given a weston_view, fill the drm_plane_state's co-ordinates to display on - * a given plane. - */ -bool -drm_plane_state_coords_for_view(struct drm_plane_state *state, - struct weston_view *ev, uint64_t zpos) -{ - struct drm_output *output = state->output; - struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; - pixman_region32_t dest_rect, src_rect; - pixman_box32_t *box, tbox; - float sxf1, syf1, sxf2, syf2; - - if (!drm_view_transform_supported(ev, &output->base)) - return false; - - /* Update the base weston_plane co-ordinates. */ - box = pixman_region32_extents(&ev->transform.boundingbox); - state->plane->base.x = box->x1; - state->plane->base.y = box->y1; - - /* First calculate the destination co-ordinates by taking the - * area of the view which is visible on this output, performing any - * transforms to account for output rotation and scale as necessary. */ - pixman_region32_init(&dest_rect); - pixman_region32_intersect(&dest_rect, &ev->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); - state->dest_x = tbox.x1; - state->dest_y = tbox.y1; - state->dest_w = tbox.x2 - tbox.x1; - state->dest_h = tbox.y2 - tbox.y1; - pixman_region32_fini(&dest_rect); - - /* Now calculate the source rectangle, by finding the extents of the - * view, and working backwards to source co-ordinates. */ - pixman_region32_init(&src_rect); - pixman_region32_intersect(&src_rect, &ev->transform.boundingbox, - &output->base.region); - box = pixman_region32_extents(&src_rect); - weston_view_from_global_float(ev, box->x1, box->y1, &sxf1, &syf1); - weston_surface_to_buffer_float(ev->surface, sxf1, syf1, &sxf1, &syf1); - weston_view_from_global_float(ev, box->x2, box->y2, &sxf2, &syf2); - weston_surface_to_buffer_float(ev->surface, sxf2, syf2, &sxf2, &syf2); - pixman_region32_fini(&src_rect); - - /* Buffer transforms may mean that x2 is to the left of x1, and/or that - * y2 is above y1. */ - if (sxf2 < sxf1) { - double tmp = sxf1; - sxf1 = sxf2; - sxf2 = tmp; - } - if (syf2 < syf1) { - double tmp = syf1; - syf1 = syf2; - syf2 = tmp; - } - - /* Shift from S23.8 wl_fixed to U16.16 KMS fixed-point encoding. */ - state->src_x = wl_fixed_from_double(sxf1) << 8; - state->src_y = wl_fixed_from_double(syf1) << 8; - state->src_w = wl_fixed_from_double(sxf2 - sxf1) << 8; - state->src_h = wl_fixed_from_double(syf2 - syf1) << 8; - - /* Clamp our source co-ordinates to surface bounds; it's possible - * for intermediate translations to give us slightly incorrect - * co-ordinates if we have, for example, multiple zooming - * transformations. View bounding boxes are also explicitly rounded - * greedily. */ - if (state->src_x < 0) - state->src_x = 0; - if (state->src_y < 0) - state->src_y = 0; - if (state->src_w > (uint32_t) ((buffer->width << 16) - state->src_x)) - state->src_w = (buffer->width << 16) - state->src_x; - if (state->src_h > (uint32_t) ((buffer->height << 16) - state->src_y)) - state->src_h = (buffer->height << 16) - state->src_y; - - /* apply zpos if available */ - state->zpos = zpos; - - return true; -} - -/** - * Reset the current state of a DRM plane - * - * The current state will be freed and replaced by a pristine state. - * - * @param plane The plane to reset the current state of - */ -void -drm_plane_reset_state(struct drm_plane *plane) -{ - drm_plane_state_free(plane->state_cur, true); - plane->state_cur = drm_plane_state_alloc(NULL, plane); - plane->state_cur->complete = true; -} - -/** - * Return a plane state from a drm_output_state. - */ -struct drm_plane_state * -drm_output_state_get_existing_plane(struct drm_output_state *state_output, - struct drm_plane *plane) -{ - struct drm_plane_state *ps; - - wl_list_for_each(ps, &state_output->plane_list, link) { - if (ps->plane == plane) - return ps; - } - - return NULL; -} - -/** - * Return a plane state from a drm_output_state, either existing or - * freshly allocated. - */ -struct drm_plane_state * -drm_output_state_get_plane(struct drm_output_state *state_output, - struct drm_plane *plane) -{ - struct drm_plane_state *ps; - - ps = drm_output_state_get_existing_plane(state_output, plane); - if (ps) - return ps; - - return drm_plane_state_alloc(state_output, plane); -} - -/** - * Allocate a new, empty drm_output_state. This should not generally be used - * in the repaint cycle; see drm_output_state_duplicate. - */ -struct drm_output_state * -drm_output_state_alloc(struct drm_output *output, - struct drm_pending_state *pending_state) -{ - struct drm_output_state *state = zalloc(sizeof(*state)); - - assert(state); - state->output = output; - state->dpms = WESTON_DPMS_OFF; - state->protection = WESTON_HDCP_DISABLE; - state->pending_state = pending_state; - if (pending_state) - wl_list_insert(&pending_state->output_list, &state->link); - else - wl_list_init(&state->link); - - wl_list_init(&state->plane_list); - - return state; -} - -/** - * Duplicate an existing drm_output_state into a new one. This is generally - * used during the repaint cycle, to capture the existing state of an output - * and modify it to create a new state to be used. - * - * The mode determines whether the output will be reset to an a blank state, - * or an exact mirror of the current state. - */ -struct drm_output_state * -drm_output_state_duplicate(struct drm_output_state *src, - struct drm_pending_state *pending_state, - enum drm_output_state_duplicate_mode plane_mode) -{ - struct drm_output_state *dst = malloc(sizeof(*dst)); - struct drm_plane_state *ps; - - assert(dst); - - /* Copy the whole structure, then individually modify the - * pending_state, as well as the list link into our pending - * state. */ - *dst = *src; - - dst->pending_state = pending_state; - if (pending_state) - wl_list_insert(&pending_state->output_list, &dst->link); - else - wl_list_init(&dst->link); - - wl_list_init(&dst->plane_list); - - wl_list_for_each(ps, &src->plane_list, link) { - /* Don't carry planes which are now disabled; these should be - * free for other outputs to reuse. */ - if (!ps->output) - continue; - - if (plane_mode == DRM_OUTPUT_STATE_CLEAR_PLANES) - (void) drm_plane_state_alloc(dst, ps->plane); - else - (void) drm_plane_state_duplicate(dst, ps); - } - - return dst; -} - -/** - * Free an unused drm_output_state. - */ -void -drm_output_state_free(struct drm_output_state *state) -{ - struct drm_plane_state *ps, *next; - - if (!state) - return; - - wl_list_for_each_safe(ps, next, &state->plane_list, link) - drm_plane_state_free(ps, false); - - wl_list_remove(&state->link); - - free(state); -} - -/** - * Allocate a new drm_pending_state - * - * Allocate a new, empty, 'pending state' structure to be used across a - * repaint cycle or similar. - * - * @param backend DRM backend - * @returns Newly-allocated pending state structure - */ -struct drm_pending_state * -drm_pending_state_alloc(struct drm_backend *backend) -{ - struct drm_pending_state *ret; - - ret = calloc(1, sizeof(*ret)); - if (!ret) - return NULL; - - ret->backend = backend; - wl_list_init(&ret->output_list); - - return ret; -} - -/** - * Free a drm_pending_state structure - * - * Frees a pending_state structure, as well as any output_states connected - * to this pending state. - * - * @param pending_state Pending state structure to free - */ -void -drm_pending_state_free(struct drm_pending_state *pending_state) -{ - struct drm_output_state *output_state, *tmp; - - if (!pending_state) - return; - - wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, - link) { - drm_output_state_free(output_state); - } - - free(pending_state); -} - -/** - * Find an output state in a pending state - * - * Given a pending_state structure, find the output_state for a particular - * output. - * - * @param pending_state Pending state structure to search - * @param output Output to find state for - * @returns Output state if present, or NULL if not - */ -struct drm_output_state * -drm_pending_state_get_output(struct drm_pending_state *pending_state, - struct drm_output *output) -{ - struct drm_output_state *output_state; - - wl_list_for_each(output_state, &pending_state->output_list, link) { - if (output_state->output == output) - return output_state; - } - - return NULL; -} diff --git a/libweston/backend-drm/state-propose.c b/libweston/backend-drm/state-propose.c deleted file mode 100644 index b403e30..0000000 --- a/libweston/backend-drm/state-propose.c +++ /dev/null @@ -1,1149 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2011 Intel Corporation - * Copyright © 2017, 2018 Collabora, Ltd. - * Copyright © 2017, 2018 General Electric Company - * Copyright (c) 2018 DisplayLink (UK) Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include -#include -#include - -#include "drm-internal.h" - -#include "linux-dmabuf.h" -#include "presentation-time-server-protocol.h" - -enum drm_output_propose_state_mode { - DRM_OUTPUT_PROPOSE_STATE_MIXED, /**< mix renderer & planes */ - DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY, /**< only assign to renderer & cursor */ - DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY, /**< no renderer use, only planes */ -}; - -static const char *const drm_output_propose_state_mode_as_string[] = { - [DRM_OUTPUT_PROPOSE_STATE_MIXED] = "mixed state", - [DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY] = "render-only state", - [DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY] = "plane-only state" -}; - -static const char * -drm_propose_state_mode_to_string(enum drm_output_propose_state_mode mode) -{ - if (mode < 0 || mode >= ARRAY_LENGTH(drm_output_propose_state_mode_as_string)) - return " unknown compositing mode"; - - return drm_output_propose_state_mode_as_string[mode]; -} - -static void -drm_output_add_zpos_plane(struct drm_plane *plane, struct wl_list *planes) -{ - struct drm_backend *b = plane->backend; - struct drm_plane_zpos *h_plane; - struct drm_plane_zpos *plane_zpos; - - plane_zpos = zalloc(sizeof(*plane_zpos)); - if (!plane_zpos) - return; - - plane_zpos->plane = plane; - - drm_debug(b, "\t\t\t\t[plane] plane %d added to candidate list\n", - plane->plane_id); - - if (wl_list_empty(planes)) { - wl_list_insert(planes, &plane_zpos->link); - return; - } - - h_plane = wl_container_of(planes->next, h_plane, link); - if (h_plane->plane->zpos_max >= plane->zpos_max) { - wl_list_insert(planes->prev, &plane_zpos->link); - } else { - struct drm_plane_zpos *p_zpos = NULL; - - if (wl_list_length(planes) == 1) { - wl_list_insert(planes->prev, &plane_zpos->link); - return; - } - - wl_list_for_each(p_zpos, planes, link) { - if (p_zpos->plane->zpos_max > - plane_zpos->plane->zpos_max) - break; - } - - wl_list_insert(p_zpos->link.prev, &plane_zpos->link); - } -} - -static void -drm_output_destroy_zpos_plane(struct drm_plane_zpos *plane_zpos) -{ - wl_list_remove(&plane_zpos->link); - free(plane_zpos); -} - -static bool -drm_output_check_plane_has_view_assigned(struct drm_plane *plane, - struct drm_output_state *output_state) -{ - struct drm_plane_state *ps; - wl_list_for_each(ps, &output_state->plane_list, link) { - if (ps->plane == plane && ps->fb) - return true; - } - return false; -} - -static bool -drm_output_plane_has_valid_format(struct drm_plane *plane, - struct drm_output_state *state, - struct drm_fb *fb) -{ - struct drm_backend *b = plane->backend; - unsigned int i; - - if (!fb) - return false; - - /* Check whether the format is supported */ - for (i = 0; i < plane->count_formats; i++) { - unsigned int j; - - if (plane->formats[i].format != fb->format->format) - continue; - - if (fb->modifier == DRM_FORMAT_MOD_INVALID) - return true; - - for (j = 0; j < plane->formats[i].count_modifiers; j++) { - if (plane->formats[i].modifiers[j] == fb->modifier) - return true; - } - } - - drm_debug(b, "\t\t\t\t[%s] not placing view on %s: " - "no free %s planes matching format %s (0x%lx) " - "modifier 0x%llx\n", - drm_output_get_plane_type_name(plane), - drm_output_get_plane_type_name(plane), - drm_output_get_plane_type_name(plane), - fb->format->drm_format_name, - (unsigned long) fb->format, - (unsigned long long) fb->modifier); - - return false; -} - -static bool -drm_output_plane_cursor_has_valid_format(struct weston_view *ev) -{ - struct wl_shm_buffer *shmbuf = - wl_shm_buffer_get(ev->surface->buffer_ref.buffer->resource); - - if (shmbuf && wl_shm_buffer_get_format(shmbuf) == WL_SHM_FORMAT_ARGB8888) - return true; - - return false; -} - -static struct drm_plane_state * -drm_output_prepare_overlay_view(struct drm_plane *plane, - struct drm_output_state *output_state, - struct weston_view *ev, - enum drm_output_propose_state_mode mode, - struct drm_fb *fb, uint64_t zpos) -{ - struct drm_output *output = output_state->output; - struct weston_compositor *ec = output->base.compositor; - struct drm_backend *b = to_drm_backend(ec); - struct drm_plane_state *state = NULL; - int ret; - - assert(!b->sprites_are_broken); - assert(b->atomic_modeset); - - if (!fb) { - drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " - " couldn't get fb\n", ev); - return NULL; - } - - state = drm_output_state_get_plane(output_state, plane); - /* we can't have a 'pending' framebuffer as never set one before reaching here */ - assert(!state->fb); - - state->ev = ev; - state->output = output; - - if (!drm_plane_state_coords_for_view(state, ev, zpos)) { - drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " - "unsuitable transform\n", ev); - drm_plane_state_put_back(state); - state = NULL; - goto out; - } - - /* If the surface buffer has an in-fence fd, but the plane - * doesn't support fences, we can't place the buffer on this - * plane. */ - if (ev->surface->acquire_fence_fd >= 0 && - plane->props[WDRM_PLANE_IN_FENCE_FD].prop_id == 0) { - drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " - "no in-fence support\n", ev); - drm_plane_state_put_back(state); - state = NULL; - goto out; - } - - /* We hold one reference for the lifetime of this function; from - * calling drm_fb_get_from_view() in drm_output_prepare_plane_view(), - * so, we take another reference here to live within the state. */ - state->fb = drm_fb_ref(fb); - - state->in_fence_fd = ev->surface->acquire_fence_fd; - - /* In planes-only mode, we don't have an incremental state to - * test against, so we just hope it'll work. */ - if (mode == DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY) { - drm_debug(b, "\t\t\t[overlay] provisionally placing " - "view %p on overlay %lu in planes-only mode\n", - ev, (unsigned long) plane->plane_id); - goto out; - } - - ret = drm_pending_state_test(output_state->pending_state); - if (ret == 0) { - drm_debug(b, "\t\t\t[overlay] provisionally placing " - "view %p on overlay %d in mixed mode\n", - ev, plane->plane_id); - goto out; - } - - drm_debug(b, "\t\t\t[overlay] not placing view %p on overlay %lu " - "in mixed mode: kernel test failed\n", - ev, (unsigned long) plane->plane_id); - - drm_plane_state_put_back(state); - state = NULL; - -out: - return state; -} - -#ifdef BUILD_DRM_GBM -/** - * Update the image for the current cursor surface - * - * @param plane_state DRM cursor plane state - * @param ev Source view for cursor - */ -static void -cursor_bo_update(struct drm_plane_state *plane_state, struct weston_view *ev) -{ - struct drm_backend *b = plane_state->plane->backend; - struct gbm_bo *bo = plane_state->fb->bo; - struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; - uint32_t buf[b->cursor_width * b->cursor_height]; - int32_t stride; - uint8_t *s; - int i; - - assert(buffer && buffer->shm_buffer); - assert(buffer->shm_buffer == wl_shm_buffer_get(buffer->resource)); - assert(buffer->width <= b->cursor_width); - assert(buffer->height <= b->cursor_height); - - memset(buf, 0, sizeof buf); - stride = wl_shm_buffer_get_stride(buffer->shm_buffer); - s = wl_shm_buffer_get_data(buffer->shm_buffer); - - wl_shm_buffer_begin_access(buffer->shm_buffer); - for (i = 0; i < buffer->height; i++) - memcpy(buf + i * b->cursor_width, - s + i * stride, - buffer->width * 4); - wl_shm_buffer_end_access(buffer->shm_buffer); - - if (gbm_bo_write(bo, buf, sizeof buf) < 0) - weston_log("failed update cursor: %s\n", strerror(errno)); -} - -static struct drm_plane_state * -drm_output_prepare_cursor_view(struct drm_output_state *output_state, - struct weston_view *ev, uint64_t zpos) -{ - struct drm_output *output = output_state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); - struct drm_plane *plane = output->cursor_plane; - struct drm_plane_state *plane_state; - bool needs_update = false; - const char *p_name = drm_output_get_plane_type_name(plane); - - assert(!b->cursors_are_broken); - - if (!plane) - return NULL; - - if (!plane->state_cur->complete) - return NULL; - - if (plane->state_cur->output && plane->state_cur->output != output) - return NULL; - - /* We use GBM to import SHM buffers. */ - if (b->gbm == NULL) - return NULL; - - plane_state = - drm_output_state_get_plane(output_state, output->cursor_plane); - - if (plane_state && plane_state->fb) - return NULL; - - /* We can't scale with the legacy API, and we don't try to account for - * simple cropping/translation in cursor_bo_update. */ - plane_state->output = output; - if (!drm_plane_state_coords_for_view(plane_state, ev, zpos)) { - drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " - "unsuitable transform\n", p_name, ev, p_name); - goto err; - } - - if (plane_state->src_x != 0 || plane_state->src_y != 0 || - plane_state->src_w > (unsigned) b->cursor_width << 16 || - plane_state->src_h > (unsigned) b->cursor_height << 16 || - plane_state->src_w != plane_state->dest_w << 16 || - plane_state->src_h != plane_state->dest_h << 16) { - drm_debug(b, "\t\t\t\t[%s] not assigning view %p to %s plane " - "(positioning requires cropping or scaling)\n", - p_name, ev, p_name); - goto err; - } - - /* Since we're setting plane state up front, we need to work out - * whether or not we need to upload a new cursor. We can't use the - * plane damage, since the planes haven't actually been calculated - * yet: instead try to figure it out directly. KMS cursor planes are - * pretty unique here, in that they lie partway between a Weston plane - * (direct scanout) and a renderer. */ - if (ev != output->cursor_view || - pixman_region32_not_empty(&ev->surface->damage)) { - output->current_cursor++; - output->current_cursor = - output->current_cursor % - ARRAY_LENGTH(output->gbm_cursor_fb); - needs_update = true; - } - - output->cursor_view = ev; - plane_state->ev = ev; - - plane_state->fb = - drm_fb_ref(output->gbm_cursor_fb[output->current_cursor]); - - if (needs_update) { - drm_debug(b, "\t\t\t\t[%s] copying new content to cursor BO\n", p_name); - cursor_bo_update(plane_state, ev); - } - - /* The cursor API is somewhat special: in cursor_bo_update(), we upload - * a buffer which is always cursor_width x cursor_height, even if the - * surface we want to promote is actually smaller than this. Manually - * mangle the plane state to deal with this. */ - plane_state->src_w = b->cursor_width << 16; - plane_state->src_h = b->cursor_height << 16; - plane_state->dest_w = b->cursor_width; - plane_state->dest_h = b->cursor_height; - - drm_debug(b, "\t\t\t\t[%s] provisionally assigned view %p to cursor\n", - p_name, ev); - - return plane_state; - -err: - drm_plane_state_put_back(plane_state); - return NULL; -} -#else -static struct drm_plane_state * -drm_output_prepare_cursor_view(struct drm_output_state *output_state, - struct weston_view *ev, uint64_t zpos) -{ - return NULL; -} -#endif - -static struct drm_plane_state * -drm_output_prepare_scanout_view(struct drm_output_state *output_state, - struct weston_view *ev, - enum drm_output_propose_state_mode mode, - struct drm_fb *fb, uint64_t zpos) -{ - struct drm_output *output = output_state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); - struct drm_plane *scanout_plane = output->scanout_plane; - struct drm_plane_state *state; - const char *p_name = drm_output_get_plane_type_name(scanout_plane); - - assert(!b->sprites_are_broken); - assert(b->atomic_modeset); - assert(mode == DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY); - - /* Check the view spans exactly the output size, calculated in the - * logical co-ordinate space. */ - if (!weston_view_matches_output_entirely(ev, &output->base)) { - drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " - " view does not match output entirely\n", - p_name, ev, p_name); - return NULL; - } - - /* If the surface buffer has an in-fence fd, but the plane doesn't - * support fences, we can't place the buffer on this plane. */ - if (ev->surface->acquire_fence_fd >= 0 && - scanout_plane->props[WDRM_PLANE_IN_FENCE_FD].prop_id == 0) { - drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " - "no in-fence support\n", p_name, ev, p_name); - return NULL; - } - - if (!fb) { - drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " - " couldn't get fb\n", p_name, ev, p_name); - return NULL; - } - - state = drm_output_state_get_plane(output_state, scanout_plane); - - /* The only way we can already have a buffer in the scanout plane is - * if we are in mixed mode, or if a client buffer has already been - * placed into scanout. The former case will never call into here, - * and in the latter case, the view must have been marked as occluded, - * meaning we should never have ended up here. */ - assert(!state->fb); - - /* take another reference here to live within the state */ - state->fb = drm_fb_ref(fb); - state->ev = ev; - state->output = output; - if (!drm_plane_state_coords_for_view(state, ev, zpos)) { - drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " - "unsuitable transform\n", p_name, ev, p_name); - goto err; - } - - if (state->dest_x != 0 || state->dest_y != 0 || - state->dest_w != (unsigned) output->base.current_mode->width || - state->dest_h != (unsigned) output->base.current_mode->height) { - drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " - " invalid plane state\n", p_name, ev, p_name); - goto err; - } - - state->in_fence_fd = ev->surface->acquire_fence_fd; - - /* In plane-only mode, we don't need to test the state now, as we - * will only test it once at the end. */ - return state; - -err: - drm_plane_state_put_back(state); - return NULL; -} - -static bool -drm_output_plane_view_has_valid_format(struct drm_plane *plane, - struct drm_output_state *state, - struct weston_view *ev, - struct drm_fb *fb) -{ - /* depending on the type of the plane we have different requirements */ - switch (plane->type) { - case WDRM_PLANE_TYPE_CURSOR: - return drm_output_plane_cursor_has_valid_format(ev); - case WDRM_PLANE_TYPE_OVERLAY: - return drm_output_plane_has_valid_format(plane, state, fb); - case WDRM_PLANE_TYPE_PRIMARY: - return drm_output_plane_has_valid_format(plane, state, fb); - default: - assert(0); - return false; - } - - return false; -} - -static struct drm_plane_state * -drm_output_try_view_on_plane(struct drm_plane *plane, - struct drm_output_state *state, - struct weston_view *ev, - enum drm_output_propose_state_mode mode, - struct drm_fb *fb, uint64_t zpos) -{ - struct drm_backend *b = state->pending_state->backend; - struct weston_output *wet_output = &state->output->base; - bool view_matches_entire_output, scanout_has_view_assigned; - struct drm_plane *scanout_plane = state->output->scanout_plane; - struct drm_plane_state *ps = NULL; - const char *p_name = drm_output_get_plane_type_name(plane); - enum { - NO_PLANES, /* generic err-handle */ - NO_PLANES_ACCEPTED, - PLACED_ON_PLANE, - } availability = NO_PLANES; - - /* sanity checks in case we over/underflow zpos or pass incorrect - * values */ - assert(zpos <= plane->zpos_max || - zpos != DRM_PLANE_ZPOS_INVALID_PLANE); - - switch (plane->type) { - case WDRM_PLANE_TYPE_CURSOR: - if (b->cursors_are_broken) { - availability = NO_PLANES_ACCEPTED; - goto out; - } - - ps = drm_output_prepare_cursor_view(state, ev, zpos); - if (ps) - availability = PLACED_ON_PLANE; - break; - case WDRM_PLANE_TYPE_OVERLAY: - /* do not attempt to place it in the overlay if we don't have - * anything in the scanout/primary and the view doesn't cover - * the entire output */ - view_matches_entire_output = - weston_view_matches_output_entirely(ev, wet_output); - scanout_has_view_assigned = - drm_output_check_plane_has_view_assigned(scanout_plane, - state); - - if (view_matches_entire_output && !scanout_has_view_assigned) { - availability = NO_PLANES_ACCEPTED; - goto out; - } - - ps = drm_output_prepare_overlay_view(plane, state, ev, mode, - fb, zpos); - if (ps) - availability = PLACED_ON_PLANE; - break; - case WDRM_PLANE_TYPE_PRIMARY: - if (mode != DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY) { - availability = NO_PLANES_ACCEPTED; - goto out; - } - - ps = drm_output_prepare_scanout_view(state, ev, mode, - fb, zpos); - if (ps) - availability = PLACED_ON_PLANE; - break; - default: - assert(0); - break; - } - -out: - switch (availability) { - case NO_PLANES: - /* set initial to this catch-all case, such that - * prepare_cursor/overlay/scanout() should have/contain the - * reason for failling */ - break; - case NO_PLANES_ACCEPTED: - drm_debug(b, "\t\t\t\t[plane] plane %d refusing to " - "place view %p in %s\n", - plane->plane_id, ev, p_name); - break; - case PLACED_ON_PLANE: - break; - } - - - return ps; -} - -static void -drm_output_check_zpos_plane_states(struct drm_output_state *state) -{ - struct drm_plane_state *ps; - - wl_list_for_each(ps, &state->plane_list, link) { - struct wl_list *next_node = ps->link.next; - bool found_dup = false; - - /* skip any plane that is not enabled */ - if (!ps->fb) - continue; - - assert(ps->zpos != DRM_PLANE_ZPOS_INVALID_PLANE); - - /* find another plane with the same zpos value */ - if (next_node == &state->plane_list) - break; - - while (next_node && next_node != &state->plane_list) { - struct drm_plane_state *ps_next; - - ps_next = container_of(next_node, - struct drm_plane_state, - link); - - if (ps->zpos == ps_next->zpos) { - found_dup = true; - break; - } - next_node = next_node->next; - } - - /* this should never happen so exit hard in case - * we screwed up that bad */ - assert(!found_dup); - } -} - -static struct drm_plane_state * -drm_output_prepare_plane_view(struct drm_output_state *state, - struct weston_view *ev, - enum drm_output_propose_state_mode mode, - struct drm_plane_state *scanout_state, - uint64_t current_lowest_zpos) -{ - struct drm_output *output = state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); - - struct drm_plane_state *ps = NULL; - struct drm_plane *plane; - struct drm_plane_zpos *p_zpos, *p_zpos_next; - struct wl_list zpos_candidate_list; - - struct drm_fb *fb; - - wl_list_init(&zpos_candidate_list); - - /* check view for valid buffer, doesn't make sense to even try */ - if (!weston_view_has_valid_buffer(ev)) - return ps; - - fb = drm_fb_get_from_view(state, ev); - - /* assemble a list with possible candidates */ - wl_list_for_each(plane, &b->plane_list, link) { - if (!drm_plane_is_available(plane, output)) - continue; - - if (drm_output_check_plane_has_view_assigned(plane, state)) { - drm_debug(b, "\t\t\t\t[plane] not adding plane %d to" - " candidate list: view already assigned " - "to a plane\n", plane->plane_id); - continue; - } - - if (plane->zpos_min >= current_lowest_zpos) { - drm_debug(b, "\t\t\t\t[plane] not adding plane %d to " - "candidate list: minium zpos (%"PRIu64") " - "plane's above current lowest zpos " - "(%"PRIu64")\n", plane->plane_id, - plane->zpos_min, current_lowest_zpos); - continue; - } - - if (mode == DRM_OUTPUT_PROPOSE_STATE_MIXED) { - assert(scanout_state != NULL); - if (scanout_state->zpos >= plane->zpos_max) { - drm_debug(b, "\t\t\t\t[plane] not adding plane %d to " - "candidate list: primary's zpos " - "value (%"PRIu64") higher than " - "plane's maximum value (%"PRIu64")\n", - plane->plane_id, scanout_state->zpos, - plane->zpos_max); - continue; - } - } - - if (mode == DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY && - (plane->type == WDRM_PLANE_TYPE_OVERLAY || - plane->type == WDRM_PLANE_TYPE_PRIMARY)) { - drm_debug(b, "\t\t\t\t[plane] not adding plane %d to " - "candidate list: renderer-only mode\n", - plane->plane_id); - continue; - } - - if (plane->type != WDRM_PLANE_TYPE_CURSOR && - b->sprites_are_broken) { - drm_debug(b, "\t\t\t\t[plane] not adding plane %d, type %s to " - "candidate list: sprites are broken!\n", - plane->plane_id, - drm_output_get_plane_type_name(plane)); - continue; - } - - if (!drm_output_plane_view_has_valid_format(plane, state, ev, fb)) { - drm_debug(b, "\t\t\t\t[plane] not adding plane %d to " - "candidate list: invalid pixel format\n", - plane->plane_id); - continue; - } - - drm_output_add_zpos_plane(plane, &zpos_candidate_list); - } - - /* go over the potential candidate list and try to find a possible - * plane suitable for \c ev; start with the highest zpos value of a - * plane to maximize our chances, but do note we pass the zpos value - * based on current tracked value by \c current_lowest_zpos_in_use */ - while (!wl_list_empty(&zpos_candidate_list)) { - struct drm_plane_zpos *head_p_zpos = - wl_container_of(zpos_candidate_list.next, - head_p_zpos, link); - struct drm_plane *plane = head_p_zpos->plane; - const char *p_name = drm_output_get_plane_type_name(plane); - uint64_t zpos; - - if (current_lowest_zpos == DRM_PLANE_ZPOS_INVALID_PLANE) - zpos = plane->zpos_max; - else - zpos = MIN(current_lowest_zpos - 1, plane->zpos_max); - - drm_debug(b, "\t\t\t\t[plane] plane %d picked " - "from candidate list, type: %s\n", - plane->plane_id, p_name); - - ps = drm_output_try_view_on_plane(plane, state, ev, - mode, fb, zpos); - drm_output_destroy_zpos_plane(head_p_zpos); - if (ps) { - drm_debug(b, "\t\t\t\t[view] view %p has been placed to " - "%s plane with computed zpos %"PRIu64"\n", - ev, p_name, zpos); - break; - } - } - - wl_list_for_each_safe(p_zpos, p_zpos_next, &zpos_candidate_list, link) - drm_output_destroy_zpos_plane(p_zpos); - - drm_fb_unref(fb); - return ps; -} - -static struct drm_output_state * -drm_output_propose_state(struct weston_output *output_base, - struct drm_pending_state *pending_state, - enum drm_output_propose_state_mode mode) -{ - struct drm_output *output = to_drm_output(output_base); - struct drm_backend *b = to_drm_backend(output->base.compositor); - struct drm_output_state *state; - struct drm_plane_state *scanout_state = NULL; - struct weston_view *ev; - - pixman_region32_t surface_overlap, renderer_region, planes_region; - pixman_region32_t occluded_region; - - bool renderer_ok = (mode != DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY); - int ret; - uint64_t current_lowest_zpos = DRM_PLANE_ZPOS_INVALID_PLANE; - - assert(!output->state_last); - state = drm_output_state_duplicate(output->state_cur, - pending_state, - DRM_OUTPUT_STATE_CLEAR_PLANES); - - /* We implement mixed mode by progressively creating and testing - * incremental states, of scanout + overlay + cursor. Since we - * walk our views top to bottom, the scanout plane is last, however - * we always need it in our scene for the test modeset to be - * meaningful. To do this, we steal a reference to the last - * renderer framebuffer we have, if we think it's basically - * compatible. If we don't have that, then we conservatively fall - * back to only using the renderer for this repaint. */ - if (mode == DRM_OUTPUT_PROPOSE_STATE_MIXED) { - struct drm_plane *plane = output->scanout_plane; - struct drm_fb *scanout_fb = plane->state_cur->fb; - - if (!scanout_fb || - (scanout_fb->type != BUFFER_GBM_SURFACE && - scanout_fb->type != BUFFER_PIXMAN_DUMB)) { - drm_debug(b, "\t\t[state] cannot propose mixed mode: " - "for output %s (%lu): no previous renderer " - "fb\n", - output->base.name, - (unsigned long) output->base.id); - drm_output_state_free(state); - return NULL; - } - - if (scanout_fb->width != output_base->current_mode->width || - scanout_fb->height != output_base->current_mode->height) { - drm_debug(b, "\t\t[state] cannot propose mixed mode " - "for output %s (%lu): previous fb has " - "different size\n", - output->base.name, - (unsigned long) output->base.id); - drm_output_state_free(state); - return NULL; - } - - scanout_state = drm_plane_state_duplicate(state, - plane->state_cur); - /* assign the primary primary the lowest zpos value */ - scanout_state->zpos = plane->zpos_min; - drm_debug(b, "\t\t[state] using renderer FB ID %lu for mixed " - "mode for output %s (%lu)\n", - (unsigned long) scanout_fb->fb_id, output->base.name, - (unsigned long) output->base.id); - drm_debug(b, "\t\t[state] scanout will use for zpos %"PRIu64"\n", - scanout_state->zpos); - } - - /* - renderer_region contains the total region which which will be - * covered by the renderer - * - planes_region contains the total region which has been covered by - * hardware planes - * - occluded_region contains the total region which which will be - * covered by the renderer and hardware planes, where the view's - * visible-and-opaque region is added in both cases (the view's - * opaque region accumulates there for each view); it is being used - * to skip the view, if it is completely occluded; includes the - * situation where occluded_region covers entire output's region. - */ - pixman_region32_init(&renderer_region); - pixman_region32_init(&planes_region); - pixman_region32_init(&occluded_region); - - wl_list_for_each(ev, &output_base->compositor->view_list, link) { - struct drm_plane_state *ps = NULL; - bool force_renderer = false; - pixman_region32_t clipped_view; - bool totally_occluded = false; - - drm_debug(b, "\t\t\t[view] evaluating view %p for " - "output %s (%lu)\n", - ev, output->base.name, - (unsigned long) output->base.id); - - /* If this view doesn't touch our output at all, there's no - * reason to do anything with it. */ - if (!(ev->output_mask & (1u << output->base.id))) { - drm_debug(b, "\t\t\t\t[view] ignoring view %p " - "(not on our output)\n", ev); - continue; - } - - /* Ignore views we know to be totally occluded. */ - pixman_region32_init(&clipped_view); - pixman_region32_intersect(&clipped_view, - &ev->transform.boundingbox, - &output->base.region); - - pixman_region32_init(&surface_overlap); - pixman_region32_subtract(&surface_overlap, &clipped_view, - &occluded_region); - /* if the view is completely occluded then ignore that - * view; includes the case where occluded_region covers - * the entire output */ - totally_occluded = !pixman_region32_not_empty(&surface_overlap); - if (totally_occluded) { - drm_debug(b, "\t\t\t\t[view] ignoring view %p " - "(occluded on our output)\n", ev); - pixman_region32_fini(&surface_overlap); - pixman_region32_fini(&clipped_view); - continue; - } - - /* We only assign planes to views which are exclusively present - * on our output. */ - if (ev->output_mask != (1u << output->base.id)) { - drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " - "(on multiple outputs)\n", ev); - force_renderer = true; - } - - if (!weston_view_has_valid_buffer(ev)) { - drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " - "(no buffer available)\n", ev); - force_renderer = true; - } - - /* Since we process views from top to bottom, we know that if - * the view intersects the calculated renderer region, it must - * be part of, or occluded by, it, and cannot go on a plane. */ - pixman_region32_intersect(&surface_overlap, &renderer_region, - &clipped_view); - if (pixman_region32_not_empty(&surface_overlap)) { - drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " - "(occluded by renderer views)\n", ev); - force_renderer = true; - } - pixman_region32_fini(&surface_overlap); - - /* In case of enforced mode of content-protection do not - * assign planes for a protected surface on an unsecured output. - */ - if (ev->surface->protection_mode == WESTON_SURFACE_PROTECTION_MODE_ENFORCED && - ev->surface->desired_protection > output_base->current_protection) { - drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " - "(enforced protection mode on unsecured output)\n", ev); - force_renderer = true; - } - - if (!force_renderer) { - drm_debug(b, "\t\t\t[plane] started with zpos %"PRIu64"\n", - current_lowest_zpos); - ps = drm_output_prepare_plane_view(state, ev, mode, - scanout_state, - current_lowest_zpos); - } - - if (ps) { - current_lowest_zpos = ps->zpos; - drm_debug(b, "\t\t\t[plane] next zpos to use %"PRIu64"\n", - current_lowest_zpos); - - /* If we have been assigned to an overlay or scanout - * plane, add this area to the occluded region, so - * other views are known to be behind it. The cursor - * plane, however, is special, in that it blends with - * the content underneath it: the area should neither - * be added to the renderer region nor the occluded - * region. */ - if (ps->plane->type != WDRM_PLANE_TYPE_CURSOR) { - pixman_region32_union(&planes_region, - &planes_region, - &clipped_view); - - if (!weston_view_is_opaque(ev, &clipped_view)) - pixman_region32_intersect(&clipped_view, - &clipped_view, - &ev->transform.opaque); - /* the visible-and-opaque region of this view - * will occlude views underneath it */ - pixman_region32_union(&occluded_region, - &occluded_region, - &clipped_view); - - pixman_region32_fini(&clipped_view); - - } - continue; - } - - /* We have been assigned to the primary (renderer) plane: - * check if this is OK, and add ourselves to the renderer - * region if so. */ - if (!renderer_ok) { - drm_debug(b, "\t\t[view] failing state generation: " - "placing view %p to renderer not allowed\n", - ev); - pixman_region32_fini(&clipped_view); - goto err_region; - } - - pixman_region32_union(&renderer_region, - &renderer_region, - &clipped_view); - - if (!weston_view_is_opaque(ev, &clipped_view)) - pixman_region32_intersect(&clipped_view, - &clipped_view, - &ev->transform.opaque); - - pixman_region32_union(&occluded_region, - &occluded_region, - &clipped_view); - - pixman_region32_fini(&clipped_view); - - drm_debug(b, "\t\t\t\t[view] view %p will be placed " - "on the renderer\n", ev); - } - - pixman_region32_fini(&renderer_region); - pixman_region32_fini(&planes_region); - pixman_region32_fini(&occluded_region); - - /* In renderer-only mode, we can't test the state as we don't have a - * renderer buffer yet. */ - if (mode == DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY) - return state; - - /* check if we have invalid zpos values, like duplicate(s) */ - drm_output_check_zpos_plane_states(state); - - /* Check to see if this state will actually work. */ - ret = drm_pending_state_test(state->pending_state); - if (ret != 0) { - drm_debug(b, "\t\t[view] failing state generation: " - "atomic test not OK\n"); - goto err; - } - - /* Counterpart to duplicating scanout state at the top of this - * function: if we have taken a renderer framebuffer and placed it in - * the pending state in order to incrementally test overlay planes, - * remove it now. */ - if (mode == DRM_OUTPUT_PROPOSE_STATE_MIXED) { - assert(scanout_state->fb->type == BUFFER_GBM_SURFACE || - scanout_state->fb->type == BUFFER_PIXMAN_DUMB); - drm_plane_state_put_back(scanout_state); - } - return state; - -err_region: - pixman_region32_fini(&renderer_region); - pixman_region32_fini(&occluded_region); -err: - drm_output_state_free(state); - return NULL; -} - -void -drm_assign_planes(struct weston_output *output_base, void *repaint_data) -{ - struct drm_backend *b = to_drm_backend(output_base->compositor); - struct drm_pending_state *pending_state = repaint_data; - struct drm_output *output = to_drm_output(output_base); - struct drm_output_state *state = NULL; - struct drm_plane_state *plane_state; - struct weston_view *ev; - struct weston_plane *primary = &output_base->compositor->primary_plane; - enum drm_output_propose_state_mode mode = DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY; - - drm_debug(b, "\t[repaint] preparing state for output %s (%lu)\n", - output_base->name, (unsigned long) output_base->id); - - if (!b->sprites_are_broken && !output->virtual) { - drm_debug(b, "\t[repaint] trying planes-only build state\n"); - state = drm_output_propose_state(output_base, pending_state, mode); - if (!state) { - drm_debug(b, "\t[repaint] could not build planes-only " - "state, trying mixed\n"); - mode = DRM_OUTPUT_PROPOSE_STATE_MIXED; - state = drm_output_propose_state(output_base, - pending_state, - mode); - } - if (!state) { - drm_debug(b, "\t[repaint] could not build mixed-mode " - "state, trying renderer-only\n"); - } - } else { - drm_debug(b, "\t[state] no overlay plane support\n"); - } - - if (!state) { - mode = DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY; - state = drm_output_propose_state(output_base, pending_state, - mode); - } - - assert(state); - drm_debug(b, "\t[repaint] Using %s composition\n", - drm_propose_state_mode_to_string(mode)); - - wl_list_for_each(ev, &output_base->compositor->view_list, link) { - struct drm_plane *target_plane = NULL; - - /* If this view doesn't touch our output at all, there's no - * reason to do anything with it. */ - if (!(ev->output_mask & (1u << output->base.id))) - continue; - - /* Test whether this buffer can ever go into a plane: - * non-shm, or small enough to be a cursor. - * - * Also, keep a reference when using the pixman renderer. - * That makes it possible to do a seamless switch to the GL - * renderer and since the pixman renderer keeps a reference - * to the buffer anyway, there is no side effects. - */ - if (b->use_pixman || - (weston_view_has_valid_buffer(ev) && - (!wl_shm_buffer_get(ev->surface->buffer_ref.buffer->resource) || - (ev->surface->width <= b->cursor_width && - ev->surface->height <= b->cursor_height)))) - ev->surface->keep_buffer = true; - else - ev->surface->keep_buffer = false; - - /* This is a bit unpleasant, but lacking a temporary place to - * hang a plane off the view, we have to do a nested walk. - * Our first-order iteration has to be planes rather than - * views, because otherwise we won't reset views which were - * previously on planes to being on the primary plane. */ - wl_list_for_each(plane_state, &state->plane_list, link) { - if (plane_state->ev == ev) { - plane_state->ev = NULL; - target_plane = plane_state->plane; - break; - } - } - - if (target_plane) { - drm_debug(b, "\t[repaint] view %p on %s plane %lu\n", - ev, plane_type_enums[target_plane->type].name, - (unsigned long) target_plane->plane_id); - weston_view_move_to_plane(ev, &target_plane->base); - } else { - drm_debug(b, "\t[repaint] view %p using renderer " - "composition\n", ev); - weston_view_move_to_plane(ev, primary); - } - - if (!target_plane || - target_plane->type == WDRM_PLANE_TYPE_CURSOR) { - /* cursor plane & renderer involve a copy */ - ev->psf_flags = 0; - } else { - /* All other planes are a direct scanout of a - * single client buffer. - */ - ev->psf_flags = WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY; - } - } - - /* We rely on output->cursor_view being both an accurate reflection of - * the cursor plane's state, but also being maintained across repaints - * to avoid unnecessary damage uploads, per the comment in - * drm_output_prepare_cursor_view. In the event that we go from having - * a cursor view to not having a cursor view, we need to clear it. */ - if (output->cursor_view) { - plane_state = - drm_output_state_get_existing_plane(state, - output->cursor_plane); - if (!plane_state || !plane_state->fb) - output->cursor_view = NULL; - } -} diff --git a/libweston/backend-drm/vaapi-recorder.c b/libweston/backend-drm/vaapi-recorder.c deleted file mode 100644 index 0a74357..0000000 --- a/libweston/backend-drm/vaapi-recorder.c +++ /dev/null @@ -1,1161 +0,0 @@ -/* - * Copyright (c) 2012 Intel Corporation. All Rights Reserved. - * Copyright © 2013 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include -#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 error; - 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 *); - -/* bitstream 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(unsigned 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; -} - -enum output_write_status { - OUTPUT_WRITE_SUCCESS, - OUTPUT_WRITE_OVERFLOW, - OUTPUT_WRITE_FATAL -}; - -static enum output_write_status -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 OUTPUT_WRITE_FATAL; - - if (segment->status & VA_CODED_BUF_STATUS_SLICE_OVERFLOW_MASK) { - r->encoder.output_size *= 2; - vaUnmapBuffer(r->va_dpy, output_buf); - return OUTPUT_WRITE_OVERFLOW; - } - - count = write(r->output_fd, segment->buf, segment->size); - - vaUnmapBuffer(r->va_dpy, output_buf); - - if (count < 0) - return OUTPUT_WRITE_FATAL; - - return OUTPUT_WRITE_SUCCESS; -} - -static void -encoder_encode(struct vaapi_recorder *r, VASurfaceID input) -{ - VABufferID output_buf = VA_INVALID_ID; - - VABufferID buffers[8]; - int count = 0; - int i, slice_type; - enum output_write_status ret; - - 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 == OUTPUT_WRITE_OVERFLOW); - - if (ret == OUTPUT_WRITE_FATAL) - r->error = errno; - - 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 = zalloc(sizeof *r); - if (r == NULL) - 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; -} - -int -vaapi_recorder_frame(struct vaapi_recorder *r, int prime_fd, int stride) -{ - int ret = 0; - - pthread_mutex_lock(&r->mutex); - - if (r->error) { - errno = r->error; - ret = -1; - goto unlock; - } - - /* 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); - -unlock: - pthread_mutex_unlock(&r->mutex); - - return ret; -} diff --git a/libweston/backend-drm/vaapi-recorder.h b/libweston/backend-drm/vaapi-recorder.h deleted file mode 100644 index 6b194aa..0000000 --- a/libweston/backend-drm/vaapi-recorder.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright © 2013 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#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); -int -vaapi_recorder_frame(struct vaapi_recorder *r, int fd, int stride); - -#endif /* _VAAPI_RECORDER_H_ */ diff --git a/libweston/backend-fbdev/fbdev.c b/libweston/backend-fbdev/fbdev.c deleted file mode 100755 index 2034877..0000000 --- a/libweston/backend-fbdev/fbdev.c +++ /dev/null @@ -1,999 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2011 Intel Corporation - * Copyright © 2012 Raspberry Pi Foundation - * Copyright © 2013 Philip Withnall - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "shared/helpers.h" -#include -#include -#include "launcher-util.h" -#include "pixman-renderer.h" -#include "libinput-seat.h" -#include "presentation-time-server-protocol.h" - -struct fbdev_backend { - struct weston_backend base; - struct weston_compositor *compositor; - uint32_t prev_state; - - struct udev *udev; - struct udev_input input; - uint32_t output_transform; - 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_head { - struct weston_head base; - - /* Frame buffer details. */ - char *device; - struct fbdev_screeninfo fb_info; -}; - -struct fbdev_output { - struct fbdev_backend *backend; - struct weston_output base; - - struct weston_mode mode; - struct wl_event_source *finish_frame_timer; - - /* framebuffer mmap details */ - size_t buffer_length; - void *fb; - - /* pixman details. */ - pixman_image_t *hw_surface; -}; - -static const char default_seat[] = "seat0"; - -static inline struct fbdev_head * -to_fbdev_head(struct weston_head *base) -{ - return container_of(base, struct fbdev_head, base); -} - -static inline struct fbdev_output * -to_fbdev_output(struct weston_output *base) -{ - return container_of(base, struct fbdev_output, base); -} - -static inline struct fbdev_backend * -to_fbdev_backend(struct weston_compositor *base) -{ - return container_of(base->backend, struct fbdev_backend, base); -} - -static struct fbdev_head * -fbdev_output_get_head(struct fbdev_output *output) -{ - if (wl_list_length(&output->base.head_list) != 1) - return NULL; - - return container_of(output->base.head_list.next, - struct fbdev_head, base.output_link); -} - -static int -fbdev_output_start_repaint_loop(struct weston_output *output) -{ - struct timespec ts; - - weston_compositor_read_presentation_clock(output->compositor, &ts); - weston_output_finish_frame(output, &ts, WP_PRESENTATION_FEEDBACK_INVALID); - - return 0; -} - -static int -fbdev_output_repaint(struct weston_output *base, pixman_region32_t *damage, - void *repaint_data) -{ - struct fbdev_output *output = to_fbdev_output(base); - struct weston_compositor *ec = output->base.compositor; - - /* Repaint the damaged region onto the back buffer. */ - pixman_renderer_output_set_buffer(base, output->hw_surface); - ec->renderer->repaint_output(base, damage); - - /* 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); - - return 0; -} - -static int -finish_frame_handler(void *data) -{ - struct fbdev_output *output = data; - struct timespec ts; - - weston_compositor_read_presentation_clock(output->base.compositor, &ts); - weston_output_finish_frame(&output->base, &ts, 0); - - 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, ARGB - * and ABGR 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; - else if (vinfo->transp.offset >= vinfo->blue.offset && - vinfo->blue.offset >= vinfo->green.offset && - vinfo->green.offset >= vinfo->red.offset) - type = PIXMAN_TYPE_ABGR; - - 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 */ - - if (refresh_rate >= 1000) /* at least 1 Hz */ - return refresh_rate; - } - - return 60 * 1000; /* default to 60 Hz */ -} - -static int -fbdev_query_screen_info(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)); - info->id[sizeof(info->id)-1] = '\0'; - - 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(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 int -fbdev_wakeup_screen(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; - } - - /* force the framebuffer to wake up */ - varinfo.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; - - /* Set the device's screen information. */ - if (ioctl(fd, FBIOPUT_VSCREENINFO, &varinfo) < 0) { - return -1; - } - - return 1; -} - -/* Returns an FD for the frame buffer device. */ -static int -fbdev_frame_buffer_open(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(fd, screen_info) < 0) { - weston_log("Failed to get frame buffer info: %s\n", - strerror(errno)); - - close(fd); - return -1; - } - - /* Attempt to wake up the framebuffer device, needed for secondary - * framebuffer devices */ - if (fbdev_wakeup_screen(fd, screen_info) < 0) { - weston_log("Failed to activate framebuffer display. " - "Attempting to open output anyway.\n"); - } - - - return fd; -} - -/* Closes the FD on success or failure. */ -static int -fbdev_frame_buffer_map(struct fbdev_output *output, int fd) -{ - struct fbdev_head *head; - int retval = -1; - - head = fbdev_output_get_head(output); - - 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->buffer_length = head->fb_info.buffer_length; - output->fb = mmap(NULL, output->buffer_length, - PROT_WRITE, MAP_SHARED, fd, 0); - if (output->fb == MAP_FAILED) { - weston_log("Failed to mmap frame buffer: %s\n", - strerror(errno)); - output->fb = NULL; - goto out_close; - } - - /* Create a pixman image to wrap the memory mapped frame buffer. */ - output->hw_surface = - pixman_image_create_bits(head->fb_info.pixel_format, - head->fb_info.x_resolution, - head->fb_info.y_resolution, - output->fb, - head->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) { - munmap(output->fb, output->buffer_length); - output->fb = NULL; - } - -out_close: - if (fd >= 0) - close(fd); - - return retval; -} - -static void -fbdev_frame_buffer_unmap(struct fbdev_output *output) -{ - if (!output->fb) { - assert(!output->hw_surface); - return; - } - - weston_log("Unmapping fbdev frame buffer.\n"); - - if (output->hw_surface) - pixman_image_unref(output->hw_surface); - output->hw_surface = NULL; - - if (munmap(output->fb, output->buffer_length) < 0) - weston_log("Failed to munmap frame buffer: %s\n", - strerror(errno)); - - output->fb = NULL; -} - - -static int -fbdev_output_attach_head(struct weston_output *output_base, - struct weston_head *head_base) -{ - struct fbdev_output *output = to_fbdev_output(output_base); - struct fbdev_head *head = to_fbdev_head(head_base); - - /* Clones not supported. */ - if (!wl_list_empty(&output->base.head_list)) - return -1; - - /* only one static mode in list */ - output->mode.flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; - output->mode.width = head->fb_info.x_resolution; - output->mode.height = head->fb_info.y_resolution; - output->mode.refresh = head->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; - - return 0; -} - -static void fbdev_output_destroy(struct weston_output *base); - -static int -fbdev_output_enable(struct weston_output *base) -{ - struct fbdev_output *output = to_fbdev_output(base); - struct fbdev_backend *backend = to_fbdev_backend(base->compositor); - struct fbdev_head *head; - int fb_fd; - struct wl_event_loop *loop; - const struct pixman_renderer_output_options options = { - .use_shadow = true, - }; - - head = fbdev_output_get_head(output); - - /* Create the frame buffer. */ - fb_fd = fbdev_frame_buffer_open(head->device, &head->fb_info); - if (fb_fd < 0) { - weston_log("Creating frame buffer failed.\n"); - return -1; - } - - if (fbdev_frame_buffer_map(output, fb_fd) < 0) { - weston_log("Mapping frame buffer failed.\n"); - return -1; - } - - output->base.start_repaint_loop = fbdev_output_start_repaint_loop; - output->base.repaint = fbdev_output_repaint; - - if (pixman_renderer_output_create(&output->base, &options) < 0) - goto out_hw_surface; - - loop = wl_display_get_event_loop(backend->compositor->wl_display); - output->finish_frame_timer = - wl_event_loop_add_timer(loop, finish_frame_handler, output); - - 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_hw_surface: - fbdev_frame_buffer_unmap(output); - - return -1; -} - -static int -fbdev_output_disable(struct weston_output *base) -{ - struct fbdev_output *output = to_fbdev_output(base); - - if (!base->enabled) - return 0; - - wl_event_source_remove(output->finish_frame_timer); - output->finish_frame_timer = NULL; - - pixman_renderer_output_destroy(&output->base); - fbdev_frame_buffer_unmap(output); - - return 0; -} - -static struct fbdev_head * -fbdev_head_create(struct fbdev_backend *backend, const char *device) -{ - struct fbdev_head *head; - int fb_fd; - - head = zalloc(sizeof *head); - if (!head) - return NULL; - - head->device = strdup(device); - - /* Create the frame buffer. */ - fb_fd = fbdev_frame_buffer_open(head->device, &head->fb_info); - if (fb_fd < 0) { - weston_log("Creating frame buffer head failed.\n"); - goto out_free; - } - close(fb_fd); - - weston_head_init(&head->base, "fbdev"); - weston_head_set_connection_status(&head->base, true); - weston_head_set_monitor_strings(&head->base, "unknown", - head->fb_info.id, NULL); - weston_head_set_subpixel(&head->base, WL_OUTPUT_SUBPIXEL_UNKNOWN); - weston_head_set_physical_size(&head->base, head->fb_info.width_mm, - head->fb_info.height_mm); - - weston_compositor_add_head(backend->compositor, &head->base); - - weston_log("Created head '%s' for device %s (%s)\n", - head->base.name, head->device, head->base.model); - - return head; - -out_free: - free(head->device); - free(head); - - return NULL; -} - -static void -fbdev_head_destroy(struct fbdev_head *head) -{ - weston_head_release(&head->base); - free(head->device); - free(head); -} - -static struct weston_output * -fbdev_output_create(struct weston_compositor *compositor, - const char *name) -{ - struct fbdev_output *output; - - weston_log("Creating fbdev output.\n"); - - output = zalloc(sizeof *output); - if (output == NULL) - return NULL; - - output->backend = to_fbdev_backend(compositor); - - weston_output_init(&output->base, compositor, name); - - output->base.destroy = fbdev_output_destroy; - output->base.disable = fbdev_output_disable; - output->base.enable = fbdev_output_enable; - output->base.attach_head = fbdev_output_attach_head; - - weston_compositor_add_pending_output(&output->base, compositor); - - return &output->base; -} - -static void -fbdev_output_destroy(struct weston_output *base) -{ - struct fbdev_output *output = to_fbdev_output(base); - - weston_log("Destroying fbdev output.\n"); - - fbdev_output_disable(base); - - /* Remove the output. */ - weston_output_release(&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_backend *backend, - struct weston_output *base) -{ - struct fbdev_output *output = to_fbdev_output(base); - struct fbdev_head *head; - struct fbdev_screeninfo new_screen_info; - int fb_fd; - - head = fbdev_output_get_head(output); - - weston_log("Re-enabling fbdev output.\n"); - assert(output->base.enabled); - - /* Create the frame buffer. */ - fb_fd = fbdev_frame_buffer_open(head->device, &new_screen_info); - if (fb_fd < 0) { - weston_log("Creating frame buffer failed.\n"); - return -1; - } - - /* Check whether the frame buffer details have changed since we were - * disabled. */ - if (compare_screen_info(&head->fb_info, &new_screen_info) != 0) { - /* Perform a mode-set to restore the old mode. */ - if (fbdev_set_screen_info(fb_fd, &head->fb_info) < 0) { - weston_log("Failed to restore mode settings. " - "Attempting to re-open output anyway.\n"); - } - - close(fb_fd); - - /* Disable and enable the output so that resources depending on - * the frame buffer X/Y resolution (such as the shadow buffer) - * are re-initialised. */ - fbdev_output_disable(&output->base); - return fbdev_output_enable(&output->base); - } - - /* Map the device if it has the same details as before. */ - if (fbdev_frame_buffer_map(output, fb_fd) < 0) { - weston_log("Mapping frame buffer failed.\n"); - return -1; - } - - return 0; -} - -static void -fbdev_backend_destroy(struct weston_compositor *base) -{ - struct fbdev_backend *backend = to_fbdev_backend(base); - struct weston_head *head, *next; - - udev_input_destroy(&backend->input); - - /* Destroy the output. */ - weston_compositor_shutdown(base); - - wl_list_for_each_safe(head, next, &base->head_list, compositor_link) - fbdev_head_destroy(to_fbdev_head(head)); - - /* Chain up. */ - weston_launcher_destroy(base->launcher); - - udev_unref(backend->udev); - - free(backend); -} - -static void -session_notify(struct wl_listener *listener, void *data) -{ - struct weston_compositor *compositor = data; - struct fbdev_backend *backend = to_fbdev_backend(compositor); - struct weston_output *output; - - if (compositor->session_active) { - weston_log("entering VT\n"); - compositor->state = backend->prev_state; - - wl_list_for_each(output, &compositor->output_list, link) { - fbdev_output_reenable(backend, output); - } - - weston_compositor_damage_all(compositor); - - udev_input_enable(&backend->input); - } else { - weston_log("leaving VT\n"); - udev_input_disable(&backend->input); - - wl_list_for_each(output, &compositor->output_list, link) { - fbdev_frame_buffer_unmap(to_fbdev_output(output)); - } - - backend->prev_state = compositor->state; - weston_compositor_offscreen(compositor); - - /* 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 attempts at repainting. When we switch - * back, we schedule a repaint, which will process - * pending frame callbacks. */ - - wl_list_for_each(output, - &compositor->output_list, link) { - output->repaint_needed = false; - } - } -} - -static char * -find_framebuffer_device(struct fbdev_backend *b, const char *seat) -{ - struct udev_enumerate *e; - struct udev_list_entry *entry; - const char *path, *device_seat, *id; - char *fb_device_path = NULL; - struct udev_device *device, *fb_device, *pci; - - e = udev_enumerate_new(b->udev); - udev_enumerate_add_match_subsystem(e, "graphics"); - udev_enumerate_add_match_sysname(e, "fb[0-9]*"); - - udev_enumerate_scan_devices(e); - fb_device = NULL; - udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { - bool is_boot_vga = false; - - path = udev_list_entry_get_name(entry); - device = udev_device_new_from_syspath(b->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")) - is_boot_vga = true; - } - - /* If a framebuffer device was found, and this device isn't - * the boot-VGA device, don't use it. */ - if (!is_boot_vga && fb_device) { - udev_device_unref(device); - continue; - } - - /* There can only be one boot_vga device. Try to use it - * at all costs. */ - if (is_boot_vga) { - if (fb_device) - udev_device_unref(fb_device); - fb_device = device; - break; - } - - /* Per the (!is_boot_vga && fb_device) test above, only - * trump existing saved devices with boot-VGA devices, so if - * the test ends up here, this must be the first device seen. */ - assert(!fb_device); - fb_device = device; - } - - udev_enumerate_unref(e); - - if (fb_device) { - fb_device_path = strdup(udev_device_get_devnode(fb_device)); - udev_device_unref(fb_device); - } - -// OHOS -// return fb_device_path; - weston_log("find_framebuffer_device %s", fb_device_path); - return strdup("/dev/graphics/fb0"); -} - -static struct fbdev_backend * -fbdev_backend_create(struct weston_compositor *compositor, - struct weston_fbdev_backend_config *param) -{ - struct fbdev_backend *backend; - const char *seat_id = default_seat; - const char *session_seat; - - session_seat = getenv("XDG_SEAT"); - if (session_seat) - seat_id = session_seat; - if (param->seat_id) - seat_id = param->seat_id; - - weston_log("initializing fbdev backend\n"); - - backend = zalloc(sizeof *backend); - if (backend == NULL) - return NULL; - - backend->compositor = compositor; - compositor->backend = &backend->base; - if (weston_compositor_set_presentation_clock_software( - compositor) < 0) - goto out_compositor; - - backend->udev = udev_new(); - if (backend->udev == NULL) { - weston_log("Failed to initialize udev context.\n"); - goto out_compositor; - } - - if (!param->device) - param->device = find_framebuffer_device(backend, seat_id); - if (!param->device) { - weston_log("fatal: no framebuffer devices detected.\n"); - goto out_udev; - } - - /* Set up the TTY. */ - backend->session_listener.notify = session_notify; - wl_signal_add(&compositor->session_signal, - &backend->session_listener); - compositor->launcher = - weston_launcher_connect(compositor, param->tty, seat_id, false); - if (!compositor->launcher) { - weston_log("fatal: fbdev backend should be run using " - "weston-launch binary, or your system should " - "provide the logind D-Bus API.\n"); - goto out_udev; - } - - backend->base.destroy = fbdev_backend_destroy; - backend->base.create_output = fbdev_output_create; - - backend->prev_state = WESTON_COMPOSITOR_ACTIVE; - - weston_setup_vt_switch_bindings(compositor); - - if (pixman_renderer_init(compositor) < 0) - goto out_launcher; - - if (!fbdev_head_create(backend, param->device)) - goto out_launcher; - - free(param->device); - - udev_input_init(&backend->input, compositor, backend->udev, - seat_id, param->configure_device); - - return backend; - -out_launcher: - free(param->device); - weston_launcher_destroy(compositor->launcher); - -out_udev: - udev_unref(backend->udev); - -out_compositor: - weston_compositor_shutdown(compositor); - free(backend); - - return NULL; -} - -static void -config_init_to_defaults(struct weston_fbdev_backend_config *config) -{ - config->tty = 0; /* default to current tty */ - config->device = NULL; - config->seat_id = NULL; -} - -WL_EXPORT int -weston_backend_init(struct weston_compositor *compositor, - struct weston_backend_config *config_base) -{ - struct fbdev_backend *b; - struct weston_fbdev_backend_config config = {{ 0, }}; - - if (config_base == NULL || - config_base->struct_version != WESTON_FBDEV_BACKEND_CONFIG_VERSION || - config_base->struct_size > sizeof(struct weston_fbdev_backend_config)) { - weston_log("fbdev backend config structure is invalid\n"); - return -1; - } - - config_init_to_defaults(&config); - memcpy(&config, config_base, config_base->struct_size); - - b = fbdev_backend_create(compositor, &config); - if (b == NULL) - return -1; - return 0; -} diff --git a/libweston/backend-fbdev/meson.build b/libweston/backend-fbdev/meson.build deleted file mode 100644 index e7b1544..0000000 --- a/libweston/backend-fbdev/meson.build +++ /dev/null @@ -1,30 +0,0 @@ -if not get_option('backend-fbdev') - subdir_done() -endif - -config_h.set('BUILD_FBDEV_COMPOSITOR', '1') - -srcs_fbdev = [ - 'fbdev.c', - presentation_time_server_protocol_h, -] - -deps_fbdev = [ - dep_libweston_private, - dep_session_helper, - dep_libinput_backend, - dependency('libudev', version: '>= 136'), -] - -plugin_fbdev = shared_library( - 'fbdev-backend', - srcs_fbdev, - include_directories: common_inc, - dependencies: deps_fbdev, - name_prefix: '', - install: true, - install_dir: dir_module_libweston -) -env_modmap += 'fbdev-backend.so=@0@;'.format(plugin_fbdev.full_path()) - -install_headers(backend_fbdev_h, subdir: dir_include_libweston_install) diff --git a/libweston/backend-headless/headless.c b/libweston/backend-headless/headless.c deleted file mode 100644 index c312a0f..0000000 --- a/libweston/backend-headless/headless.c +++ /dev/null @@ -1,521 +0,0 @@ -/* - * Copyright © 2010-2011 Benjamin Franzke - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include "shared/helpers.h" -#include "linux-explicit-synchronization.h" -#include "pixman-renderer.h" -#include "renderer-gl/gl-renderer.h" -#include "shared/weston-egl-ext.h" -#include "linux-dmabuf.h" -#include "presentation-time-server-protocol.h" -#include - -enum headless_renderer_type { - HEADLESS_NOOP, - HEADLESS_PIXMAN, - HEADLESS_GL, -}; - -struct headless_backend { - struct weston_backend base; - struct weston_compositor *compositor; - - struct weston_seat fake_seat; - enum headless_renderer_type renderer_type; - - struct gl_renderer_interface *glri; -}; - -struct headless_head { - struct weston_head base; -}; - -struct headless_output { - struct weston_output base; - - struct weston_mode mode; - struct wl_event_source *finish_frame_timer; - uint32_t *image_buf; - pixman_image_t *image; -}; - -static const uint32_t headless_formats[] = { - DRM_FORMAT_XRGB8888, - DRM_FORMAT_ARGB8888, -}; - -static inline struct headless_head * -to_headless_head(struct weston_head *base) -{ - return container_of(base, struct headless_head, base); -} - -static inline struct headless_output * -to_headless_output(struct weston_output *base) -{ - return container_of(base, struct headless_output, base); -} - -static inline struct headless_backend * -to_headless_backend(struct weston_compositor *base) -{ - return container_of(base->backend, struct headless_backend, base); -} - -static int -headless_output_start_repaint_loop(struct weston_output *output) -{ - struct timespec ts; - - weston_compositor_read_presentation_clock(output->compositor, &ts); - weston_output_finish_frame(output, &ts, WP_PRESENTATION_FEEDBACK_INVALID); - - return 0; -} - -static int -finish_frame_handler(void *data) -{ - struct headless_output *output = data; - struct timespec ts; - - weston_compositor_read_presentation_clock(output->base.compositor, &ts); - weston_output_finish_frame(&output->base, &ts, 0); - - return 1; -} - -static int -headless_output_repaint(struct weston_output *output_base, - pixman_region32_t *damage, - void *repaint_data) -{ - struct headless_output *output = to_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_disable_gl(struct headless_output *output) -{ - struct weston_compositor *compositor = output->base.compositor; - struct headless_backend *b = to_headless_backend(compositor); - - b->glri->output_destroy(&output->base); -} - -static void -headless_output_disable_pixman(struct headless_output *output) -{ - pixman_renderer_output_destroy(&output->base); - pixman_image_unref(output->image); - free(output->image_buf); -} - -static int -headless_output_disable(struct weston_output *base) -{ - struct headless_output *output = to_headless_output(base); - struct headless_backend *b = to_headless_backend(base->compositor); - - if (!output->base.enabled) - return 0; - - wl_event_source_remove(output->finish_frame_timer); - - switch (b->renderer_type) { - case HEADLESS_GL: - headless_output_disable_gl(output); - break; - case HEADLESS_PIXMAN: - headless_output_disable_pixman(output); - break; - case HEADLESS_NOOP: - break; - } - - return 0; -} - -static void -headless_output_destroy(struct weston_output *base) -{ - struct headless_output *output = to_headless_output(base); - - headless_output_disable(&output->base); - weston_output_release(&output->base); - - free(output); -} - -static int -headless_output_enable_gl(struct headless_output *output) -{ - struct weston_compositor *compositor = output->base.compositor; - struct headless_backend *b = to_headless_backend(compositor); - const struct gl_renderer_pbuffer_options options = { - .width = output->base.current_mode->width, - .height = output->base.current_mode->height, - .drm_formats = headless_formats, - .drm_formats_count = ARRAY_LENGTH(headless_formats), - }; - - if (b->glri->output_pbuffer_create(&output->base, &options) < 0) { - weston_log("failed to create gl renderer output state\n"); - return -1; - } - - return 0; -} - -static int -headless_output_enable_pixman(struct headless_output *output) -{ - const struct pixman_renderer_output_options options = { - .use_shadow = true, - }; - - output->image_buf = malloc(output->base.current_mode->width * - output->base.current_mode->height * 4); - if (!output->image_buf) - return -1; - - output->image = pixman_image_create_bits(PIXMAN_x8r8g8b8, - output->base.current_mode->width, - output->base.current_mode->height, - output->image_buf, - output->base.current_mode->width * 4); - - if (pixman_renderer_output_create(&output->base, &options) < 0) - goto err_renderer; - - pixman_renderer_output_set_buffer(&output->base, output->image); - - return 0; - -err_renderer: - pixman_image_unref(output->image); - free(output->image_buf); - - return -1; -} - -static int -headless_output_enable(struct weston_output *base) -{ - struct headless_output *output = to_headless_output(base); - struct headless_backend *b = to_headless_backend(base->compositor); - struct wl_event_loop *loop; - int ret = 0; - - loop = wl_display_get_event_loop(b->compositor->wl_display); - output->finish_frame_timer = - wl_event_loop_add_timer(loop, finish_frame_handler, output); - - switch (b->renderer_type) { - case HEADLESS_GL: - ret = headless_output_enable_gl(output); - break; - case HEADLESS_PIXMAN: - ret = headless_output_enable_pixman(output); - break; - case HEADLESS_NOOP: - break; - } - - if (ret < 0) { - wl_event_source_remove(output->finish_frame_timer); - return -1; - } - - return 0; -} - -static int -headless_output_set_size(struct weston_output *base, - int width, int height) -{ - struct headless_output *output = to_headless_output(base); - struct weston_head *head; - int output_width, output_height; - - /* We can only be called once. */ - assert(!output->base.current_mode); - - /* Make sure we have scale set. */ - assert(output->base.scale); - - wl_list_for_each(head, &output->base.head_list, output_link) { - weston_head_set_monitor_strings(head, "weston", "headless", - NULL); - - /* XXX: Calculate proper size. */ - weston_head_set_physical_size(head, width, height); - } - - output_width = width * output->base.scale; - output_height = height * output->base.scale; - - 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; - wl_list_insert(&output->base.mode_list, &output->mode.link); - - output->base.current_mode = &output->mode; - - output->base.start_repaint_loop = headless_output_start_repaint_loop; - output->base.repaint = headless_output_repaint; - output->base.assign_planes = NULL; - output->base.set_backlight = NULL; - output->base.set_dpms = NULL; - output->base.switch_mode = NULL; - - return 0; -} - -static struct weston_output * -headless_output_create(struct weston_compositor *compositor, const char *name) -{ - struct headless_output *output; - - /* name can't be NULL. */ - assert(name); - - output = zalloc(sizeof *output); - if (!output) - return NULL; - - weston_output_init(&output->base, compositor, name); - - output->base.destroy = headless_output_destroy; - output->base.disable = headless_output_disable; - output->base.enable = headless_output_enable; - output->base.attach_head = NULL; - - weston_compositor_add_pending_output(&output->base, compositor); - - return &output->base; -} - -static int -headless_head_create(struct weston_compositor *compositor, - const char *name) -{ - struct headless_head *head; - - /* name can't be NULL. */ - assert(name); - - head = zalloc(sizeof *head); - if (head == NULL) - return -1; - - weston_head_init(&head->base, name); - weston_head_set_connection_status(&head->base, true); - - /* Ideally all attributes of the head would be set here, so that the - * user has all the information when deciding to create outputs. - * We do not have those until set_size() time through. - */ - - weston_compositor_add_head(compositor, &head->base); - - return 0; -} - -static void -headless_head_destroy(struct headless_head *head) -{ - weston_head_release(&head->base); - free(head); -} - -static void -headless_destroy(struct weston_compositor *ec) -{ - struct headless_backend *b = to_headless_backend(ec); - struct weston_head *base, *next; - - weston_compositor_shutdown(ec); - - wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) - headless_head_destroy(to_headless_head(base)); - - free(b); -} - -static int -headless_gl_renderer_init(struct headless_backend *b) -{ - const struct gl_renderer_display_options options = { - .egl_platform = EGL_PLATFORM_SURFACELESS_MESA, - .egl_native_display = EGL_DEFAULT_DISPLAY, - .egl_surface_type = EGL_PBUFFER_BIT, - .drm_formats = headless_formats, - .drm_formats_count = ARRAY_LENGTH(headless_formats), - }; - - b->glri = weston_load_module("gl-renderer.so", "gl_renderer_interface"); - if (!b->glri) - return -1; - - return b->glri->display_create(b->compositor, &options); -} - -static const struct weston_windowed_output_api api = { - headless_output_set_size, - headless_head_create, -}; - -static struct headless_backend * -headless_backend_create(struct weston_compositor *compositor, - struct weston_headless_backend_config *config) -{ - struct headless_backend *b; - int ret; - - b = zalloc(sizeof *b); - if (b == NULL) - return NULL; - - b->compositor = compositor; - compositor->backend = &b->base; - - if (weston_compositor_set_presentation_clock_software(compositor) < 0) - goto err_free; - - b->base.destroy = headless_destroy; - b->base.create_output = headless_output_create; - - if (config->use_pixman && config->use_gl) { - weston_log("Error: cannot use both Pixman *and* GL renderers.\n"); - goto err_free; - } - - if (config->use_gl) - b->renderer_type = HEADLESS_GL; - else if (config->use_pixman) - b->renderer_type = HEADLESS_PIXMAN; - else - b->renderer_type = HEADLESS_NOOP; - - switch (b->renderer_type) { - case HEADLESS_GL: - ret = headless_gl_renderer_init(b); - break; - case HEADLESS_PIXMAN: - ret = pixman_renderer_init(compositor); - break; - case HEADLESS_NOOP: - ret = noop_renderer_init(compositor); - break; - default: - assert(0 && "invalid renderer type"); - ret = -1; - } - - if (ret < 0) - goto err_input; - - if (compositor->renderer->import_dmabuf) { - if (linux_dmabuf_setup(compositor) < 0) { - weston_log("Error: dmabuf protocol setup failed.\n"); - goto err_input; - } - } - - /* Support zwp_linux_explicit_synchronization_unstable_v1 to enable - * testing. */ - if (linux_explicit_synchronization_setup(compositor) < 0) - goto err_input; - - ret = weston_plugin_api_register(compositor, WESTON_WINDOWED_OUTPUT_API_NAME, - &api, sizeof(api)); - - if (ret < 0) { - weston_log("Failed to register output API.\n"); - goto err_input; - } - - return b; - -err_input: - weston_compositor_shutdown(compositor); -err_free: - free(b); - return NULL; -} - -static void -config_init_to_defaults(struct weston_headless_backend_config *config) -{ -} - -WL_EXPORT int -weston_backend_init(struct weston_compositor *compositor, - struct weston_backend_config *config_base) -{ - struct headless_backend *b; - struct weston_headless_backend_config config = {{ 0, }}; - - if (config_base == NULL || - config_base->struct_version != WESTON_HEADLESS_BACKEND_CONFIG_VERSION || - config_base->struct_size > sizeof(struct weston_headless_backend_config)) { - weston_log("headless backend config structure is invalid\n"); - return -1; - } - - config_init_to_defaults(&config); - memcpy(&config, config_base, config_base->struct_size); - - b = headless_backend_create(compositor, &config); - if (b == NULL) - return -1; - - return 0; -} diff --git a/libweston/backend-headless/meson.build b/libweston/backend-headless/meson.build deleted file mode 100644 index c603bb0..0000000 --- a/libweston/backend-headless/meson.build +++ /dev/null @@ -1,21 +0,0 @@ -if not get_option('backend-headless') - subdir_done() -endif - -config_h.set('BUILD_HEADLESS_COMPOSITOR', '1') - -srcs_headless = [ - 'headless.c', - presentation_time_server_protocol_h, -] -plugin_headless = shared_library( - 'headless-backend', - srcs_headless, - include_directories: common_inc, - dependencies: [ dep_libweston_private, dep_libdrm_headers ], - name_prefix: '', - install: true, - install_dir: dir_module_libweston, -) -env_modmap += 'headless-backend.so=@0@;'.format(plugin_headless.full_path()) -install_headers(backend_headless_h, subdir: dir_include_libweston_install) diff --git a/libweston/backend-rdp/meson.build b/libweston/backend-rdp/meson.build deleted file mode 100644 index e3b6025..0000000 --- a/libweston/backend-rdp/meson.build +++ /dev/null @@ -1,60 +0,0 @@ -if not get_option('backend-rdp') - subdir_done() -endif - -config_h.set('BUILD_RDP_COMPOSITOR', '1') - -dep_frdp = dependency('freerdp2', version: '>= 2.0.0', required: false) -if not dep_frdp.found() - error('RDP-backend requires freerdp2 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') -endif - -dep_wpr = dependency('winpr2', version: '>= 2.0.0', required: false) -if not dep_wpr.found() - error('RDP-backend requires winpr2 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') -endif - -if cc.has_header('freerdp/version.h', dependencies: dep_frdp) - config_h.set('HAVE_FREERDP_VERSION_H', '1') -endif - -if cc.has_member( - 'SURFACE_BITS_COMMAND', 'bmp', - dependencies : dep_frdp, - prefix : '#include ' -) - config_h.set('HAVE_SURFACE_BITS_BMP', '1') -endif - -if cc.has_type( - 'enum SURFCMD_CMDTYPE', - dependencies : dep_frdp, - prefix : '#include ' -) - config_h.set('HAVE_SURFCMD_CMDTYPE', '1') -endif - -if cc.has_function( - 'nsc_context_set_parameters', - dependencies : dep_frdp, - prefix: '#include ' -) - config_h.set('HAVE_NSC_CONTEXT_SET_PARAMETERS', '1') -endif - -deps_rdp = [ - dep_libweston_private, - dep_frdp, - dep_wpr, -] -plugin_rdp = shared_library( - 'rdp-backend', - 'rdp.c', - include_directories: common_inc, - dependencies: deps_rdp, - name_prefix: '', - install: true, - install_dir: dir_module_libweston -) -env_modmap += 'rdp-backend.so=@0@;'.format(plugin_rdp.full_path()) -install_headers(backend_rdp_h, subdir: dir_include_libweston_install) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c deleted file mode 100644 index 9e414aa..0000000 --- a/libweston/backend-rdp/rdp.c +++ /dev/null @@ -1,1515 +0,0 @@ -/* - * Copyright © 2013 Hardening - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#if HAVE_FREERDP_VERSION_H -#include -#else -/* assume it's a early 1.1 version */ -#define FREERDP_VERSION_MAJOR 1 -#define FREERDP_VERSION_MINOR 1 -#define FREERDP_VERSION_REVISION 0 -#endif - -#define FREERDP_VERSION_NUMBER ((FREERDP_VERSION_MAJOR * 0x10000) + \ - (FREERDP_VERSION_MINOR * 0x100) + FREERDP_VERSION_REVISION) - - -#if FREERDP_VERSION_NUMBER >= 0x10201 -#define HAVE_SKIP_COMPRESSION -#endif - -#if FREERDP_VERSION_NUMBER < 0x10202 -# define FREERDP_CB_RET_TYPE void -# define FREERDP_CB_RETURN(V) return -# define NSC_RESET(C, W, H) -# define RFX_RESET(C, W, H) do { rfx_context_reset(C); C->width = W; C->height = H; } while(0) -#else -#if FREERDP_VERSION_MAJOR >= 2 -# define NSC_RESET(C, W, H) nsc_context_reset(C, W, H) -# define RFX_RESET(C, W, H) rfx_context_reset(C, W, H) -#else -# define NSC_RESET(C, W, H) do { nsc_context_reset(C); C->width = W; C->height = H; } while(0) -# define RFX_RESET(C, W, H) do { rfx_context_reset(C); C->width = W; C->height = H; } while(0) -#endif -#define FREERDP_CB_RET_TYPE BOOL -#define FREERDP_CB_RETURN(V) return TRUE -#endif - -#ifdef HAVE_SURFACE_BITS_BMP -#define SURFACE_BPP(cmd) cmd.bmp.bpp -#define SURFACE_CODECID(cmd) cmd.bmp.codecID -#define SURFACE_WIDTH(cmd) cmd.bmp.width -#define SURFACE_HEIGHT(cmd) cmd.bmp.height -#define SURFACE_BITMAP_DATA(cmd) cmd.bmp.bitmapData -#define SURFACE_BITMAP_DATA_LEN(cmd) cmd.bmp.bitmapDataLength -#else -#define SURFACE_BPP(cmd) cmd.bpp -#define SURFACE_CODECID(cmd) cmd.codecID -#define SURFACE_WIDTH(cmd) cmd.width -#define SURFACE_HEIGHT(cmd) cmd.height -#define SURFACE_BITMAP_DATA(cmd) cmd.bitmapData -#define SURFACE_BITMAP_DATA_LEN(cmd) cmd.bitmapDataLength -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if FREERDP_VERSION_MAJOR >= 2 -#include -#endif - -#include "shared/helpers.h" -#include "shared/timespec-util.h" -#include -#include -#include "pixman-renderer.h" - -#define MAX_FREERDP_FDS 32 -#define DEFAULT_AXIS_STEP_DISTANCE 10 -#define RDP_MODE_FREQ 60 * 1000 - -#if FREERDP_VERSION_MAJOR >= 2 && defined(PIXEL_FORMAT_BGRA32) && !defined(PIXEL_FORMAT_B8G8R8A8) - /* The RDP API is truly wonderful: the pixel format definition changed - * from BGRA32 to B8G8R8A8, but some versions ship with a definition of - * PIXEL_FORMAT_BGRA32 which doesn't actually build. Try really, really, - * hard to find one which does. */ -# define DEFAULT_PIXEL_FORMAT PIXEL_FORMAT_BGRA32 -#else -# define DEFAULT_PIXEL_FORMAT RDP_PIXEL_FORMAT_B8G8R8A8 -#endif - -struct rdp_output; - -struct rdp_backend { - struct weston_backend base; - struct weston_compositor *compositor; - - 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; - int no_clients_resize; - int force_no_compression; -}; - -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_head { - struct weston_head base; -}; - -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_backend *rdpBackend; - 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 inline struct rdp_head * -to_rdp_head(struct weston_head *base) -{ - return container_of(base, struct rdp_head, base); -} - -static inline struct rdp_output * -to_rdp_output(struct weston_output *base) -{ - return container_of(base, struct rdp_output, base); -} - -static inline struct rdp_backend * -to_rdp_backend(struct weston_compositor *base) -{ - return container_of(base->backend, struct rdp_backend, base); -} - -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; - 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); - -#ifdef HAVE_SKIP_COMPRESSION - cmd.skipCompression = TRUE; -#else - memset(&cmd, 0, sizeof(*cmd)); -#endif -#ifdef HAVE_SURFCMD_CMDTYPE - cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS; -#endif - cmd.destLeft = damage->extents.x1; - cmd.destTop = damage->extents.y1; - cmd.destRight = damage->extents.x2; - cmd.destBottom = damage->extents.y2; - SURFACE_BPP(cmd) = 32; - SURFACE_CODECID(cmd) = peer->settings->RemoteFxCodecId; - SURFACE_WIDTH(cmd) = width; - SURFACE_HEIGHT(cmd) = 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) - ); - - SURFACE_BITMAP_DATA_LEN(cmd) = Stream_GetPosition(context->encode_stream); - SURFACE_BITMAP_DATA(cmd) = 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; - 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); - -#ifdef HAVE_SKIP_COMPRESSION - cmd.skipCompression = TRUE; -#else - memset(cmd, 0, sizeof(*cmd)); -#endif -#ifdef HAVE_SURFCMD_CMDTYPE - cmd.cmdType = CMDTYPE_SET_SURFACE_BITS; -#endif - cmd.destLeft = damage->extents.x1; - cmd.destTop = damage->extents.y1; - cmd.destRight = damage->extents.x2; - cmd.destBottom = damage->extents.y2; - SURFACE_BPP(cmd) = 32; - SURFACE_CODECID(cmd) = peer->settings->NSCodecId; - SURFACE_WIDTH(cmd) = width; - SURFACE_HEIGHT(cmd) = 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, - width, height, - pixman_image_get_stride(image)); - - SURFACE_BITMAP_DATA_LEN(cmd) = Stream_GetPosition(context->encode_stream); - SURFACE_BITMAP_DATA(cmd) = 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; - SURFACE_FRAME_MARKER 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); - - memset(&cmd, 0, sizeof(cmd)); -#ifdef HAVE_SURFCMD_CMDTYPE - cmd.cmdType = CMDTYPE_SET_SURFACE_BITS; -#endif - SURFACE_BPP(cmd) = 32; - SURFACE_CODECID(cmd) = 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; - SURFACE_WIDTH(cmd) = rect->x2 - rect->x1; - - heightIncrement = peer->settings->MultifragMaxRequestSize / (16 + SURFACE_WIDTH(cmd) * 4); - remainingHeight = rect->y2 - rect->y1; - top = rect->y1; - - subrect.x1 = rect->x1; - subrect.x2 = rect->x2; - - while (remainingHeight) { - SURFACE_HEIGHT(cmd) = (remainingHeight > heightIncrement) ? heightIncrement : remainingHeight; - cmd.destTop = top; - cmd.destBottom = top + SURFACE_HEIGHT(cmd); - SURFACE_BITMAP_DATA_LEN(cmd) = SURFACE_WIDTH(cmd) * SURFACE_HEIGHT(cmd) * 4; - SURFACE_BITMAP_DATA(cmd) = (BYTE *)realloc(SURFACE_BITMAP_DATA(cmd), SURFACE_BITMAP_DATA_LEN(cmd)); - - subrect.y1 = top; - subrect.y2 = top + SURFACE_HEIGHT(cmd); - pixman_image_flipped_subrect(&subrect, image, SURFACE_BITMAP_DATA(cmd)); - - /*weston_log("* sending (%d,%d, %d,%d)\n", subrect.x1, subrect.y1, subrect.x2, subrect.y2); */ - update->SurfaceBits(peer->context, &cmd); - - remainingHeight -= SURFACE_HEIGHT(cmd); - top += SURFACE_HEIGHT(cmd); - } - } - - free(SURFACE_BITMAP_DATA(cmd)); - - 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->rdpBackend->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 int -rdp_output_start_repaint_loop(struct weston_output *output) -{ - struct timespec ts; - - weston_compositor_read_presentation_clock(output->compositor, &ts); - weston_output_finish_frame(output, &ts, WP_PRESENTATION_FEEDBACK_INVALID); - - return 0; -} - -static int -rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage, - void *repaint_data) -{ - 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); - - if (pixman_region32_not_empty(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 int -finish_frame_handler(void *data) -{ - struct rdp_output *output = data; - struct timespec ts; - - weston_compositor_read_presentation_clock(output->base.compositor, &ts); - weston_output_finish_frame(&output->base, &ts, 0); - - return 1; -} - -static struct weston_mode * -rdp_insert_new_mode(struct weston_output *output, int width, int height, int rate) -{ - struct weston_mode *ret; - ret = zalloc(sizeof *ret); - if (!ret) - return NULL; - ret->width = width; - ret->height = height; - ret->refresh = rate; - wl_list_insert(&output->mode_list, &ret->link); - return ret; -} - -static struct weston_mode * -ensure_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 rdp_insert_new_mode(output, target->width, target->height, RDP_MODE_FREQ); -} - -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; - const struct pixman_renderer_output_options options = { }; - - local_mode = ensure_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, &options); - - 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->DesktopWidth == (UINT32)target_mode->width && - settings->DesktopHeight == (UINT32)target_mode->height) - continue; - - 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 -rdp_output_set_size(struct weston_output *base, - int width, int height) -{ - struct rdp_output *output = to_rdp_output(base); - struct weston_head *head; - struct weston_mode *currentMode; - struct weston_mode initMode; - - /* We can only be called once. */ - assert(!output->base.current_mode); - - wl_list_for_each(head, &output->base.head_list, output_link) { - weston_head_set_monitor_strings(head, "weston", "rdp", NULL); - - /* This is a virtual output, so report a zero physical size. - * It's better to let frontends/clients use their defaults. */ - weston_head_set_physical_size(head, 0, 0); - } - - wl_list_init(&output->peers); - - initMode.flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; - initMode.width = width; - initMode.height = height; - initMode.refresh = RDP_MODE_FREQ; - - currentMode = ensure_matching_mode(&output->base, &initMode); - if (!currentMode) - return -1; - - output->base.current_mode = output->base.native_mode = currentMode; - - output->base.start_repaint_loop = rdp_output_start_repaint_loop; - output->base.repaint = rdp_output_repaint; - output->base.assign_planes = NULL; - output->base.set_backlight = NULL; - output->base.set_dpms = NULL; - output->base.switch_mode = rdp_switch_mode; - - return 0; -} - -static int -rdp_output_enable(struct weston_output *base) -{ - struct rdp_output *output = to_rdp_output(base); - struct rdp_backend *b = to_rdp_backend(base->compositor); - struct wl_event_loop *loop; - const struct pixman_renderer_output_options options = { - .use_shadow = true, - }; - - output->shadow_surface = pixman_image_create_bits(PIXMAN_x8r8g8b8, - output->base.current_mode->width, - output->base.current_mode->height, - NULL, - output->base.current_mode->width * 4); - if (output->shadow_surface == NULL) { - weston_log("Failed to create surface for frame buffer.\n"); - return -1; - } - - if (pixman_renderer_output_create(&output->base, &options) < 0) { - pixman_image_unref(output->shadow_surface); - return -1; - } - - loop = wl_display_get_event_loop(b->compositor->wl_display); - output->finish_frame_timer = wl_event_loop_add_timer(loop, finish_frame_handler, output); - - b->output = output; - - return 0; -} - -static int -rdp_output_disable(struct weston_output *base) -{ - struct rdp_output *output = to_rdp_output(base); - struct rdp_backend *b = to_rdp_backend(base->compositor); - - if (!output->base.enabled) - return 0; - - pixman_image_unref(output->shadow_surface); - pixman_renderer_output_destroy(&output->base); - - wl_event_source_remove(output->finish_frame_timer); - b->output = NULL; - - return 0; -} - -static void -rdp_output_destroy(struct weston_output *base) -{ - struct rdp_output *output = to_rdp_output(base); - - rdp_output_disable(&output->base); - weston_output_release(&output->base); - - free(output); -} - -static struct weston_output * -rdp_output_create(struct weston_compositor *compositor, const char *name) -{ - struct rdp_output *output; - - output = zalloc(sizeof *output); - if (output == NULL) - return NULL; - - weston_output_init(&output->base, compositor, name); - - output->base.destroy = rdp_output_destroy; - output->base.disable = rdp_output_disable; - output->base.enable = rdp_output_enable; - output->base.attach_head = NULL; - - weston_compositor_add_pending_output(&output->base, compositor); - - return &output->base; -} - -static int -rdp_head_create(struct weston_compositor *compositor, const char *name) -{ - struct rdp_head *head; - - head = zalloc(sizeof *head); - if (!head) - return -1; - - weston_head_init(&head->base, name); - weston_head_set_connection_status(&head->base, true); - weston_compositor_add_head(compositor, &head->base); - - return 0; -} - -static void -rdp_head_destroy(struct rdp_head *head) -{ - weston_head_release(&head->base); - free(head); -} - -static void -rdp_destroy(struct weston_compositor *ec) -{ - struct rdp_backend *b = to_rdp_backend(ec); - struct weston_head *base, *next; - struct rdp_peers_item *rdp_peer, *tmp; - int i; - - wl_list_for_each_safe(rdp_peer, tmp, &b->output->peers, link) { - freerdp_peer* client = rdp_peer->peer; - - client->Disconnect(client); - freerdp_peer_context_free(client); - freerdp_peer_free(client); - } - - for (i = 0; i < MAX_FREERDP_FDS; i++) - if (b->listener_events[i]) - wl_event_source_remove(b->listener_events[i]); - - weston_compositor_shutdown(ec); - - wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) - rdp_head_destroy(to_rdp_head(base)); - - freerdp_listener_free(b->listener); - - free(b->server_cert); - free(b->server_key); - free(b->rdp_key); - free(b); -} - -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_backend *b, 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(b->compositor->wl_display); - for (i = 0; i < rcount; i++) { - fd = (int)(long)(rfds[i]); - b->listener_events[i] = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, - rdp_listener_activity, instance); - } - - for ( ; i < MAX_FREERDP_FDS; i++) - b->listener_events[i] = 0; - return 0; -} - - -static FREERDP_CB_RET_TYPE -rdp_peer_context_new(freerdp_peer* client, RdpPeerContext* context) -{ - context->item.peer = client; - context->item.flags = RDP_PEER_OUTPUT_ENABLED; - -#if FREERDP_VERSION_MAJOR == 1 && FREERDP_VERSION_MINOR == 1 - context->rfx_context = rfx_context_new(); -#else - context->rfx_context = rfx_context_new(TRUE); -#endif - if (!context->rfx_context) { - FREERDP_CB_RETURN(FALSE); - } - - 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, DEFAULT_PIXEL_FORMAT); - - context->nsc_context = nsc_context_new(); - if (!context->nsc_context) - goto out_error_nsc; - -#ifdef HAVE_NSC_CONTEXT_SET_PARAMETERS - nsc_context_set_parameters(context->nsc_context, NSC_COLOR_FORMAT, DEFAULT_PIXEL_FORMAT); -#else - nsc_context_set_pixel_format(context->nsc_context, DEFAULT_PIXEL_FORMAT); -#endif - context->encode_stream = Stream_New(NULL, 65536); - if (!context->encode_stream) - goto out_error_stream; - - FREERDP_CB_RETURN(TRUE); - -out_error_nsc: - rfx_context_free(context->rfx_context); -out_error_stream: - nsc_context_free(context->nsc_context); - FREERDP_CB_RETURN(FALSE); -} - -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_keyboard(context->item.seat); - weston_seat_release_pointer(context->item.seat); - /* XXX we should weston_seat_release(context->item.seat); here - * but it would crash on reconnect */ - } - - 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; - const char *xkbLayout; - const char *xkbVariant; -}; - -/* table reversed from - https://github.com/awakecoding/FreeRDP/blob/master/libfreerdp/locale/xkb_layout_ids.c#L811 */ -static const -struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { - {KBD_ARABIC_101, "ara", 0}, - {KBD_BULGARIAN, 0, 0}, - {KBD_CHINESE_TRADITIONAL_US, 0}, - {KBD_CZECH, "cz", 0}, - {KBD_CZECH_PROGRAMMERS, "cz", "bksl"}, - {KBD_CZECH_QWERTY, "cz", "qwerty"}, - {KBD_DANISH, "dk", 0}, - {KBD_GERMAN, "de", 0}, - {KBD_GERMAN_NEO, "de", "neo"}, - {KBD_GERMAN_IBM, "de", "qwerty"}, - {KBD_GREEK, "gr", 0}, - {KBD_GREEK_220, "gr", "simple"}, - {KBD_GREEK_319, "gr", "extended"}, - {KBD_GREEK_POLYTONIC, "gr", "polytonic"}, - {KBD_US, "us", 0}, - {KBD_US_ENGLISH_TABLE_FOR_IBM_ARABIC_238_L, "ara", "buckwalter"}, - {KBD_SPANISH, "es", 0}, - {KBD_SPANISH_VARIATION, "es", "nodeadkeys"}, - {KBD_FINNISH, "fi", 0}, - {KBD_FRENCH, "fr", 0}, - {KBD_HEBREW, "il", 0}, - {KBD_HUNGARIAN, "hu", 0}, - {KBD_HUNGARIAN_101_KEY, "hu", "standard"}, - {KBD_ICELANDIC, "is", 0}, - {KBD_ITALIAN, "it", 0}, - {KBD_ITALIAN_142, "it", "nodeadkeys"}, - {KBD_JAPANESE, "jp", 0}, - {KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002, "jp", "kana"}, - {KBD_KOREAN, "kr", 0}, - {KBD_KOREAN_INPUT_SYSTEM_IME_2000, "kr", "kr104"}, - {KBD_DUTCH, "nl", 0}, - {KBD_NORWEGIAN, "no", 0}, - {KBD_POLISH_PROGRAMMERS, "pl", 0}, - {KBD_POLISH_214, "pl", "qwertz"}, - {KBD_ROMANIAN, "ro", 0}, - {KBD_RUSSIAN, "ru", 0}, - {KBD_RUSSIAN_TYPEWRITER, "ru", "typewriter"}, - {KBD_CROATIAN, "hr", 0}, - {KBD_SLOVAK, "sk", 0}, - {KBD_SLOVAK_QWERTY, "sk", "qwerty"}, - {KBD_ALBANIAN, 0, 0}, - {KBD_SWEDISH, "se", 0}, - {KBD_THAI_KEDMANEE, "th", 0}, - {KBD_THAI_KEDMANEE_NON_SHIFTLOCK, "th", "tis"}, - {KBD_TURKISH_Q, "tr", 0}, - {KBD_TURKISH_F, "tr", "f"}, - {KBD_URDU, "in", "urd-phonetic3"}, - {KBD_UKRAINIAN, "ua", 0}, - {KBD_BELARUSIAN, "by", 0}, - {KBD_SLOVENIAN, "si", 0}, - {KBD_ESTONIAN, "ee", 0}, - {KBD_LATVIAN, "lv", 0}, - {KBD_LITHUANIAN_IBM, "lt", "ibm"}, - {KBD_FARSI, "af", 0}, - {KBD_VIETNAMESE, "vn", 0}, - {KBD_ARMENIAN_EASTERN, "am", 0}, - {KBD_AZERI_LATIN, 0, 0}, - {KBD_FYRO_MACEDONIAN, "mk", 0}, - {KBD_GEORGIAN, "ge", 0}, - {KBD_FAEROESE, 0, 0}, - {KBD_DEVANAGARI_INSCRIPT, 0, 0}, - {KBD_MALTESE_47_KEY, 0, 0}, - {KBD_NORWEGIAN_WITH_SAMI, "no", "smi"}, - {KBD_KAZAKH, "kz", 0}, - {KBD_KYRGYZ_CYRILLIC, "kg", "phonetic"}, - {KBD_TATAR, "ru", "tt"}, - {KBD_BENGALI, "bd", 0}, - {KBD_BENGALI_INSCRIPT, "bd", "probhat"}, - {KBD_PUNJABI, 0, 0}, - {KBD_GUJARATI, "in", "guj"}, - {KBD_TAMIL, "in", "tam"}, - {KBD_TELUGU, "in", "tel"}, - {KBD_KANNADA, "in", "kan"}, - {KBD_MALAYALAM, "in", "mal"}, - {KBD_HINDI_TRADITIONAL, "in", 0}, - {KBD_MARATHI, 0, 0}, - {KBD_MONGOLIAN_CYRILLIC, "mn", 0}, - {KBD_UNITED_KINGDOM_EXTENDED, "gb", "intl"}, - {KBD_SYRIAC, "syc", 0}, - {KBD_SYRIAC_PHONETIC, "syc", "syc_phonetic"}, - {KBD_NEPALI, "np", 0}, - {KBD_PASHTO, "af", "ps"}, - {KBD_DIVEHI_PHONETIC, 0, 0}, - {KBD_LUXEMBOURGISH, 0, 0}, - {KBD_MAORI, "mao", 0}, - {KBD_CHINESE_SIMPLIFIED_US, 0, 0}, - {KBD_SWISS_GERMAN, "ch", "de_nodeadkeys"}, - {KBD_UNITED_KINGDOM, "gb", 0}, - {KBD_LATIN_AMERICAN, "latam", 0}, - {KBD_BELGIAN_FRENCH, "be", 0}, - {KBD_BELGIAN_PERIOD, "be", "oss_sundeadkeys"}, - {KBD_PORTUGUESE, "pt", 0}, - {KBD_SERBIAN_LATIN, "rs", 0}, - {KBD_AZERI_CYRILLIC, "az", "cyrillic"}, - {KBD_SWEDISH_WITH_SAMI, "se", "smi"}, - {KBD_UZBEK_CYRILLIC, "af", "uz"}, - {KBD_INUKTITUT_LATIN, "ca", "ike"}, - {KBD_CANADIAN_FRENCH_LEGACY, "ca", "fr-legacy"}, - {KBD_SERBIAN_CYRILLIC, "rs", 0}, - {KBD_CANADIAN_FRENCH, "ca", "fr-legacy"}, - {KBD_SWISS_FRENCH, "ch", "fr"}, - {KBD_BOSNIAN, "ba", 0}, - {KBD_IRISH, 0, 0}, - {KBD_BOSNIAN_CYRILLIC, "ba", "us"}, - {KBD_UNITED_STATES_DVORAK, "us", "dvorak"}, - {KBD_PORTUGUESE_BRAZILIAN_ABNT2, "br", "nativo"}, - {KBD_CANADIAN_MULTILINGUAL_STANDARD, "ca", "multix"}, - {KBD_GAELIC, "ie", "CloGaelach"}, - - {0x00000000, 0, 0}, -}; - -/* taken from 2.2.7.1.6 Input Capability Set (TS_INPUT_CAPABILITYSET) */ -static const 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_activate(freerdp_peer* client) -{ - RdpPeerContext *peerCtx; - struct rdp_backend *b; - struct rdp_output *output; - rdpSettings *settings; - rdpPointerUpdate *pointer; - struct rdp_peers_item *peersItem; - struct xkb_rule_names xkbRuleNames; - struct xkb_keymap *keymap; - struct weston_output *weston_output; - int i; - pixman_box32_t box; - pixman_region32_t damage; - char seat_name[50]; - POINTER_SYSTEM_UPDATE pointer_system; - - peerCtx = (RdpPeerContext *)client->context; - b = peerCtx->rdpBackend; - peersItem = &peerCtx->item; - output = b->output; - settings = client->settings; - - if (!settings->SurfaceCommandsEnabled) { - weston_log("client doesn't support required SurfaceCommands\n"); - return FALSE; - } - - if (b->force_no_compression && settings->CompressionEnabled) { - weston_log("Forcing compression off\n"); - settings->CompressionEnabled = FALSE; - } - - if (output->base.width != (int)settings->DesktopWidth || - output->base.height != (int)settings->DesktopHeight) - { - if (b->no_clients_resize) { - /* RDP peers don't dictate their resolution to weston */ - if (!settings->DesktopResize) { - /* peer does not support desktop resize */ - weston_log("%s: client doesn't support resizing, closing connection\n", __FUNCTION__); - return FALSE; - } else { - settings->DesktopWidth = output->base.width; - settings->DesktopHeight = output->base.height; - client->update->DesktopResize(client->context); - } - } else { - /* ask weston to adjust size */ - struct weston_mode new_mode; - struct weston_mode *target_mode; - new_mode.width = (int)settings->DesktopWidth; - new_mode.height = (int)settings->DesktopHeight; - target_mode = ensure_matching_mode(&output->base, &new_mode); - if (!target_mode) { - weston_log("client mode not found\n"); - return FALSE; - } - weston_output_mode_set_native(&output->base, target_mode, 1); - output->base.width = new_mode.width; - output->base.height = new_mode.height; - } - } - - weston_output = &output->base; - RFX_RESET(peerCtx->rfx_context, weston_output->width, weston_output->height); - NSC_RESET(peerCtx->nsc_context, weston_output->width, weston_output->height); - - if (peersItem->flags & RDP_PEER_ACTIVATED) - return TRUE; - - /* when here it's the first reactivation, we need to setup a little more */ - weston_log("kbd_layout:0x%x kbd_type:0x%x kbd_subType:0x%x kbd_functionKeys:0x%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].rdpLayoutCode; i++) { - if (rdp_keyboards[i].rdpLayoutCode == settings->KeyboardLayout) { - xkbRuleNames.layout = rdp_keyboards[i].xkbLayout; - xkbRuleNames.variant = rdp_keyboards[i].xkbVariant; - weston_log("%s: matching layout=%s variant=%s\n", __FUNCTION__, - xkbRuleNames.layout, xkbRuleNames.variant); - break; - } - } - - keymap = NULL; - if (xkbRuleNames.layout) { - keymap = xkb_keymap_new_from_names(b->compositor->xkb_context, - &xkbRuleNames, 0); - } - - if (settings->ClientHostname) - snprintf(seat_name, sizeof(seat_name), "RDP %s", settings->ClientHostname); - else - snprintf(seat_name, sizeof(seat_name), "RDP peer @%s", settings->ClientAddress); - - peersItem->seat = zalloc(sizeof(*peersItem->seat)); - if (!peersItem->seat) { - xkb_keymap_unref(keymap); - weston_log("unable to create a weston_seat\n"); - return FALSE; - } - - weston_seat_init(peersItem->seat, b->compositor, seat_name); - weston_seat_init_keyboard(peersItem->seat, keymap); - xkb_keymap_unref(keymap); - weston_seat_init_pointer(peersItem->seat); - - peersItem->flags |= RDP_PEER_ACTIVATED; - - /* disable pointer on the client side */ - pointer = client->update->pointer; - pointer_system.type = SYSPTR_NULL; - pointer->PointerSystem(client->context, &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_post_connect(freerdp_peer *client) -{ - return TRUE; -} - -static FREERDP_CB_RET_TYPE -xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) -{ - RdpPeerContext *peerContext = (RdpPeerContext *)input->context; - struct rdp_output *output; - uint32_t button = 0; - bool need_frame = false; - struct timespec time; - - if (flags & PTR_FLAGS_MOVE) { - output = peerContext->rdpBackend->output; - if (x < output->base.width && y < output->base.height) { - weston_compositor_get_time(&time); - notify_motion_absolute(peerContext->item.seat, &time, - x, y); - need_frame = true; - } - } - - 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) { - weston_compositor_get_time(&time); - notify_button(peerContext->item.seat, &time, button, - (flags & PTR_FLAGS_DOWN) ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED - ); - need_frame = true; - } - - if (flags & PTR_FLAGS_WHEEL) { - struct weston_pointer_axis_event weston_event; - double value; - - /* 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". - * - * https://blogs.msdn.microsoft.com/oldnewthing/20130123-00/?p=5473 explains the 120 value - */ - value = -(flags & 0xff) / 120.0; - if (flags & PTR_FLAGS_WHEEL_NEGATIVE) - value = -value; - - weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; - weston_event.value = DEFAULT_AXIS_STEP_DISTANCE * value; - weston_event.discrete = (int)value; - weston_event.has_discrete = true; - - weston_compositor_get_time(&time); - - notify_axis(peerContext->item.seat, &time, &weston_event); - need_frame = true; - } - - if (need_frame) - notify_pointer_frame(peerContext->item.seat); - - FREERDP_CB_RETURN(TRUE); -} - -static FREERDP_CB_RET_TYPE -xf_extendedMouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) -{ - RdpPeerContext *peerContext = (RdpPeerContext *)input->context; - struct rdp_output *output; - struct timespec time; - - output = peerContext->rdpBackend->output; - if (x < output->base.width && y < output->base.height) { - weston_compositor_get_time(&time); - notify_motion_absolute(peerContext->item.seat, &time, x, y); - } - - FREERDP_CB_RETURN(TRUE); -} - - -static FREERDP_CB_RET_TYPE -xf_input_synchronize_event(rdpInput *input, UINT32 flags) -{ - freerdp_peer *client = input->context->peer; - RdpPeerContext *peerCtx = (RdpPeerContext *)input->context; - struct rdp_output *output = peerCtx->rdpBackend->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); - FREERDP_CB_RETURN(TRUE); -} - - -static FREERDP_CB_RET_TYPE -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; - struct timespec time; - - if (!(peerContext->item.flags & RDP_PEER_ACTIVATED)) - FREERDP_CB_RETURN(TRUE); - - 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 (flags & KBD_FLAGS_EXTENDED) - vk_code |= KBDEXT; - - scan_code = GetKeycodeFromVirtualKeyCode(vk_code, KEYCODE_TYPE_EVDEV); - - /*weston_log("code=%x ext=%d vk_code=%x scan_code=%x\n", code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, - vk_code, scan_code);*/ - weston_compositor_get_time(&time); - notify_key(peerContext->item.seat, &time, - scan_code - 8, keyState, STATE_UPDATE_AUTOMATIC); - } - - FREERDP_CB_RETURN(TRUE); -} - -static FREERDP_CB_RET_TYPE -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); - FREERDP_CB_RETURN(TRUE); -} - - -static FREERDP_CB_RET_TYPE -xf_suppress_output(rdpContext *context, BYTE allow, const RECTANGLE_16 *area) -{ - RdpPeerContext *peerContext = (RdpPeerContext *)context; - - if (allow) - peerContext->item.flags |= RDP_PEER_OUTPUT_ENABLED; - else - peerContext->item.flags &= (~RDP_PEER_OUTPUT_ENABLED); - - FREERDP_CB_RETURN(TRUE); -} - -static int -rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) -{ - int rcount = 0; - void *rfds[MAX_FREERDP_FDS]; - int i, fd; - struct wl_event_loop *loop; - rdpSettings *settings; - rdpInput *input; - RdpPeerContext *peerCtx; - - 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->rdpBackend = b; - - settings = client->settings; - /* configure security settings */ - if (b->rdp_key) - settings->RdpKeyFile = strdup(b->rdp_key); - if (b->tls_enabled) { - settings->CertificateFile = strdup(b->server_cert); - settings->PrivateKeyFile = strdup(b->server_key); - } else { - settings->TlsSecurity = FALSE; - } - settings->NlaSecurity = FALSE; - - if (!client->Initialize(client)) { - weston_log("peer initialization failed\n"); - goto error_initialize; - } - - settings->OsMajorType = OSMAJORTYPE_UNIX; - settings->OsMinorType = OSMINORTYPE_PSEUDO_XSERVER; - settings->ColorDepth = 32; - settings->RefreshRect = TRUE; - settings->RemoteFxCodec = TRUE; - settings->NSCodec = TRUE; - settings->FrameMarkerCommandEnabled = TRUE; - settings->SurfaceFrameMarkerEnabled = TRUE; - - client->Capabilities = xf_peer_capabilities; - client->PostConnect = xf_peer_post_connect; - client->Activate = xf_peer_activate; - - client->update->SuppressOutput = (pSuppressOutput)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 (!client->GetFileDescriptor(client, rfds, &rcount)) { - weston_log("unable to retrieve client fds\n"); - goto error_initialize; - } - - loop = wl_display_get_event_loop(b->compositor->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(&b->output->peers, &peerCtx->item.link); - return 0; - -error_initialize: - client->Close(client); - return -1; -} - - -static FREERDP_CB_RET_TYPE -rdp_incoming_peer(freerdp_listener *instance, freerdp_peer *client) -{ - struct rdp_backend *b = (struct rdp_backend *)instance->param4; - if (rdp_peer_init(client, b) < 0) { - weston_log("error when treating incoming peer\n"); - FREERDP_CB_RETURN(FALSE); - } - - FREERDP_CB_RETURN(TRUE); -} - -static const struct weston_rdp_output_api api = { - rdp_output_set_size, -}; - -static struct rdp_backend * -rdp_backend_create(struct weston_compositor *compositor, - struct weston_rdp_backend_config *config) -{ - struct rdp_backend *b; - char *fd_str; - char *fd_tail; - int fd, ret; - - b = zalloc(sizeof *b); - if (b == NULL) - return NULL; - - b->compositor = compositor; - b->base.destroy = rdp_destroy; - b->base.create_output = rdp_output_create; - b->rdp_key = config->rdp_key ? strdup(config->rdp_key) : NULL; - b->no_clients_resize = config->no_clients_resize; - b->force_no_compression = config->force_no_compression; - - compositor->backend = &b->base; - - /* activate TLS only if certificate/key are available */ - if (config->server_cert && config->server_key) { - weston_log("TLS support activated\n"); - b->server_cert = strdup(config->server_cert); - b->server_key = strdup(config->server_key); - if (!b->server_cert || !b->server_key) - goto err_free_strings; - b->tls_enabled = 1; - } - - if (weston_compositor_set_presentation_clock_software(compositor) < 0) - goto err_compositor; - - if (pixman_renderer_init(compositor) < 0) - goto err_compositor; - - if (rdp_head_create(compositor, "rdp") < 0) - goto err_compositor; - - compositor->capabilities |= WESTON_CAP_ARBITRARY_MODES; - - if (!config->env_socket) { - b->listener = freerdp_listener_new(); - b->listener->PeerAccepted = rdp_incoming_peer; - b->listener->param4 = b; - if (!b->listener->Open(b->listener, config->bind_address, config->port)) { - weston_log("unable to bind rdp socket\n"); - goto err_listener; - } - - if (rdp_implant_listener(b, b->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\n"); - goto err_output; - } - - fd = strtoul(fd_str, &fd_tail, 10); - if (errno != 0 || fd_tail == fd_str || *fd_tail != '\0' - || rdp_peer_init(freerdp_peer_new(fd), b)) - goto err_output; - } - - ret = weston_plugin_api_register(compositor, WESTON_RDP_OUTPUT_API_NAME, - &api, sizeof(api)); - - if (ret < 0) { - weston_log("Failed to register output API.\n"); - goto err_output; - } - - return b; - -err_listener: - freerdp_listener_free(b->listener); -err_output: - weston_output_release(&b->output->base); -err_compositor: - weston_compositor_shutdown(compositor); -err_free_strings: - free(b->rdp_key); - free(b->server_cert); - free(b->server_key); - free(b); - return NULL; -} - -static void -config_init_to_defaults(struct weston_rdp_backend_config *config) -{ - config->bind_address = NULL; - config->port = 3389; - config->rdp_key = NULL; - config->server_cert = NULL; - config->server_key = NULL; - config->env_socket = 0; - config->no_clients_resize = 0; - config->force_no_compression = 0; -} - -WL_EXPORT int -weston_backend_init(struct weston_compositor *compositor, - struct weston_backend_config *config_base) -{ - struct rdp_backend *b; - struct weston_rdp_backend_config config = {{ 0, }}; - int major, minor, revision; - -#if FREERDP_VERSION_MAJOR >= 2 - winpr_InitializeSSL(0); -#endif - freerdp_get_version(&major, &minor, &revision); - weston_log("using FreeRDP version %d.%d.%d\n", major, minor, revision); - - if (config_base == NULL || - config_base->struct_version != WESTON_RDP_BACKEND_CONFIG_VERSION || - config_base->struct_size > sizeof(struct weston_rdp_backend_config)) { - weston_log("RDP backend config structure is invalid\n"); - return -1; - } - - config_init_to_defaults(&config); - memcpy(&config, config_base, config_base->struct_size); - - if (!config.rdp_key && (!config.server_cert || !config.server_key)) { - weston_log("the RDP compositor requires keys and an optional certificate for RDP or TLS security (" - "--rdp4-key or --rdp-tls-cert/--rdp-tls-key)\n"); - return -1; - } - - b = rdp_backend_create(compositor, &config); - if (b == NULL) - return -1; - return 0; -} diff --git a/libweston/backend-wayland/meson.build b/libweston/backend-wayland/meson.build deleted file mode 100644 index 7e82513..0000000 --- a/libweston/backend-wayland/meson.build +++ /dev/null @@ -1,44 +0,0 @@ -if not get_option('backend-wayland') - subdir_done() -endif - -config_h.set('BUILD_WAYLAND_COMPOSITOR', '1') - -srcs_wlwl = [ - 'wayland.c', - fullscreen_shell_unstable_v1_client_protocol_h, - fullscreen_shell_unstable_v1_protocol_c, - presentation_time_protocol_c, - presentation_time_server_protocol_h, - xdg_shell_server_protocol_h, - xdg_shell_protocol_c, -] - -deps_wlwl = [ - dependency('wayland-client'), - dependency('wayland-cursor'), - dep_pixman, - dep_libweston_private, - dep_libdrm_headers, - dep_lib_cairo_shared, -] - -if get_option('renderer-gl') - d = dependency('wayland-egl', required: false) - if not d.found() - error('wayland-backend + gl-renderer requires wayland-egl which was not found. Or, you can use \'-Dbackend-wayland=false\' or \'-Drenderer-gl=false\'.') - endif - deps_wlwl += d -endif - -plugin_wlwl = shared_library( - 'wayland-backend', - srcs_wlwl, - include_directories: common_inc, - dependencies: deps_wlwl, - name_prefix: '', - install: true, - install_dir: dir_module_libweston -) -env_modmap += 'wayland-backend.so=@0@;'.format(plugin_wlwl.full_path()) -install_headers(backend_wayland_h, subdir: dir_include_libweston_install) diff --git a/libweston/backend-wayland/wayland.c b/libweston/backend-wayland/wayland.c deleted file mode 100644 index 60d42bf..0000000 --- a/libweston/backend-wayland/wayland.c +++ /dev/null @@ -1,2915 +0,0 @@ -/* - * Copyright © 2010-2011 Benjamin Franzke - * Copyright © 2013 Jason Ekstrand - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#ifdef ENABLE_EGL -#include -#endif - -#include -#include -#include "renderer-gl/gl-renderer.h" -#include "shared/weston-egl-ext.h" -#include "pixman-renderer.h" -#include "shared/helpers.h" -#include "shared/image-loader.h" -#include "shared/os-compatibility.h" -#include "shared/cairo-util.h" -#include "shared/timespec-util.h" -#include "fullscreen-shell-unstable-v1-client-protocol.h" -#include "xdg-shell-client-protocol.h" -#include "presentation-time-server-protocol.h" -#include "linux-dmabuf.h" -#include - -#define WINDOW_TITLE "Weston Compositor" - -static const uint32_t wayland_formats[] = { - DRM_FORMAT_ARGB8888, -}; - -struct wayland_backend { - struct weston_backend base; - struct weston_compositor *compositor; - - struct { - struct wl_display *wl_display; - struct wl_registry *registry; - struct wl_compositor *compositor; - struct wl_shell *shell; - struct xdg_wm_base *xdg_wm_base; - struct zwp_fullscreen_shell_v1 *fshell; - struct wl_shm *shm; - - struct wl_list output_list; - - struct wl_event_source *wl_source; - uint32_t event_mask; - } parent; - - bool use_pixman; - bool sprawl_across_outputs; - bool fullscreen; - - struct theme *theme; - cairo_device_t *frame_device; - struct wl_cursor_theme *cursor_theme; - struct wl_cursor *cursor; - - struct wl_list input_list; -}; - -struct wayland_output { - struct weston_output base; - - struct { - bool draw_initial_frame; - struct wl_surface *surface; - - struct wl_output *output; - uint32_t global_id; - - struct wl_shell_surface *shell_surface; - struct xdg_surface *xdg_surface; - struct xdg_toplevel *xdg_toplevel; - int configure_width, configure_height; - bool wait_for_configure; - } parent; - - int keyboard_count; - - char *title; - struct frame *frame; - - struct { - struct wl_egl_window *egl_window; - struct { - cairo_surface_t *top; - cairo_surface_t *left; - cairo_surface_t *right; - cairo_surface_t *bottom; - } border; - } gl; - - struct { - struct wl_list buffers; - struct wl_list free_buffers; - } shm; - - struct weston_mode mode; - - struct wl_callback *frame_cb; -}; - -struct wayland_parent_output { - struct wayland_backend *backend; /**< convenience */ - struct wayland_head *head; - struct wl_list link; - - struct wl_output *global; - uint32_t id; - - struct { - char *make; - char *model; - int32_t width, height; - uint32_t subpixel; - } physical; - - int32_t x, y; - uint32_t transform; - uint32_t scale; - - struct wl_callback *sync_cb; /**< wl_output < 2 done replacement */ - - struct wl_list mode_list; - struct weston_mode *preferred_mode; - struct weston_mode *current_mode; -}; - -struct wayland_head { - struct weston_head base; - struct wayland_parent_output *parent_output; -}; - -struct wayland_shm_buffer { - struct wayland_output *output; - struct wl_list link; - struct wl_list free_link; - - struct wl_buffer *buffer; - void *data; - size_t size; - int width; - int height; - pixman_region32_t damage; /**< in global coords */ - int frame_damaged; - - pixman_image_t *pm_image; - cairo_surface_t *c_surface; -}; - -struct wayland_input { - struct weston_seat base; - struct wayland_backend *backend; - struct wl_list link; - - struct { - struct wl_seat *seat; - struct wl_pointer *pointer; - struct wl_keyboard *keyboard; - struct wl_touch *touch; - - struct { - struct wl_surface *surface; - int32_t hx, hy; - } cursor; - } parent; - - struct weston_touch_device *touch_device; - - enum weston_key_state_update keyboard_state_update; - uint32_t key_serial; - uint32_t enter_serial; - uint32_t touch_points; - bool touch_active; - bool has_focus; - int seat_version; - - struct wayland_output *output; - struct wayland_output *touch_focus; - struct wayland_output *keyboard_focus; - - struct weston_pointer_axis_event vert, horiz; -}; - -struct gl_renderer_interface *gl_renderer; - -static inline struct wayland_head * -to_wayland_head(struct weston_head *base) -{ - return container_of(base, struct wayland_head, base); -} - -static inline struct wayland_output * -to_wayland_output(struct weston_output *base) -{ - return container_of(base, struct wayland_output, base); -} - -static inline struct wayland_backend * -to_wayland_backend(struct weston_compositor *base) -{ - return container_of(base->backend, struct wayland_backend, base); -} - -static void -wayland_shm_buffer_destroy(struct wayland_shm_buffer *buffer) -{ - cairo_surface_destroy(buffer->c_surface); - pixman_image_unref(buffer->pm_image); - - wl_buffer_destroy(buffer->buffer); - munmap(buffer->data, buffer->size); - - pixman_region32_fini(&buffer->damage); - - wl_list_remove(&buffer->link); - wl_list_remove(&buffer->free_link); - free(buffer); -} - -static void -buffer_release(void *data, struct wl_buffer *buffer) -{ - struct wayland_shm_buffer *sb = data; - - if (sb->output) { - wl_list_insert(&sb->output->shm.free_buffers, &sb->free_link); - } else { - wayland_shm_buffer_destroy(sb); - } -} - -static const struct wl_buffer_listener buffer_listener = { - buffer_release -}; - -static struct wayland_shm_buffer * -wayland_output_get_shm_buffer(struct wayland_output *output) -{ - struct wayland_backend *b = - to_wayland_backend(output->base.compositor); - struct wl_shm *shm = b->parent.shm; - struct wayland_shm_buffer *sb; - - struct wl_shm_pool *pool; - int width, height, stride; - int32_t fx, fy; - int fd; - unsigned char *data; - - if (!wl_list_empty(&output->shm.free_buffers)) { - sb = container_of(output->shm.free_buffers.next, - struct wayland_shm_buffer, free_link); - wl_list_remove(&sb->free_link); - wl_list_init(&sb->free_link); - - return sb; - } - - if (output->frame) { - width = frame_width(output->frame); - height = frame_height(output->frame); - } else { - width = output->base.current_mode->width; - height = output->base.current_mode->height; - } - - stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); - - fd = os_create_anonymous_file(height * stride); - if (fd < 0) { - weston_log("could not create an anonymous file buffer: %s\n", - strerror(errno)); - return NULL; - } - - data = mmap(NULL, height * stride, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (data == MAP_FAILED) { - weston_log("could not mmap %d memory for data: %s\n", height * stride, - strerror(errno)); - close(fd); - return NULL; - } - - sb = zalloc(sizeof *sb); - if (sb == NULL) { - weston_log("could not zalloc %zu memory for sb: %s\n", sizeof *sb, - strerror(errno)); - close(fd); - munmap(data, height * stride); - return NULL; - } - - sb->output = output; - wl_list_init(&sb->free_link); - wl_list_insert(&output->shm.buffers, &sb->link); - - pixman_region32_init(&sb->damage); - pixman_region32_copy(&sb->damage, &output->base.region); - sb->frame_damaged = 1; - - sb->data = data; - sb->width = width; - sb->height = height; - sb->size = height * stride; - - pool = wl_shm_create_pool(shm, fd, sb->size); - - sb->buffer = wl_shm_pool_create_buffer(pool, 0, - width, height, - stride, - WL_SHM_FORMAT_ARGB8888); - wl_buffer_add_listener(sb->buffer, &buffer_listener, sb); - wl_shm_pool_destroy(pool); - close(fd); - - memset(data, 0, sb->size); - - sb->c_surface = - cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, - width, height, stride); - - fx = 0; - fy = 0; - if (output->frame) - frame_interior(output->frame, &fx, &fy, 0, 0); - sb->pm_image = - pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, - (uint32_t *)(data + fy * stride) + fx, - stride); - - return sb; -} - -static void -frame_done(void *data, struct wl_callback *callback, uint32_t time) -{ - struct wayland_output *output = data; - struct timespec ts; - - assert(callback == output->frame_cb); - wl_callback_destroy(callback); - output->frame_cb = NULL; - - /* XXX: use the presentation extension for proper timings */ - - /* - * This is the fallback case, where Presentation extension is not - * available from the parent compositor. We do not know the base for - * 'time', so we cannot feed it to finish_frame(). Do the only thing - * we can, and pretend finish_frame time is when we process this - * event. - */ - weston_compositor_read_presentation_clock(output->base.compositor, &ts); - weston_output_finish_frame(&output->base, &ts, 0); -} - -static const struct wl_callback_listener frame_listener = { - frame_done -}; - -static void -draw_initial_frame(struct wayland_output *output) -{ - struct wayland_shm_buffer *sb; - - sb = wayland_output_get_shm_buffer(output); - - /* If we are rendering with GL, then orphan it so that it gets - * destroyed immediately */ - if (output->gl.egl_window) - sb->output = NULL; - - wl_surface_attach(output->parent.surface, sb->buffer, 0, 0); - wl_surface_damage(output->parent.surface, 0, 0, - sb->width, sb->height); -} - -#ifdef ENABLE_EGL -static void -wayland_output_update_gl_border(struct wayland_output *output) -{ - int32_t ix, iy, iwidth, iheight, fwidth, fheight; - cairo_t *cr; - - if (!output->frame) - return; - if (!(frame_status(output->frame) & FRAME_STATUS_REPAINT)) - return; - - fwidth = frame_width(output->frame); - fheight = frame_height(output->frame); - frame_interior(output->frame, &ix, &iy, &iwidth, &iheight); - - if (!output->gl.border.top) - output->gl.border.top = - cairo_image_surface_create(CAIRO_FORMAT_ARGB32, - fwidth, iy); - cr = cairo_create(output->gl.border.top); - frame_repaint(output->frame, cr); - cairo_destroy(cr); - gl_renderer->output_set_border(&output->base, GL_RENDERER_BORDER_TOP, - fwidth, iy, - cairo_image_surface_get_stride(output->gl.border.top) / 4, - cairo_image_surface_get_data(output->gl.border.top)); - - - if (!output->gl.border.left) - output->gl.border.left = - cairo_image_surface_create(CAIRO_FORMAT_ARGB32, - ix, 1); - cr = cairo_create(output->gl.border.left); - cairo_translate(cr, 0, -iy); - frame_repaint(output->frame, cr); - cairo_destroy(cr); - gl_renderer->output_set_border(&output->base, GL_RENDERER_BORDER_LEFT, - ix, 1, - cairo_image_surface_get_stride(output->gl.border.left) / 4, - cairo_image_surface_get_data(output->gl.border.left)); - - - if (!output->gl.border.right) - output->gl.border.right = - cairo_image_surface_create(CAIRO_FORMAT_ARGB32, - fwidth - (ix + iwidth), 1); - cr = cairo_create(output->gl.border.right); - cairo_translate(cr, -(iwidth + ix), -iy); - frame_repaint(output->frame, cr); - cairo_destroy(cr); - gl_renderer->output_set_border(&output->base, GL_RENDERER_BORDER_RIGHT, - fwidth - (ix + iwidth), 1, - cairo_image_surface_get_stride(output->gl.border.right) / 4, - cairo_image_surface_get_data(output->gl.border.right)); - - - if (!output->gl.border.bottom) - output->gl.border.bottom = - cairo_image_surface_create(CAIRO_FORMAT_ARGB32, - fwidth, fheight - (iy + iheight)); - cr = cairo_create(output->gl.border.bottom); - cairo_translate(cr, 0, -(iy + iheight)); - frame_repaint(output->frame, cr); - cairo_destroy(cr); - gl_renderer->output_set_border(&output->base, GL_RENDERER_BORDER_BOTTOM, - fwidth, fheight - (iy + iheight), - cairo_image_surface_get_stride(output->gl.border.bottom) / 4, - cairo_image_surface_get_data(output->gl.border.bottom)); -} -#endif - -static int -wayland_output_start_repaint_loop(struct weston_output *output_base) -{ - struct wayland_output *output = to_wayland_output(output_base); - struct wayland_backend *wb = - to_wayland_backend(output->base.compositor); - - /* 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 = false; - - draw_initial_frame(output); - } - - output->frame_cb = wl_surface_frame(output->parent.surface); - wl_callback_add_listener(output->frame_cb, &frame_listener, output); - wl_surface_commit(output->parent.surface); - wl_display_flush(wb->parent.wl_display); - - return 0; -} - -#ifdef ENABLE_EGL -static int -wayland_output_repaint_gl(struct weston_output *output_base, - pixman_region32_t *damage, - void *repaint_data) -{ - struct wayland_output *output = to_wayland_output(output_base); - struct weston_compositor *ec = output->base.compositor; - - output->frame_cb = wl_surface_frame(output->parent.surface); - wl_callback_add_listener(output->frame_cb, &frame_listener, output); - - wayland_output_update_gl_border(output); - - ec->renderer->repaint_output(&output->base, damage); - - pixman_region32_subtract(&ec->primary_plane.damage, - &ec->primary_plane.damage, damage); - return 0; -} -#endif - -static void -wayland_output_update_shm_border(struct wayland_shm_buffer *buffer) -{ - int32_t ix, iy, iwidth, iheight, fwidth, fheight; - cairo_t *cr; - - if (!buffer->output->frame || !buffer->frame_damaged) - return; - - cr = cairo_create(buffer->c_surface); - - frame_interior(buffer->output->frame, &ix, &iy, &iwidth, &iheight); - fwidth = frame_width(buffer->output->frame); - fheight = frame_height(buffer->output->frame); - - /* Set the clip so we don't unnecisaraly damage the surface */ - cairo_move_to(cr, ix, iy); - cairo_rel_line_to(cr, iwidth, 0); - cairo_rel_line_to(cr, 0, iheight); - cairo_rel_line_to(cr, -iwidth, 0); - cairo_line_to(cr, ix, iy); - cairo_line_to(cr, 0, iy); - cairo_line_to(cr, 0, fheight); - cairo_line_to(cr, fwidth, fheight); - cairo_line_to(cr, fwidth, 0); - cairo_line_to(cr, 0, 0); - cairo_line_to(cr, 0, iy); - cairo_close_path(cr); - cairo_clip(cr); - - /* Draw using a pattern so that the final result gets clipped */ - cairo_push_group(cr); - frame_repaint(buffer->output->frame, cr); - cairo_pop_group_to_source(cr); - cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); - cairo_paint(cr); - - cairo_destroy(cr); -} - -static void -wayland_shm_buffer_attach(struct wayland_shm_buffer *sb) -{ - pixman_region32_t damage; - pixman_box32_t *rects; - int32_t ix, iy, iwidth, iheight, fwidth, fheight; - int i, n; - - pixman_region32_init(&damage); - pixman_region32_copy(&damage, &sb->damage); - pixman_region32_translate(&damage, -sb->output->base.x, - -sb->output->base.y); - - weston_transformed_region(sb->output->base.width, - sb->output->base.height, - sb->output->base.transform, - sb->output->base.current_scale, - &damage, &damage); - - if (sb->output->frame) { - frame_interior(sb->output->frame, &ix, &iy, &iwidth, &iheight); - fwidth = frame_width(sb->output->frame); - fheight = frame_height(sb->output->frame); - - pixman_region32_translate(&damage, ix, iy); - - if (sb->frame_damaged) { - pixman_region32_union_rect(&damage, &damage, - 0, 0, fwidth, iy); - pixman_region32_union_rect(&damage, &damage, - 0, iy, ix, iheight); - pixman_region32_union_rect(&damage, &damage, - ix + iwidth, iy, - fwidth - (ix + iwidth), iheight); - pixman_region32_union_rect(&damage, &damage, - 0, iy + iheight, - fwidth, fheight - (iy + iheight)); - } - } - - rects = pixman_region32_rectangles(&damage, &n); - wl_surface_attach(sb->output->parent.surface, sb->buffer, 0, 0); - for (i = 0; i < n; ++i) - wl_surface_damage(sb->output->parent.surface, rects[i].x1, - rects[i].y1, rects[i].x2 - rects[i].x1, - rects[i].y2 - rects[i].y1); - - if (sb->output->frame) - pixman_region32_fini(&damage); -} - -static int -wayland_output_repaint_pixman(struct weston_output *output_base, - pixman_region32_t *damage, - void *repaint_data) -{ - struct wayland_output *output = to_wayland_output(output_base); - struct wayland_backend *b = - to_wayland_backend(output->base.compositor); - struct wayland_shm_buffer *sb; - - if (output->frame) { - if (frame_status(output->frame) & FRAME_STATUS_REPAINT) - wl_list_for_each(sb, &output->shm.buffers, link) - sb->frame_damaged = 1; - } - - wl_list_for_each(sb, &output->shm.buffers, link) - pixman_region32_union(&sb->damage, &sb->damage, damage); - - sb = wayland_output_get_shm_buffer(output); - - wayland_output_update_shm_border(sb); - pixman_renderer_output_set_buffer(output_base, sb->pm_image); - b->compositor->renderer->repaint_output(output_base, &sb->damage); - - wayland_shm_buffer_attach(sb); - - output->frame_cb = wl_surface_frame(output->parent.surface); - wl_callback_add_listener(output->frame_cb, &frame_listener, output); - wl_surface_commit(output->parent.surface); - wl_display_flush(b->parent.wl_display); - - pixman_region32_fini(&sb->damage); - pixman_region32_init(&sb->damage); - sb->frame_damaged = 0; - - pixman_region32_subtract(&b->compositor->primary_plane.damage, - &b->compositor->primary_plane.damage, damage); - return 0; -} - -static void -wayland_backend_destroy_output_surface(struct wayland_output *output) -{ - assert(output->parent.surface); - - if (output->parent.xdg_toplevel) { - xdg_toplevel_destroy(output->parent.xdg_toplevel); - output->parent.xdg_toplevel = NULL; - } - - if (output->parent.xdg_surface) { - xdg_surface_destroy(output->parent.xdg_surface); - output->parent.xdg_surface = NULL; - } - - if (output->parent.shell_surface) { - wl_shell_surface_destroy(output->parent.shell_surface); - output->parent.shell_surface = NULL; - } - - wl_surface_destroy(output->parent.surface); - output->parent.surface = NULL; -} - -static void -wayland_output_destroy_shm_buffers(struct wayland_output *output) -{ - struct wayland_shm_buffer *buffer, *next; - - /* Throw away any remaining SHM buffers */ - wl_list_for_each_safe(buffer, next, &output->shm.free_buffers, free_link) - wayland_shm_buffer_destroy(buffer); - /* These will get thrown away when they get released */ - wl_list_for_each(buffer, &output->shm.buffers, link) - buffer->output = NULL; -} - -static int -wayland_output_disable(struct weston_output *base) -{ - struct wayland_output *output = to_wayland_output(base); - struct wayland_backend *b = to_wayland_backend(base->compositor); - - if (!output->base.enabled) - return 0; - - if (b->use_pixman) { - pixman_renderer_output_destroy(&output->base); -#ifdef ENABLE_EGL - } else { - gl_renderer->output_destroy(&output->base); - wl_egl_window_destroy(output->gl.egl_window); -#endif - } - - wayland_output_destroy_shm_buffers(output); - - wayland_backend_destroy_output_surface(output); - - if (output->frame) - frame_destroy(output->frame); - - cairo_surface_destroy(output->gl.border.top); - cairo_surface_destroy(output->gl.border.left); - cairo_surface_destroy(output->gl.border.right); - cairo_surface_destroy(output->gl.border.bottom); - - return 0; -} - -static void -wayland_output_destroy(struct weston_output *base) -{ - struct wayland_output *output = to_wayland_output(base); - - wayland_output_disable(&output->base); - - weston_output_release(&output->base); - - if (output->frame_cb) - wl_callback_destroy(output->frame_cb); - - free(output->title); - free(output); -} - -static const struct wl_shell_surface_listener shell_surface_listener; - -#ifdef ENABLE_EGL -static int -wayland_output_init_gl_renderer(struct wayland_output *output) -{ - int32_t fwidth = 0, fheight = 0; - struct gl_renderer_output_options options = { - .drm_formats = wayland_formats, - .drm_formats_count = ARRAY_LENGTH(wayland_formats), - }; - - if (output->frame) { - fwidth = frame_width(output->frame); - fheight = frame_height(output->frame); - } else { - fwidth = output->base.current_mode->width; - fheight = output->base.current_mode->height; - } - - output->gl.egl_window = - wl_egl_window_create(output->parent.surface, - fwidth, fheight); - if (!output->gl.egl_window) { - weston_log("failure to create wl_egl_window\n"); - return -1; - } - options.window_for_legacy = output->gl.egl_window; - options.window_for_platform = output->gl.egl_window; - - if (gl_renderer->output_window_create(&output->base, &options) < 0) - goto cleanup_window; - - return 0; - -cleanup_window: - wl_egl_window_destroy(output->gl.egl_window); - return -1; -} -#endif - -static int -wayland_output_init_pixman_renderer(struct wayland_output *output) -{ - const struct pixman_renderer_output_options options = { - .use_shadow = true, - }; - return pixman_renderer_output_create(&output->base, &options); -} - -static void -wayland_output_resize_surface(struct wayland_output *output) -{ - struct wayland_backend *b = - to_wayland_backend(output->base.compositor); - int32_t ix, iy, iwidth, iheight; - int32_t width, height; - struct wl_region *region; - - width = output->base.current_mode->width; - height = output->base.current_mode->height; - - if (output->frame) { - frame_resize_inside(output->frame, width, height); - - frame_input_rect(output->frame, &ix, &iy, &iwidth, &iheight); - region = wl_compositor_create_region(b->parent.compositor); - wl_region_add(region, ix, iy, iwidth, iheight); - wl_surface_set_input_region(output->parent.surface, region); - wl_region_destroy(region); - - if (output->parent.xdg_surface) { - xdg_surface_set_window_geometry(output->parent.xdg_surface, - ix, - iy, - iwidth, - iheight); - } - - frame_opaque_rect(output->frame, &ix, &iy, &iwidth, &iheight); - region = wl_compositor_create_region(b->parent.compositor); - wl_region_add(region, ix, iy, iwidth, iheight); - wl_surface_set_opaque_region(output->parent.surface, region); - wl_region_destroy(region); - - width = frame_width(output->frame); - height = frame_height(output->frame); - } else { - region = wl_compositor_create_region(b->parent.compositor); - wl_region_add(region, 0, 0, width, height); - wl_surface_set_input_region(output->parent.surface, region); - wl_region_destroy(region); - - region = wl_compositor_create_region(b->parent.compositor); - wl_region_add(region, 0, 0, width, height); - wl_surface_set_opaque_region(output->parent.surface, region); - wl_region_destroy(region); - - if (output->parent.xdg_surface) { - xdg_surface_set_window_geometry(output->parent.xdg_surface, - 0, - 0, - width, - height); - } - } - -#ifdef ENABLE_EGL - if (output->gl.egl_window) { - wl_egl_window_resize(output->gl.egl_window, - width, height, 0, 0); - - /* These will need to be re-created due to the resize */ - gl_renderer->output_set_border(&output->base, - GL_RENDERER_BORDER_TOP, - 0, 0, 0, NULL); - cairo_surface_destroy(output->gl.border.top); - output->gl.border.top = NULL; - gl_renderer->output_set_border(&output->base, - GL_RENDERER_BORDER_LEFT, - 0, 0, 0, NULL); - cairo_surface_destroy(output->gl.border.left); - output->gl.border.left = NULL; - gl_renderer->output_set_border(&output->base, - GL_RENDERER_BORDER_RIGHT, - 0, 0, 0, NULL); - cairo_surface_destroy(output->gl.border.right); - output->gl.border.right = NULL; - gl_renderer->output_set_border(&output->base, - GL_RENDERER_BORDER_BOTTOM, - 0, 0, 0, NULL); - cairo_surface_destroy(output->gl.border.bottom); - output->gl.border.bottom = NULL; - } -#endif - - wayland_output_destroy_shm_buffers(output); -} - -static int -wayland_output_set_windowed(struct wayland_output *output) -{ - struct wayland_backend *b = - to_wayland_backend(output->base.compositor); - - if (output->frame) - return 0; - - if (!b->theme) { - b->theme = theme_create(); - if (!b->theme) - return -1; - } - output->frame = frame_create(b->theme, 100, 100, - FRAME_BUTTON_CLOSE, output->title, NULL); - if (!output->frame) - return -1; - - if (output->keyboard_count) - frame_set_flag(output->frame, FRAME_FLAG_ACTIVE); - - wayland_output_resize_surface(output); - - if (output->parent.xdg_toplevel) { - xdg_toplevel_unset_fullscreen(output->parent.xdg_toplevel); - } else if (output->parent.shell_surface) { - wl_shell_surface_set_toplevel(output->parent.shell_surface); - } else { - abort(); - } - - return 0; -} - -static void -wayland_output_set_fullscreen(struct wayland_output *output, - enum wl_shell_surface_fullscreen_method method, - uint32_t framerate, struct wl_output *target) -{ - if (output->frame) { - frame_destroy(output->frame); - output->frame = NULL; - } - - wayland_output_resize_surface(output); - - if (output->parent.xdg_toplevel) { - xdg_toplevel_set_fullscreen(output->parent.xdg_toplevel, target); - } else if (output->parent.shell_surface) { - wl_shell_surface_set_fullscreen(output->parent.shell_surface, - method, framerate, target); - } else { - abort(); - } -} - -static struct weston_mode * -wayland_output_choose_mode(struct wayland_output *output, - struct weston_mode *ref_mode) -{ - struct weston_mode *mode; - - /* First look for an exact match */ - wl_list_for_each(mode, &output->base.mode_list, link) - if (mode->width == ref_mode->width && - mode->height == ref_mode->height && - mode->refresh == ref_mode->refresh) - return mode; - - /* If we can't find an exact match, ignore refresh and try again */ - wl_list_for_each(mode, &output->base.mode_list, link) - if (mode->width == ref_mode->width && - mode->height == ref_mode->height) - return mode; - - /* Yeah, we failed */ - return NULL; -} - -enum mode_status { - MODE_STATUS_UNKNOWN, - MODE_STATUS_SUCCESS, - MODE_STATUS_FAIL, - MODE_STATUS_CANCEL, -}; - -static void -mode_feedback_successful(void *data, - struct zwp_fullscreen_shell_mode_feedback_v1 *fb) -{ - enum mode_status *value = data; - - printf("Mode switch successful\n"); - - *value = MODE_STATUS_SUCCESS; -} - -static void -mode_feedback_failed(void *data, struct zwp_fullscreen_shell_mode_feedback_v1 *fb) -{ - enum mode_status *value = data; - - printf("Mode switch failed\n"); - - *value = MODE_STATUS_FAIL; -} - -static void -mode_feedback_cancelled(void *data, struct zwp_fullscreen_shell_mode_feedback_v1 *fb) -{ - enum mode_status *value = data; - - printf("Mode switch cancelled\n"); - - *value = MODE_STATUS_CANCEL; -} - -struct zwp_fullscreen_shell_mode_feedback_v1_listener mode_feedback_listener = { - mode_feedback_successful, - mode_feedback_failed, - mode_feedback_cancelled, -}; - -static enum mode_status -wayland_output_fullscreen_shell_mode_feedback(struct wayland_output *output, - struct weston_mode *mode) -{ - struct wayland_backend *b = to_wayland_backend(output->base.compositor); - struct zwp_fullscreen_shell_mode_feedback_v1 *mode_feedback; - enum mode_status mode_status; - int ret = 0; - - mode_feedback = - zwp_fullscreen_shell_v1_present_surface_for_mode(b->parent.fshell, - output->parent.surface, - output->parent.output, - mode->refresh); - - zwp_fullscreen_shell_mode_feedback_v1_add_listener(mode_feedback, - &mode_feedback_listener, - &mode_status); - - output->parent.draw_initial_frame = false; - draw_initial_frame(output); - wl_surface_commit(output->parent.surface); - - mode_status = MODE_STATUS_UNKNOWN; - while (mode_status == MODE_STATUS_UNKNOWN && ret >= 0) - ret = wl_display_dispatch(b->parent.wl_display); - - zwp_fullscreen_shell_mode_feedback_v1_destroy(mode_feedback); - - return mode_status; -} - -static int -wayland_output_switch_mode(struct weston_output *output_base, - struct weston_mode *mode) -{ - struct wayland_output *output = to_wayland_output(output_base); - struct wayland_backend *b; - struct wl_surface *old_surface; - struct weston_mode *old_mode; - enum mode_status mode_status; - - if (output_base == NULL) { - weston_log("output is NULL.\n"); - return -1; - } - - if (mode == NULL) { - weston_log("mode is NULL.\n"); - return -1; - } - - b = to_wayland_backend(output_base->compositor); - - if (output->parent.xdg_surface || output->parent.shell_surface || !b->parent.fshell) - return -1; - - mode = wayland_output_choose_mode(output, mode); - if (mode == NULL) - return -1; - - if (output->base.current_mode == mode) - return 0; - - old_mode = output->base.current_mode; - old_surface = output->parent.surface; - output->base.current_mode = mode; - output->parent.surface = - wl_compositor_create_surface(b->parent.compositor); - wl_surface_set_user_data(output->parent.surface, output); - - /* Blow the old buffers because we changed size/surfaces */ - wayland_output_resize_surface(output); - - mode_status = wayland_output_fullscreen_shell_mode_feedback(output, mode); - - /* This should kick-start things again */ - wayland_output_start_repaint_loop(&output->base); - - if (mode_status == MODE_STATUS_FAIL) { - output->base.current_mode = old_mode; - wl_surface_destroy(output->parent.surface); - output->parent.surface = old_surface; - wayland_output_resize_surface(output); - - return -1; - } - - old_mode->flags &= ~WL_OUTPUT_MODE_CURRENT; - output->base.current_mode->flags |= WL_OUTPUT_MODE_CURRENT; - - if (b->use_pixman) { - pixman_renderer_output_destroy(output_base); - if (wayland_output_init_pixman_renderer(output) < 0) - goto err_output; -#ifdef ENABLE_EGL - } else { - gl_renderer->output_destroy(output_base); - wl_egl_window_destroy(output->gl.egl_window); - if (wayland_output_init_gl_renderer(output) < 0) - goto err_output; -#endif - } - wl_surface_destroy(old_surface); - - weston_output_schedule_repaint(&output->base); - - return 0; - -err_output: - /* XXX */ - return -1; -} - -static void -handle_xdg_surface_configure(void *data, struct xdg_surface *surface, - uint32_t serial) -{ - xdg_surface_ack_configure(surface, serial); -} - -static const struct xdg_surface_listener xdg_surface_listener = { - handle_xdg_surface_configure -}; - -static void -handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *toplevel, - int32_t width, int32_t height, - struct wl_array *states) -{ - struct wayland_output *output = data; - - output->parent.configure_width = width; - output->parent.configure_height = height; - - output->parent.wait_for_configure = false; - /* FIXME: implement resizing */ -} - -static void -handle_xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) -{ - struct wayland_output *output = data; - struct weston_compositor *compositor = output->base.compositor; - - wayland_output_destroy(&output->base); - - if (wl_list_empty(&compositor->output_list)) - weston_compositor_exit(compositor); -} - -static const struct xdg_toplevel_listener xdg_toplevel_listener = { - handle_xdg_toplevel_configure, - handle_xdg_toplevel_close, -}; - -static int -wayland_backend_create_output_surface(struct wayland_output *output) -{ - struct wayland_backend *b = to_wayland_backend(output->base.compositor); - - assert(!output->parent.surface); - - output->parent.surface = - wl_compositor_create_surface(b->parent.compositor); - if (!output->parent.surface) - return -1; - - wl_surface_set_user_data(output->parent.surface, output); - - output->parent.draw_initial_frame = true; - - if (b->parent.xdg_wm_base) { - output->parent.xdg_surface = - xdg_wm_base_get_xdg_surface(b->parent.xdg_wm_base, - output->parent.surface); - xdg_surface_add_listener(output->parent.xdg_surface, - &xdg_surface_listener, output); - - output->parent.xdg_toplevel = - xdg_surface_get_toplevel(output->parent.xdg_surface); - xdg_toplevel_add_listener(output->parent.xdg_toplevel, - &xdg_toplevel_listener, output); - - xdg_toplevel_set_title(output->parent.xdg_toplevel, output->title); - - wl_surface_commit(output->parent.surface); - - output->parent.wait_for_configure = true; - - while (output->parent.wait_for_configure) - wl_display_dispatch(b->parent.wl_display); - - weston_log("wayland-backend: Using xdg_wm_base\n"); - } - else if (b->parent.shell) { - output->parent.shell_surface = - wl_shell_get_shell_surface(b->parent.shell, - output->parent.surface); - if (!output->parent.shell_surface) { - wl_surface_destroy(output->parent.surface); - return -1; - } - - wl_shell_surface_add_listener(output->parent.shell_surface, - &shell_surface_listener, output); - - weston_log("wayland-backend: Using wl_shell\n"); - } - - return 0; -} - -static int -wayland_output_enable(struct weston_output *base) -{ - struct wayland_output *output = to_wayland_output(base); - struct wayland_backend *b = to_wayland_backend(base->compositor); - enum mode_status mode_status; - int ret = 0; - - weston_log("Creating %dx%d wayland output at (%d, %d)\n", - output->base.current_mode->width, - output->base.current_mode->height, - output->base.x, output->base.y); - - if (!output->parent.surface) - ret = wayland_backend_create_output_surface(output); - - if (ret < 0) - return -1; - - wl_list_init(&output->shm.buffers); - wl_list_init(&output->shm.free_buffers); - - if (b->use_pixman) { - if (wayland_output_init_pixman_renderer(output) < 0) - goto err_output; - - output->base.repaint = wayland_output_repaint_pixman; -#ifdef ENABLE_EGL - } else { - if (wayland_output_init_gl_renderer(output) < 0) - goto err_output; - - output->base.repaint = wayland_output_repaint_gl; -#endif - } - - output->base.start_repaint_loop = wayland_output_start_repaint_loop; - output->base.assign_planes = NULL; - output->base.set_backlight = NULL; - output->base.set_dpms = NULL; - output->base.switch_mode = wayland_output_switch_mode; - - if (b->sprawl_across_outputs) { - if (b->parent.fshell) { - wayland_output_resize_surface(output); - - mode_status = wayland_output_fullscreen_shell_mode_feedback(output, &output->mode); - - if (mode_status == MODE_STATUS_FAIL) { - zwp_fullscreen_shell_v1_present_surface(b->parent.fshell, - output->parent.surface, - ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_CENTER, - output->parent.output); - - output->parent.draw_initial_frame = true; - } - } else { - wayland_output_set_fullscreen(output, - WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER, - output->mode.refresh, output->parent.output); - } - } else if (b->fullscreen) { - wayland_output_set_fullscreen(output, 0, 0, NULL); - } else { - wayland_output_set_windowed(output); - } - - return 0; - -err_output: - wayland_backend_destroy_output_surface(output); - - return -1; -} - -static int -wayland_output_setup_for_parent_output(struct wayland_output *output, - struct wayland_parent_output *poutput); - -static int -wayland_output_setup_fullscreen(struct wayland_output *output, - struct wayland_head *head); - -static int -wayland_output_attach_head(struct weston_output *output_base, - struct weston_head *head_base) -{ - struct wayland_backend *b = to_wayland_backend(output_base->compositor); - struct wayland_output *output = to_wayland_output(output_base); - struct wayland_head *head = to_wayland_head(head_base); - - if (!wl_list_empty(&output->base.head_list)) - return -1; - - if (head->parent_output) { - if (wayland_output_setup_for_parent_output(output, - head->parent_output) < 0) - return -1; - } else if (b->fullscreen) { - if (wayland_output_setup_fullscreen(output, head) < 0) - return -1; - } else { - /* A floating window, nothing to do. */ - } - - return 0; -} - -static void -wayland_output_detach_head(struct weston_output *output_base, - struct weston_head *head) -{ - struct wayland_output *output = to_wayland_output(output_base); - - /* Rely on the disable hook if the output was enabled. We do not - * support cloned heads, so detaching is guaranteed to disable the - * output. - */ - if (output->base.enabled) - return; - - /* undo setup fullscreen */ - if (output->parent.surface) - wayland_backend_destroy_output_surface(output); -} - -static struct weston_output * -wayland_output_create(struct weston_compositor *compositor, const char *name) -{ - struct wayland_output *output; - char *title; - - /* name can't be NULL. */ - assert(name); - - output = zalloc(sizeof *output); - if (output == NULL) { - perror("zalloc"); - return NULL; - } - - if (asprintf(&title, "%s - %s", WINDOW_TITLE, name) < 0) { - free(output); - return NULL; - } - output->title = title; - - weston_output_init(&output->base, compositor, name); - - output->base.destroy = wayland_output_destroy; - output->base.disable = wayland_output_disable; - output->base.enable = wayland_output_enable; - output->base.attach_head = wayland_output_attach_head; - output->base.detach_head = wayland_output_detach_head; - - weston_compositor_add_pending_output(&output->base, compositor); - - return &output->base; -} - -static struct wayland_head * -wayland_head_create(struct weston_compositor *compositor, const char *name) -{ - struct wayland_head *head; - - assert(name); - - head = zalloc(sizeof *head); - if (!head) - return NULL; - - weston_head_init(&head->base, name); - weston_head_set_connection_status(&head->base, true); - weston_compositor_add_head(compositor, &head->base); - - return head; -} - -static int -wayland_head_create_windowed(struct weston_compositor *compositor, - const char *name) -{ - if (!wayland_head_create(compositor, name)) - return -1; - - return 0; -} - -static int -wayland_head_create_for_parent_output(struct weston_compositor *compositor, - struct wayland_parent_output *poutput) -{ - struct wayland_head *head; - char name[100]; - int ret; - - ret = snprintf(name, sizeof(name), "wlparent-%d", poutput->id); - if (ret < 1 || (unsigned)ret >= sizeof(name)) - return -1; - - head = wayland_head_create(compositor, name); - if (!head) - return -1; - - assert(!poutput->head); - head->parent_output = poutput; - poutput->head = head; - - weston_head_set_monitor_strings(&head->base, - poutput->physical.make, - poutput->physical.model, NULL); - weston_head_set_physical_size(&head->base, - poutput->physical.width, - poutput->physical.height); - - return 0; -} - -static void -wayland_head_destroy(struct wayland_head *head) -{ - if (head->parent_output) - head->parent_output->head = NULL; - - weston_head_release(&head->base); - free(head); -} - -static int -wayland_output_set_size(struct weston_output *base, int width, int height) -{ - struct wayland_output *output = to_wayland_output(base); - struct weston_head *head; - int output_width, output_height; - - /* We can only be called once. */ - assert(!output->base.current_mode); - - /* Make sure we have scale set. */ - assert(output->base.scale); - - if (width < 1) { - weston_log("Invalid width \"%d\" for output %s\n", - width, output->base.name); - return -1; - } - - if (height < 1) { - weston_log("Invalid height \"%d\" for output %s\n", - height, output->base.name); - return -1; - } - - wl_list_for_each(head, &output->base.head_list, output_link) { - weston_head_set_monitor_strings(head, "wayland", "none", NULL); - - /* XXX: Calculate proper size. */ - weston_head_set_physical_size(head, width, height); - } - - output_width = width * output->base.scale; - output_height = height * output->base.scale; - - 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; - wl_list_insert(&output->base.mode_list, &output->mode.link); - - output->base.current_mode = &output->mode; - - return 0; -} - -static int -wayland_output_setup_for_parent_output(struct wayland_output *output, - struct wayland_parent_output *poutput) -{ - struct weston_mode *mode; - - if (poutput->current_mode) { - mode = poutput->current_mode; - } else if (poutput->preferred_mode) { - mode = poutput->preferred_mode; - } else if (!wl_list_empty(&poutput->mode_list)) { - mode = container_of(poutput->mode_list.next, - struct weston_mode, link); - } else { - weston_log("No valid modes found. Skipping output.\n"); - return -1; - } - - output->base.scale = 1; - output->base.transform = WL_OUTPUT_TRANSFORM_NORMAL; - - output->parent.output = poutput->global; - - wl_list_insert_list(&output->base.mode_list, &poutput->mode_list); - wl_list_init(&poutput->mode_list); - - /* No other mode should have CURRENT already. */ - mode->flags |= WL_OUTPUT_MODE_CURRENT; - output->base.current_mode = mode; - - /* output->mode is unused in this path. */ - - return 0; -} - -static int -wayland_output_setup_fullscreen(struct wayland_output *output, - struct wayland_head *head) -{ - struct wayland_backend *b = to_wayland_backend(output->base.compositor); - int width = 0, height = 0; - - output->base.scale = 1; - output->base.transform = WL_OUTPUT_TRANSFORM_NORMAL; - - if (wayland_backend_create_output_surface(output) < 0) - return -1; - - /* What should size be set if conditional is false? */ - if (b->parent.xdg_wm_base || b->parent.shell) { - if (output->parent.xdg_toplevel) - xdg_toplevel_set_fullscreen(output->parent.xdg_toplevel, - output->parent.output); - else if (output->parent.shell_surface) - wl_shell_surface_set_fullscreen(output->parent.shell_surface, - 0, 0, NULL); - - wl_display_roundtrip(b->parent.wl_display); - - width = output->parent.configure_width; - height = output->parent.configure_height; - } - - if (wayland_output_set_size(&output->base, width, height) < 0) - goto err_set_size; - - /* The head is not attached yet, so set_size did not set these. */ - weston_head_set_monitor_strings(&head->base, "wayland", "none", NULL); - /* XXX: Calculate proper size. */ - weston_head_set_physical_size(&head->base, width, height); - - return 0; - -err_set_size: - wayland_backend_destroy_output_surface(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) -{ - struct wayland_output *output = data; - - output->parent.configure_width = width; - output->parent.configure_height = 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 input interface */ -static void -input_set_cursor(struct wayland_input *input) -{ - - struct wl_buffer *buffer; - struct wl_cursor_image *image; - - if (!input->backend->cursor) - return; /* Couldn't load the cursor. Can't set it */ - - image = input->backend->cursor->images[0]; - buffer = wl_cursor_image_get_buffer(image); - if (!buffer) - return; - - wl_pointer_set_cursor(input->parent.pointer, input->enter_serial, - input->parent.cursor.surface, - image->hotspot_x, image->hotspot_y); - - wl_surface_attach(input->parent.cursor.surface, buffer, 0, 0); - wl_surface_damage(input->parent.cursor.surface, 0, 0, - image->width, image->height); - wl_surface_commit(input->parent.cursor.surface); -} - -static void -input_handle_pointer_enter(void *data, struct wl_pointer *pointer, - uint32_t serial, struct wl_surface *surface, - wl_fixed_t fixed_x, wl_fixed_t fixed_y) -{ - struct wayland_input *input = data; - int32_t fx, fy; - enum theme_location location; - double x, y; - - if (!surface) { - input->output = NULL; - input->has_focus = false; - notify_pointer_focus(&input->base, NULL, 0, 0); - return; - } - - x = wl_fixed_to_double(fixed_x); - y = wl_fixed_to_double(fixed_y); - - /* 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); - - if (input->output->frame) { - location = frame_pointer_enter(input->output->frame, input, - x, y); - frame_interior(input->output->frame, &fx, &fy, NULL, NULL); - x -= fx; - y -= fy; - - if (frame_status(input->output->frame) & FRAME_STATUS_REPAINT) - weston_output_schedule_repaint(&input->output->base); - } else { - location = THEME_LOCATION_CLIENT_AREA; - } - - weston_output_transform_coordinate(&input->output->base, x, y, &x, &y); - - if (location == THEME_LOCATION_CLIENT_AREA) { - input->has_focus = true; - notify_pointer_focus(&input->base, &input->output->base, x, y); - wl_pointer_set_cursor(input->parent.pointer, - input->enter_serial, NULL, 0, 0); - } else { - input->has_focus = false; - notify_pointer_focus(&input->base, NULL, 0, 0); - input_set_cursor(input); - } -} - -static void -input_handle_pointer_leave(void *data, struct wl_pointer *pointer, - uint32_t serial, struct wl_surface *surface) -{ - struct wayland_input *input = data; - - if (!input->output) - return; - - if (input->output->frame) { - frame_pointer_leave(input->output->frame, input); - - if (frame_status(input->output->frame) & FRAME_STATUS_REPAINT) - weston_output_schedule_repaint(&input->output->base); - } - - notify_pointer_focus(&input->base, NULL, 0, 0); - input->output = NULL; - input->has_focus = false; -} - -static void -input_handle_motion(void *data, struct wl_pointer *pointer, - uint32_t time, wl_fixed_t fixed_x, wl_fixed_t fixed_y) -{ - struct wayland_input *input = data; - int32_t fx, fy; - enum theme_location location; - bool want_frame = false; - double x, y; - struct timespec ts; - - if (!input->output) - return; - - x = wl_fixed_to_double(fixed_x); - y = wl_fixed_to_double(fixed_y); - - if (input->output->frame) { - location = frame_pointer_motion(input->output->frame, input, - x, y); - frame_interior(input->output->frame, &fx, &fy, NULL, NULL); - x -= fx; - y -= fy; - - if (frame_status(input->output->frame) & FRAME_STATUS_REPAINT) - weston_output_schedule_repaint(&input->output->base); - } else { - location = THEME_LOCATION_CLIENT_AREA; - } - - weston_output_transform_coordinate(&input->output->base, x, y, &x, &y); - - if (input->has_focus && location != THEME_LOCATION_CLIENT_AREA) { - input_set_cursor(input); - notify_pointer_focus(&input->base, NULL, 0, 0); - input->has_focus = false; - want_frame = true; - } else if (!input->has_focus && - location == THEME_LOCATION_CLIENT_AREA) { - wl_pointer_set_cursor(input->parent.pointer, - input->enter_serial, NULL, 0, 0); - notify_pointer_focus(&input->base, &input->output->base, x, y); - input->has_focus = true; - want_frame = true; - } - - if (location == THEME_LOCATION_CLIENT_AREA) { - timespec_from_msec(&ts, time); - notify_motion_absolute(&input->base, &ts, x, y); - want_frame = true; - } - - if (want_frame && input->seat_version < WL_POINTER_FRAME_SINCE_VERSION) - notify_pointer_frame(&input->base); -} - -static void -input_handle_button(void *data, struct wl_pointer *pointer, - uint32_t serial, uint32_t time, uint32_t button, - enum wl_pointer_button_state state) -{ - struct wayland_input *input = data; - enum theme_location location; - struct timespec ts; - - if (!input->output) - return; - - if (input->output->frame) { - location = frame_pointer_button(input->output->frame, input, - button, state); - - if (frame_status(input->output->frame) & FRAME_STATUS_MOVE) { - if (input->output->parent.xdg_toplevel) - xdg_toplevel_move(input->output->parent.xdg_toplevel, - input->parent.seat, serial); - else if (input->output->parent.shell_surface) - wl_shell_surface_move(input->output->parent.shell_surface, - input->parent.seat, serial); - frame_status_clear(input->output->frame, - FRAME_STATUS_MOVE); - return; - } - - if (frame_status(input->output->frame) & FRAME_STATUS_CLOSE) { - wayland_output_destroy(&input->output->base); - input->output = NULL; - input->keyboard_focus = NULL; - - if (wl_list_empty(&input->backend->compositor->output_list)) - weston_compositor_exit(input->backend->compositor); - - return; - } - - if (frame_status(input->output->frame) & FRAME_STATUS_REPAINT) - weston_output_schedule_repaint(&input->output->base); - } else { - location = THEME_LOCATION_CLIENT_AREA; - } - - if (location == THEME_LOCATION_CLIENT_AREA) { - timespec_from_msec(&ts, time); - notify_button(&input->base, &ts, button, state); - if (input->seat_version < WL_POINTER_FRAME_SINCE_VERSION) - notify_pointer_frame(&input->base); - } -} - -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; - struct weston_pointer_axis_event weston_event; - struct timespec ts; - - weston_event.axis = axis; - weston_event.value = wl_fixed_to_double(value); - weston_event.has_discrete = false; - - if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL && - input->vert.has_discrete) { - weston_event.has_discrete = true; - weston_event.discrete = input->vert.discrete; - input->vert.has_discrete = false; - } else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL && - input->horiz.has_discrete) { - weston_event.has_discrete = true; - weston_event.discrete = input->horiz.discrete; - input->horiz.has_discrete = false; - } - - timespec_from_msec(&ts, time); - - notify_axis(&input->base, &ts, &weston_event); - - if (input->seat_version < WL_POINTER_FRAME_SINCE_VERSION) - notify_pointer_frame(&input->base); -} - -static void -input_handle_frame(void *data, struct wl_pointer *pointer) -{ - struct wayland_input *input = data; - - notify_pointer_frame(&input->base); -} - -static void -input_handle_axis_source(void *data, struct wl_pointer *pointer, - uint32_t source) -{ - struct wayland_input *input = data; - - notify_axis_source(&input->base, source); -} - -static void -input_handle_axis_stop(void *data, struct wl_pointer *pointer, - uint32_t time, uint32_t axis) -{ - struct wayland_input *input = data; - struct weston_pointer_axis_event weston_event; - struct timespec ts; - - weston_event.axis = axis; - weston_event.value = 0; - - timespec_from_msec(&ts, time); - - notify_axis(&input->base, &ts, &weston_event); -} - -static void -input_handle_axis_discrete(void *data, struct wl_pointer *pointer, - uint32_t axis, int32_t discrete) -{ - struct wayland_input *input = data; - - if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { - input->vert.has_discrete = true; - input->vert.discrete = discrete; - } else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { - input->horiz.has_discrete = true; - input->horiz.discrete = discrete; - } -} - -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, - input_handle_frame, - input_handle_axis_source, - input_handle_axis_stop, - input_handle_axis_discrete, -}; - -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) { - map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); - if (map_str == MAP_FAILED) { - weston_log("mmap failed: %s\n", strerror(errno)); - goto error; - } - - keymap = xkb_keymap_new_from_string(input->backend->compositor->xkb_context, - map_str, - XKB_KEYMAP_FORMAT_TEXT_V1, - 0); - munmap(map_str, size); - - if (!keymap) { - weston_log("failed to compile keymap\n"); - goto error; - } - - input->keyboard_state_update = STATE_UPDATE_NONE; - } else if (format == WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP) { - weston_log("No keymap provided; falling back to default\n"); - keymap = NULL; - input->keyboard_state_update = STATE_UPDATE_AUTOMATIC; - } else { - weston_log("Invalid keymap\n"); - goto error; - } - - close(fd); - - if (weston_seat_get_keyboard(&input->base)) - weston_seat_update_keymap(&input->base, keymap); - else - weston_seat_init_keyboard(&input->base, keymap); - - xkb_keymap_unref(keymap); - - return; - -error: - wl_keyboard_release(input->parent.keyboard); - close(fd); -} - -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; - struct wayland_output *focus; - - focus = input->keyboard_focus; - if (focus) { - /* This shouldn't happen */ - focus->keyboard_count--; - if (!focus->keyboard_count && focus->frame) - frame_unset_flag(focus->frame, FRAME_FLAG_ACTIVE); - if (frame_status(focus->frame) & FRAME_STATUS_REPAINT) - weston_output_schedule_repaint(&focus->base); - } - - if (!surface) { - input->keyboard_focus = NULL; - return; - } - - input->keyboard_focus = wl_surface_get_user_data(surface); - input->keyboard_focus->keyboard_count++; - - focus = input->keyboard_focus; - if (focus->frame) { - frame_set_flag(focus->frame, FRAME_FLAG_ACTIVE); - if (frame_status(focus->frame) & FRAME_STATUS_REPAINT) - weston_output_schedule_repaint(&focus->base); - } - - - /* 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; - struct wayland_output *focus; - - notify_keyboard_focus_out(&input->base); - - focus = input->keyboard_focus; - if (!focus) - return; - - focus->keyboard_count--; - if (!focus->keyboard_count && focus->frame) { - frame_unset_flag(focus->frame, FRAME_FLAG_ACTIVE); - if (frame_status(focus->frame) & FRAME_STATUS_REPAINT) - weston_output_schedule_repaint(&focus->base); - } - - input->keyboard_focus = NULL; -} - -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; - struct timespec ts; - - if (!input->keyboard_focus) - return; - - timespec_from_msec(&ts, time); - - input->key_serial = serial; - notify_key(&input->base, &ts, key, - state ? WL_KEYBOARD_KEY_STATE_PRESSED : - WL_KEYBOARD_KEY_STATE_RELEASED, - input->keyboard_state_update); -} - -static void -input_handle_modifiers(void *data, struct wl_keyboard *wl_keyboard, - uint32_t serial_in, uint32_t mods_depressed, - uint32_t mods_latched, uint32_t mods_locked, - uint32_t group) -{ - struct weston_keyboard *keyboard; - struct wayland_input *input = data; - struct wayland_backend *b = input->backend; - 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(b->compositor->wl_display); - else - serial_out = wl_display_next_serial(b->compositor->wl_display); - - keyboard = weston_seat_get_keyboard(&input->base); - xkb_state_update_mask(keyboard->xkb_state.state, - mods_depressed, mods_latched, - mods_locked, 0, 0, group); - notify_modifiers(&input->base, serial_out); -} - -static void -input_handle_repeat_info(void *data, struct wl_keyboard *keyboard, - int32_t rate, int32_t delay) -{ - struct wayland_input *input = data; - struct wayland_backend *b = input->backend; - - b->compositor->kb_repeat_rate = rate; - b->compositor->kb_repeat_delay = delay; -} - -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, - input_handle_repeat_info, -}; - -static void -input_handle_touch_down(void *data, struct wl_touch *wl_touch, - uint32_t serial, uint32_t time, - struct wl_surface *surface, int32_t id, - wl_fixed_t fixed_x, wl_fixed_t fixed_y) -{ - struct wayland_input *input = data; - struct wayland_output *output; - enum theme_location location; - bool first_touch; - int32_t fx, fy; - double x, y; - struct timespec ts; - - x = wl_fixed_to_double(fixed_x); - y = wl_fixed_to_double(fixed_y); - - timespec_from_msec(&ts, time); - - first_touch = (input->touch_points == 0); - input->touch_points++; - - input->touch_focus = wl_surface_get_user_data(surface); - output = input->touch_focus; - if (!first_touch && !input->touch_active) - return; - - if (output->frame) { - location = frame_touch_down(output->frame, input, id, x, y); - - frame_interior(output->frame, &fx, &fy, NULL, NULL); - x -= fx; - y -= fy; - - if (frame_status(output->frame) & FRAME_STATUS_REPAINT) - weston_output_schedule_repaint(&output->base); - - if (first_touch && (frame_status(output->frame) & FRAME_STATUS_MOVE)) { - input->touch_points--; - if (output->parent.xdg_toplevel) - xdg_toplevel_move(output->parent.xdg_toplevel, - input->parent.seat, serial); - else if (output->parent.shell_surface) - wl_shell_surface_move(output->parent.shell_surface, - input->parent.seat, serial); - frame_status_clear(output->frame, - FRAME_STATUS_MOVE); - return; - } - - if (first_touch && location != THEME_LOCATION_CLIENT_AREA) - return; - } - - weston_output_transform_coordinate(&output->base, x, y, &x, &y); - - notify_touch(input->touch_device, &ts, id, x, y, WL_TOUCH_DOWN); - input->touch_active = true; -} - -static void -input_handle_touch_up(void *data, struct wl_touch *wl_touch, - uint32_t serial, uint32_t time, int32_t id) -{ - struct wayland_input *input = data; - struct wayland_output *output = input->touch_focus; - bool active = input->touch_active; - struct timespec ts; - - timespec_from_msec(&ts, time); - - input->touch_points--; - if (input->touch_points == 0) { - input->touch_focus = NULL; - input->touch_active = false; - } - - if (!output) - return; - - if (output->frame) { - frame_touch_up(output->frame, input, id); - - if (frame_status(output->frame) & FRAME_STATUS_CLOSE) { - wayland_output_destroy(&output->base); - input->touch_focus = NULL; - input->keyboard_focus = NULL; - if (wl_list_empty(&input->backend->compositor->output_list)) - weston_compositor_exit(input->backend->compositor); - - return; - } - if (frame_status(output->frame) & FRAME_STATUS_REPAINT) - weston_output_schedule_repaint(&output->base); - } - - if (active) - notify_touch(input->touch_device, &ts, id, 0, 0, WL_TOUCH_UP); -} - -static void -input_handle_touch_motion(void *data, struct wl_touch *wl_touch, - uint32_t time, int32_t id, - wl_fixed_t fixed_x, wl_fixed_t fixed_y) -{ - struct wayland_input *input = data; - struct wayland_output *output = input->touch_focus; - int32_t fx, fy; - double x, y; - struct timespec ts; - - x = wl_fixed_to_double(fixed_x); - y = wl_fixed_to_double(fixed_y); - timespec_from_msec(&ts, time); - - if (!output || !input->touch_active) - return; - - if (output->frame) { - frame_interior(output->frame, &fx, &fy, NULL, NULL); - x -= fx; - y -= fy; - } - - weston_output_transform_coordinate(&output->base, x, y, &x, &y); - - notify_touch(input->touch_device, &ts, id, x, y, WL_TOUCH_MOTION); -} - -static void -input_handle_touch_frame(void *data, struct wl_touch *wl_touch) -{ - struct wayland_input *input = data; - - if (!input->touch_focus || !input->touch_active) - return; - - notify_touch_frame(input->touch_device); -} - -static void -input_handle_touch_cancel(void *data, struct wl_touch *wl_touch) -{ - struct wayland_input *input = data; - - if (!input->touch_focus || !input->touch_active) - return; - - notify_touch_cancel(input->touch_device); -} - -static const struct wl_touch_listener touch_listener = { - input_handle_touch_down, - input_handle_touch_up, - input_handle_touch_motion, - input_handle_touch_frame, - input_handle_touch_cancel, -}; - - -static struct weston_touch_device * -create_touch_device(struct wayland_input *input) -{ - struct weston_touch_device *touch_device; - char str[128]; - - /* manufacture a unique'ish name */ - snprintf(str, sizeof str, "wayland-touch[%u]", - wl_proxy_get_id((struct wl_proxy *)input->parent.seat)); - - touch_device = weston_touch_create_touch_device(input->base.touch_state, - str, NULL, NULL); - - return touch_device; -} - -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->parent.pointer) { - input->parent.pointer = wl_seat_get_pointer(seat); - wl_pointer_set_user_data(input->parent.pointer, input); - wl_pointer_add_listener(input->parent.pointer, - &pointer_listener, input); - weston_seat_init_pointer(&input->base); - } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && input->parent.pointer) { - if (input->seat_version >= WL_POINTER_RELEASE_SINCE_VERSION) - wl_pointer_release(input->parent.pointer); - else - wl_pointer_destroy(input->parent.pointer); - input->parent.pointer = NULL; - weston_seat_release_pointer(&input->base); - } - - if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !input->parent.keyboard) { - input->parent.keyboard = wl_seat_get_keyboard(seat); - wl_keyboard_set_user_data(input->parent.keyboard, input); - wl_keyboard_add_listener(input->parent.keyboard, - &keyboard_listener, input); - } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && input->parent.keyboard) { - if (input->seat_version >= WL_KEYBOARD_RELEASE_SINCE_VERSION) - wl_keyboard_release(input->parent.keyboard); - else - wl_keyboard_destroy(input->parent.keyboard); - input->parent.keyboard = NULL; - weston_seat_release_keyboard(&input->base); - } - - if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !input->parent.touch) { - input->parent.touch = wl_seat_get_touch(seat); - wl_touch_set_user_data(input->parent.touch, input); - wl_touch_add_listener(input->parent.touch, - &touch_listener, input); - weston_seat_init_touch(&input->base); - input->touch_device = create_touch_device(input); - } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && input->parent.touch) { - weston_touch_device_destroy(input->touch_device); - input->touch_device = NULL; - if (input->seat_version >= WL_TOUCH_RELEASE_SINCE_VERSION) - wl_touch_release(input->parent.touch); - else - wl_touch_destroy(input->parent.touch); - input->parent.touch = NULL; - weston_seat_release_touch(&input->base); - } -} - -static void -input_handle_name(void *data, struct wl_seat *seat, - const char *name) -{ -} - -static const struct wl_seat_listener seat_listener = { - input_handle_capabilities, - input_handle_name, -}; - -static void -display_add_seat(struct wayland_backend *b, uint32_t id, uint32_t available_version) -{ - struct wayland_input *input; - uint32_t version = MIN(available_version, 4); - - input = zalloc(sizeof *input); - if (input == NULL) - return; - - weston_seat_init(&input->base, b->compositor, "default"); - input->backend = b; - input->parent.seat = wl_registry_bind(b->parent.registry, id, - &wl_seat_interface, version); - input->seat_version = version; - wl_list_insert(b->input_list.prev, &input->link); - - wl_seat_add_listener(input->parent.seat, &seat_listener, input); - wl_seat_set_user_data(input->parent.seat, input); - - input->parent.cursor.surface = - wl_compositor_create_surface(b->parent.compositor); - - input->vert.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; - input->horiz.axis = WL_POINTER_AXIS_HORIZONTAL_SCROLL; -} - -static void -wayland_parent_output_geometry(void *data, struct wl_output *output_proxy, - int32_t x, int32_t y, - int32_t physical_width, int32_t physical_height, - int32_t subpixel, const char *make, - const char *model, int32_t transform) -{ - struct wayland_parent_output *output = data; - - output->x = x; - output->y = y; - output->physical.width = physical_width; - output->physical.height = physical_height; - output->physical.subpixel = subpixel; - - free(output->physical.make); - output->physical.make = strdup(make); - free(output->physical.model); - output->physical.model = strdup(model); - - output->transform = transform; -} - -static struct weston_mode * -find_mode(struct wl_list *list, int32_t width, int32_t height, uint32_t refresh) -{ - struct weston_mode *mode; - - wl_list_for_each(mode, list, link) { - if (mode->width == width && mode->height == height && - mode->refresh == refresh) - return mode; - } - - mode = zalloc(sizeof *mode); - if (!mode) - return NULL; - - mode->width = width; - mode->height = height; - mode->refresh = refresh; - wl_list_insert(list, &mode->link); - - return mode; -} - -static struct weston_output * -wayland_parent_output_get_enabled_output(struct wayland_parent_output *poutput) -{ - struct wayland_head *head = poutput->head; - - if (!head) - return NULL; - - if (!weston_head_is_enabled(&head->base)) - return NULL; - - return weston_head_get_output(&head->base); -} - -static void -wayland_parent_output_mode(void *data, struct wl_output *wl_output_proxy, - uint32_t flags, int32_t width, int32_t height, - int32_t refresh) -{ - struct wayland_parent_output *output = data; - struct weston_output *enabled_output; - struct weston_mode *mode; - - enabled_output = wayland_parent_output_get_enabled_output(output); - if (enabled_output) { - mode = find_mode(&enabled_output->mode_list, - width, height, refresh); - if (!mode) - return; - mode->flags = flags; - /* Do a mode-switch on current mode change? */ - } else { - mode = find_mode(&output->mode_list, width, height, refresh); - if (!mode) - return; - mode->flags = flags; - if (flags & WL_OUTPUT_MODE_CURRENT) - output->current_mode = mode; - if (flags & WL_OUTPUT_MODE_PREFERRED) - output->preferred_mode = mode; - } -} - -static const struct wl_output_listener output_listener = { - wayland_parent_output_geometry, - wayland_parent_output_mode -}; - -static void -output_sync_callback(void *data, struct wl_callback *callback, uint32_t unused) -{ - struct wayland_parent_output *output = data; - - assert(output->sync_cb == callback); - wl_callback_destroy(callback); - output->sync_cb = NULL; - - assert(output->backend->sprawl_across_outputs); - - wayland_head_create_for_parent_output(output->backend->compositor, output); -} - -static const struct wl_callback_listener output_sync_listener = { - output_sync_callback -}; - -static void -wayland_backend_register_output(struct wayland_backend *b, uint32_t id) -{ - struct wayland_parent_output *output; - - output = zalloc(sizeof *output); - if (!output) - return; - - output->backend = b; - output->id = id; - output->global = wl_registry_bind(b->parent.registry, id, - &wl_output_interface, 1); - if (!output->global) { - free(output); - return; - } - - wl_output_add_listener(output->global, &output_listener, output); - - output->scale = 0; - output->transform = WL_OUTPUT_TRANSFORM_NORMAL; - output->physical.subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; - wl_list_init(&output->mode_list); - wl_list_insert(&b->parent.output_list, &output->link); - - if (b->sprawl_across_outputs) { - output->sync_cb = wl_display_sync(b->parent.wl_display); - wl_callback_add_listener(output->sync_cb, - &output_sync_listener, output); - } -} - -static void -wayland_parent_output_destroy(struct wayland_parent_output *output) -{ - struct weston_mode *mode, *next; - - if (output->sync_cb) - wl_callback_destroy(output->sync_cb); - - if (output->head) - wayland_head_destroy(output->head); - - wl_output_destroy(output->global); - free(output->physical.make); - free(output->physical.model); - - wl_list_for_each_safe(mode, next, &output->mode_list, link) { - wl_list_remove(&mode->link); - free(mode); - } - - wl_list_remove(&output->link); - free(output); -} - -static void -xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) -{ - xdg_wm_base_pong(shell, serial); -} - -static const struct xdg_wm_base_listener wm_base_listener = { - xdg_wm_base_ping, -}; - -static void -registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, - const char *interface, uint32_t version) -{ - struct wayland_backend *b = data; - - if (strcmp(interface, "wl_compositor") == 0) { - b->parent.compositor = - wl_registry_bind(registry, name, - &wl_compositor_interface, - MIN(version, 4)); - } else if (strcmp(interface, "xdg_wm_base") == 0) { - b->parent.xdg_wm_base = - wl_registry_bind(registry, name, - &xdg_wm_base_interface, 1); - xdg_wm_base_add_listener(b->parent.xdg_wm_base, - &wm_base_listener, b); - } else if (strcmp(interface, "wl_shell") == 0) { - b->parent.shell = - wl_registry_bind(registry, name, - &wl_shell_interface, 1); - } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { - b->parent.fshell = - wl_registry_bind(registry, name, - &zwp_fullscreen_shell_v1_interface, 1); - } else if (strcmp(interface, "wl_seat") == 0) { - display_add_seat(b, name, version); - } else if (strcmp(interface, "wl_output") == 0) { - wayland_backend_register_output(b, name); - } else if (strcmp(interface, "wl_shm") == 0) { - b->parent.shm = - wl_registry_bind(registry, name, &wl_shm_interface, 1); - } -} - -static void -registry_handle_global_remove(void *data, struct wl_registry *registry, - uint32_t name) -{ - struct wayland_backend *b = data; - struct wayland_parent_output *output, *next; - - wl_list_for_each_safe(output, next, &b->parent.output_list, link) - if (output->id == name) - wayland_parent_output_destroy(output); -} - -static const struct wl_registry_listener registry_listener = { - registry_handle_global, - registry_handle_global_remove -}; - -static int -wayland_backend_handle_event(int fd, uint32_t mask, void *data) -{ - struct wayland_backend *b = data; - int count = 0; - - if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { - weston_compositor_exit(b->compositor); - return 0; - } - - if (mask & WL_EVENT_READABLE) - count = wl_display_dispatch(b->parent.wl_display); - if (mask & WL_EVENT_WRITABLE) - wl_display_flush(b->parent.wl_display); - - if (mask == 0) { - count = wl_display_dispatch_pending(b->parent.wl_display); - wl_display_flush(b->parent.wl_display); - } - - return count; -} - -static void -wayland_destroy(struct weston_compositor *ec) -{ - struct wayland_backend *b = to_wayland_backend(ec); - struct weston_head *base, *next; - - wl_event_source_remove(b->parent.wl_source); - - weston_compositor_shutdown(ec); - - wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) - wayland_head_destroy(to_wayland_head(base)); - - if (b->parent.shm) - wl_shm_destroy(b->parent.shm); - - if (b->parent.xdg_wm_base) - xdg_wm_base_destroy(b->parent.xdg_wm_base); - - if (b->parent.shell) - wl_shell_destroy(b->parent.shell); - - if (b->parent.fshell) - zwp_fullscreen_shell_v1_release(b->parent.fshell); - - if (b->parent.compositor) - wl_compositor_destroy(b->parent.compositor); - - if (b->theme) - theme_destroy(b->theme); - - if (b->frame_device) - cairo_device_destroy(b->frame_device); - - wl_cursor_theme_destroy(b->cursor_theme); - - wl_registry_destroy(b->parent.registry); - wl_display_flush(b->parent.wl_display); - wl_display_disconnect(b->parent.wl_display); - - free(b); -} - -static const char *left_ptrs[] = { - "left_ptr", - "default", - "top_left_arrow", - "left-arrow" -}; - -static void -create_cursor(struct wayland_backend *b, - struct weston_wayland_backend_config *config) -{ - unsigned int i; - - b->cursor_theme = wl_cursor_theme_load(config->cursor_theme, - config->cursor_size, - b->parent.shm); - if (!b->cursor_theme) { - fprintf(stderr, "could not load cursor theme\n"); - return; - } - - b->cursor = NULL; - for (i = 0; !b->cursor && i < ARRAY_LENGTH(left_ptrs); ++i) - b->cursor = wl_cursor_theme_get_cursor(b->cursor_theme, - left_ptrs[i]); - if (!b->cursor) { - fprintf(stderr, "could not load left cursor\n"); - return; - } -} - -static void -fullscreen_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data) -{ - struct wayland_backend *b = data; - struct wayland_input *input = NULL; - - wl_list_for_each(input, &b->input_list, link) - if (&input->base == keyboard->seat) - break; - - if (!input || !input->output) - return; - - if (input->output->frame) - wayland_output_set_fullscreen(input->output, 0, 0, NULL); - else - wayland_output_set_windowed(input->output); - - weston_output_schedule_repaint(&input->output->base); -} - -static struct wayland_backend * -wayland_backend_create(struct weston_compositor *compositor, - struct weston_wayland_backend_config *new_config) -{ - struct wayland_backend *b; - struct wl_event_loop *loop; - int fd; - - b = zalloc(sizeof *b); - if (b == NULL) - return NULL; - - b->compositor = compositor; - compositor->backend = &b->base; - - if (weston_compositor_set_presentation_clock_software(compositor) < 0) - goto err_compositor; - - b->parent.wl_display = wl_display_connect(new_config->display_name); - if (b->parent.wl_display == NULL) { - weston_log("Error: Failed to connect to parent Wayland compositor: %s\n", - strerror(errno)); - weston_log_continue(STAMP_SPACE "display option: %s, WAYLAND_DISPLAY=%s\n", - new_config->display_name ?: "(none)", - getenv("WAYLAND_DISPLAY") ?: "(not set)"); - goto err_compositor; - } - - wl_list_init(&b->parent.output_list); - wl_list_init(&b->input_list); - b->parent.registry = wl_display_get_registry(b->parent.wl_display); - wl_registry_add_listener(b->parent.registry, ®istry_listener, b); - wl_display_roundtrip(b->parent.wl_display); - - create_cursor(b, new_config); - -#ifdef ENABLE_EGL - b->use_pixman = new_config->use_pixman; -#else - b->use_pixman = true; -#endif - b->fullscreen = new_config->fullscreen; - - if (!b->use_pixman) { - gl_renderer = weston_load_module("gl-renderer.so", - "gl_renderer_interface"); - if (!gl_renderer) - b->use_pixman = true; - } - - if (!b->use_pixman) { - const struct gl_renderer_display_options options = { - .egl_platform = EGL_PLATFORM_WAYLAND_KHR, - .egl_native_display = b->parent.wl_display, - .egl_surface_type = EGL_WINDOW_BIT, - .drm_formats = wayland_formats, - .drm_formats_count = ARRAY_LENGTH(wayland_formats), - }; - if (gl_renderer->display_create(compositor, &options) < 0) { - weston_log("Failed to initialize the GL renderer; " - "falling back to pixman.\n"); - b->use_pixman = true; - } - } - - if (b->use_pixman) { - if (pixman_renderer_init(compositor) < 0) { - weston_log("Failed to initialize pixman renderer\n"); - goto err_display; - } - } - - b->base.destroy = wayland_destroy; - b->base.create_output = wayland_output_create; - - loop = wl_display_get_event_loop(compositor->wl_display); - - fd = wl_display_get_fd(b->parent.wl_display); - b->parent.wl_source = - wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, - wayland_backend_handle_event, b); - if (b->parent.wl_source == NULL) - goto err_display; - - wl_event_source_check(b->parent.wl_source); - - if (compositor->renderer->import_dmabuf) { - if (linux_dmabuf_setup(compositor) < 0) - weston_log("Error: initializing dmabuf " - "support failed.\n"); - } - - return b; -err_display: - wl_display_disconnect(b->parent.wl_display); -err_compositor: - weston_compositor_shutdown(compositor); - free(b); - return NULL; -} - -static void -wayland_backend_destroy(struct wayland_backend *b) -{ - wl_display_disconnect(b->parent.wl_display); - - if (b->theme) - theme_destroy(b->theme); - if (b->frame_device) - cairo_device_destroy(b->frame_device); - wl_cursor_theme_destroy(b->cursor_theme); - - weston_compositor_shutdown(b->compositor); - free(b); -} - -static const struct weston_windowed_output_api windowed_api = { - wayland_output_set_size, - wayland_head_create_windowed, -}; - -static void -config_init_to_defaults(struct weston_wayland_backend_config *config) -{ -} - -WL_EXPORT int -weston_backend_init(struct weston_compositor *compositor, - struct weston_backend_config *config_base) -{ - struct wayland_backend *b; - struct wayland_parent_output *poutput; - struct weston_wayland_backend_config new_config; - int ret; - - if (config_base == NULL || - config_base->struct_version != WESTON_WAYLAND_BACKEND_CONFIG_VERSION || - config_base->struct_size > sizeof(struct weston_wayland_backend_config)) { - weston_log("wayland backend config structure is invalid\n"); - return -1; - } - - config_init_to_defaults(&new_config); - memcpy(&new_config, config_base, config_base->struct_size); - - b = wayland_backend_create(compositor, &new_config); - - if (!b) - return -1; - - if (new_config.sprawl || b->parent.fshell) { - b->sprawl_across_outputs = true; - wl_display_roundtrip(b->parent.wl_display); - - wl_list_for_each(poutput, &b->parent.output_list, link) - wayland_head_create_for_parent_output(compositor, poutput); - - return 0; - } - - if (new_config.fullscreen) { - if (!wayland_head_create(compositor, "wayland-fullscreen")) { - weston_log("Unable to create a fullscreen head.\n"); - goto err_outputs; - } - - return 0; - } - - ret = weston_plugin_api_register(compositor, WESTON_WINDOWED_OUTPUT_API_NAME, - &windowed_api, sizeof(windowed_api)); - - if (ret < 0) { - weston_log("Failed to register output API.\n"); - wayland_backend_destroy(b); - return -1; - } - - weston_compositor_add_key_binding(compositor, KEY_F, - MODIFIER_CTRL | MODIFIER_ALT, - fullscreen_binding, b); - return 0; - -err_outputs: - wayland_backend_destroy(b); - return -1; -} diff --git a/libweston/backend-x11/meson.build b/libweston/backend-x11/meson.build deleted file mode 100644 index 3e6ca54..0000000 --- a/libweston/backend-x11/meson.build +++ /dev/null @@ -1,58 +0,0 @@ - -if not get_option('backend-x11') - subdir_done() -endif - -config_h.set('BUILD_X11_COMPOSITOR', '1') - -srcs_x11 = [ - 'x11.c', - presentation_time_server_protocol_h, -] - -dep_x11_xcb = dependency('xcb', version: '>= 1.8', required: false) -if not dep_x11_xcb.found() - error('x11-backend requires xcb >= 1.8 which was not found. Or, you can use \'-Dbackend-x11=false\'.') -endif - -deps_x11 = [ - dep_libweston_private, - dep_libdrm_headers, - dep_x11_xcb, - dep_lib_cairo_shared, - dep_pixman, -] - -foreach name : [ 'xcb-shm', 'x11', 'x11-xcb' ] - d = dependency(name, required: false) - if not d.found() - error('x11-backend requires @0@ which was not found. Or, you can use \'-Dbackend-x11=false\'.'.format(name)) - endif - deps_x11 += d -endforeach - -dep_xcb_xkb = dependency('xcb-xkb', version: '>= 1.9', required: false) -if dep_xcb_xkb.found() - deps_x11 += dep_xcb_xkb - config_h.set('HAVE_XCB_XKB', '1') -endif - -if get_option('renderer-gl') - if not dep_egl.found() - error('x11-backend + gl-renderer requires egl which was not found. Or, you can use \'-Dbackend-x11=false\' or \'-Drenderer-gl=false\'.') - endif - deps_x11 += dep_egl -endif - -plugin_x11 = shared_library( - 'x11-backend', - srcs_x11, - include_directories: common_inc, - dependencies: deps_x11, - name_prefix: '', - install: true, - install_dir: dir_module_libweston -) -env_modmap += 'x11-backend.so=@0@;'.format(plugin_x11.full_path()) - -install_headers(backend_x11_h, subdir: dir_include_libweston_install) diff --git a/libweston/backend-x11/x11.c b/libweston/backend-x11/x11.c deleted file mode 100644 index 387e97a..0000000 --- a/libweston/backend-x11/x11.c +++ /dev/null @@ -1,1957 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2010-2011 Intel Corporation - * Copyright © 2013 Vasily Khoruzhick - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#ifdef HAVE_XCB_XKB -#include -#endif - -#include -#include - -#include - -#include -#include -#include "shared/helpers.h" -#include "shared/image-loader.h" -#include "shared/timespec-util.h" -#include "shared/file-util.h" -#include "renderer-gl/gl-renderer.h" -#include "shared/weston-egl-ext.h" -#include "pixman-renderer.h" -#include "presentation-time-server-protocol.h" -#include "linux-dmabuf.h" -#include "linux-explicit-synchronization.h" -#include - -#define DEFAULT_AXIS_STEP_DISTANCE 10 - -#define WINDOW_MIN_WIDTH 128 -#define WINDOW_MIN_HEIGHT 128 - -#define WINDOW_MAX_WIDTH 8192 -#define WINDOW_MAX_HEIGHT 8192 - -static const uint32_t x11_formats[] = { - DRM_FORMAT_XRGB8888, -}; - -struct x11_backend { - struct weston_backend base; - struct weston_compositor *compositor; - - 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 fullscreen; - int no_input; - 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; - double prev_x; - double 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_head { - struct weston_head base; -}; - -struct x11_output { - struct weston_output base; - - xcb_window_t window; - struct weston_mode mode; - struct weston_mode native; - 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; - bool resize_pending; - bool window_resized; -}; - -struct window_delete_data { - struct x11_backend *backend; - xcb_window_t window; -}; - -struct gl_renderer_interface *gl_renderer; - -static inline struct x11_head * -to_x11_head(struct weston_head *base) -{ - return container_of(base, struct x11_head, base); -} - -static inline struct x11_output * -to_x11_output(struct weston_output *base) -{ - return container_of(base, struct x11_output, base); -} - -static inline struct x11_backend * -to_x11_backend(struct weston_compositor *base) -{ - return container_of(base->backend, struct x11_backend, base); -} - -static xcb_screen_t * -x11_compositor_get_default_screen(struct x11_backend *b) -{ - xcb_screen_iterator_t iter; - int i, screen_nbr = XDefaultScreen(b->dpy); - - iter = xcb_setup_roots_iterator(xcb_get_setup(b->conn)); - for (i = 0; iter.rem; xcb_screen_next(&iter), i++) - if (i == screen_nbr) - return iter.data; - - return xcb_setup_roots_iterator(xcb_get_setup(b->conn)).data; -} - -static struct xkb_keymap * -x11_backend_get_keymap(struct x11_backend *b) -{ - 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(b->conn, 0, b->screen->root, - b->atom.xkb_names, b->atom.string, 0, 1024); - reply = xcb_get_property_reply(b->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_keymap_new_from_names(b->compositor->xkb_context, &names, 0); - - free(reply); - return ret; -} - -static uint32_t -get_xkb_mod_mask(struct x11_backend *b, uint32_t in) -{ - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(&b->core_seat); - struct weston_xkb_info *info = keyboard->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_backend_setup_xkb(struct x11_backend *b) -{ -#ifndef HAVE_XCB_XKB - weston_log("XCB-XKB not available during build\n"); - b->has_xkb = 0; - b->xkb_event_base = 0; - return; -#else - struct weston_keyboard *keyboard; - 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; - uint32_t values[1] = { XCB_EVENT_MASK_PROPERTY_CHANGE }; - - b->has_xkb = 0; - b->xkb_event_base = 0; - - ext = xcb_get_extension_data(b->conn, &xcb_xkb_id); - if (!ext) { - weston_log("XKB extension not available on host X11 server\n"); - return; - } - b->xkb_event_base = ext->first_event; - - select = xcb_xkb_select_events_checked(b->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(b->conn, select); - if (error) { - weston_log("error: failed to select for XKB state events\n"); - free(error); - return; - } - - use_ext = xcb_xkb_use_extension(b->conn, - XCB_XKB_MAJOR_VERSION, - XCB_XKB_MINOR_VERSION); - use_ext_reply = xcb_xkb_use_extension_reply(b->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(b->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(b->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(b->conn, XCB_XKB_ID_USE_CORE_KBD); - state_reply = xcb_xkb_get_state_reply(b->conn, state, NULL); - if (!state_reply) { - weston_log("failed to get initial XKB state\n"); - return; - } - - keyboard = weston_seat_get_keyboard(&b->core_seat); - xkb_state_update_mask(keyboard->xkb_state.state, - get_xkb_mod_mask(b, state_reply->baseMods), - get_xkb_mod_mask(b, state_reply->latchedMods), - get_xkb_mod_mask(b, state_reply->lockedMods), - 0, - 0, - state_reply->group); - - free(state_reply); - - xcb_change_window_attributes(b->conn, b->screen->root, - XCB_CW_EVENT_MASK, values); - - b->has_xkb = 1; -#endif -} - -#ifdef HAVE_XCB_XKB -static void -update_xkb_keymap(struct x11_backend *b) -{ - struct xkb_keymap *keymap; - - keymap = x11_backend_get_keymap(b); - if (!keymap) { - weston_log("failed to get XKB keymap\n"); - return; - } - weston_seat_update_keymap(&b->core_seat, keymap); - xkb_keymap_unref(keymap); -} -#endif - -static int -x11_input_create(struct x11_backend *b, int no_input) -{ - struct xkb_keymap *keymap; - - weston_seat_init(&b->core_seat, b->compositor, "default"); - - if (no_input) - return 0; - - weston_seat_init_pointer(&b->core_seat); - - keymap = x11_backend_get_keymap(b); - if (weston_seat_init_keyboard(&b->core_seat, keymap) < 0) - return -1; - xkb_keymap_unref(keymap); - - x11_backend_setup_xkb(b); - - return 0; -} - -static void -x11_input_destroy(struct x11_backend *b) -{ - weston_seat_release(&b->core_seat); -} - -static int -x11_output_start_repaint_loop(struct weston_output *output) -{ - struct timespec ts; - - weston_compositor_read_presentation_clock(output->compositor, &ts); - weston_output_finish_frame(output, &ts, WP_PRESENTATION_FEEDBACK_INVALID); - - return 0; -} - -static int -x11_output_repaint_gl(struct weston_output *output_base, - pixman_region32_t *damage, - void *repaint_data) -{ - struct x11_output *output = to_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 = to_x11_output(output_base); - struct weston_compositor *ec = output->base.compositor; - struct x11_backend *b = to_x11_backend(ec); - pixman_region32_t transformed_region; - pixman_box32_t *rects; - xcb_rectangle_t *output_rects; - xcb_void_cookie_t cookie; - int nrects, i; - xcb_generic_error_t *err; - - pixman_region32_init(&transformed_region); - pixman_region32_copy(&transformed_region, region); - pixman_region32_translate(&transformed_region, - -output_base->x, -output_base->y); - weston_transformed_region(output_base->width, output_base->height, - output_base->transform, - output_base->current_scale, - &transformed_region, &transformed_region); - - rects = pixman_region32_rectangles(&transformed_region, &nrects); - output_rects = calloc(nrects, sizeof(xcb_rectangle_t)); - - if (output_rects == NULL) { - pixman_region32_fini(&transformed_region); - return; - } - - for (i = 0; i < nrects; i++) { - output_rects[i].x = rects[i].x1; - output_rects[i].y = rects[i].y1; - output_rects[i].width = rects[i].x2 - rects[i].x1; - output_rects[i].height = rects[i].y2 - rects[i].y1; - } - - pixman_region32_fini(&transformed_region); - - cookie = xcb_set_clip_rectangles_checked(b->conn, XCB_CLIP_ORDERING_UNSORTED, - output->gc, - 0, 0, nrects, - output_rects); - err = xcb_request_check(b->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, - void *repaint_data) -{ - struct x11_output *output = to_x11_output(output_base); - struct weston_compositor *ec = output->base.compositor; - struct x11_backend *b = to_x11_backend(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(b->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(b->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; - struct timespec ts; - - weston_compositor_read_presentation_clock(output->base.compositor, &ts); - weston_output_finish_frame(&output->base, &ts, 0); - - return 1; -} - -static void -x11_output_deinit_shm(struct x11_backend *b, struct x11_output *output) -{ - xcb_void_cookie_t cookie; - xcb_generic_error_t *err; - xcb_free_gc(b->conn, output->gc); - - pixman_image_unref(output->hw_surface); - output->hw_surface = NULL; - cookie = xcb_shm_detach_checked(b->conn, output->segment); - err = xcb_request_check(b->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_set_wm_protocols(struct x11_backend *b, - struct x11_output *output) -{ - xcb_atom_t list[1]; - - list[0] = b->atom.wm_delete_window; - xcb_change_property (b->conn, - XCB_PROP_MODE_REPLACE, - output->window, - b->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_backend *b, - 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(b->conn, XCB_PROP_MODE_REPLACE, output->window, - b->atom.net_wm_icon, b->atom.cardinal, 32, - width * height + 2, icon); - free(icon); - pixman_image_unref(image); -} - -static void -x11_output_wait_for_map(struct x11_backend *b, 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(b->conn); - - while (!mapped || !configured) { - event = xcb_wait_for_event(b->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->mode.height = configure_notify->height; - 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_backend *b, struct x11_output *output, - int width, int height) -{ - xcb_visualtype_t *visual_type; - xcb_screen_t *screen; - 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(b->conn, &xcb_shm_id); - if (ext == NULL || !ext->present) { - /* SHM is missing */ - weston_log("SHM extension is not available\n"); - errno = ENOENT; - return -1; - } - - screen = x11_compositor_get_default_screen(b); - visual_type = find_visual_by_id(screen, screen->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(screen, screen->root_visual); - weston_log("Visual depth is %d\n", output->depth); - - for (fmt = xcb_setup_pixmap_formats_iterator(xcb_get_setup(b->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 if (bitsperpixel == 16 && - visual_type->red_mask == 0x00f800 && - visual_type->green_mask == 0x0007e0 && - visual_type->blue_mask == 0x00001f) { - weston_log("Will use r5g6b5 format for SHM surfaces\n"); - pixman_format = PIXMAN_r5g6b5; - } 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(b->conn); - cookie = xcb_shm_attach_checked(b->conn, output->segment, output->shm_id, 1); - err = xcb_request_check(b->conn, cookie); - if (err) { - weston_log("x11shm: xcb_shm_attach error %d, op code %d, resource id %d\n", - err->error_code, err->major_code, err->minor_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(b->conn); - xcb_create_gc(b->conn, output->gc, output->window, 0, NULL); - - return 0; -} - -static int -x11_output_switch_mode(struct weston_output *base, struct weston_mode *mode) -{ - struct x11_backend *b; - struct x11_output *output; - static uint32_t values[2]; - int ret; - - b = to_x11_backend(base->compositor); - output = to_x11_output(base); - - if (mode->width == output->mode.width && - mode->height == output->mode.height) - return 0; - - if (mode->width < WINDOW_MIN_WIDTH || mode->width > WINDOW_MAX_WIDTH) - return -1; - - if (mode->height < WINDOW_MIN_HEIGHT || mode->height > WINDOW_MAX_HEIGHT) - return -1; - - /* xcb_configure_window will create an event, and we could end up - being called twice */ - output->resize_pending = true; - - /* window could've been resized by the user, so don't do it twice */ - if (!output->window_resized) { - values[0] = mode->width; - values[1] = mode->height; - xcb_configure_window(b->conn, output->window, XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT, values); - } - - output->mode.width = mode->width; - output->mode.height = mode->height; - - if (b->use_pixman) { - const struct pixman_renderer_output_options options = { - .use_shadow = true, - }; - pixman_renderer_output_destroy(&output->base); - x11_output_deinit_shm(b, output); - - if (x11_output_init_shm(b, output, - output->base.current_mode->width, - output->base.current_mode->height) < 0) { - weston_log("Failed to initialize SHM for the X11 output\n"); - return -1; - } - - if (pixman_renderer_output_create(&output->base, &options) < 0) { - weston_log("Failed to create pixman renderer for output\n"); - x11_output_deinit_shm(b, output); - return -1; - } - } else { - Window xid = (Window) output->window; - const struct gl_renderer_output_options options = { - .window_for_legacy = (EGLNativeWindowType) output->window, - .window_for_platform = &xid, - .drm_formats = x11_formats, - .drm_formats_count = ARRAY_LENGTH(x11_formats), - }; - - gl_renderer->output_destroy(&output->base); - - ret = gl_renderer->output_window_create(&output->base, &options); - if (ret < 0) - return -1; - } - - output->resize_pending = false; - output->window_resized = false; - - return 0; -} - -static int -x11_output_disable(struct weston_output *base) -{ - struct x11_output *output = to_x11_output(base); - struct x11_backend *backend = to_x11_backend(base->compositor); - - if (!output->base.enabled) - return 0; - - wl_event_source_remove(output->finish_frame_timer); - - if (backend->use_pixman) { - pixman_renderer_output_destroy(&output->base); - x11_output_deinit_shm(backend, output); - } else { - gl_renderer->output_destroy(&output->base); - } - - xcb_destroy_window(backend->conn, output->window); - xcb_flush(backend->conn); - - return 0; -} - -static void -x11_output_destroy(struct weston_output *base) -{ - struct x11_output *output = to_x11_output(base); - - x11_output_disable(&output->base); - weston_output_release(&output->base); - - free(output); -} - -static int -x11_output_enable(struct weston_output *base) -{ - struct x11_output *output = to_x11_output(base); - struct x11_backend *b = to_x11_backend(base->compositor); - - static const char name[] = "Weston Compositor"; - static const char class[] = "weston-1\0Weston Compositor"; - char *title = NULL; - xcb_screen_t *screen; - struct wm_normal_hints normal_hints; - struct wl_event_loop *loop; - char *icon_filename; - - int ret; - 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 - }; - - if (!b->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; - - values[1] = b->null_cursor; - output->window = xcb_generate_id(b->conn); - screen = x11_compositor_get_default_screen(b); - xcb_create_window(b->conn, - XCB_COPY_FROM_PARENT, - output->window, - screen->root, - 0, 0, - output->base.current_mode->width, - output->base.current_mode->height, - 0, - XCB_WINDOW_CLASS_INPUT_OUTPUT, - screen->root_visual, - mask, values); - - if (b->fullscreen) { - atom_list[0] = b->atom.net_wm_state_fullscreen; - xcb_change_property(b->conn, XCB_PROP_MODE_REPLACE, - output->window, - b->atom.net_wm_state, - XCB_ATOM_ATOM, 32, - ARRAY_LENGTH(atom_list), atom_list); - } else { - memset(&normal_hints, 0, sizeof normal_hints); - normal_hints.flags = - WM_NORMAL_HINTS_MAX_SIZE | WM_NORMAL_HINTS_MIN_SIZE; - normal_hints.min_width = WINDOW_MIN_WIDTH; - normal_hints.min_height = WINDOW_MIN_HEIGHT; - normal_hints.max_width = WINDOW_MAX_WIDTH; - normal_hints.max_height = WINDOW_MAX_HEIGHT; - xcb_change_property(b->conn, XCB_PROP_MODE_REPLACE, output->window, - b->atom.wm_normal_hints, - b->atom.wm_size_hints, 32, - sizeof normal_hints / 4, - (uint8_t *) &normal_hints); - } - - /* Set window name. Don't bother with non-EWMH WMs. */ - if (output->base.name) { - if (asprintf(&title, "%s - %s", name, output->base.name) < 0) - title = NULL; - } else { - title = strdup(name); - } - - if (title) { - xcb_change_property(b->conn, XCB_PROP_MODE_REPLACE, output->window, - b->atom.net_wm_name, b->atom.utf8_string, 8, - strlen(title), title); - free(title); - } else { - goto err; - } - - xcb_change_property(b->conn, XCB_PROP_MODE_REPLACE, output->window, - b->atom.wm_class, b->atom.string, 8, - sizeof class, class); - - icon_filename = file_name_with_datadir("wayland.png"); - x11_output_set_icon(b, output, icon_filename); - free(icon_filename); - - x11_output_set_wm_protocols(b, output); - - xcb_map_window(b->conn, output->window); - - if (b->fullscreen) - x11_output_wait_for_map(b, output); - - if (b->use_pixman) { - const struct pixman_renderer_output_options options = { - .use_shadow = true, - }; - if (x11_output_init_shm(b, output, - output->base.current_mode->width, - output->base.current_mode->height) < 0) { - weston_log("Failed to initialize SHM for the X11 output\n"); - goto err; - } - if (pixman_renderer_output_create(&output->base, &options) < 0) { - weston_log("Failed to create pixman renderer for output\n"); - x11_output_deinit_shm(b, output); - goto err; - } - - output->base.repaint = x11_output_repaint_shm; - } else { - /* eglCreatePlatformWindowSurfaceEXT takes a Window* - * but eglCreateWindowSurface takes a Window. */ - Window xid = (Window) output->window; - const struct gl_renderer_output_options options = { - .window_for_legacy = (EGLNativeWindowType) output->window, - .window_for_platform = &xid, - .drm_formats = x11_formats, - .drm_formats_count = ARRAY_LENGTH(x11_formats), - }; - - ret = gl_renderer->output_window_create(&output->base, - &options); - if (ret < 0) - goto err; - - output->base.repaint = x11_output_repaint_gl; - } - - output->base.start_repaint_loop = x11_output_start_repaint_loop; - output->base.assign_planes = NULL; - output->base.set_backlight = NULL; - output->base.set_dpms = NULL; - output->base.switch_mode = x11_output_switch_mode; - - loop = wl_display_get_event_loop(b->compositor->wl_display); - output->finish_frame_timer = - wl_event_loop_add_timer(loop, finish_frame_handler, output); - - weston_log("x11 output %dx%d, window id %d\n", - output->base.current_mode->width, - output->base.current_mode->height, - output->window); - - return 0; - -err: - xcb_destroy_window(b->conn, output->window); - xcb_flush(b->conn); - - return -1; -} - -static int -x11_output_set_size(struct weston_output *base, int width, int height) -{ - struct x11_output *output = to_x11_output(base); - struct x11_backend *b = to_x11_backend(base->compositor); - struct weston_head *head; - xcb_screen_t *scrn = b->screen; - int output_width, output_height; - - /* We can only be called once. */ - assert(!output->base.current_mode); - - /* Make sure we have scale set. */ - assert(output->base.scale); - - if (width < WINDOW_MIN_WIDTH) { - weston_log("Invalid width \"%d\" for output %s\n", - width, output->base.name); - return -1; - } - - if (height < WINDOW_MIN_HEIGHT) { - weston_log("Invalid height \"%d\" for output %s\n", - height, output->base.name); - return -1; - } - - wl_list_for_each(head, &output->base.head_list, output_link) { - weston_head_set_monitor_strings(head, "weston-X11", "none", NULL); - weston_head_set_physical_size(head, - width * scrn->width_in_millimeters / scrn->width_in_pixels, - height * scrn->height_in_millimeters / scrn->height_in_pixels); - } - - output_width = width * output->base.scale; - output_height = height * output->base.scale; - - 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->native = output->mode; - output->scale = output->base.scale; - wl_list_insert(&output->base.mode_list, &output->mode.link); - - output->base.current_mode = &output->mode; - output->base.native_mode = &output->native; - output->base.native_scale = output->base.scale; - - return 0; -} - -static struct weston_output * -x11_output_create(struct weston_compositor *compositor, const char *name) -{ - struct x11_output *output; - - /* name can't be NULL. */ - assert(name); - - output = zalloc(sizeof *output); - if (!output) - return NULL; - - weston_output_init(&output->base, compositor, name); - - output->base.destroy = x11_output_destroy; - output->base.disable = x11_output_disable; - output->base.enable = x11_output_enable; - output->base.attach_head = NULL; - - weston_compositor_add_pending_output(&output->base, compositor); - - return &output->base; -} - -static int -x11_head_create(struct weston_compositor *compositor, const char *name) -{ - struct x11_head *head; - - assert(name); - - head = zalloc(sizeof *head); - if (!head) - return -1; - - weston_head_init(&head->base, name); - weston_head_set_connection_status(&head->base, true); - weston_compositor_add_head(compositor, &head->base); - - return 0; -} - -static void -x11_head_destroy(struct x11_head *head) -{ - weston_head_release(&head->base); - free(head); -} - -static struct x11_output * -x11_backend_find_output(struct x11_backend *b, xcb_window_t window) -{ - struct x11_output *output; - - wl_list_for_each(output, &b->compositor->output_list, base.link) { - if (output->window == window) - return output; - } - - return NULL; -} - -static void -x11_backend_delete_window(struct x11_backend *b, xcb_window_t window) -{ - struct x11_output *output; - - output = x11_backend_find_output(b, window); - if (output) - x11_output_destroy(&output->base); - - if (wl_list_empty(&b->compositor->output_list)) - weston_compositor_exit(b->compositor); -} - -static void delete_cb(void *data) -{ - struct window_delete_data *wd = data; - - x11_backend_delete_window(wd->backend, wd->window); - free(wd); -} - -#ifdef HAVE_XCB_XKB -static void -update_xkb_state(struct x11_backend *b, xcb_xkb_state_notify_event_t *state) -{ - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(&b->core_seat); - - xkb_state_update_mask(keyboard->xkb_state.state, - get_xkb_mod_mask(b, state->baseMods), - get_xkb_mod_mask(b, state->latchedMods), - get_xkb_mod_mask(b, state->lockedMods), - 0, - 0, - state->group); - - notify_modifiers(&b->core_seat, - wl_display_next_serial(b->compositor->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_backend *b, uint16_t x11_mask) -{ - uint32_t mask = get_xkb_mod_mask(b, x11_mask); - struct weston_keyboard *keyboard - = weston_seat_get_keyboard(&b->core_seat); - - xkb_state_update_mask(keyboard->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(&b->core_seat, - wl_display_next_serial(b->compositor->wl_display)); -} - -static void -x11_backend_deliver_button_event(struct x11_backend *b, - xcb_generic_event_t *event) -{ - xcb_button_press_event_t *button_event = - (xcb_button_press_event_t *) event; - uint32_t button; - struct x11_output *output; - struct weston_pointer_axis_event weston_event; - bool is_button_pressed = event->response_type == XCB_BUTTON_PRESS; - struct timespec time = { 0 }; - - assert(event->response_type == XCB_BUTTON_PRESS || - event->response_type == XCB_BUTTON_RELEASE); - - output = x11_backend_find_output(b, button_event->event); - if (!output) - return; - - if (is_button_pressed) - xcb_grab_pointer(b->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(b->conn, button_event->time); - - if (!b->has_xkb) - update_xkb_state_from_core(b, button_event->state); - - switch (button_event->detail) { - case 1: - button = BTN_LEFT; - 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 (is_button_pressed) { - weston_event.value = -DEFAULT_AXIS_STEP_DISTANCE; - weston_event.discrete = -1; - weston_event.has_discrete = true; - weston_event.axis = - WL_POINTER_AXIS_VERTICAL_SCROLL; - weston_compositor_get_time(&time); - notify_axis(&b->core_seat, &time, &weston_event); - notify_pointer_frame(&b->core_seat); - } - return; - case 5: - if (is_button_pressed) { - weston_event.value = DEFAULT_AXIS_STEP_DISTANCE; - weston_event.discrete = 1; - weston_event.has_discrete = true; - weston_event.axis = - WL_POINTER_AXIS_VERTICAL_SCROLL; - weston_compositor_get_time(&time); - notify_axis(&b->core_seat, &time, &weston_event); - notify_pointer_frame(&b->core_seat); - } - return; - case 6: - if (is_button_pressed) { - weston_event.value = -DEFAULT_AXIS_STEP_DISTANCE; - weston_event.discrete = -1; - weston_event.has_discrete = true; - weston_event.axis = - WL_POINTER_AXIS_HORIZONTAL_SCROLL; - weston_compositor_get_time(&time); - notify_axis(&b->core_seat, &time, &weston_event); - notify_pointer_frame(&b->core_seat); - } - return; - case 7: - if (is_button_pressed) { - weston_event.value = DEFAULT_AXIS_STEP_DISTANCE; - weston_event.discrete = 1; - weston_event.has_discrete = true; - weston_event.axis = - WL_POINTER_AXIS_HORIZONTAL_SCROLL; - weston_compositor_get_time(&time); - notify_axis(&b->core_seat, &time, &weston_event); - notify_pointer_frame(&b->core_seat); - } - return; - default: - button = button_event->detail + BTN_SIDE - 8; - break; - } - - weston_compositor_get_time(&time); - - notify_button(&b->core_seat, &time, button, - is_button_pressed ? WL_POINTER_BUTTON_STATE_PRESSED : - WL_POINTER_BUTTON_STATE_RELEASED); - notify_pointer_frame(&b->core_seat); -} - -static void -x11_backend_deliver_motion_event(struct x11_backend *b, - xcb_generic_event_t *event) -{ - struct x11_output *output; - double x, y; - struct weston_pointer_motion_event motion_event = { 0 }; - xcb_motion_notify_event_t *motion_notify = - (xcb_motion_notify_event_t *) event; - struct timespec time; - - if (!b->has_xkb) - update_xkb_state_from_core(b, motion_notify->state); - output = x11_backend_find_output(b, motion_notify->event); - if (!output) - return; - - weston_output_transform_coordinate(&output->base, - motion_notify->event_x, - motion_notify->event_y, - &x, &y); - - motion_event = (struct weston_pointer_motion_event) { - .mask = WESTON_POINTER_MOTION_REL, - .dx = x - b->prev_x, - .dy = y - b->prev_y - }; - - weston_compositor_get_time(&time); - notify_motion(&b->core_seat, &time, &motion_event); - notify_pointer_frame(&b->core_seat); - - b->prev_x = x; - b->prev_y = y; -} - -static void -x11_backend_deliver_enter_event(struct x11_backend *b, - xcb_generic_event_t *event) -{ - struct x11_output *output; - double x, y; - - xcb_enter_notify_event_t *enter_notify = - (xcb_enter_notify_event_t *) event; - if (enter_notify->state >= Button1Mask) - return; - if (!b->has_xkb) - update_xkb_state_from_core(b, enter_notify->state); - output = x11_backend_find_output(b, enter_notify->event); - if (!output) - return; - - weston_output_transform_coordinate(&output->base, - enter_notify->event_x, - enter_notify->event_y, &x, &y); - - notify_pointer_focus(&b->core_seat, &output->base, x, y); - - b->prev_x = x; - b->prev_y = y; -} - -static int -x11_backend_next_event(struct x11_backend *b, - xcb_generic_event_t **event, uint32_t mask) -{ - if (mask & WL_EVENT_READABLE) - *event = xcb_poll_for_event(b->conn); - else - *event = xcb_poll_for_queued_event(b->conn); - - return *event != NULL; -} - -static int -x11_backend_handle_event(int fd, uint32_t mask, void *data) -{ - struct x11_backend *b = 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_configure_notify_event_t *configure; - xcb_atom_t atom; - xcb_window_t window; - uint32_t *k; - uint32_t i, set; - uint8_t response_type; - int count; - struct timespec time; - - prev = NULL; - count = 0; - while (x11_backend_next_event(b, &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(b, key_release->state); - weston_compositor_get_time(&time); - notify_key(&b->core_seat, - &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; - b->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(&b->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(&b->core_seat, &b->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 (!b->has_xkb) - update_xkb_state_from_core(b, key_press->state); - weston_compositor_get_time(&time); - notify_key(&b->core_seat, - &time, - key_press->detail - 8, - WL_KEYBOARD_KEY_STATE_PRESSED, - b->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 (!b->has_xkb) { - prev = event; - break; - } - key_release = (xcb_key_press_event_t *) event; - weston_compositor_get_time(&time); - notify_key(&b->core_seat, - &time, - key_release->detail - 8, - WL_KEYBOARD_KEY_STATE_RELEASED, - STATE_UPDATE_NONE); - break; - case XCB_BUTTON_PRESS: - case XCB_BUTTON_RELEASE: - x11_backend_deliver_button_event(b, event); - break; - case XCB_MOTION_NOTIFY: - x11_backend_deliver_motion_event(b, event); - break; - - case XCB_EXPOSE: - expose = (xcb_expose_event_t *) event; - output = x11_backend_find_output(b, expose->window); - if (!output) - break; - - weston_output_damage(&output->base); - weston_output_schedule_repaint(&output->base); - break; - - case XCB_ENTER_NOTIFY: - x11_backend_deliver_enter_event(b, event); - break; - - case XCB_LEAVE_NOTIFY: - enter_notify = (xcb_enter_notify_event_t *) event; - if (enter_notify->state >= Button1Mask) - break; - if (!b->has_xkb) - update_xkb_state_from_core(b, enter_notify->state); - notify_pointer_focus(&b->core_seat, NULL, 0, 0); - break; - - case XCB_CLIENT_MESSAGE: - client_message = (xcb_client_message_event_t *) event; - atom = client_message->data.data32[0]; - window = client_message->window; - if (atom == b->atom.wm_delete_window) { - struct wl_event_loop *loop; - struct window_delete_data *data = malloc(sizeof *data); - - /* if malloc failed we should at least try to - * delete the window, even if it may result in - * a crash. - */ - if (!data) { - x11_backend_delete_window(b, window); - break; - } - data->backend = b; - data->window = window; - loop = wl_display_get_event_loop(b->compositor->wl_display); - wl_event_loop_add_idle(loop, delete_cb, data); - } - break; - - case XCB_CONFIGURE_NOTIFY: - configure = (struct xcb_configure_notify_event_t *) event; - struct x11_output *output = - x11_backend_find_output(b, configure->window); - - if (!output || output->resize_pending) - break; - - struct weston_mode mode = output->mode; - - if (mode.width == configure->width && - mode.height == configure->height) - break; - - output->window_resized = true; - - mode.width = configure->width; - mode.height = configure->height; - - if (weston_output_mode_set_native(&output->base, - &mode, output->scale) < 0) - weston_log("Mode switch failed\n"); - - 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(&b->core_seat); - break; - - default: - break; - } - -#ifdef HAVE_XCB_XKB - if (b->has_xkb) { - if (response_type == b->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(b, state); - } else if (response_type == XCB_PROPERTY_NOTIFY) { - xcb_property_notify_event_t *prop_notify = - (xcb_property_notify_event_t *) event; - if (prop_notify->window == b->screen->root && - prop_notify->atom == b->atom.xkb_names && - prop_notify->state == XCB_PROPERTY_NEW_VALUE) - update_xkb_keymap(b); - } - } -#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(b, key_release->state); - weston_compositor_get_time(&time); - notify_key(&b->core_seat, - &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_backend, field) - -static void -x11_backend_get_resources(struct x11_backend *b) -{ - 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 (b->conn, 0, - strlen(atoms[i].name), - atoms[i].name); - - for (i = 0; i < ARRAY_LENGTH(atoms); i++) { - reply = xcb_intern_atom_reply (b->conn, cookies[i], NULL); - *(xcb_atom_t *) ((char *) b + atoms[i].offset) = reply->atom; - free(reply); - } - - pixmap = xcb_generate_id(b->conn); - gc = xcb_generate_id(b->conn); - xcb_create_pixmap(b->conn, 1, pixmap, b->screen->root, 1, 1); - xcb_create_gc(b->conn, gc, pixmap, 0, NULL); - xcb_put_image(b->conn, XCB_IMAGE_FORMAT_XY_PIXMAP, - pixmap, gc, 1, 1, 0, 0, 0, 32, sizeof data, data); - b->null_cursor = xcb_generate_id(b->conn); - xcb_create_cursor (b->conn, b->null_cursor, - pixmap, pixmap, 0, 0, 0, 0, 0, 0, 1, 1); - xcb_free_gc(b->conn, gc); - xcb_free_pixmap(b->conn, pixmap); -} - -static void -x11_backend_get_wm_info(struct x11_backend *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; - } - - free(reply); -} - -static void -x11_destroy(struct weston_compositor *ec) -{ - struct x11_backend *backend = to_x11_backend(ec); - struct weston_head *base, *next; - - wl_event_source_remove(backend->xcb_source); - x11_input_destroy(backend); - - weston_compositor_shutdown(ec); /* destroys outputs, too */ - - wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) - x11_head_destroy(to_x11_head(base)); - - XCloseDisplay(backend->dpy); - free(backend); -} - -static int -init_gl_renderer(struct x11_backend *b) -{ - const struct gl_renderer_display_options options = { - .egl_platform = EGL_PLATFORM_X11_KHR, - .egl_native_display = b->dpy, - .egl_surface_type = EGL_WINDOW_BIT, - .drm_formats = x11_formats, - .drm_formats_count = ARRAY_LENGTH(x11_formats), - }; - - gl_renderer = weston_load_module("gl-renderer.so", - "gl_renderer_interface"); - if (!gl_renderer) - return -1; - - return gl_renderer->display_create(b->compositor, &options); -} - -static const struct weston_windowed_output_api api = { - x11_output_set_size, - x11_head_create, -}; - -static struct x11_backend * -x11_backend_create(struct weston_compositor *compositor, - struct weston_x11_backend_config *config) -{ - struct x11_backend *b; - struct wl_event_loop *loop; - int ret; - - b = zalloc(sizeof *b); - if (b == NULL) - return NULL; - - b->compositor = compositor; - b->fullscreen = config->fullscreen; - b->no_input = config->no_input; - - compositor->backend = &b->base; - - if (weston_compositor_set_presentation_clock_software(compositor) < 0) - goto err_free; - - b->dpy = XOpenDisplay(NULL); - if (b->dpy == NULL) - goto err_free; - - b->conn = XGetXCBConnection(b->dpy); - XSetEventQueueOwner(b->dpy, XCBOwnsEventQueue); - - if (xcb_connection_has_error(b->conn)) - goto err_xdisplay; - - b->screen = x11_compositor_get_default_screen(b); - wl_array_init(&b->keys); - - x11_backend_get_resources(b); - x11_backend_get_wm_info(b); - - if (!b->has_net_wm_state_fullscreen && config->fullscreen) { - weston_log("Can not fullscreen without window manager support" - "(need _NET_WM_STATE_FULLSCREEN)\n"); - config->fullscreen = 0; - } - - b->use_pixman = config->use_pixman; - if (b->use_pixman) { - if (pixman_renderer_init(compositor) < 0) { - weston_log("Failed to initialize pixman renderer for X11 backend\n"); - goto err_xdisplay; - } - } - else if (init_gl_renderer(b) < 0) { - goto err_xdisplay; - } - weston_log("Using %s renderer\n", config->use_pixman ? "pixman" : "gl"); - - b->base.destroy = x11_destroy; - b->base.create_output = x11_output_create; - - if (x11_input_create(b, config->no_input) < 0) { - weston_log("Failed to create X11 input\n"); - goto err_renderer; - } - - loop = wl_display_get_event_loop(compositor->wl_display); - b->xcb_source = - wl_event_loop_add_fd(loop, - xcb_get_file_descriptor(b->conn), - WL_EVENT_READABLE, - x11_backend_handle_event, b); - wl_event_source_check(b->xcb_source); - - if (compositor->renderer->import_dmabuf) { - if (linux_dmabuf_setup(compositor) < 0) - weston_log("Error: initializing dmabuf " - "support failed.\n"); - } - - if (compositor->capabilities & WESTON_CAP_EXPLICIT_SYNC) { - if (linux_explicit_synchronization_setup(compositor) < 0) - weston_log("Error: initializing explicit " - " synchronization support failed.\n"); - } - - ret = weston_plugin_api_register(compositor, WESTON_WINDOWED_OUTPUT_API_NAME, - &api, sizeof(api)); - - if (ret < 0) { - weston_log("Failed to register output API.\n"); - goto err_x11_input; - } - - return b; - -err_x11_input: - x11_input_destroy(b); -err_renderer: - compositor->renderer->destroy(compositor); -err_xdisplay: - XCloseDisplay(b->dpy); -err_free: - free(b); - return NULL; -} - -static void -config_init_to_defaults(struct weston_x11_backend_config *config) -{ -} - -WL_EXPORT int -weston_backend_init(struct weston_compositor *compositor, - struct weston_backend_config *config_base) -{ - struct x11_backend *b; - struct weston_x11_backend_config config = {{ 0, }}; - - if (config_base == NULL || - config_base->struct_version != WESTON_X11_BACKEND_CONFIG_VERSION || - config_base->struct_size > sizeof(struct weston_x11_backend_config)) { - weston_log("X11 backend config structure is invalid\n"); - return -1; - } - - config_init_to_defaults(&config); - memcpy(&config, config_base, config_base->struct_size); - - b = x11_backend_create(compositor, &config); - if (b == NULL) - return -1; - - return 0; -} diff --git a/libweston/backend.h b/libweston/backend.h deleted file mode 100644 index 8e91746..0000000 --- a/libweston/backend.h +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2017, 2018 General Electric Company - * Copyright © 2012, 2017-2019 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* - * This header contains the libweston ABI exported only for internal backends. - */ - -#ifndef LIBWESTON_BACKEND_INTERNAL_H -#define LIBWESTON_BACKEND_INTERNAL_H - -struct weston_backend { - void (*destroy)(struct weston_compositor *compositor); - - /** Begin a repaint sequence - * - * Provides the backend with explicit markers around repaint - * sequences, which may allow the backend to aggregate state - * application. This call will be bracketed by the repaint_flush (on - * success), or repaint_cancel (when any output in the grouping fails - * repaint). - * - * Returns an opaque pointer, which the backend may use as private - * data referring to the repaint cycle. - */ - void * (*repaint_begin)(struct weston_compositor *compositor); - - /** Cancel a repaint sequence - * - * Cancels a repaint sequence, when an error has occurred during - * one output's repaint; see repaint_begin. - * - * @param repaint_data Data returned by repaint_begin - */ - void (*repaint_cancel)(struct weston_compositor *compositor, - void *repaint_data); - - /** Conclude a repaint sequence - * - * Called on successful completion of a repaint sequence; see - * repaint_begin. - * - * @param repaint_data Data returned by repaint_begin - */ - int (*repaint_flush)(struct weston_compositor *compositor, - void *repaint_data); - - /** Allocate a new output - * - * @param compositor The compositor. - * @param name Name for the new output. - * - * Allocates a new output structure that embeds a weston_output, - * initializes it, and returns the pointer to the weston_output - * member. - * - * Must set weston_output members @c destroy, @c enable and @c disable. - */ - struct weston_output * - (*create_output)(struct weston_compositor *compositor, - const char *name); - - /** Notify of device addition/removal - * - * @param compositor The compositor. - * @param device The device that has changed. - * @param added Where it was added (or removed) - * - * Called when a device has been added/removed from the session. - * The backend can decide what to do based on whether it is a - * device that it is controlling or not. - */ - void (*device_changed)(struct weston_compositor *compositor, - dev_t device, bool added); - - /** Verifies if the dmabuf can be used directly/scanned-out by the HW. - * - * @param compositor The compositor. - * @param buffer The dmabuf to verify. - * - * Determines if the buffer can be imported directly by the display - * controller/HW. Back-ends can use this to check if the supplied - * buffer can be scanned-out, as to void importing it into the GPU. - */ - bool (*can_scanout_dmabuf)(struct weston_compositor *compositor, - struct linux_dmabuf_buffer *buffer); -}; - -/* weston_head */ - -void -weston_head_init(struct weston_head *head, const char *name); - -void -weston_head_release(struct weston_head *head); - -void -weston_head_set_connection_status(struct weston_head *head, bool connected); - -void -weston_head_set_internal(struct weston_head *head); - -void -weston_head_set_monitor_strings(struct weston_head *head, - const char *make, - const char *model, - const char *serialno); -void -weston_head_set_non_desktop(struct weston_head *head, bool non_desktop); - -void -weston_head_set_physical_size(struct weston_head *head, - int32_t mm_width, int32_t mm_height); - -void -weston_head_set_subpixel(struct weston_head *head, - enum wl_output_subpixel sp); - -void -weston_head_set_transform(struct weston_head *head, uint32_t transform); - -/* weston_output */ - -void -weston_output_init(struct weston_output *output, - struct weston_compositor *compositor, - const char *name); -void -weston_output_damage(struct weston_output *output); - -void -weston_output_release(struct weston_output *output); - -void -weston_output_init_zoom(struct weston_output *output); - -void -weston_output_finish_frame(struct weston_output *output, - const struct timespec *stamp, - uint32_t presented_flags); -int -weston_output_mode_set_native(struct weston_output *output, - struct weston_mode *mode, - int32_t scale); -void -weston_output_transform_coordinate(struct weston_output *output, - double device_x, double device_y, - double *x, double *y); - -/* weston_seat */ - -void -notify_axis(struct weston_seat *seat, const struct timespec *time, - struct weston_pointer_axis_event *event); -void -notify_axis_source(struct weston_seat *seat, uint32_t source); - -void -notify_button(struct weston_seat *seat, const struct timespec *time, - int32_t button, enum wl_pointer_button_state state); - -void -notify_key(struct weston_seat *seat, const struct timespec *time, uint32_t key, - enum wl_keyboard_key_state state, - enum weston_key_state_update update_state); -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_motion(struct weston_seat *seat, const struct timespec *time, - struct weston_pointer_motion_event *event); -void -notify_motion_absolute(struct weston_seat *seat, const struct timespec *time, - double x, double y); -void -notify_modifiers(struct weston_seat *seat, uint32_t serial); - -void -notify_pointer_frame(struct weston_seat *seat); - -void -notify_pointer_focus(struct weston_seat *seat, struct weston_output *output, - double x, double y); - -/* weston_touch_device */ - -void -notify_touch_normalized(struct weston_touch_device *device, - const struct timespec *time, - int touch_id, - double x, double y, - const struct weston_point2d_device_normalized *norm, - int touch_type); - -/** Feed in touch down, motion, and up events, non-calibratable device. - * - * @sa notify_touch_cal - */ -static inline void -notify_touch(struct weston_touch_device *device, const struct timespec *time, - int touch_id, double x, double y, int touch_type) -{ - notify_touch_normalized(device, time, touch_id, x, y, NULL, touch_type); -} - -void -notify_touch_frame(struct weston_touch_device *device); - -void -notify_touch_cancel(struct weston_touch_device *device); - -void -notify_touch_calibrator(struct weston_touch_device *device, - const struct timespec *time, int32_t slot, - const struct weston_point2d_device_normalized *norm, - int touch_type); -void -notify_touch_calibrator_cancel(struct weston_touch_device *device); -void -notify_touch_calibrator_frame(struct weston_touch_device *device); - -#endif diff --git a/libweston/bindings.c b/libweston/bindings.c deleted file mode 100755 index f71551a..0000000 --- a/libweston/bindings.c +++ /dev/null @@ -1,590 +0,0 @@ -/* - * Copyright © 2011-2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include - -#include -#include "libweston-internal.h" -#include "shared/helpers.h" -#include "shared/timespec-util.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_modifier_binding(struct weston_compositor *compositor, - uint32_t modifier, - weston_modifier_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->modifier_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; -} - -// OHOS remove debugger -//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); -} - -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, - const struct timespec *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; - uint32_t msecs; - - 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 { - /* Don't send the key press event for the binding key */ - return; - } - } - if (!wl_list_empty(&keyboard->focus_resource_list)) { - serial = wl_display_next_serial(display); - msecs = timespec_to_msec(time); - wl_resource_for_each(resource, &keyboard->focus_resource_list) { - wl_keyboard_send_key(resource, - serial, - msecs, - 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_keyboard *keyboard, - const struct timespec *time, uint32_t key, - struct weston_surface *focus) -{ - struct binding_keyboard_grab *grab; - - grab = malloc(sizeof *grab); - grab->key = key; - grab->grab.interface = &binding_grab; - weston_keyboard_start_grab(keyboard, &grab->grab); - - /* Notify the surface which had the focus before this binding - * triggered that we stole a keypress from under it, by forcing - * a wl_keyboard leave/enter pair. The enter event will contain - * the pressed key in the keys array, so the client will know - * the exact state of the keyboard. - * If the old focus surface is different than the new one it - * means it was changed in the binding handler, so it received - * the enter event already. */ - if (focus && keyboard->focus == focus) { - weston_keyboard_set_focus(keyboard, NULL); - weston_keyboard_set_focus(keyboard, focus); - } -} - -void -weston_compositor_run_key_binding(struct weston_compositor *compositor, - struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, - enum wl_keyboard_key_state state) -{ - struct weston_binding *b, *tmp; - struct weston_surface *focus; - struct weston_seat *seat = keyboard->seat; - - if (state == WL_KEYBOARD_KEY_STATE_RELEASED) - return; - - /* Invalidate all active modifier bindings. */ - wl_list_for_each(b, &compositor->modifier_binding_list, link) - b->key = key; - - wl_list_for_each_safe(b, tmp, &compositor->key_binding_list, link) { - if (b->key == key && b->modifier == seat->modifier_state) { - weston_key_binding_handler_t handler = b->handler; - focus = keyboard->focus; - handler(keyboard, 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 press. */ - if (keyboard->grab == - &keyboard->default_grab) - install_binding_grab(keyboard, - time, - key, - focus); - } - } -} - -void -weston_compositor_run_modifier_binding(struct weston_compositor *compositor, - struct weston_keyboard *keyboard, - enum weston_keyboard_modifier modifier, - enum wl_keyboard_key_state state) -{ - struct weston_binding *b, *tmp; - - if (keyboard->grab != &keyboard->default_grab) - return; - - wl_list_for_each_safe(b, tmp, &compositor->modifier_binding_list, link) { - weston_modifier_binding_handler_t handler = b->handler; - - if (b->modifier != modifier) - continue; - - /* Prime the modifier binding. */ - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { - b->key = 0; - continue; - } - /* Ignore the binding if a key was pressed in between. */ - else if (b->key != 0) { - return; - } - - handler(keyboard, modifier, b->data); - } -} - -void -weston_compositor_run_button_binding(struct weston_compositor *compositor, - struct weston_pointer *pointer, - const struct timespec *time, - uint32_t button, - enum wl_pointer_button_state state) -{ - struct weston_binding *b, *tmp; - - if (state == WL_POINTER_BUTTON_STATE_RELEASED) - return; - - /* Invalidate all active modifier bindings. */ - wl_list_for_each(b, &compositor->modifier_binding_list, link) - b->key = button; - - wl_list_for_each_safe(b, tmp, &compositor->button_binding_list, link) { - if (b->button == button && - b->modifier == pointer->seat->modifier_state) { - weston_button_binding_handler_t handler = b->handler; - handler(pointer, time, button, b->data); - } - } -} - -void -weston_compositor_run_touch_binding(struct weston_compositor *compositor, - struct weston_touch *touch, - const struct timespec *time, - int touch_type) -{ - struct weston_binding *b, *tmp; - - if (touch->num_tp != 1 || touch_type != WL_TOUCH_DOWN) - return; - - wl_list_for_each_safe(b, tmp, &compositor->touch_binding_list, link) { - if (b->modifier == touch->seat->modifier_state) { - weston_touch_binding_handler_t handler = b->handler; - handler(touch, time, b->data); - } - } -} - -int -weston_compositor_run_axis_binding(struct weston_compositor *compositor, - struct weston_pointer *pointer, - const struct timespec *time, - struct weston_pointer_axis_event *event) -{ - struct weston_binding *b, *tmp; - - /* Invalidate all active modifier bindings. */ - wl_list_for_each(b, &compositor->modifier_binding_list, link) - b->key = event->axis; - - wl_list_for_each_safe(b, tmp, &compositor->axis_binding_list, link) { - if (b->axis == event->axis && - b->modifier == pointer->seat->modifier_state) { - weston_axis_binding_handler_t handler = b->handler; - handler(pointer, time, event, b->data); - return 1; - } - } - - return 0; -} - -int -weston_compositor_run_debug_binding(struct weston_compositor *compositor, - struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, - enum wl_keyboard_key_state state) -{ - weston_key_binding_handler_t handler; - struct weston_binding *binding, *tmp; - int count = 0; - - wl_list_for_each_safe(binding, tmp, &compositor->debug_binding_list, link) { - if (key != binding->key) - continue; - - count++; - handler = binding->handler; - handler(keyboard, time, key, binding->data); - } - - return count; -} - -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, const struct timespec *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; - uint32_t msecs; - - 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, grab->keyboard, - 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; - msecs = timespec_to_msec(time); - wl_resource_for_each(resource, resource_list) { - wl_keyboard_send_key(resource, serial, msecs, 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_keyboard *keyboard, const struct timespec *time, - uint32_t key, void *data) -{ - struct debug_binding_grab *grab; - - grab = calloc(1, sizeof *grab); - if (!grab) - return; - - grab->seat = keyboard->seat; - grab->key[0] = key; - grab->grab.interface = &debug_binding_keyboard_grab; - weston_keyboard_start_grab(keyboard, &grab->grab); -} - -/** Install the trigger binding for debug bindings. - * - * \param compositor The compositor. - * \param mod The modifier. - * - * This will add a key binding for modifier+SHIFT+SPACE that will trigger - * debug key bindings. - */ -WL_EXPORT void -weston_install_debug_key_binding(struct weston_compositor *compositor, - uint32_t mod) -{ - weston_compositor_add_key_binding(compositor, KEY_SPACE, - mod | MODIFIER_SHIFT, - debug_binding, NULL); -} diff --git a/libweston/clipboard.c b/libweston/clipboard.c deleted file mode 100644 index 7d60351..0000000 --- a/libweston/clipboard.c +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include "libweston-internal.h" -#include "shared/helpers.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 = zalloc(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; - source->fd = fd; - - 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 = zalloc(sizeof *client); - if (client == NULL) - return; - - 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 (!mime_types || 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/libweston/compositor.c b/libweston/compositor.c deleted file mode 100755 index 2ffc821..0000000 --- a/libweston/compositor.c +++ /dev/null @@ -1,8005 +0,0 @@ -/* - * Copyright © 2010-2011 Intel Corporation - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2012-2018 Collabora, Ltd. - * Copyright © 2017, 2018 General Electric Company - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "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 - -// OHOS remove timeline -//#include "timeline.h" - -#include -#include -#include "linux-dmabuf.h" -#include "viewporter-server-protocol.h" -#include "presentation-time-server-protocol.h" -#include "xdg-output-unstable-v1-server-protocol.h" -#include "linux-explicit-synchronization-unstable-v1-server-protocol.h" -#include "linux-explicit-synchronization.h" -#include "shared/fd-util.h" -#include "shared/helpers.h" -#include "shared/os-compatibility.h" -#include "shared/string-helpers.h" -#include "shared/timespec-util.h" -#include "git-version.h" -#include -#include -#include "pixel-formats.h" -#include "backend.h" -#include "libweston-internal.h" - -// OHOS remove logger -//#include "weston-log-internal.h" - -/** - * \defgroup head Head - * \defgroup output Output - * \defgroup compositor Compositor - */ - -#define DEFAULT_REPAINT_WINDOW 7 /* milliseconds */ - -static void -weston_output_update_matrix(struct weston_output *output); - -static void -weston_output_transform_scale_init(struct weston_output *output, - uint32_t transform, uint32_t scale); - -static void -weston_compositor_build_view_list(struct weston_compositor *compositor); - -static char * -weston_output_create_heads_string(struct weston_output *output); - -/** Send wl_output events for mode and scale changes - * - * \param head Send on all resources bound to this head. - * \param mode_changed If true, send the current mode. - * \param scale_changed If true, send the current scale. - */ -static void -weston_mode_switch_send_events(struct weston_head *head, - bool mode_changed, bool scale_changed) -{ - struct weston_output *output = head->output; - struct wl_resource *resource; - int version; - - wl_resource_for_each(resource, &head->resource_list) { - if (mode_changed) { - wl_output_send_mode(resource, - output->current_mode->flags, - output->current_mode->width, - output->current_mode->height, - output->current_mode->refresh); - } - - version = wl_resource_get_version(resource); - if (version >= WL_OUTPUT_SCALE_SINCE_VERSION && scale_changed) - wl_output_send_scale(resource, output->current_scale); - - if (version >= WL_OUTPUT_DONE_SINCE_VERSION) - wl_output_send_done(resource); - } - wl_resource_for_each(resource, &head->xdg_output_resource_list) { - zxdg_output_v1_send_logical_position(resource, - output->x, - output->y); - zxdg_output_v1_send_logical_size(resource, - output->width, - output->height); - zxdg_output_v1_send_done(resource); - } -} - -static void -weston_mode_switch_finish(struct weston_output *output, - int mode_changed, int scale_changed) -{ - struct weston_seat *seat; - struct weston_head *head; - pixman_region32_t old_output_region; - - 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_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 = weston_seat_get_pointer(seat); - 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); - - if (!mode_changed && !scale_changed) - return; - - /* notify clients of the changes */ - wl_list_for_each(head, &output->head_list, output_link) - weston_mode_switch_send_events(head, - mode_changed, scale_changed); -} - -static void -weston_compositor_reflow_outputs(struct weston_compositor *compositor, - struct weston_output *resized_output, int delta_width); - -/** - * \ingroup output - */ -WL_EXPORT int -weston_output_mode_set_native(struct weston_output *output, - struct weston_mode *mode, - int32_t scale) -{ - int ret; - int mode_changed = 0, scale_changed = 0; - int32_t old_width; - - if (!output->switch_mode) - return -1; - - if (!output->original_mode) { - mode_changed = 1; - ret = output->switch_mode(output, mode); - if (ret < 0) - return ret; - if (output->current_scale != scale) { - scale_changed = 1; - output->current_scale = scale; - } - } - - old_width = output->width; - output->native_mode = mode; - output->native_scale = scale; - - weston_mode_switch_finish(output, mode_changed, scale_changed); - - if (mode_changed || scale_changed) { - weston_compositor_reflow_outputs(output->compositor, output, output->width - old_width); - - wl_signal_emit(&output->compositor->output_resized_signal, output); - } - return 0; -} - -/** - * \ingroup output - */ -WL_EXPORT int -weston_output_mode_switch_to_native(struct weston_output *output) -{ - int ret; - int mode_changed = 0, scale_changed = 0; - - if (!output->switch_mode) - return -1; - - if (!output->original_mode) { - weston_log("already in the native mode\n"); - return -1; - } - /* the non fullscreen clients haven't seen a mode set since we - * switched into a temporary, so we need to notify them if the - * mode at that time is different from the native mode now. - */ - mode_changed = (output->original_mode != output->native_mode); - scale_changed = (output->original_scale != output->native_scale); - - ret = output->switch_mode(output, output->native_mode); - if (ret < 0) - return ret; - - output->current_scale = output->native_scale; - - output->original_mode = NULL; - output->original_scale = 0; - - weston_mode_switch_finish(output, mode_changed, scale_changed); - - return 0; -} - -/** - * \ingroup output - */ -WL_EXPORT int -weston_output_mode_switch_to_temporary(struct weston_output *output, - struct weston_mode *mode, - int32_t scale) -{ - int ret; - - if (!output->switch_mode) - return -1; - - /* original_mode is the last mode non full screen clients have seen, - * so we shouldn't change it if we already have one set. - */ - if (!output->original_mode) { - output->original_mode = output->native_mode; - output->original_scale = output->native_scale; - } - ret = output->switch_mode(output, mode); - if (ret < 0) - return ret; - - output->current_scale = scale; - - weston_mode_switch_finish(output, 0, 0); - - return 0; -} - -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_view * -weston_view_create(struct weston_surface *surface) -{ - struct weston_view *view; - - view = zalloc(sizeof *view); - if (view == NULL) - return NULL; - - view->surface = surface; - view->plane = &surface->compositor->primary_plane; - - /* Assign to surface */ - wl_list_insert(&surface->views, &view->surface_link); - - wl_signal_init(&view->destroy_signal); - wl_list_init(&view->link); - wl_list_init(&view->layer_link.link); - - pixman_region32_init(&view->clip); - - view->alpha = 1.0; - pixman_region32_init(&view->transform.opaque); - - wl_list_init(&view->geometry.transformation_list); - wl_list_insert(&view->geometry.transformation_list, - &view->transform.position.link); - weston_matrix_init(&view->transform.position.matrix); - wl_list_init(&view->geometry.child_list); - pixman_region32_init(&view->geometry.scissor); - pixman_region32_init(&view->transform.boundingbox); - view->transform.dirty = 1; - - return view; -} - -struct weston_frame_callback { - struct wl_resource *resource; - struct wl_list link; -}; - -struct weston_presentation_feedback { - struct wl_resource *resource; - - /* XXX: could use just wl_resource_get_link() instead */ - struct wl_list link; - - /* The per-surface feedback flags */ - uint32_t psf_flags; -}; - -static void -weston_presentation_feedback_discard( - struct weston_presentation_feedback *feedback) -{ - wp_presentation_feedback_send_discarded(feedback->resource); - wl_resource_destroy(feedback->resource); -} - -static void -weston_presentation_feedback_discard_list(struct wl_list *list) -{ - struct weston_presentation_feedback *feedback, *tmp; - - wl_list_for_each_safe(feedback, tmp, list, link) - weston_presentation_feedback_discard(feedback); -} - -static void -weston_presentation_feedback_present( - struct weston_presentation_feedback *feedback, - struct weston_output *output, - uint32_t refresh_nsec, - const struct timespec *ts, - uint64_t seq, - uint32_t flags) -{ - struct wl_client *client = wl_resource_get_client(feedback->resource); - struct weston_head *head; - struct wl_resource *o; - uint32_t tv_sec_hi; - uint32_t tv_sec_lo; - uint32_t tv_nsec; - bool done = false; - - wl_list_for_each(head, &output->head_list, output_link) { - wl_resource_for_each(o, &head->resource_list) { - if (wl_resource_get_client(o) != client) - continue; - - wp_presentation_feedback_send_sync_output(feedback->resource, o); - done = true; - } - - /* For clone mode, send it for just one wl_output global, - * they are all equivalent anyway. - */ - if (done) - break; - } - - timespec_to_proto(ts, &tv_sec_hi, &tv_sec_lo, &tv_nsec); - wp_presentation_feedback_send_presented(feedback->resource, - tv_sec_hi, tv_sec_lo, tv_nsec, - refresh_nsec, - seq >> 32, seq & 0xffffffff, - flags | feedback->psf_flags); - wl_resource_destroy(feedback->resource); -} - -static void -weston_presentation_feedback_present_list(struct wl_list *list, - struct weston_output *output, - uint32_t refresh_nsec, - const struct timespec *ts, - uint64_t seq, - uint32_t flags) -{ - struct weston_presentation_feedback *feedback, *tmp; - - assert(!(flags & WP_PRESENTATION_FEEDBACK_INVALID) || - wl_list_empty(list)); - - wl_list_for_each_safe(feedback, tmp, list, link) - weston_presentation_feedback_present(feedback, output, - refresh_nsec, ts, seq, - flags); -} - -static void -surface_state_handle_buffer_destroy(struct wl_listener *listener, void *data) -{ - struct weston_surface_state *state = - container_of(listener, struct weston_surface_state, - buffer_destroy_listener); - - state->buffer = NULL; -} - -static void -weston_surface_state_init(struct weston_surface_state *state) -{ - state->newly_attached = 0; - state->buffer = NULL; - state->buffer_destroy_listener.notify = - surface_state_handle_buffer_destroy; - state->sx = 0; - state->sy = 0; - - pixman_region32_init(&state->damage_surface); - pixman_region32_init(&state->damage_buffer); - pixman_region32_init(&state->opaque); - region_init_infinite(&state->input); - - wl_list_init(&state->frame_callback_list); - wl_list_init(&state->feedback_list); - - state->buffer_viewport.buffer.transform = WL_OUTPUT_TRANSFORM_NORMAL; - state->buffer_viewport.buffer.scale = 1; - state->buffer_viewport.buffer.src_width = wl_fixed_from_int(-1); - state->buffer_viewport.surface.width = -1; - state->buffer_viewport.changed = 0; - - state->acquire_fence_fd = -1; - - state->desired_protection = WESTON_HDCP_DISABLE; - state->protection_mode = WESTON_SURFACE_PROTECTION_MODE_RELAXED; -} - -static void -weston_surface_state_fini(struct weston_surface_state *state) -{ - struct weston_frame_callback *cb, *next; - - wl_list_for_each_safe(cb, next, - &state->frame_callback_list, link) - wl_resource_destroy(cb->resource); - - weston_presentation_feedback_discard_list(&state->feedback_list); - - pixman_region32_fini(&state->input); - pixman_region32_fini(&state->opaque); - pixman_region32_fini(&state->damage_surface); - pixman_region32_fini(&state->damage_buffer); - - if (state->buffer) - wl_list_remove(&state->buffer_destroy_listener.link); - state->buffer = NULL; - - fd_clear(&state->acquire_fence_fd); - weston_buffer_release_reference(&state->buffer_release_ref, NULL); -} - -static void -weston_surface_state_set_buffer(struct weston_surface_state *state, - struct weston_buffer *buffer) -{ - if (state->buffer == buffer) - return; - - if (state->buffer) - wl_list_remove(&state->buffer_destroy_listener.link); - state->buffer = buffer; - if (state->buffer) - wl_signal_add(&state->buffer->destroy_signal, - &state->buffer_destroy_listener); -} - -WL_EXPORT struct weston_surface * -weston_surface_create(struct weston_compositor *compositor) -{ - struct weston_surface *surface; - - surface = zalloc(sizeof *surface); - if (surface == NULL) - return NULL; - - wl_signal_init(&surface->destroy_signal); - wl_signal_init(&surface->commit_signal); - - surface->compositor = compositor; - surface->ref_count = 1; - - surface->buffer_viewport.buffer.transform = WL_OUTPUT_TRANSFORM_NORMAL; - surface->buffer_viewport.buffer.scale = 1; - surface->buffer_viewport.buffer.src_width = wl_fixed_from_int(-1); - surface->buffer_viewport.surface.width = -1; - - weston_surface_state_init(&surface->pending); - - pixman_region32_init(&surface->damage); - pixman_region32_init(&surface->opaque); - region_init_infinite(&surface->input); - - wl_list_init(&surface->views); - - wl_list_init(&surface->frame_callback_list); - wl_list_init(&surface->feedback_list); - - wl_list_init(&surface->subsurface_list); - wl_list_init(&surface->subsurface_list_pending); - - weston_matrix_init(&surface->buffer_to_surface_matrix); - weston_matrix_init(&surface->surface_to_buffer_matrix); - - wl_list_init(&surface->pointer_constraints); - - surface->acquire_fence_fd = -1; - - surface->desired_protection = WESTON_HDCP_DISABLE; - surface->current_protection = WESTON_HDCP_DISABLE; - surface->protection_mode = WESTON_SURFACE_PROTECTION_MODE_RELAXED; - - 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); - surface->is_opaque = !(alpha < 1.0); -} - -WL_EXPORT void -weston_view_to_global_float(struct weston_view *view, - float sx, float sy, float *x, float *y) -{ - if (view->transform.enabled) { - struct weston_vector v = { { sx, sy, 0.0f, 1.0f } }; - - weston_matrix_transform(&view->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 + view->geometry.x; - *y = sy + view->geometry.y; - } -} - -/** Transform a point to buffer coordinates - * - * \param width Surface width. - * \param height Surface height. - * \param transform Buffer transform. - * \param scale Buffer scale. - * \param sx Surface x coordinate of a point. - * \param sy Surface y coordinate of a point. - * \param[out] bx Buffer x coordinate of the point. - * \param[out] by Buffer Y coordinate of the point. - * - * Converts the given surface-local coordinates to buffer coordinates - * according to the given buffer transform and scale. - * This ignores wp_viewport. - * - * The given width and height must be the result of inverse scaled and - * inverse transformed buffer size. - */ -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 = sy; - *by = width - sx; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - *bx = sy; - *by = 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 = height - sy; - *by = sx; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - *bx = height - sy; - *by = width - sx; - break; - } - - *bx *= scale; - *by *= scale; -} - -/** Transform a rectangle to buffer coordinates - * - * \param width Surface width. - * \param height Surface height. - * \param transform Buffer transform. - * \param scale Buffer scale. - * \param rect Rectangle in surface coordinates. - * \return Rectangle in buffer coordinates. - * - * Converts the given surface-local rectangle to buffer coordinates - * according to the given buffer transform and scale. The resulting - * rectangle is guaranteed to be well-formed. - * This ignores wp_viewport. - * - * The given width and height must be the result of inverse scaled and - * inverse transformed buffer size. - */ -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; -} - -/** Transform a region by a matrix, restricted to axis-aligned transformations - * - * Warning: This function does not work for projective, affine, or matrices - * that encode arbitrary rotations. Only 90-degree step rotations are - * supported. - */ -WL_EXPORT void -weston_matrix_transform_region(pixman_region32_t *dest, - struct weston_matrix *matrix, - pixman_region32_t *src) -{ - pixman_box32_t *src_rects, *dest_rects; - int nrects, i; - - src_rects = pixman_region32_rectangles(src, &nrects); - dest_rects = malloc(nrects * sizeof(*dest_rects)); - if (!dest_rects) - return; - - for (i = 0; i < nrects; i++) { - struct weston_vector vec1 = {{ - src_rects[i].x1, src_rects[i].y1, 0, 1 - }}; - weston_matrix_transform(matrix, &vec1); - vec1.f[0] /= vec1.f[3]; - vec1.f[1] /= vec1.f[3]; - - struct weston_vector vec2 = {{ - src_rects[i].x2, src_rects[i].y2, 0, 1 - }}; - weston_matrix_transform(matrix, &vec2); - vec2.f[0] /= vec2.f[3]; - vec2.f[1] /= vec2.f[3]; - - if (vec1.f[0] < vec2.f[0]) { - dest_rects[i].x1 = floor(vec1.f[0]); - dest_rects[i].x2 = ceil(vec2.f[0]); - } else { - dest_rects[i].x1 = floor(vec2.f[0]); - dest_rects[i].x2 = ceil(vec1.f[0]); - } - - if (vec1.f[1] < vec2.f[1]) { - dest_rects[i].y1 = floor(vec1.f[1]); - dest_rects[i].y2 = ceil(vec2.f[1]); - } else { - dest_rects[i].y1 = floor(vec2.f[1]); - dest_rects[i].y2 = ceil(vec1.f[1]); - } - } - - pixman_region32_clear(dest); - pixman_region32_init_rects(dest, dest_rects, nrects); - free(dest_rects); -} - -/** Transform a region to buffer coordinates - * - * \param width Surface width. - * \param height Surface height. - * \param transform Buffer transform. - * \param scale Buffer scale. - * \param[in] src Region in surface coordinates. - * \param[out] dest Resulting region in buffer coordinates. - * - * Converts the given surface-local region to buffer coordinates - * according to the given buffer transform and scale. - * This ignores wp_viewport. - * - * The given width and height must be the result of inverse scaled and - * inverse transformed buffer size. - * - * src and dest are allowed to point to the same memory for in-place conversion. - */ -WL_EXPORT void -weston_transformed_region(int width, int height, - enum wl_output_transform transform, - int32_t scale, - pixman_region32_t *src, pixman_region32_t *dest) -{ - pixman_box32_t *src_rects, *dest_rects; - int nrects, i; - - if (transform == WL_OUTPUT_TRANSFORM_NORMAL && scale == 1) { - if (src != dest) - pixman_region32_copy(dest, src); - return; - } - - src_rects = pixman_region32_rectangles(src, &nrects); - dest_rects = malloc(nrects * sizeof(*dest_rects)); - if (!dest_rects) - return; - - if (transform == WL_OUTPUT_TRANSFORM_NORMAL) { - memcpy(dest_rects, src_rects, nrects * sizeof(*dest_rects)); - } else { - for (i = 0; i < nrects; i++) { - switch (transform) { - default: - case WL_OUTPUT_TRANSFORM_NORMAL: - dest_rects[i].x1 = src_rects[i].x1; - dest_rects[i].y1 = src_rects[i].y1; - dest_rects[i].x2 = src_rects[i].x2; - dest_rects[i].y2 = src_rects[i].y2; - break; - case WL_OUTPUT_TRANSFORM_90: - dest_rects[i].x1 = src_rects[i].y1; - dest_rects[i].y1 = width - src_rects[i].x2; - dest_rects[i].x2 = src_rects[i].y2; - dest_rects[i].y2 = width - src_rects[i].x1; - break; - case WL_OUTPUT_TRANSFORM_180: - dest_rects[i].x1 = width - src_rects[i].x2; - dest_rects[i].y1 = height - src_rects[i].y2; - dest_rects[i].x2 = width - src_rects[i].x1; - dest_rects[i].y2 = height - src_rects[i].y1; - break; - case WL_OUTPUT_TRANSFORM_270: - dest_rects[i].x1 = height - src_rects[i].y2; - dest_rects[i].y1 = src_rects[i].x1; - dest_rects[i].x2 = height - src_rects[i].y1; - dest_rects[i].y2 = src_rects[i].x2; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED: - dest_rects[i].x1 = width - src_rects[i].x2; - dest_rects[i].y1 = src_rects[i].y1; - dest_rects[i].x2 = width - src_rects[i].x1; - dest_rects[i].y2 = src_rects[i].y2; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - dest_rects[i].x1 = src_rects[i].y1; - dest_rects[i].y1 = src_rects[i].x1; - dest_rects[i].x2 = src_rects[i].y2; - dest_rects[i].y2 = src_rects[i].x2; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - dest_rects[i].x1 = src_rects[i].x1; - dest_rects[i].y1 = height - src_rects[i].y2; - dest_rects[i].x2 = src_rects[i].x2; - dest_rects[i].y2 = height - src_rects[i].y1; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - dest_rects[i].x1 = height - src_rects[i].y2; - dest_rects[i].y1 = width - src_rects[i].x2; - dest_rects[i].x2 = height - src_rects[i].y1; - dest_rects[i].y2 = width - src_rects[i].x1; - break; - } - } - } - - if (scale != 1) { - for (i = 0; i < nrects; i++) { - dest_rects[i].x1 *= scale; - dest_rects[i].x2 *= scale; - dest_rects[i].y1 *= scale; - dest_rects[i].y2 *= scale; - } - } - - pixman_region32_clear(dest); - pixman_region32_init_rects(dest, dest_rects, nrects); - free(dest_rects); -} - -static void -viewport_surface_to_buffer(struct weston_surface *surface, - float sx, float sy, float *bx, float *by) -{ - struct weston_buffer_viewport *vp = &surface->buffer_viewport; - double src_width, src_height; - double src_x, src_y; - - if (vp->buffer.src_width == wl_fixed_from_int(-1)) { - if (vp->surface.width == -1) { - *bx = sx; - *by = sy; - return; - } - - src_x = 0.0; - src_y = 0.0; - src_width = surface->width_from_buffer; - src_height = surface->height_from_buffer; - } else { - src_x = wl_fixed_to_double(vp->buffer.src_x); - src_y = wl_fixed_to_double(vp->buffer.src_y); - src_width = wl_fixed_to_double(vp->buffer.src_width); - src_height = wl_fixed_to_double(vp->buffer.src_height); - } - - *bx = sx * src_width / surface->width + src_x; - *by = sy * src_height / surface->height + src_y; -} - -WL_EXPORT void -weston_surface_to_buffer_float(struct weston_surface *surface, - float sx, float sy, float *bx, float *by) -{ - struct weston_buffer_viewport *vp = &surface->buffer_viewport; - - /* first transform coordinates if the viewport is set */ - viewport_surface_to_buffer(surface, sx, sy, bx, by); - - weston_transformed_coord(surface->width_from_buffer, - surface->height_from_buffer, - vp->buffer.transform, vp->buffer.scale, - *bx, *by, bx, by); -} - -/** Transform a rectangle from surface coordinates to buffer coordinates - * - * \param surface The surface to fetch wp_viewport and buffer transformation - * from. - * \param rect The rectangle to transform. - * \return The transformed rectangle. - * - * Viewport and buffer transformations can only do translation, scaling, - * and rotations in 90-degree steps. Therefore the only loss in the - * conversion is coordinate rounding. - * - * However, some coordinate rounding takes place as an intermediate - * step before the buffer scale factor is applied, so the rectangle - * boundary may not be exactly as expected. - * - * This is OK for damage tracking since a little extra coverage is - * not a problem. - */ -WL_EXPORT pixman_box32_t -weston_surface_to_buffer_rect(struct weston_surface *surface, - pixman_box32_t rect) -{ - struct weston_buffer_viewport *vp = &surface->buffer_viewport; - float xf, yf; - - /* first transform box coordinates if the viewport is set */ - viewport_surface_to_buffer(surface, rect.x1, rect.y1, &xf, &yf); - rect.x1 = floorf(xf); - rect.y1 = floorf(yf); - - viewport_surface_to_buffer(surface, rect.x2, rect.y2, &xf, &yf); - rect.x2 = ceilf(xf); - rect.y2 = ceilf(yf); - - return weston_transformed_rect(surface->width_from_buffer, - surface->height_from_buffer, - vp->buffer.transform, vp->buffer.scale, - rect); -} - -/** Transform a region from surface coordinates to buffer coordinates - * - * \param surface The surface to fetch wp_viewport and buffer transformation - * from. - * \param[in] surface_region The region in surface coordinates. - * \param[out] buffer_region The region converted to buffer coordinates. - * - * Buffer_region must be init'd, but will be completely overwritten. - * - * Viewport and buffer transformations can only do translation, scaling, - * and rotations in 90-degree steps. Therefore the only loss in the - * conversion is from the coordinate rounding that takes place in - * \ref weston_surface_to_buffer_rect. - * - */ -WL_EXPORT void -weston_surface_to_buffer_region(struct weston_surface *surface, - pixman_region32_t *surface_region, - pixman_region32_t *buffer_region) -{ - pixman_box32_t *src_rects, *dest_rects; - int nrects, i; - - src_rects = pixman_region32_rectangles(surface_region, &nrects); - dest_rects = malloc(nrects * sizeof(*dest_rects)); - if (!dest_rects) - return; - - for (i = 0; i < nrects; i++) { - dest_rects[i] = weston_surface_to_buffer_rect(surface, - src_rects[i]); - } - - pixman_region32_fini(buffer_region); - pixman_region32_init_rects(buffer_region, dest_rects, nrects); - free(dest_rects); -} - -WL_EXPORT void -weston_view_move_to_plane(struct weston_view *view, - struct weston_plane *plane) -{ - if (view->plane == plane) - return; - - weston_view_damage_below(view); - view->plane = plane; - weston_surface_damage(view->surface); -} - -/** Inflict damage on the plane where the view is visible. - * - * \param view The view that causes the damage. - * - * If the view is currently on a plane (including the primary plane), - * take the view's boundingbox, subtract all the opaque views that cover it, - * and add the remaining region as damage to the plane. This corresponds - * to the damage inflicted to the plane if this view disappeared. - * - * A repaint is scheduled for this view. - * - * The region of all opaque views covering this view is stored in - * weston_view::clip and updated by view_accumulate_damage() during - * weston_output_repaint(). Specifically, that region matches the - * scenegraph as it was last painted. - */ -WL_EXPORT void -weston_view_damage_below(struct weston_view *view) -{ - pixman_region32_t damage; - - pixman_region32_init(&damage); - pixman_region32_subtract(&damage, &view->transform.boundingbox, - &view->clip); - if (view->plane) - pixman_region32_union(&view->plane->damage, - &view->plane->damage, &damage); - pixman_region32_fini(&damage); - weston_view_schedule_repaint(view); -} - -/** Send wl_surface.enter/leave events - * - * \param surface The surface. - * \param head A head of the entered/left output. - * \param enter True if entered. - * \param leave True if left. - * - * Send the enter/leave events for all protocol objects bound to the given - * output by the client owning the surface. - */ -static void -weston_surface_send_enter_leave(struct weston_surface *surface, - struct weston_head *head, - bool enter, - bool leave) -{ - struct wl_resource *wloutput; - struct wl_client *client; - - assert(enter != leave); - - client = wl_resource_get_client(surface->resource); - wl_resource_for_each(wloutput, &head->resource_list) { - if (wl_resource_get_client(wloutput) != client) - continue; - - if (enter) - wl_surface_send_enter(surface->resource, wloutput); - if (leave) - wl_surface_send_leave(surface->resource, wloutput); - } -} - -static void -weston_surface_compute_protection(struct protected_surface *psurface) -{ - enum weston_hdcp_protection min_protection; - bool min_protection_valid = false; - struct weston_surface *surface = psurface->surface; - struct weston_output *output; - - wl_list_for_each(output, &surface->compositor->output_list, link) - if (surface->output_mask & (1u << output->id)) { - /* - * If the content-protection is enabled with protection - * mode as RELAXED for a surface, and if - * content-recording features like: screen-shooter, - * recorder, screen-sharing, etc are on, then notify the - * client, that the protection is disabled. - * - * Note: If the protection mode is ENFORCED then there - * is no need to bother the client as the renderer takes - * care of censoring the visibility of the protected - * content. - */ - - if (output->disable_planes > 0 && - surface->protection_mode == WESTON_SURFACE_PROTECTION_MODE_RELAXED) { - min_protection = WESTON_HDCP_DISABLE; - min_protection_valid = true; - break; - } - if (!min_protection_valid) { - min_protection = output->current_protection; - min_protection_valid = true; - } - if (output->current_protection < min_protection) - min_protection = output->current_protection; - } - if (!min_protection_valid) - min_protection = WESTON_HDCP_DISABLE; - - surface->current_protection = min_protection; - - weston_protected_surface_send_event(psurface, surface->current_protection); -} - -static void -notify_surface_protection_change(void *data) -{ - struct weston_compositor *compositor = data; - struct content_protection *cp; - struct protected_surface *psurface; - - cp = compositor->content_protection; - cp->surface_protection_update = NULL; - - /* Notify the clients, whose surfaces are changed */ - wl_list_for_each(psurface, &cp->protected_list, link) - if (psurface && psurface->surface) - weston_surface_compute_protection(psurface); -} - -/** - * \param compositor weston_compositor - * - * Schedule an idle task to notify surface about the update in protection, - * if not already scheduled. - */ -static void -weston_schedule_surface_protection_update(struct weston_compositor *compositor) -{ - struct content_protection *cp = compositor->content_protection; - struct wl_event_loop *loop; - - if (!cp || cp->surface_protection_update) - return; - loop = wl_display_get_event_loop(compositor->wl_display); - cp->surface_protection_update = wl_event_loop_add_idle(loop, - notify_surface_protection_change, - compositor); -} - -/** - * \param es The surface - * \param mask The new set of outputs for the surface - * - * Sets the surface's set of outputs to the ones specified by - * the new output mask provided. Identifies the outputs that - * have changed, the posts enter and leave events for these - * outputs as appropriate. - */ -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; - uint32_t output_bit; - struct weston_output *output; - struct weston_head *head; - - es->output_mask = mask; - if (es->resource == NULL) - return; - if (different == 0) - return; - - wl_list_for_each(output, &es->compositor->output_list, link) { - output_bit = 1u << output->id; - if (!(output_bit & different)) - continue; - - wl_list_for_each(head, &output->head_list, output_link) { - weston_surface_send_enter_leave(es, head, - output_bit & entered, - output_bit & left); - } - } - /* - * Change in surfaces' output mask might trigger a change in its - * protection. - */ - weston_schedule_surface_protection_update(es->compositor); -} - -static void -notify_view_output_destroy(struct wl_listener *listener, void *data) -{ - struct weston_view *view = - container_of(listener, - struct weston_view, output_destroy_listener); - - view->output = NULL; - view->output_destroy_listener.notify = NULL; -} - -/** Set the primary output of the view - * - * \param view The view whose primary output to set - * \param output The new primary output for the view - * - * Set \a output to be the primary output of the \a view. - * - * Notice that the assignment may be temporary; the primary output could be - * automatically changed. Hence, one cannot rely on the value persisting. - * - * Passing NULL as /a output will set the primary output to NULL. - */ -WL_EXPORT void -weston_view_set_output(struct weston_view *view, struct weston_output *output) -{ - if (view->output_destroy_listener.notify) { - wl_list_remove(&view->output_destroy_listener.link); - view->output_destroy_listener.notify = NULL; - } - view->output = output; - if (output) { - view->output_destroy_listener.notify = - notify_view_output_destroy; - wl_signal_add(&output->destroy_signal, - &view->output_destroy_listener); - } -} - -/** Recalculate which output(s) the surface has views displayed on - * - * \param es The surface to remap to outputs - * - * Finds the output that is showing the largest amount of one - * of the surface's various views. This output becomes the - * surface's primary output for vsync and frame callback purposes. - * - * Also notes all outputs of all of the surface's views - * in the output_mask for the surface. - */ -static void -weston_surface_assign_output(struct weston_surface *es) -{ - struct weston_output *new_output; - struct weston_view *view; - 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(view, &es->views, surface_link) { - if (!view->output) - continue; - - pixman_region32_intersect(®ion, &view->transform.boundingbox, - &view->output->region); - - e = pixman_region32_extents(®ion); - area = (e->x2 - e->x1) * (e->y2 - e->y1); - - mask |= view->output_mask; - - if (area >= max) { - new_output = view->output; - max = area; - } - } - pixman_region32_fini(®ion); - - es->output = new_output; - weston_surface_update_output_mask(es, mask); -} - -/** Recalculate which output(s) the view is displayed on - * - * \param ev The view to remap to outputs - * - * Identifies the set of outputs that the view is visible on, - * noting them into the output_mask. The output that the view - * is most visible on is set as the view's primary output. - * - * Also does the same for the view's surface. See - * weston_surface_assign_output(). - */ -static void -weston_view_assign_output(struct weston_view *ev) -{ - struct weston_compositor *ec = ev->surface->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) { - if (output->destroying) - continue; - - pixman_region32_intersect(®ion, &ev->transform.boundingbox, - &output->region); - - e = pixman_region32_extents(®ion); - area = (e->x2 - e->x1) * (e->y2 - e->y1); - - if (area > 0) - mask |= 1u << output->id; - - if (area >= max) { - new_output = output; - max = area; - } - } - pixman_region32_fini(®ion); - - weston_view_set_output(ev, new_output); - ev->output_mask = mask; - - weston_surface_assign_output(ev->surface); -} - -static void -weston_view_to_view_map(struct weston_view *from, struct weston_view *to, - int from_x, int from_y, int *to_x, int *to_y) -{ - float x, y; - - weston_view_to_global_float(from, from_x, from_y, &x, &y); - weston_view_from_global_float(to, x, y, &x, &y); - - *to_x = round(x); - *to_y = round(y); -} - -static void -weston_view_transfer_scissor(struct weston_view *from, struct weston_view *to) -{ - pixman_box32_t *a; - pixman_box32_t b; - - a = pixman_region32_extents(&from->geometry.scissor); - - weston_view_to_view_map(from, to, a->x1, a->y1, &b.x1, &b.y1); - weston_view_to_view_map(from, to, a->x2, a->y2, &b.x2, &b.y2); - - pixman_region32_fini(&to->geometry.scissor); - pixman_region32_init_with_extents(&to->geometry.scissor, &b); -} - -static void -view_compute_bbox(struct weston_view *view, const pixman_box32_t *inbox, - 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] = { - { inbox->x1, inbox->y1 }, - { inbox->x1, inbox->y2 }, - { inbox->x2, inbox->y1 }, - { inbox->x2, inbox->y2 }, - }; - float int_x, int_y; - int i; - - if (inbox->x1 == inbox->x2 || inbox->y1 == inbox->y2) { - /* avoid rounding empty bbox to 1x1 */ - pixman_region32_init(bbox); - return; - } - - for (i = 0; i < 4; ++i) { - float x, y; - weston_view_to_global_float(view, 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_view_update_transform_disable(struct weston_view *view) -{ - view->transform.enabled = 0; - - /* round off fractions when not transformed */ - view->geometry.x = roundf(view->geometry.x); - view->geometry.y = roundf(view->geometry.y); - - /* Otherwise identity matrix, but with x and y translation. */ - view->transform.position.matrix.type = WESTON_MATRIX_TRANSFORM_TRANSLATE; - view->transform.position.matrix.d[12] = view->geometry.x; - view->transform.position.matrix.d[13] = view->geometry.y; - - view->transform.matrix = view->transform.position.matrix; - - view->transform.inverse = view->transform.position.matrix; - view->transform.inverse.d[12] = -view->geometry.x; - view->transform.inverse.d[13] = -view->geometry.y; - - pixman_region32_init_rect(&view->transform.boundingbox, - 0, 0, - view->surface->width, - view->surface->height); - if (view->geometry.scissor_enabled) - pixman_region32_intersect(&view->transform.boundingbox, - &view->transform.boundingbox, - &view->geometry.scissor); - - pixman_region32_translate(&view->transform.boundingbox, - view->geometry.x, view->geometry.y); - - if (view->alpha == 1.0) { - pixman_region32_copy(&view->transform.opaque, - &view->surface->opaque); - pixman_region32_translate(&view->transform.opaque, - view->geometry.x, - view->geometry.y); - } -} - -static int -weston_view_update_transform_enable(struct weston_view *view) -{ - struct weston_view *parent = view->geometry.parent; - struct weston_matrix *matrix = &view->transform.matrix; - struct weston_matrix *inverse = &view->transform.inverse; - struct weston_transform *tform; - pixman_region32_t surfregion; - const pixman_box32_t *surfbox; - - view->transform.enabled = 1; - - /* Otherwise identity matrix, but with x and y translation. */ - view->transform.position.matrix.type = WESTON_MATRIX_TRANSFORM_TRANSLATE; - view->transform.position.matrix.d[12] = view->geometry.x; - view->transform.position.matrix.d[13] = view->geometry.y; - - weston_matrix_init(matrix); - wl_list_for_each(tform, &view->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_view %p" - " transformation not invertible.\n", view); - return -1; - } - - if (view->alpha == 1.0 && - matrix->type == WESTON_MATRIX_TRANSFORM_TRANSLATE) { - pixman_region32_copy(&view->transform.opaque, - &view->surface->opaque); - pixman_region32_translate(&view->transform.opaque, - matrix->d[12], - matrix->d[13]); - } - - pixman_region32_init_rect(&surfregion, 0, 0, - view->surface->width, view->surface->height); - if (view->geometry.scissor_enabled) - pixman_region32_intersect(&surfregion, &surfregion, - &view->geometry.scissor); - surfbox = pixman_region32_extents(&surfregion); - - view_compute_bbox(view, surfbox, &view->transform.boundingbox); - pixman_region32_fini(&surfregion); - - return 0; -} - -static struct weston_layer * -get_view_layer(struct weston_view *view) -{ - if (view->parent_view) - return get_view_layer(view->parent_view); - return view->layer_link.layer; -} - -WL_EXPORT void -weston_view_update_transform(struct weston_view *view) -{ - struct weston_view *parent = view->geometry.parent; - struct weston_layer *layer; - pixman_region32_t mask; - - if (!view->transform.dirty) - return; - - if (parent) - weston_view_update_transform(parent); - - view->transform.dirty = 0; - - weston_view_damage_below(view); - - pixman_region32_fini(&view->transform.boundingbox); - pixman_region32_fini(&view->transform.opaque); - pixman_region32_init(&view->transform.opaque); - - /* transform.position is always in transformation_list */ - if (view->geometry.transformation_list.next == - &view->transform.position.link && - view->geometry.transformation_list.prev == - &view->transform.position.link && - !parent) { - weston_view_update_transform_disable(view); - } else { - if (weston_view_update_transform_enable(view) < 0) - weston_view_update_transform_disable(view); - } - - layer = get_view_layer(view); - if (layer) { - pixman_region32_init_with_extents(&mask, &layer->mask); - pixman_region32_intersect(&view->transform.boundingbox, - &view->transform.boundingbox, &mask); - pixman_region32_intersect(&view->transform.opaque, - &view->transform.opaque, &mask); - pixman_region32_fini(&mask); - } - - if (parent) { - if (parent->geometry.scissor_enabled) { - view->geometry.scissor_enabled = true; - weston_view_transfer_scissor(parent, view); - } else { - view->geometry.scissor_enabled = false; - } - } - - weston_view_damage_below(view); - - weston_view_assign_output(view); - - wl_signal_emit(&view->surface->compositor->transform_signal, - view->surface); -} - -WL_EXPORT void -weston_view_geometry_dirty(struct weston_view *view) -{ - struct weston_view *child; - - /* - * The invariant: if view->geometry.dirty, then all views - * in view->geometry.child_list have geometry.dirty too. - * Corollary: if not parent->geometry.dirty, then all ancestors - * are not dirty. - */ - - if (view->transform.dirty) - return; - - view->transform.dirty = 1; - - wl_list_for_each(child, &view->geometry.child_list, - geometry.parent_link) - weston_view_geometry_dirty(child); -} - -WL_EXPORT void -weston_view_to_global_fixed(struct weston_view *view, - wl_fixed_t vx, wl_fixed_t vy, - wl_fixed_t *x, wl_fixed_t *y) -{ - float xf, yf; - - weston_view_to_global_float(view, - wl_fixed_to_double(vx), - wl_fixed_to_double(vy), - &xf, &yf); - *x = wl_fixed_from_double(xf); - *y = wl_fixed_from_double(yf); -} - -WL_EXPORT void -weston_view_from_global_float(struct weston_view *view, - float x, float y, float *vx, float *vy) -{ - if (view->transform.enabled) { - struct weston_vector v = { { x, y, 0.0f, 1.0f } }; - - weston_matrix_transform(&view->transform.inverse, &v); - - if (fabsf(v.f[3]) < 1e-6) { - weston_log("warning: numerical instability in " - "weston_view_from_global(), divisor = %g\n", - v.f[3]); - *vx = 0; - *vy = 0; - return; - } - - *vx = v.f[0] / v.f[3]; - *vy = v.f[1] / v.f[3]; - } else { - *vx = x - view->geometry.x; - *vy = y - view->geometry.y; - } -} - -WL_EXPORT void -weston_view_from_global_fixed(struct weston_view *view, - wl_fixed_t x, wl_fixed_t y, - wl_fixed_t *vx, wl_fixed_t *vy) -{ - float vxf, vyf; - - weston_view_from_global_float(view, - wl_fixed_to_double(x), - wl_fixed_to_double(y), - &vxf, &vyf); - *vx = wl_fixed_from_double(vxf); - *vy = wl_fixed_from_double(vyf); -} - -WL_EXPORT void -weston_view_from_global(struct weston_view *view, - int32_t x, int32_t y, int32_t *vx, int32_t *vy) -{ - float vxf, vyf; - - weston_view_from_global_float(view, x, y, &vxf, &vyf); - *vx = floorf(vxf); - *vy = floorf(vyf); -} - -/** - * \param surface The surface to be repainted - * - * Marks the output(s) that the surface is shown on as needing to be - * repainted. See weston_output_schedule_repaint(). - */ -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 & (1u << output->id)) - weston_output_schedule_repaint(output); -} - -/** - * \param view The view to be repainted - * - * Marks the output(s) that the view is shown on as needing to be - * repainted. See weston_output_schedule_repaint(). - */ -WL_EXPORT void -weston_view_schedule_repaint(struct weston_view *view) -{ - struct weston_output *output; - - wl_list_for_each(output, &view->surface->compositor->output_list, link) - if (view->output_mask & (1u << output->id)) - weston_output_schedule_repaint(output); -} - -/** - * XXX: This function does it the wrong way. - * surface->damage is the damage from the client, and causes - * surface_flush_damage() to copy pixels. No window management action can - * cause damage to the client-provided content, warranting re-upload! - * - * Instead of surface->damage, this function should record the damage - * with all the views for this surface to avoid extraneous texture - * uploads. - */ -WL_EXPORT void -weston_surface_damage(struct weston_surface *surface) -{ - pixman_region32_union_rect(&surface->damage, &surface->damage, - 0, 0, surface->width, - surface->height); - - weston_surface_schedule_repaint(surface); -} - -WL_EXPORT void -weston_view_set_position(struct weston_view *view, float x, float y) -{ - if (view->geometry.x == x && view->geometry.y == y) - return; - - view->geometry.x = x; - view->geometry.y = y; - weston_view_geometry_dirty(view); -} - -static void -transform_parent_handle_parent_destroy(struct wl_listener *listener, - void *data) -{ - struct weston_view *view = - container_of(listener, struct weston_view, - geometry.parent_destroy_listener); - - weston_view_set_transform_parent(view, NULL); -} - -WL_EXPORT void -weston_view_set_transform_parent(struct weston_view *view, - struct weston_view *parent) -{ - if (view->geometry.parent) { - wl_list_remove(&view->geometry.parent_destroy_listener.link); - wl_list_remove(&view->geometry.parent_link); - - if (!parent) - view->geometry.scissor_enabled = false; - } - - view->geometry.parent = parent; - - view->geometry.parent_destroy_listener.notify = - transform_parent_handle_parent_destroy; - if (parent) { - wl_signal_add(&parent->destroy_signal, - &view->geometry.parent_destroy_listener); - wl_list_insert(&parent->geometry.child_list, - &view->geometry.parent_link); - } - - weston_view_geometry_dirty(view); -} - -/** Set a clip mask rectangle on a view - * - * \param view The view to set the clip mask on. - * \param x Top-left corner X coordinate of the clip rectangle. - * \param y Top-left corner Y coordinate of the clip rectangle. - * \param width Width of the clip rectangle, non-negative. - * \param height Height of the clip rectangle, non-negative. - * - * A shell may set a clip mask rectangle on a view. Everything outside - * the rectangle is cut away for input and output purposes: it is - * not drawn and cannot be hit by hit-test based input like pointer - * motion or touch-downs. Everything inside the rectangle will behave - * normally. Clients are unaware of clipping. - * - * The rectangle is set in surface-local coordinates. Setting a clip - * mask rectangle does not affect the view position, the view is positioned - * as it would be without a clip. The clip also does not change - * weston_surface::width,height. - * - * The clip mask rectangle is part of transformation inheritance - * (weston_view_set_transform_parent()). A clip set in the root of the - * transformation inheritance tree will affect all views in the tree. - * A clip can be set only on the root view. Attempting to set a clip - * on view that has a transformation parent will fail. Assigning a parent - * to a view that has a clip set will cause the clip to be forgotten. - * - * Because the clip mask is an axis-aligned rectangle, it poses restrictions - * on the additional transformations in the child views. These transformations - * may not rotate the coordinate axes, i.e., only translation and scaling - * are allowed. Violating this restriction causes the clipping to malfunction. - * Furthermore, using scaling may cause rounding errors in child clipping. - * - * The clip mask rectangle is not automatically adjusted based on - * wl_surface.attach dx and dy arguments. - * - * A clip mask rectangle can be set only if the compositor capability - * WESTON_CAP_VIEW_CLIP_MASK is present. - * - * This function sets the clip mask rectangle and schedules a repaint for - * the view. - */ -WL_EXPORT void -weston_view_set_mask(struct weston_view *view, - int x, int y, int width, int height) -{ - struct weston_compositor *compositor = view->surface->compositor; - - if (!(compositor->capabilities & WESTON_CAP_VIEW_CLIP_MASK)) { - weston_log("%s not allowed without capability!\n", __func__); - return; - } - - if (view->geometry.parent) { - weston_log("view %p has a parent, clip forbidden!\n", view); - return; - } - - if (width < 0 || height < 0) { - weston_log("%s: illegal args %d, %d, %d, %d\n", __func__, - x, y, width, height); - return; - } - - pixman_region32_fini(&view->geometry.scissor); - pixman_region32_init_rect(&view->geometry.scissor, x, y, width, height); - view->geometry.scissor_enabled = true; - weston_view_geometry_dirty(view); - weston_view_schedule_repaint(view); -} - -/** Remove the clip mask from a view - * - * \param view The view to remove the clip mask from. - * - * Removed the clip mask rectangle and schedules a repaint. - * - * \sa weston_view_set_mask - */ -WL_EXPORT void -weston_view_set_mask_infinite(struct weston_view *view) -{ - view->geometry.scissor_enabled = false; - weston_view_geometry_dirty(view); - weston_view_schedule_repaint(view); -} - -/* Check if view should be displayed - * - * The indicator is set manually when assigning - * a view to a surface. - * - * This needs reworking. See the thread starting at: - * - * https://lists.freedesktop.org/archives/wayland-devel/2016-June/029656.html - */ -WL_EXPORT bool -weston_view_is_mapped(struct weston_view *view) -{ - return view->is_mapped; -} - -/* Check if view is opaque in specified region - * - * \param view The view to check for opacity. - * \param region The region to check for opacity, in view coordinates. - * - * Returns true if the view is opaque in the specified region, because view - * alpha is 1.0 and either the opaque region set by the client contains the - * specified region, or the buffer pixel format or solid color is opaque. - */ -WL_EXPORT bool -weston_view_is_opaque(struct weston_view *ev, pixman_region32_t *region) -{ - pixman_region32_t r; - bool ret = false; - - if (ev->alpha < 1.0) - return false; - - if (ev->surface->is_opaque) - return true; - - if (ev->transform.dirty) - return false; - - pixman_region32_init(&r); - pixman_region32_subtract(&r, region, &ev->transform.opaque); - - if (!pixman_region32_not_empty(&r)) - ret = true; - - pixman_region32_fini(&r); - - return ret; -} - -/** Check if the view has a valid buffer available - * - * @param ev The view to check if it has a valid buffer. - * - * Returns true if the view has a valid buffer or false otherwise. - */ -WL_EXPORT bool -weston_view_has_valid_buffer(struct weston_view *ev) -{ - return ev->surface->buffer_ref.buffer != NULL; -} - -/** Check if the view matches the entire output - * - * @param ev The view to check. - * @param output The output to check against. - * - * Returns true if the view does indeed matches the entire output. - */ -WL_EXPORT bool -weston_view_matches_output_entirely(struct weston_view *ev, - struct weston_output *output) -{ - pixman_box32_t *extents = - pixman_region32_extents(&ev->transform.boundingbox); - - if (extents->x1 != output->x || - extents->y1 != output->y || - extents->x2 != output->x + output->width || - extents->y2 != output->y + output->height) - return false; - - return true; -} - -/* Check if a surface has a view assigned to it - * - * The indicator is set manually when mapping - * a surface and creating a view for it. - * - * This needs to go. See the thread starting at: - * - * https://lists.freedesktop.org/archives/wayland-devel/2016-June/029656.html - * - */ -WL_EXPORT bool -weston_surface_is_mapped(struct weston_surface *surface) -{ - return surface->is_mapped; -} - -static void -surface_set_size(struct weston_surface *surface, int32_t width, int32_t height) -{ - struct weston_view *view; - - if (surface->width == width && surface->height == height) - return; - - surface->width = width; - surface->height = height; - - wl_list_for_each(view, &surface->views, surface_link) - weston_view_geometry_dirty(view); -} - -WL_EXPORT void -weston_surface_set_size(struct weston_surface *surface, - int32_t width, int32_t height) -{ - assert(!surface->resource); - surface_set_size(surface, width, height); -} - -static int -fixed_round_up_to_int(wl_fixed_t f) -{ - return wl_fixed_to_int(wl_fixed_from_int(1) - 1 + f); -} - -static void -convert_size_by_transform_scale(int32_t *width_out, int32_t *height_out, - int32_t width, int32_t height, - uint32_t transform, - int32_t scale) -{ - assert(scale > 0); - - switch (transform) { - case WL_OUTPUT_TRANSFORM_NORMAL: - case WL_OUTPUT_TRANSFORM_180: - case WL_OUTPUT_TRANSFORM_FLIPPED: - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - *width_out = width / scale; - *height_out = height / scale; - break; - case WL_OUTPUT_TRANSFORM_90: - case WL_OUTPUT_TRANSFORM_270: - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - *width_out = height / scale; - *height_out = width / scale; - break; - default: - assert(0 && "invalid transform"); - } -} - -static void -weston_surface_calculate_size_from_buffer(struct weston_surface *surface) -{ - struct weston_buffer_viewport *vp = &surface->buffer_viewport; - - if (!surface->buffer_ref.buffer) { - surface->width_from_buffer = 0; - surface->height_from_buffer = 0; - return; - } - - convert_size_by_transform_scale(&surface->width_from_buffer, - &surface->height_from_buffer, - surface->buffer_ref.buffer->width, - surface->buffer_ref.buffer->height, - vp->buffer.transform, - vp->buffer.scale); -} - -static void -weston_surface_update_size(struct weston_surface *surface) -{ - struct weston_buffer_viewport *vp = &surface->buffer_viewport; - int32_t width, height; - - width = surface->width_from_buffer; - height = surface->height_from_buffer; - - if (width != 0 && vp->surface.width != -1) { - surface_set_size(surface, - vp->surface.width, vp->surface.height); - return; - } - - if (width != 0 && vp->buffer.src_width != wl_fixed_from_int(-1)) { - int32_t w = fixed_round_up_to_int(vp->buffer.src_width); - int32_t h = fixed_round_up_to_int(vp->buffer.src_height); - - surface_set_size(surface, w ?: 1, h ?: 1); - return; - } - - surface_set_size(surface, width, height); -} - -/** weston_compositor_get_time - * \ingroup compositor - */ -WL_EXPORT void -weston_compositor_get_time(struct timespec *time) -{ - clock_gettime(CLOCK_REALTIME, time); -} - -/** weston_compositor_pick_view - * \ingroup compositor - */ -WL_EXPORT struct weston_view * -weston_compositor_pick_view(struct weston_compositor *compositor, - wl_fixed_t x, wl_fixed_t y, - wl_fixed_t *vx, wl_fixed_t *vy) -{ - struct weston_view *view; - wl_fixed_t view_x, view_y; - int view_ix, view_iy; - int ix = wl_fixed_to_int(x); - int iy = wl_fixed_to_int(y); - - wl_list_for_each(view, &compositor->view_list, link) { - if (!pixman_region32_contains_point( - &view->transform.boundingbox, ix, iy, NULL)) - continue; - - weston_view_from_global_fixed(view, x, y, &view_x, &view_y); - view_ix = wl_fixed_to_int(view_x); - view_iy = wl_fixed_to_int(view_y); - - if (!pixman_region32_contains_point(&view->surface->input, - view_ix, view_iy, NULL)) - continue; - - if (view->geometry.scissor_enabled && - !pixman_region32_contains_point(&view->geometry.scissor, - view_ix, view_iy, NULL)) - continue; - - *vx = view_x; - *vy = view_y; - return view; - } - - *vx = wl_fixed_from_int(-1000000); - *vy = wl_fixed_from_int(-1000000); - return NULL; -} - -static void -weston_compositor_repick(struct weston_compositor *compositor) -{ - struct weston_seat *seat; - - if (!compositor->session_active) - return; - - wl_list_for_each(seat, &compositor->seat_list, link) - weston_seat_repick(seat); -} - -WL_EXPORT void -weston_view_unmap(struct weston_view *view) -{ - struct weston_seat *seat; - - if (!weston_view_is_mapped(view)) - return; - - weston_view_damage_below(view); - weston_view_set_output(view, NULL); - view->plane = NULL; - view->is_mapped = false; - weston_layer_entry_remove(&view->layer_link); - wl_list_remove(&view->link); - wl_list_init(&view->link); - view->output_mask = 0; - weston_surface_assign_output(view->surface); - - if (weston_surface_is_mapped(view->surface)) - return; - - wl_list_for_each(seat, &view->surface->compositor->seat_list, link) { - struct weston_touch *touch = weston_seat_get_touch(seat); - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(seat); - - if (keyboard && keyboard->focus == view->surface) - weston_keyboard_set_focus(keyboard, NULL); - if (pointer && pointer->focus == view) - weston_pointer_clear_focus(pointer); - if (touch && touch->focus == view) - weston_touch_set_focus(touch, NULL); - } -} - -WL_EXPORT void -weston_surface_unmap(struct weston_surface *surface) -{ - struct weston_view *view; - - surface->is_mapped = false; - wl_list_for_each(view, &surface->views, surface_link) - weston_view_unmap(view); - surface->output = NULL; -} - -static void -weston_surface_reset_pending_buffer(struct weston_surface *surface) -{ - weston_surface_state_set_buffer(&surface->pending, NULL); - surface->pending.sx = 0; - surface->pending.sy = 0; - surface->pending.newly_attached = 0; - surface->pending.buffer_viewport.changed = 0; -} - -WL_EXPORT void -weston_view_destroy(struct weston_view *view) -{ - wl_signal_emit(&view->destroy_signal, view); - - assert(wl_list_empty(&view->geometry.child_list)); - - if (weston_view_is_mapped(view)) { - weston_view_unmap(view); - weston_compositor_build_view_list(view->surface->compositor); - } - - wl_list_remove(&view->link); - weston_layer_entry_remove(&view->layer_link); - - pixman_region32_fini(&view->clip); - pixman_region32_fini(&view->geometry.scissor); - pixman_region32_fini(&view->transform.boundingbox); - pixman_region32_fini(&view->transform.opaque); - - weston_view_set_transform_parent(view, NULL); - weston_view_set_output(view, NULL); - - wl_list_remove(&view->surface_link); - - free(view); -} - -WL_EXPORT void -weston_surface_destroy(struct weston_surface *surface) -{ - struct weston_frame_callback *cb, *next; - struct weston_view *ev, *nv; - struct weston_pointer_constraint *constraint, *next_constraint; - - if (--surface->ref_count > 0) - return; - - assert(surface->resource == NULL); - - wl_signal_emit(&surface->destroy_signal, surface); - - assert(wl_list_empty(&surface->subsurface_list_pending)); - assert(wl_list_empty(&surface->subsurface_list)); - - wl_list_for_each_safe(ev, nv, &surface->views, surface_link) - weston_view_destroy(ev); - - weston_surface_state_fini(&surface->pending); - - weston_buffer_reference(&surface->buffer_ref, NULL); - weston_buffer_release_reference(&surface->buffer_release_ref, NULL); - - pixman_region32_fini(&surface->damage); - pixman_region32_fini(&surface->opaque); - pixman_region32_fini(&surface->input); - - wl_list_for_each_safe(cb, next, &surface->frame_callback_list, link) - wl_resource_destroy(cb->resource); - - weston_presentation_feedback_discard_list(&surface->feedback_list); - - wl_list_for_each_safe(constraint, next_constraint, - &surface->pointer_constraints, - link) - weston_pointer_constraint_destroy(constraint); - - fd_clear(&surface->acquire_fence_fd); - - free(surface); -} - -static void -destroy_surface(struct wl_resource *resource) -{ - struct weston_surface *surface = wl_resource_get_user_data(resource); - - assert(surface); - - /* Set the resource to NULL, since we don't want to leave a - * dangling pointer if the surface was refcounted and survives - * the weston_surface_destroy() call. */ - surface->resource = NULL; - - if (surface->viewport_resource) - wl_resource_set_user_data(surface->viewport_resource, NULL); - - if (surface->synchronization_resource) { - wl_resource_set_user_data(surface->synchronization_resource, - NULL); - } - - 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); -} - -WL_EXPORT 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_buffer_send_release(ref->buffer->resource); - } - 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_buffer_release_reference_handle_destroy(struct wl_listener *listener, - void *data) -{ - struct weston_buffer_release_reference *ref = - container_of(listener, struct weston_buffer_release_reference, - destroy_listener); - - assert((struct wl_resource *)data == ref->buffer_release->resource); - ref->buffer_release = NULL; -} - -static void -weston_buffer_release_destroy(struct weston_buffer_release *buffer_release) -{ - struct wl_resource *resource = buffer_release->resource; - int release_fence_fd = buffer_release->fence_fd; - - if (release_fence_fd >= 0) { - zwp_linux_buffer_release_v1_send_fenced_release( - resource, release_fence_fd); - } else { - zwp_linux_buffer_release_v1_send_immediate_release( - resource); - } - - wl_resource_destroy(resource); -} - -WL_EXPORT void -weston_buffer_release_reference(struct weston_buffer_release_reference *ref, - struct weston_buffer_release *buffer_release) -{ - if (buffer_release == ref->buffer_release) - return; - - if (ref->buffer_release) { - ref->buffer_release->ref_count--; - wl_list_remove(&ref->destroy_listener.link); - if (ref->buffer_release->ref_count == 0) - weston_buffer_release_destroy(ref->buffer_release); - } - - if (buffer_release) { - buffer_release->ref_count++; - wl_resource_add_destroy_listener(buffer_release->resource, - &ref->destroy_listener); - } - - ref->buffer_release = buffer_release; - ref->destroy_listener.notify = - weston_buffer_release_reference_handle_destroy; -} - -WL_EXPORT void -weston_buffer_release_move(struct weston_buffer_release_reference *dest, - struct weston_buffer_release_reference *src) -{ - weston_buffer_release_reference(dest, src->buffer_release); - weston_buffer_release_reference(src, NULL); -} - -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); - - weston_surface_calculate_size_from_buffer(surface); - weston_presentation_feedback_discard_list(&surface->feedback_list); -} - -/** weston_compositor_damage_all - * \ingroup compositor - */ -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); -} - -/** - * \ingroup 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_flush_damage(struct weston_surface *surface) -{ - if (surface->buffer_ref.buffer && - wl_shm_buffer_get(surface->buffer_ref.buffer->resource)) - surface->compositor->renderer->flush_damage(surface); - -// OHOS remove timeline -// if (pixman_region32_not_empty(&surface->damage)) -// TL_POINT(surface->compositor, "core_flush_damage", TLP_SURFACE(surface), -// TLP_OUTPUT(surface->output), TLP_END); - - pixman_region32_clear(&surface->damage); -} - -static void -view_accumulate_damage(struct weston_view *view, - pixman_region32_t *opaque) -{ - pixman_region32_t damage; - - pixman_region32_init(&damage); - if (view->transform.enabled) { - pixman_box32_t *extents; - - extents = pixman_region32_extents(&view->surface->damage); - view_compute_bbox(view, extents, &damage); - } else { - pixman_region32_copy(&damage, &view->surface->damage); - pixman_region32_translate(&damage, - view->geometry.x, view->geometry.y); - } - - pixman_region32_intersect(&damage, &damage, - &view->transform.boundingbox); - pixman_region32_subtract(&damage, &damage, opaque); - pixman_region32_union(&view->plane->damage, - &view->plane->damage, &damage); - pixman_region32_fini(&damage); - pixman_region32_copy(&view->clip, opaque); - pixman_region32_union(opaque, opaque, &view->transform.opaque); -} - -static void -output_accumulate_damage(struct weston_output *output) -{ - struct weston_compositor *ec = output->compositor; - struct weston_plane *plane; - struct weston_view *ev; - 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(ev, &ec->view_list, link) { - if (ev->plane != plane) - continue; - - view_accumulate_damage(ev, &opaque); - } - - pixman_region32_union(&clip, &clip, &opaque); - pixman_region32_fini(&opaque); - } - - pixman_region32_fini(&clip); - - wl_list_for_each(ev, &ec->view_list, link) - ev->surface->touched = false; - - wl_list_for_each(ev, &ec->view_list, link) { - /* Ignore views not visible on the current output */ - if (!(ev->output_mask & (1u << output->id))) - continue; - if (ev->surface->touched) - continue; - ev->surface->touched = true; - - surface_flush_damage(ev->surface); - - /* 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 (!ev->surface->keep_buffer) { - weston_buffer_reference(&ev->surface->buffer_ref, NULL); - weston_buffer_release_reference( - &ev->surface->buffer_release_ref, NULL); - } - } -} - -static void -surface_stash_subsurface_views(struct weston_surface *surface) -{ - struct weston_subsurface *sub; - - wl_list_for_each(sub, &surface->subsurface_list, parent_link) { - if (sub->surface == surface) - continue; - - wl_list_insert_list(&sub->unused_views, &sub->surface->views); - wl_list_init(&sub->surface->views); - - surface_stash_subsurface_views(sub->surface); - } -} - -static void -surface_free_unused_subsurface_views(struct weston_surface *surface) -{ - struct weston_subsurface *sub; - struct weston_view *view, *nv; - - wl_list_for_each(sub, &surface->subsurface_list, parent_link) { - if (sub->surface == surface) - continue; - - wl_list_for_each_safe(view, nv, &sub->unused_views, surface_link) { - weston_view_unmap (view); - weston_view_destroy(view); - } - - surface_free_unused_subsurface_views(sub->surface); - } -} - -static void -view_list_add_subsurface_view(struct weston_compositor *compositor, - struct weston_subsurface *sub, - struct weston_view *parent) -{ - struct weston_subsurface *child; - struct weston_view *view = NULL, *iv; - - if (!weston_surface_is_mapped(sub->surface)) - return; - - wl_list_for_each(iv, &sub->unused_views, surface_link) { - if (iv->geometry.parent == parent) { - view = iv; - break; - } - } - - if (view) { - /* Put it back in the surface's list of views */ - wl_list_remove(&view->surface_link); - wl_list_insert(&sub->surface->views, &view->surface_link); - } else { - view = weston_view_create(sub->surface); - weston_view_set_position(view, - sub->position.x, - sub->position.y); - weston_view_set_transform_parent(view, parent); - } - - view->parent_view = parent; - weston_view_update_transform(view); - view->is_mapped = true; - - if (wl_list_empty(&sub->surface->subsurface_list)) { - wl_list_insert(compositor->view_list.prev, &view->link); - return; - } - - wl_list_for_each(child, &sub->surface->subsurface_list, parent_link) { - if (child->surface == sub->surface) - wl_list_insert(compositor->view_list.prev, &view->link); - else - view_list_add_subsurface_view(compositor, child, view); - } -} - -/* This recursively adds the sub-surfaces for a view, relying on the - * sub-surface order. Thus, if a client restacks the sub-surfaces, that - * change first happens to the sub-surface list, and then automatically - * propagates here. See weston_surface_damage_subsurfaces() for how the - * sub-surfaces receive damage when the client changes the state. - */ -static void -view_list_add(struct weston_compositor *compositor, - struct weston_view *view) -{ - struct weston_subsurface *sub; - - weston_view_update_transform(view); - - if (wl_list_empty(&view->surface->subsurface_list)) { - wl_list_insert(compositor->view_list.prev, &view->link); - return; - } - - wl_list_for_each(sub, &view->surface->subsurface_list, parent_link) { - if (sub->surface == view->surface) - wl_list_insert(compositor->view_list.prev, &view->link); - else - view_list_add_subsurface_view(compositor, sub, view); - } -} - -static void -weston_compositor_build_view_list(struct weston_compositor *compositor) -{ - struct weston_view *view, *tmp; - struct weston_layer *layer; - - wl_list_for_each(layer, &compositor->layer_list, link) - wl_list_for_each(view, &layer->view_list.link, layer_link.link) - surface_stash_subsurface_views(view->surface); - - wl_list_for_each_safe(view, tmp, &compositor->view_list, link) - wl_list_init(&view->link); - wl_list_init(&compositor->view_list); - - wl_list_for_each(layer, &compositor->layer_list, link) { - wl_list_for_each(view, &layer->view_list.link, layer_link.link) { - view_list_add(compositor, view); - } - } - - wl_list_for_each(layer, &compositor->layer_list, link) - wl_list_for_each(view, &layer->view_list.link, layer_link.link) - surface_free_unused_subsurface_views(view->surface); -} - -static void -weston_output_take_feedback_list(struct weston_output *output, - struct weston_surface *surface) -{ - struct weston_view *view; - struct weston_presentation_feedback *feedback; - uint32_t flags = 0xffffffff; - - if (wl_list_empty(&surface->feedback_list)) - return; - - /* All views must have the flag for the flag to survive. */ - wl_list_for_each(view, &surface->views, surface_link) { - /* ignore views that are not on this output at all */ - if (view->output_mask & (1u << output->id)) - flags &= view->psf_flags; - } - - wl_list_for_each(feedback, &surface->feedback_list, link) - feedback->psf_flags = flags; - - wl_list_insert_list(&output->feedback_list, &surface->feedback_list); - wl_list_init(&surface->feedback_list); -} - -static int -weston_output_repaint(struct weston_output *output, void *repaint_data) -{ - struct weston_compositor *ec = output->compositor; - struct weston_view *ev; - struct weston_animation *animation, *next; - struct weston_frame_callback *cb, *cnext; - struct wl_list frame_callback_list; - pixman_region32_t output_damage; - int r; - uint32_t frame_time_msec; - enum weston_hdcp_protection highest_requested = WESTON_HDCP_DISABLE; - - if (output->destroying) - return 0; - -// OHOS remove timeline -// TL_POINT(ec, "core_repaint_begin", TLP_OUTPUT(output), TLP_END); - - /* Rebuild the surface list and update surface transforms up front. */ - weston_compositor_build_view_list(ec); - - /* Find the highest protection desired for an output */ - wl_list_for_each(ev, &ec->view_list, link) { - if (ev->surface->output_mask & (1u << output->id)) { - /* - * The desired_protection of the output should be the - * maximum of the desired_protection of the surfaces, - * that are displayed on that output, to avoid - * reducing the protection for existing surfaces. - */ - if (ev->surface->desired_protection > highest_requested) - highest_requested = - ev->surface->desired_protection; - } - } - - output->desired_protection = highest_requested; - - if (output->assign_planes && !output->disable_planes) { - output->assign_planes(output, repaint_data); - } else { - wl_list_for_each(ev, &ec->view_list, link) { - weston_view_move_to_plane(ev, &ec->primary_plane); - ev->psf_flags = 0; - } - } - - wl_list_init(&frame_callback_list); - wl_list_for_each(ev, &ec->view_list, link) { - /* Note: This operation is safe to do multiple times on the - * same surface. - */ - if (ev->surface->output == output) { - wl_list_insert_list(&frame_callback_list, - &ev->surface->frame_callback_list); - wl_list_init(&ev->surface->frame_callback_list); - - weston_output_take_feedback_list(output, ev->surface); - } - } - - output_accumulate_damage(output); - - 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, repaint_data); - - pixman_region32_fini(&output_damage); - - output->repaint_needed = false; - if (r == 0) - output->repaint_status = REPAINT_AWAITING_COMPLETION; - - weston_compositor_repick(ec); - - frame_time_msec = timespec_to_msec(&output->frame_time); - - wl_list_for_each_safe(cb, cnext, &frame_callback_list, link) { - wl_callback_send_done(cb->resource, frame_time_msec); - wl_resource_destroy(cb->resource); - } - - wl_list_for_each_safe(animation, next, &output->animation_list, link) { - animation->frame_counter++; - animation->frame(animation, output, &output->frame_time); - } - -// OHOS remove timeline -// TL_POINT(ec, "core_repaint_posted", TLP_OUTPUT(output), TLP_END); - - return r; -} - -static void -weston_output_schedule_repaint_reset(struct weston_output *output) -{ - output->repaint_status = REPAINT_NOT_SCHEDULED; -// OHOS remove timeline -// TL_POINT(output->compositor, "core_repaint_exit_loop", -// TLP_OUTPUT(output), TLP_END); -} - -static int -weston_output_maybe_repaint(struct weston_output *output, struct timespec *now, - void *repaint_data) -{ - struct weston_compositor *compositor = output->compositor; - int ret = 0; - int64_t msec_to_repaint; - - /* We're not ready yet; come back to make a decision later. */ - if (output->repaint_status != REPAINT_SCHEDULED) - return ret; - - msec_to_repaint = timespec_sub_to_msec(&output->next_repaint, now); - if (msec_to_repaint > 1) - return ret; - - /* If we're sleeping, drop the repaint machinery entirely; we will - * explicitly repaint all outputs when we come back. */ - if (compositor->state == WESTON_COMPOSITOR_SLEEPING || - compositor->state == WESTON_COMPOSITOR_OFFSCREEN) - goto err; - - /* We don't actually need to repaint this output; drop it from - * repaint until something causes damage. */ - if (!output->repaint_needed) - goto err; - - /* If repaint fails, we aren't going to get weston_output_finish_frame - * to trigger a new repaint, so drop it from repaint and hope - * something schedules a successful repaint later. As repainting may - * take some time, re-read our clock as a courtesy to the next - * output. */ - ret = weston_output_repaint(output, repaint_data); - weston_compositor_read_presentation_clock(compositor, now); - if (ret != 0) - goto err; - - output->repainted = true; - return ret; - -err: - weston_output_schedule_repaint_reset(output); - return ret; -} - -static void -output_repaint_timer_arm(struct weston_compositor *compositor) -{ - struct weston_output *output; - bool any_should_repaint = false; - struct timespec now; - int64_t msec_to_next = INT64_MAX; - - weston_compositor_read_presentation_clock(compositor, &now); - - wl_list_for_each(output, &compositor->output_list, link) { - int64_t msec_to_this; - - if (output->repaint_status != REPAINT_SCHEDULED) - continue; - - msec_to_this = timespec_sub_to_msec(&output->next_repaint, - &now); - if (!any_should_repaint || msec_to_this < msec_to_next) - msec_to_next = msec_to_this; - - any_should_repaint = true; - } - - if (!any_should_repaint) - return; - - /* Even if we should repaint immediately, add the minimum 1 ms delay. - * This is a workaround to allow coalescing multiple output repaints - * particularly from weston_output_finish_frame() - * into the same call, which would not happen if we called - * output_repaint_timer_handler() directly. - */ - if (msec_to_next < 1) - msec_to_next = 1; - - wl_event_source_timer_update(compositor->repaint_timer, msec_to_next); -} - -static int -output_repaint_timer_handler(void *data) -{ - struct weston_compositor *compositor = data; - struct weston_output *output; - struct timespec now; - void *repaint_data = NULL; - int ret = 0; - - weston_compositor_read_presentation_clock(compositor, &now); - - if (compositor->backend->repaint_begin) - repaint_data = compositor->backend->repaint_begin(compositor); - - wl_list_for_each(output, &compositor->output_list, link) { - ret = weston_output_maybe_repaint(output, &now, repaint_data); - if (ret) - break; - } - - if (ret == 0) { - if (compositor->backend->repaint_flush) - ret = compositor->backend->repaint_flush(compositor, - repaint_data); - } else { - if (compositor->backend->repaint_cancel) - compositor->backend->repaint_cancel(compositor, - repaint_data); - } - - if (ret != 0) { - wl_list_for_each(output, &compositor->output_list, link) { - if (output->repainted) - weston_output_schedule_repaint_reset(output); - } - } - - wl_list_for_each(output, &compositor->output_list, link) - output->repainted = false; - - output_repaint_timer_arm(compositor); - - return 0; -} - -/** Convert a presentation timestamp to another clock domain - * - * \param compositor The compositor defines the presentation clock domain. - * \param presentation_stamp The timestamp in presentation clock domain. - * \param presentation_now Current time in presentation clock domain. - * \param target_clock Defines the target clock domain. - * - * This approximation relies on presentation_stamp to be close to current time. - * The further it is from current time and the bigger the speed difference - * between the two clock domains, the bigger the conversion error. - * - * Conversion error due to system load is biased and unbounded. - */ -static struct timespec -convert_presentation_time_now(struct weston_compositor *compositor, - const struct timespec *presentation_stamp, - const struct timespec *presentation_now, - clockid_t target_clock) -{ - struct timespec target_now = {}; - struct timespec target_stamp; - int64_t delta_ns; - - if (compositor->presentation_clock == target_clock) - return *presentation_stamp; - - clock_gettime(target_clock, &target_now); - delta_ns = timespec_sub_to_nsec(presentation_stamp, presentation_now); - timespec_add_nsec(&target_stamp, &target_now, delta_ns); - - return target_stamp; -} - -/** - * \ingroup output - */ -WL_EXPORT void -weston_output_finish_frame(struct weston_output *output, - const struct timespec *stamp, - uint32_t presented_flags) -{ - struct weston_compositor *compositor = output->compositor; - int32_t refresh_nsec; - struct timespec now; - struct timespec vblank_monotonic; - int64_t msec_rel; - - assert(output->repaint_status == REPAINT_AWAITING_COMPLETION); - assert(stamp || (presented_flags & WP_PRESENTATION_FEEDBACK_INVALID)); - - weston_compositor_read_presentation_clock(compositor, &now); - - /* If we haven't been supplied any timestamp at all, we don't have a - * timebase to work against, so any delay just wastes time. Push a - * repaint as soon as possible so we can get on with it. */ - if (!stamp) { - output->next_repaint = now; - goto out; - } - - vblank_monotonic = convert_presentation_time_now(compositor, - stamp, &now, - CLOCK_MONOTONIC); -// OHOS remove timeline -// TL_POINT(compositor, "core_repaint_finished", TLP_OUTPUT(output), -// TLP_VBLANK(&vblank_monotonic), TLP_END); - - refresh_nsec = millihz_to_nsec(output->current_mode->refresh); - weston_presentation_feedback_present_list(&output->feedback_list, - output, refresh_nsec, stamp, - output->msc, - presented_flags); - - output->frame_time = *stamp; - - timespec_add_nsec(&output->next_repaint, stamp, refresh_nsec); - timespec_add_msec(&output->next_repaint, &output->next_repaint, - -compositor->repaint_msec); - msec_rel = timespec_sub_to_msec(&output->next_repaint, &now); - - if (msec_rel < -1000 || msec_rel > 1000) { - static bool warned; - - if (!warned) - weston_log("Warning: computed repaint delay is " - "insane: %lld msec\n", (long long) msec_rel); - warned = true; - - output->next_repaint = now; - } - - /* Called from restart_repaint_loop and restart happens already after - * the deadline given by repaint_msec? In that case we delay until - * the deadline of the next frame, to give clients a more predictable - * timing of the repaint cycle to lock on. */ - if (presented_flags == WP_PRESENTATION_FEEDBACK_INVALID && - msec_rel < 0) { - while (timespec_sub_to_nsec(&output->next_repaint, &now) < 0) { - timespec_add_nsec(&output->next_repaint, - &output->next_repaint, - refresh_nsec); - } - } - -out: - output->repaint_status = REPAINT_SCHEDULED; - output_repaint_timer_arm(compositor); -} - -static void -idle_repaint(void *data) -{ - struct weston_output *output = data; - int ret; - - assert(output->repaint_status == REPAINT_BEGIN_FROM_IDLE); - output->repaint_status = REPAINT_AWAITING_COMPLETION; - output->idle_repaint_source = NULL; - ret = output->start_repaint_loop(output); - if (ret != 0) - weston_output_schedule_repaint_reset(output); -} - -WL_EXPORT void -weston_layer_entry_insert(struct weston_layer_entry *list, - struct weston_layer_entry *entry) -{ - wl_list_insert(&list->link, &entry->link); - entry->layer = list->layer; -} - -WL_EXPORT void -weston_layer_entry_remove(struct weston_layer_entry *entry) -{ - wl_list_remove(&entry->link); - wl_list_init(&entry->link); - entry->layer = NULL; -} - - -/** Initialize the weston_layer struct. - * - * \param compositor The compositor instance - * \param layer The layer to initialize - */ -WL_EXPORT void -weston_layer_init(struct weston_layer *layer, - struct weston_compositor *compositor) -{ - layer->compositor = compositor; - wl_list_init(&layer->link); - wl_list_init(&layer->view_list.link); - layer->view_list.layer = layer; - weston_layer_set_mask_infinite(layer); -} - -/** Sets the position of the layer in the layer list. The layer will be placed - * below any layer with the same position value, if any. - * This function is safe to call if the layer is already on the list, but the - * layer may be moved below other layers at the same position, if any. - * - * \param layer The layer to modify - * \param position The position the layer will be placed at - */ -WL_EXPORT void -weston_layer_set_position(struct weston_layer *layer, - enum weston_layer_position position) -{ - struct weston_layer *below; - - wl_list_remove(&layer->link); - - /* layer_list is ordered from top to bottom, the last layer being the - * background with the smallest position value */ - - layer->position = position; - wl_list_for_each_reverse(below, &layer->compositor->layer_list, link) { - if (below->position >= layer->position) { - wl_list_insert(&below->link, &layer->link); - return; - } - } - wl_list_insert(&layer->compositor->layer_list, &layer->link); -} - -/** Hide a layer by taking it off the layer list. - * This function is safe to call if the layer is not on the list. - * - * \param layer The layer to hide - */ -WL_EXPORT void -weston_layer_unset_position(struct weston_layer *layer) -{ - wl_list_remove(&layer->link); - wl_list_init(&layer->link); -} - -WL_EXPORT void -weston_layer_set_mask(struct weston_layer *layer, - int x, int y, int width, int height) -{ - struct weston_view *view; - - layer->mask.x1 = x; - layer->mask.x2 = x + width; - layer->mask.y1 = y; - layer->mask.y2 = y + height; - - wl_list_for_each(view, &layer->view_list.link, layer_link.link) { - weston_view_geometry_dirty(view); - } -} - -WL_EXPORT void -weston_layer_set_mask_infinite(struct weston_layer *layer) -{ - struct weston_view *view; - - layer->mask.x1 = INT32_MIN; - layer->mask.x2 = INT32_MAX; - layer->mask.y1 = INT32_MIN; - layer->mask.y2 = INT32_MAX; - - wl_list_for_each(view, &layer->view_list.link, layer_link.link) { - weston_view_geometry_dirty(view); - } -} - -WL_EXPORT bool -weston_layer_mask_is_infinite(struct weston_layer *layer) -{ - return layer->mask.x1 == INT32_MIN && - layer->mask.y1 == INT32_MIN && - layer->mask.x2 == INT32_MAX && - layer->mask.y2 == INT32_MAX; -} - -/** - * \ingroup output - */ -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; - -// OHOS remove timeline -// if (!output->repaint_needed) -// TL_POINT(compositor, "core_repaint_req", TLP_OUTPUT(output), TLP_END); - - loop = wl_display_get_event_loop(compositor->wl_display); - output->repaint_needed = true; - - /* If we already have a repaint scheduled for our idle handler, - * no need to set it again. If the repaint has been called but - * not finished, then weston_output_finish_frame() will notice - * that a repaint is needed and schedule one. */ - if (output->repaint_status != REPAINT_NOT_SCHEDULED) - return; - - output->repaint_status = REPAINT_BEGIN_FROM_IDLE; - assert(!output->idle_repaint_source); - output->idle_repaint_source = wl_event_loop_add_idle(loop, idle_repaint, - output); -// OHOS remove timeline -// TL_POINT(compositor, "core_repaint_enter_loop", TLP_OUTPUT(output), TLP_END); -} - -/** weston_compositor_schedule_repaint - * \ingroup compositor - */ -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. */ - weston_surface_state_set_buffer(&surface->pending, buffer); - - surface->pending.sx = sx; - surface->pending.sy = sy; - surface->pending.newly_attached = 1; -} - -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); - - if (width <= 0 || height <= 0) - return; - - pixman_region32_union_rect(&surface->pending.damage_surface, - &surface->pending.damage_surface, - x, y, width, height); -} - -static void -surface_damage_buffer(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); - - if (width <= 0 || height <= 0) - return; - - pixman_region32_union_rect(&surface->pending.damage_buffer, - &surface->pending.damage_buffer, - x, y, width, height); -} - -// OHOS surface type -static void -surface_set_surface_type(struct wl_client *client, - struct wl_resource *resource, int32_t type) -{ - struct weston_surface *surface = wl_resource_get_user_data(resource); - surface->type = type; -} - -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 { - pixman_region32_clear(&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); - } -} - -/* Cause damage to this sub-surface and all its children. - * - * This is useful when there are state changes that need an implicit - * damage, e.g. a z-order change. - */ -static void -weston_surface_damage_subsurfaces(struct weston_subsurface *sub) -{ - struct weston_subsurface *child; - - weston_surface_damage(sub->surface); - sub->reordered = false; - - wl_list_for_each(child, &sub->surface->subsurface_list, parent_link) - if (child != sub) - weston_surface_damage_subsurfaces(child); -} - -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); - - if (sub->reordered) - weston_surface_damage_subsurfaces(sub); - } -} - -static void -weston_surface_build_buffer_matrix(const struct weston_surface *surface, - struct weston_matrix *matrix) -{ - const struct weston_buffer_viewport *vp = &surface->buffer_viewport; - double src_width, src_height, dest_width, dest_height; - - weston_matrix_init(matrix); - - if (vp->buffer.src_width == wl_fixed_from_int(-1)) { - src_width = surface->width_from_buffer; - src_height = surface->height_from_buffer; - } else { - src_width = wl_fixed_to_double(vp->buffer.src_width); - src_height = wl_fixed_to_double(vp->buffer.src_height); - } - - if (vp->surface.width == -1) { - dest_width = src_width; - dest_height = src_height; - } else { - dest_width = vp->surface.width; - dest_height = vp->surface.height; - } - - if (src_width != dest_width || src_height != dest_height) - weston_matrix_scale(matrix, - src_width / dest_width, - src_height / dest_height, 1); - - if (vp->buffer.src_width != wl_fixed_from_int(-1)) - weston_matrix_translate(matrix, - wl_fixed_to_double(vp->buffer.src_x), - wl_fixed_to_double(vp->buffer.src_y), - 0); - - switch (vp->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: - weston_matrix_scale(matrix, -1, 1, 1); - weston_matrix_translate(matrix, - surface->width_from_buffer, 0, 0); - break; - } - - switch (vp->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: - weston_matrix_rotate_xy(matrix, 0, -1); - weston_matrix_translate(matrix, - 0, surface->width_from_buffer, 0); - break; - case WL_OUTPUT_TRANSFORM_180: - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - weston_matrix_rotate_xy(matrix, -1, 0); - weston_matrix_translate(matrix, - surface->width_from_buffer, - surface->height_from_buffer, 0); - break; - case WL_OUTPUT_TRANSFORM_270: - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - weston_matrix_rotate_xy(matrix, 0, 1); - weston_matrix_translate(matrix, - surface->height_from_buffer, 0, 0); - break; - } - - weston_matrix_scale(matrix, vp->buffer.scale, vp->buffer.scale, 1); -} - -/** - * Compute a + b > c while being safe to overflows. - */ -static bool -fixed_sum_gt(wl_fixed_t a, wl_fixed_t b, wl_fixed_t c) -{ - return (int64_t)a + (int64_t)b > (int64_t)c; -} - -static bool -weston_surface_is_pending_viewport_source_valid( - const struct weston_surface *surface) -{ - const struct weston_surface_state *pend = &surface->pending; - const struct weston_buffer_viewport *vp = &pend->buffer_viewport; - int width_from_buffer = 0; - int height_from_buffer = 0; - wl_fixed_t w; - wl_fixed_t h; - - /* If viewport source rect is not set, it is always ok. */ - if (vp->buffer.src_width == wl_fixed_from_int(-1)) - return true; - - if (pend->newly_attached) { - if (pend->buffer) { - convert_size_by_transform_scale(&width_from_buffer, - &height_from_buffer, - pend->buffer->width, - pend->buffer->height, - vp->buffer.transform, - vp->buffer.scale); - } else { - /* No buffer: viewport is irrelevant. */ - return true; - } - } else { - width_from_buffer = surface->width_from_buffer; - height_from_buffer = surface->height_from_buffer; - } - - assert((width_from_buffer == 0) == (height_from_buffer == 0)); - assert(width_from_buffer >= 0 && height_from_buffer >= 0); - - /* No buffer: viewport is irrelevant. */ - if (width_from_buffer == 0 || height_from_buffer == 0) - return true; - - /* overflow checks for wl_fixed_from_int() */ - if (width_from_buffer > wl_fixed_to_int(INT32_MAX)) - return false; - if (height_from_buffer > wl_fixed_to_int(INT32_MAX)) - return false; - - w = wl_fixed_from_int(width_from_buffer); - h = wl_fixed_from_int(height_from_buffer); - - if (fixed_sum_gt(vp->buffer.src_x, vp->buffer.src_width, w)) - return false; - if (fixed_sum_gt(vp->buffer.src_y, vp->buffer.src_height, h)) - return false; - - return true; -} - -static bool -fixed_is_integer(wl_fixed_t v) -{ - return (v & 0xff) == 0; -} - -static bool -weston_surface_is_pending_viewport_dst_size_int( - const struct weston_surface *surface) -{ - const struct weston_buffer_viewport *vp = - &surface->pending.buffer_viewport; - - if (vp->surface.width != -1) { - assert(vp->surface.width > 0 && vp->surface.height > 0); - return true; - } - - return fixed_is_integer(vp->buffer.src_width) && - fixed_is_integer(vp->buffer.src_height); -} - -/* Translate pending damage in buffer co-ordinates to surface - * co-ordinates and union it with a pixman_region32_t. - * This should only be called after the buffer is attached. - */ -static void -apply_damage_buffer(pixman_region32_t *dest, - struct weston_surface *surface, - struct weston_surface_state *state) -{ - struct weston_buffer *buffer = surface->buffer_ref.buffer; - - /* wl_surface.damage_buffer needs to be clipped to the buffer, - * translated into surface co-ordinates and unioned with - * any other surface damage. - * None of this makes sense if there is no buffer though. - */ - if (buffer && pixman_region32_not_empty(&state->damage_buffer)) { - pixman_region32_t buffer_damage; - - pixman_region32_intersect_rect(&state->damage_buffer, - &state->damage_buffer, - 0, 0, buffer->width, - buffer->height); - pixman_region32_init(&buffer_damage); - weston_matrix_transform_region(&buffer_damage, - &surface->buffer_to_surface_matrix, - &state->damage_buffer); - pixman_region32_union(dest, dest, &buffer_damage); - pixman_region32_fini(&buffer_damage); - } - /* We should clear this on commit even if there was no buffer */ - pixman_region32_clear(&state->damage_buffer); -} - -static void -weston_surface_set_desired_protection(struct weston_surface *surface, - enum weston_hdcp_protection protection) -{ - if (surface->desired_protection == protection) - return; - surface->desired_protection = protection; - weston_surface_damage(surface); -} - -static void -weston_surface_set_protection_mode(struct weston_surface *surface, - enum weston_surface_protection_mode p_mode) -{ - struct content_protection *cp = surface->compositor->content_protection; - struct protected_surface *psurface; - - surface->protection_mode = p_mode; - wl_list_for_each(psurface, &cp->protected_list, link) { - if (!psurface || psurface->surface != surface) - continue; - weston_protected_surface_send_event(psurface, - surface->current_protection); - } -} - -static void -weston_surface_commit_state(struct weston_surface *surface, - struct weston_surface_state *state) -{ - struct weston_view *view; - pixman_region32_t opaque; - - /* wl_surface.set_buffer_transform */ - /* wl_surface.set_buffer_scale */ - /* wp_viewport.set_source */ - /* wp_viewport.set_destination */ - surface->buffer_viewport = state->buffer_viewport; - - /* wl_surface.attach */ - if (state->newly_attached) { - /* zwp_surface_synchronization_v1.set_acquire_fence */ - fd_move(&surface->acquire_fence_fd, - &state->acquire_fence_fd); - /* zwp_surface_synchronization_v1.get_release */ - weston_buffer_release_move(&surface->buffer_release_ref, - &state->buffer_release_ref); - weston_surface_attach(surface, state->buffer); - } - weston_surface_state_set_buffer(state, NULL); - assert(state->acquire_fence_fd == -1); - assert(state->buffer_release_ref.buffer_release == NULL); - - weston_surface_build_buffer_matrix(surface, - &surface->surface_to_buffer_matrix); - weston_matrix_invert(&surface->buffer_to_surface_matrix, - &surface->surface_to_buffer_matrix); - - if (state->newly_attached || state->buffer_viewport.changed) { - weston_surface_update_size(surface); - if (surface->committed) - surface->committed(surface, state->sx, state->sy); - } - - state->sx = 0; - state->sy = 0; - state->newly_attached = 0; - state->buffer_viewport.changed = 0; - -// OHOS remove timeline -// /* wl_surface.damage and wl_surface.damage_buffer */ -// if (pixman_region32_not_empty(&state->damage_surface) || -// pixman_region32_not_empty(&state->damage_buffer)) -// TL_POINT(surface->compositor, "core_commit_damage", TLP_SURFACE(surface), TLP_END); - - pixman_region32_union(&surface->damage, &surface->damage, - &state->damage_surface); - - apply_damage_buffer(&surface->damage, surface, state); - - pixman_region32_intersect_rect(&surface->damage, &surface->damage, - 0, 0, surface->width, surface->height); - pixman_region32_clear(&state->damage_surface); - - /* wl_surface.set_opaque_region */ - pixman_region32_init(&opaque); - pixman_region32_intersect_rect(&opaque, &state->opaque, - 0, 0, surface->width, surface->height); - - if (!pixman_region32_equal(&opaque, &surface->opaque)) { - pixman_region32_copy(&surface->opaque, &opaque); - wl_list_for_each(view, &surface->views, surface_link) - weston_view_geometry_dirty(view); - } - - pixman_region32_fini(&opaque); - - /* wl_surface.set_input_region */ - pixman_region32_intersect_rect(&surface->input, &state->input, - 0, 0, surface->width, surface->height); - - /* wl_surface.frame */ - wl_list_insert_list(&surface->frame_callback_list, - &state->frame_callback_list); - wl_list_init(&state->frame_callback_list); - - /* XXX: - * What should happen with a feedback request, if there - * is no wl_buffer attached for this commit? - */ - - /* presentation.feedback */ - wl_list_insert_list(&surface->feedback_list, - &state->feedback_list); - wl_list_init(&state->feedback_list); - - /* weston_protected_surface.enforced/relaxed */ - if (surface->protection_mode != state->protection_mode) - weston_surface_set_protection_mode(surface, - state->protection_mode); - - /* weston_protected_surface.set_type */ - weston_surface_set_desired_protection(surface, state->desired_protection); - - wl_signal_emit(&surface->commit_signal, surface); -} - -static void -weston_surface_commit(struct weston_surface *surface) -{ - weston_surface_commit_state(surface, &surface->pending); - - 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 (!weston_surface_is_pending_viewport_source_valid(surface)) { - assert(surface->viewport_resource); - - wl_resource_post_error(surface->viewport_resource, - WP_VIEWPORT_ERROR_OUT_OF_BUFFER, - "wl_surface@%d has viewport source outside buffer", - wl_resource_get_id(resource)); - return; - } - - if (!weston_surface_is_pending_viewport_dst_size_int(surface)) { - assert(surface->viewport_resource); - - wl_resource_post_error(surface->viewport_resource, - WP_VIEWPORT_ERROR_BAD_SIZE, - "wl_surface@%d viewport dst size not integer", - wl_resource_get_id(resource)); - return; - } - - if (surface->pending.acquire_fence_fd >= 0) { - assert(surface->synchronization_resource); - - if (!surface->pending.buffer) { - fd_clear(&surface->pending.acquire_fence_fd); - wl_resource_post_error(surface->synchronization_resource, - ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_BUFFER, - "wl_surface@%"PRIu32" no buffer for synchronization", - wl_resource_get_id(resource)); - return; - } - - /* We support fences for both wp_linux_dmabuf and opaque EGL - * buffers, as mandated by minor version 2 of the - * zwp_linux_explicit_synchronization_v1 protocol. Since - * renderers that support fences currently only support these - * two buffer types plus SHM buffers, we can just check for the - * SHM buffer case here. - */ - if (wl_shm_buffer_get(surface->pending.buffer->resource)) { - fd_clear(&surface->pending.acquire_fence_fd); - wl_resource_post_error(surface->synchronization_resource, - ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_UNSUPPORTED_BUFFER, - "wl_surface@%"PRIu32" unsupported buffer for synchronization", - wl_resource_get_id(resource)); - return; - } - } - - if (surface->pending.buffer_release_ref.buffer_release && - !surface->pending.buffer) { - assert(surface->synchronization_resource); - - wl_resource_post_error(surface->synchronization_resource, - ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_BUFFER, - "wl_surface@%"PRIu32" no buffer for synchronization", - wl_resource_get_id(resource)); - return; - } - - 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); - - /* if wl_output.transform grows more members this will need to be updated. */ - if (transform < 0 || - transform > WL_OUTPUT_TRANSFORM_FLIPPED_270) { - wl_resource_post_error(resource, - WL_SURFACE_ERROR_INVALID_TRANSFORM, - "buffer transform must be a valid transform " - "('%d' specified)", transform); - return; - } - - surface->pending.buffer_viewport.buffer.transform = transform; - surface->pending.buffer_viewport.changed = 1; -} - -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); - - if (scale < 1) { - wl_resource_post_error(resource, - WL_SURFACE_ERROR_INVALID_SCALE, - "buffer scale must be at least one " - "('%d' specified)", scale); - return; - } - - surface->pending.buffer_viewport.buffer.scale = scale; - surface->pending.buffer_viewport.changed = 1; -} - -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, - surface_damage_buffer, - surface_set_surface_type // OHOS surface type -}; - -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); - - wl_signal_emit(&ec->create_surface_signal, 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; - - weston_surface_commit_state(surface, &sub->cached); - weston_buffer_reference(&sub->cached_buffer_ref, NULL); - - weston_surface_commit_subsurface_order(surface); - - weston_surface_schedule_repaint(surface); - - sub->has_cached_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 - * origin. - */ - pixman_region32_translate(&sub->cached.damage_surface, - -surface->pending.sx, -surface->pending.sy); - pixman_region32_union(&sub->cached.damage_surface, - &sub->cached.damage_surface, - &surface->pending.damage_surface); - pixman_region32_clear(&surface->pending.damage_surface); - - if (surface->pending.newly_attached) { - sub->cached.newly_attached = 1; - weston_surface_state_set_buffer(&sub->cached, - surface->pending.buffer); - weston_buffer_reference(&sub->cached_buffer_ref, - surface->pending.buffer); - weston_presentation_feedback_discard_list( - &sub->cached.feedback_list); - /* zwp_surface_synchronization_v1.set_acquire_fence */ - fd_move(&sub->cached.acquire_fence_fd, - &surface->pending.acquire_fence_fd); - /* zwp_surface_synchronization_v1.get_release */ - weston_buffer_release_move(&sub->cached.buffer_release_ref, - &surface->pending.buffer_release_ref); - } - sub->cached.desired_protection = surface->pending.desired_protection; - sub->cached.protection_mode = surface->pending.protection_mode; - assert(surface->pending.acquire_fence_fd == -1); - assert(surface->pending.buffer_release_ref.buffer_release == NULL); - sub->cached.sx += surface->pending.sx; - sub->cached.sy += surface->pending.sy; - - apply_damage_buffer(&sub->cached.damage_surface, surface, &surface->pending); - - sub->cached.buffer_viewport.changed |= - surface->pending.buffer_viewport.changed; - sub->cached.buffer_viewport.buffer = - surface->pending.buffer_viewport.buffer; - sub->cached.buffer_viewport.surface = - surface->pending.buffer_viewport.surface; - - weston_surface_reset_pending_buffer(surface); - - 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); - - wl_list_insert_list(&sub->cached.feedback_list, - &surface->pending.feedback_list); - wl_list_init(&surface->pending.feedback_list); - - sub->has_cached_data = 1; -} - -static bool -weston_subsurface_is_synchronized(struct weston_subsurface *sub) -{ - while (sub) { - if (sub->synchronized) - return true; - - if (!sub->parent) - return false; - - sub = weston_surface_to_subsurface(sub->parent); - } - - return false; -} - -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->has_cached_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->has_cached_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) -{ - struct weston_view *view; - if (sub->position.set) { - wl_list_for_each(view, &sub->surface->views, surface_link) - weston_view_set_position(view, - sub->position.x, - sub->position.y); - - sub->position.set = 0; - } - - if (parent_is_synchronized || sub->synchronized) - weston_subsurface_synchronized_commit(sub); -} - -static int -subsurface_get_label(struct weston_surface *surface, char *buf, size_t len) -{ - return snprintf(buf, len, "sub-surface"); -} - -static void -subsurface_committed(struct weston_surface *surface, int32_t dx, int32_t dy) -{ - struct weston_view *view; - - wl_list_for_each(view, &surface->views, surface_link) - weston_view_set_position(view, - view->geometry.x + dx, - view->geometry.y + dy); - - /* 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)) { - surface->is_mapped = true; - - /* Cannot call weston_view_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 force the is_mapped flag on, to make - * weston_surface_is_mapped() return true, so that when the - * parent surface does get mapped, this one will get - * included, too. See view_list_add(). - */ - } -} - -static struct weston_subsurface * -weston_surface_to_subsurface(struct weston_surface *surface) -{ - if (surface->committed == subsurface_committed) - return surface->committed_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; -} - -WL_EXPORT int -weston_surface_set_role(struct weston_surface *surface, - const char *role_name, - struct wl_resource *error_resource, - uint32_t error_code) -{ - assert(role_name); - - if (surface->role_name == NULL || - surface->role_name == role_name || - strcmp(surface->role_name, role_name) == 0) { - surface->role_name = role_name; - - return 0; - } - - wl_resource_post_error(error_resource, error_code, - "Cannot assign role %s to wl_surface@%d," - " already has role %s\n", - role_name, - wl_resource_get_id(surface->resource), - surface->role_name); - return -1; -} - -WL_EXPORT const char * -weston_surface_get_role(struct weston_surface *surface) -{ - return surface->role_name; -} - -WL_EXPORT void -weston_surface_set_label_func(struct weston_surface *surface, - int (*desc)(struct weston_surface *, - char *, size_t)) -{ - surface->get_label = desc; -// OHOS remove timeline -// weston_timeline_refresh_subscription_objects(surface->compositor, -// surface); -} - -/** Get the size of surface contents - * - * \param surface The surface to query. - * \param width Returns the width of raw contents. - * \param height Returns the height of raw contents. - * - * Retrieves the raw surface content size in pixels for the given surface. - * This is the whole content size in buffer pixels. If the surface - * has no content or the renderer does not implement this feature, - * zeroes are returned. - * - * This function is used to determine the buffer size needed for - * a weston_surface_copy_content() call. - */ -WL_EXPORT void -weston_surface_get_content_size(struct weston_surface *surface, - int *width, int *height) -{ - struct weston_renderer *rer = surface->compositor->renderer; - - if (!rer->surface_get_content_size) { - *width = 0; - *height = 0; - return; - } - - rer->surface_get_content_size(surface, width, height); -} - -/** Get the bounding box of a surface and its subsurfaces - * - * \param surface The surface to query. - * \return The bounding box relative to the surface origin. - * - */ -WL_EXPORT struct weston_geometry -weston_surface_get_bounding_box(struct weston_surface *surface) -{ - pixman_region32_t region; - pixman_box32_t *box; - struct weston_subsurface *subsurface; - - pixman_region32_init_rect(®ion, - 0, 0, - surface->width, surface->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->width, - subsurface->surface->height); - - box = pixman_region32_extents(®ion); - struct weston_geometry geometry = { - .x = box->x1, - .y = box->y1, - .width = box->x2 - box->x1, - .height = box->y2 - box->y1, - }; - - pixman_region32_fini(®ion); - - return geometry; -} - -/** Copy surface contents to system memory. - * - * \param surface The surface to copy from. - * \param target Pointer to the target memory buffer. - * \param size Size of the target buffer in bytes. - * \param src_x X location on contents to copy from. - * \param src_y Y location on contents to copy from. - * \param width Width in pixels of the area to copy. - * \param height Height in pixels of the area to copy. - * \return 0 for success, -1 for failure. - * - * Surface contents are maintained by the renderer. They can be in a - * reserved weston_buffer or as a copy, e.g. a GL texture, or something - * else. - * - * Surface contents are copied into memory pointed to by target, - * which has size bytes of space available. The target memory - * may be larger than needed, but being smaller returns an error. - * The extra bytes in target may or may not be written; their content is - * unspecified. Size must be large enough to hold the image. - * - * The image in the target memory will be arranged in rows from - * top to bottom, and pixels on a row from left to right. The pixel - * format is PIXMAN_a8b8g8r8, 4 bytes per pixel, and stride is exactly - * width * 4. - * - * Parameters src_x and src_y define the upper-left corner in buffer - * coordinates (pixels) to copy from. Parameters width and height - * define the size of the area to copy in pixels. - * - * The rectangle defined by src_x, src_y, width, height must fit in - * the surface contents. Otherwise an error is returned. - * - * Use weston_surface_get_content_size to determine the content size; the - * needed target buffer size and rectangle limits. - * - * CURRENT IMPLEMENTATION RESTRICTIONS: - * - the machine must be little-endian due to Pixman formats. - * - * NOTE: Pixman formats are premultiplied. - */ -WL_EXPORT int -weston_surface_copy_content(struct weston_surface *surface, - void *target, size_t size, - int src_x, int src_y, - int width, int height) -{ - struct weston_renderer *rer = surface->compositor->renderer; - int cw, ch; - const size_t bytespp = 4; /* PIXMAN_a8b8g8r8 */ - - if (!rer->surface_copy_content) - return -1; - - weston_surface_get_content_size(surface, &cw, &ch); - - if (src_x < 0 || src_y < 0) - return -1; - - if (width <= 0 || height <= 0) - return -1; - - if (src_x + width > cw || src_y + height > ch) - return -1; - - if (width * bytespp * height > size) - return -1; - - return rer->surface_copy_content(surface, target, size, - src_x, src_y, width, height); -} - -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_find_sibling(struct weston_subsurface *sub, - struct weston_surface *surface) -{ - struct weston_surface *parent = sub->parent; - struct weston_subsurface *sibling; - - wl_list_for_each(sibling, &parent->subsurface_list, parent_link) { - if (sibling->surface == surface && sibling != sub) - return sibling; - } - - 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_find_sibling(sub, 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; - } - - assert(sibling->parent == sub->parent); - - 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); - - sub->reordered = true; -} - -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); - - sub->reordered = true; -} - -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_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); - - /* 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); - 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) -{ - struct weston_view *view, *next; - - assert(sub->surface); - - if (sub->resource) { - assert(weston_surface_to_subsurface(sub->surface) == sub); - assert(sub->parent_destroy_listener.notify == - subsurface_handle_parent_destroy); - - wl_list_for_each_safe(view, next, &sub->surface->views, surface_link) { - weston_view_unmap(view); - weston_view_destroy(view); - } - - if (sub->parent) - weston_subsurface_unlink_parent(sub); - - weston_surface_state_fini(&sub->cached); - weston_buffer_reference(&sub->cached_buffer_ref, NULL); - - sub->surface->committed = NULL; - sub->surface->committed_private = NULL; - weston_surface_set_label_func(sub->surface, 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 = zalloc(sizeof *sub); - if (sub == NULL) - return NULL; - - wl_list_init(&sub->unused_views); - - 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_surface_state_init(&sub->cached); - sub->cached_buffer_ref.buffer = NULL; - sub->synchronized = 1; - - 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 = zalloc(sizeof *sub); - if (sub == NULL) - 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 (weston_surface_set_role(surface, "wl_subsurface", resource, - WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE) < 0) - 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->committed = subsurface_committed; - surface->committed_private = sub; - weston_surface_set_label_func(surface, subsurface_get_label); -} - -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); -} - -/** Set a DPMS mode on all of the compositor's outputs - * - * \param compositor The compositor instance - * \param state The DPMS state the outputs will be set to - */ -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); -} - -/** Restores the compositor to active status - * - * \param compositor The compositor instance - * - * If the compositor was in a sleeping mode, all outputs are powered - * back on via DPMS. Otherwise if the compositor was inactive - * (idle/locked, offscreen, or sleeping) then the compositor's wake - * signal will fire. - * - * Restarts the idle timer. - * \ingroup compositor - */ -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: - case WESTON_COMPOSITOR_IDLE: - case WESTON_COMPOSITOR_OFFSCREEN: - weston_compositor_dpms(compositor, WESTON_DPMS_ON); - wl_signal_emit(&compositor->wake_signal, compositor); - /* fall through */ - default: - wl_event_source_timer_update(compositor->idle_source, - compositor->idle_time * 1000); - } -} - -/** Turns off rendering and frame events for the compositor. - * - * \param compositor The compositor instance - * - * This is used for example to prevent further rendering while the - * compositor is shutting down. - * - * Stops the idle timer. - * - * \ingroup compositor - */ -WL_EXPORT void -weston_compositor_offscreen(struct weston_compositor *compositor) -{ - switch (compositor->state) { - case WESTON_COMPOSITOR_OFFSCREEN: - return; - case WESTON_COMPOSITOR_SLEEPING: - default: - compositor->state = WESTON_COMPOSITOR_OFFSCREEN; - wl_event_source_timer_update(compositor->idle_source, 0); - } -} - -/** Powers down all attached output devices - * - * \param compositor The compositor instance - * - * Causes rendering to the outputs to cease, and no frame events to be - * sent. Only powers down the outputs if the compositor is not already - * in sleep mode. - * - * Stops the idle timer. - * - * \ingroup compositor - */ -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); -} - -/** Sets compositor to idle mode - * - * \param data The compositor instance - * - * This is called when the idle timer fires. Once the compositor is in - * idle mode it requires a wake action (e.g. via - * weston_compositor_wake()) to restore it. The compositor's - * idle_signal will be triggered when the idle event occurs. - * - * Idleness can be inhibited by setting the compositor's idle_inhibit - * property. - */ -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_view *view; - - pixman_region32_fini(&plane->damage); - pixman_region32_fini(&plane->clip); - - wl_list_for_each(view, &plane->compositor->view_list, link) { - if (view->plane == plane) - view->plane = NULL; - } - - wl_list_remove(&plane->link); -} - -/** weston_compositor_stack_plane - * \ingroup compositor - */ -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 -output_release(struct wl_client *client, struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static const struct wl_output_interface output_interface = { - output_release, -}; - - -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_head *head = data; - struct weston_output *output = head->output; - struct weston_mode *mode; - struct wl_resource *resource; - - resource = wl_resource_create(client, &wl_output_interface, - version, id); - if (resource == NULL) { - wl_client_post_no_memory(client); - return; - } - - wl_list_insert(&head->resource_list, wl_resource_get_link(resource)); - wl_resource_set_implementation(resource, &output_interface, head, - unbind_resource); - - assert(output); - wl_output_send_geometry(resource, - output->x, - output->y, - head->mm_width, - head->mm_height, - head->subpixel, - head->make, head->model, - output->transform); - if (version >= WL_OUTPUT_SCALE_SINCE_VERSION) - 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 >= WL_OUTPUT_DONE_SINCE_VERSION) - wl_output_send_done(resource); -} - -static void -weston_head_add_global(struct weston_head *head) -{ - head->global = wl_global_create(head->compositor->wl_display, - &wl_output_interface, 3, - head, bind_output); -} - -/** Remove the global wl_output protocol object - * - * \param head The head whose global to remove. - * - * Also orphans the wl_resources for this head (wl_output). - */ -static void -weston_head_remove_global(struct weston_head *head) -{ - struct wl_resource *resource, *tmp; - - if (head->global) - wl_global_destroy(head->global); - head->global = NULL; - - wl_resource_for_each_safe(resource, tmp, &head->resource_list) { - unbind_resource(resource); - wl_resource_set_destructor(resource, NULL); - wl_resource_set_user_data(resource, NULL); - } - - wl_resource_for_each(resource, &head->xdg_output_resource_list) { - /* It's sufficient to unset the destructor, then the list elements - * won't be accessed. - */ - wl_resource_set_destructor(resource, NULL); - } - wl_list_init(&head->xdg_output_resource_list); -} - -/** Get the backing object of wl_output - * - * \param resource A wl_output protocol object. - * \return The backing object (user data) of a wl_resource representing a - * wl_output protocol object. - * - * \ingroup head - */ -WL_EXPORT struct weston_head * -weston_head_from_resource(struct wl_resource *resource) -{ - assert(wl_resource_instance_of(resource, &wl_output_interface, - &output_interface)); - - return wl_resource_get_user_data(resource); -} - -/** Initialize a pre-allocated weston_head - * - * \param head The head to initialize. - * \param name The head name, e.g. the connector name or equivalent. - * - * The head will be safe to attach, detach and release. - * - * The name is used in logs, and can be used by compositors as a configuration - * identifier. - * - * \ingroup head - * \internal - */ -WL_EXPORT void -weston_head_init(struct weston_head *head, const char *name) -{ - /* Add some (in)sane defaults which can be used - * for checking if an output was properly configured - */ - memset(head, 0, sizeof *head); - - wl_list_init(&head->compositor_link); - wl_signal_init(&head->destroy_signal); - wl_list_init(&head->output_link); - wl_list_init(&head->resource_list); - wl_list_init(&head->xdg_output_resource_list); - head->name = strdup(name); - head->current_protection = WESTON_HDCP_DISABLE; -} - -/** Send output heads changed signal - * - * \param output The output that changed. - * - * Notify that the enabled output gained and/or lost heads, or that the - * associated heads may have changed their connection status. This does not - * include cases where the output becomes enabled or disabled. The registered - * callbacks are called after the change has successfully happened. - * - * If connection status change causes the compositor to attach or detach a head - * to an enabled output, the registered callbacks may be called multiple times. - * - * \ingroup output - */ -static void -weston_output_emit_heads_changed(struct weston_output *output) -{ - wl_signal_emit(&output->compositor->output_heads_changed_signal, - output); -} - -/** Idle task for emitting heads_changed_signal */ -static void -weston_compositor_call_heads_changed(void *data) -{ - struct weston_compositor *compositor = data; - struct weston_head *head; - - compositor->heads_changed_source = NULL; - - wl_signal_emit(&compositor->heads_changed_signal, compositor); - - wl_list_for_each(head, &compositor->head_list, compositor_link) { - if (head->output && head->output->enabled) - weston_output_emit_heads_changed(head->output); - } -} - -/** Schedule a call on idle to heads_changed callback - * - * \param compositor The Compositor. - * - * \ingroup compositor - * \internal - */ -static void -weston_compositor_schedule_heads_changed(struct weston_compositor *compositor) -{ - struct wl_event_loop *loop; - - if (compositor->heads_changed_source) - return; - - loop = wl_display_get_event_loop(compositor->wl_display); - compositor->heads_changed_source = wl_event_loop_add_idle(loop, - weston_compositor_call_heads_changed, compositor); -} - -/** Register a new head - * - * \param compositor The compositor. - * \param head The head to register, must not be already registered. - * - * This signals the core that a new head has become available, leading to - * heads_changed hook being called later. - * - * \ingroup compositor - * \internal - */ -WL_EXPORT void -weston_compositor_add_head(struct weston_compositor *compositor, - struct weston_head *head) -{ - assert(wl_list_empty(&head->compositor_link)); - assert(head->name); - - wl_list_insert(compositor->head_list.prev, &head->compositor_link); - head->compositor = compositor; - weston_compositor_schedule_heads_changed(compositor); -} - -/** Adds a listener to be called when heads change - * - * \param compositor The compositor. - * \param listener The listener to add. - * - * The listener notify function argument is weston_compositor. - * - * The listener function will be called after heads are added or their - * connection status has changed. Several changes may be accumulated into a - * single call. The user is expected to iterate over the existing heads and - * check their statuses to find out what changed. - * - * \sa weston_compositor_iterate_heads, weston_head_is_connected, - * weston_head_is_enabled - * \ingroup compositor - */ -WL_EXPORT void -weston_compositor_add_heads_changed_listener(struct weston_compositor *compositor, - struct wl_listener *listener) -{ - wl_signal_add(&compositor->heads_changed_signal, listener); -} - -/** Iterate over available heads - * - * \param compositor The compositor. - * \param iter The iterator, or NULL for start. - * \return The next available head in the list. - * - * Returns all available heads, regardless of being connected or enabled. - * - * You can iterate over all heads as follows: - * \code - * struct weston_head *head = NULL; - * - * while ((head = weston_compositor_iterate_heads(compositor, head))) { - * ... - * } - * \endcode - * - * If you cause \c iter to be removed from the list, you cannot use it to - * continue iterating. Removing any other item is safe. - * - * \ingroup compositor - */ -WL_EXPORT struct weston_head * -weston_compositor_iterate_heads(struct weston_compositor *compositor, - struct weston_head *iter) -{ - struct wl_list *list = &compositor->head_list; - struct wl_list *node; - - assert(compositor); - assert(!iter || iter->compositor == compositor); - - if (iter) - node = iter->compositor_link.next; - else - node = list->next; - - assert(node); - assert(!iter || node != &iter->compositor_link); - - if (node == list) - return NULL; - - return container_of(node, struct weston_head, compositor_link); -} - -/** Iterate over attached heads - * - * \param output The output whose heads to iterate. - * \param iter The iterator, or NULL for start. - * \return The next attached head in the list. - * - * Returns all heads currently attached to the output. - * - * You can iterate over all heads as follows: - * \code - * struct weston_head *head = NULL; - * - * while ((head = weston_output_iterate_heads(output, head))) { - * ... - * } - * \endcode - * - * If you cause \c iter to be removed from the list, you cannot use it to - * continue iterating. Removing any other item is safe. - * - * \ingroup ouput - */ -WL_EXPORT struct weston_head * -weston_output_iterate_heads(struct weston_output *output, - struct weston_head *iter) -{ - struct wl_list *list = &output->head_list; - struct wl_list *node; - - assert(output); - assert(!iter || iter->output == output); - - if (iter) - node = iter->output_link.next; - else - node = list->next; - - assert(node); - assert(!iter || node != &iter->output_link); - - if (node == list) - return NULL; - - return container_of(node, struct weston_head, output_link); -} - -/** Attach a head to an output - * - * \param output The output to attach to. - * \param head The head that is not yet attached. - * \return 0 on success, -1 on failure. - * - * Attaches the given head to the output. All heads of an output are clones - * and share the resolution and timings. - * - * Cloning heads this way uses less resources than creating an output for - * each head, but is not always possible due to environment, driver and hardware - * limitations. - * - * On failure, the head remains unattached. Success of this function does not - * guarantee the output configuration is actually valid. The final checks are - * made on weston_output_enable() unless the output was already enabled. - * - * \ingroup output - */ -WL_EXPORT int -weston_output_attach_head(struct weston_output *output, - struct weston_head *head) -{ - char *head_names; - - if (!wl_list_empty(&head->output_link)) - return -1; - - if (output->attach_head) { - if (output->attach_head(output, head) < 0) - return -1; - } else if (!wl_list_empty(&output->head_list)) { - /* No support for clones in the legacy path. */ - return -1; - } - - head->output = output; - wl_list_insert(output->head_list.prev, &head->output_link); - - if (output->enabled) { - weston_head_add_global(head); - - head_names = weston_output_create_heads_string(output); - weston_log("Output '%s' updated to have head(s) %s\n", - output->name, head_names); - free(head_names); - - weston_output_emit_heads_changed(output); - } - - return 0; -} - -/** Detach a head from its output - * - * \param head The head to detach. - * - * It is safe to detach a non-attached head. - * - * If the head is attached to an enabled output and the output will be left - * with no heads, the output will be disabled. - * - * \ingroup head - * \sa weston_output_disable - */ -WL_EXPORT void -weston_head_detach(struct weston_head *head) -{ - struct weston_output *output = head->output; - char *head_names; - - wl_list_remove(&head->output_link); - wl_list_init(&head->output_link); - head->output = NULL; - - if (!output) - return; - - if (output->detach_head) - output->detach_head(output, head); - - if (output->enabled) { - weston_head_remove_global(head); - - if (wl_list_empty(&output->head_list)) { - weston_log("Output '%s' no heads left, disabling.\n", - output->name); - weston_output_disable(output); - } else { - head_names = weston_output_create_heads_string(output); - weston_log("Output '%s' updated to have head(s) %s\n", - output->name, head_names); - free(head_names); - - weston_output_emit_heads_changed(output); - } - } -} - -/** Destroy a head - * - * \param head The head to be released. - * - * Destroys the head. The caller is responsible for freeing the memory pointed - * to by \c head. - * - * \ingroup head - * \internal - */ -WL_EXPORT void -weston_head_release(struct weston_head *head) -{ - wl_signal_emit(&head->destroy_signal, head); - - weston_head_detach(head); - - free(head->make); - free(head->model); - free(head->serial_number); - free(head->name); - - wl_list_remove(&head->compositor_link); -} - -static void -weston_head_set_device_changed(struct weston_head *head) -{ - head->device_changed = true; - - if (head->compositor) - weston_compositor_schedule_heads_changed(head->compositor); -} - -/** String equal comparison with NULLs being equal */ -static bool -str_null_eq(const char *a, const char *b) -{ - if (!a && !b) - return true; - - if (!!a != !!b) - return false; - - return strcmp(a, b) == 0; -} - -/** Store monitor make, model and serial number - * - * \param head The head to modify. - * \param make The monitor make. If EDID is available, the PNP ID. Otherwise - * any string, or NULL for none. - * \param model The monitor model or name, or a made-up string, or NULL for - * none. - * \param serialno The monitor serial number, a made-up string, or NULL for - * none. - * - * This may set the device_changed flag. - * - * \ingroup head - * \internal - */ -WL_EXPORT void -weston_head_set_monitor_strings(struct weston_head *head, - const char *make, - const char *model, - const char *serialno) -{ - if (str_null_eq(head->make, make) && - str_null_eq(head->model, model) && - str_null_eq(head->serial_number, serialno)) - return; - - free(head->make); - free(head->model); - free(head->serial_number); - - head->make = make ? strdup(make) : NULL; - head->model = model ? strdup(model) : NULL; - head->serial_number = serialno ? strdup(serialno) : NULL; - - weston_head_set_device_changed(head); -} - -/** Store display non-desktop status - * - * \param head The head to modify. - * \param non_desktop Whether the head connects to a non-desktop display. - * - * \ingroup head - * \internal - */ -WL_EXPORT void -weston_head_set_non_desktop(struct weston_head *head, bool non_desktop) -{ - if (head->non_desktop == non_desktop) - return; - - head->non_desktop = non_desktop; - - weston_head_set_device_changed(head); -} - -/** Store display transformation - * - * \param head The head to modify. - * \param transform The transformation to apply for this head - * - * This may set the device_changed flag. - * - * \ingroup head - * \internal - */ -WL_EXPORT void -weston_head_set_transform(struct weston_head *head, uint32_t transform) -{ - if (head->transform == transform) - return; - - head->transform = transform; - - weston_head_set_device_changed(head); -} - - -/** Store physical image size - * - * \param head The head to modify. - * \param mm_width Image area width in millimeters. - * \param mm_height Image area height in millimeters. - * - * This may set the device_changed flag. - * - * \ingroup head - * \internal - */ -WL_EXPORT void -weston_head_set_physical_size(struct weston_head *head, - int32_t mm_width, int32_t mm_height) -{ - if (head->mm_width == mm_width && - head->mm_height == mm_height) - return; - - head->mm_width = mm_width; - head->mm_height = mm_height; - - weston_head_set_device_changed(head); -} - -/** Store monitor sub-pixel layout - * - * \param head The head to modify. - * \param sp Sub-pixel layout. The possible values are: - * - WL_OUTPUT_SUBPIXEL_UNKNOWN, - * - WL_OUTPUT_SUBPIXEL_NONE, - * - WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB, - * - WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR, - * - WL_OUTPUT_SUBPIXEL_VERTICAL_RGB, - * - WL_OUTPUT_SUBPIXEL_VERTICAL_BGR - * - * This may set the device_changed flag. - * - * \ingroup head - * \internal - */ -WL_EXPORT void -weston_head_set_subpixel(struct weston_head *head, - enum wl_output_subpixel sp) -{ - if (head->subpixel == sp) - return; - - head->subpixel = sp; - - weston_head_set_device_changed(head); -} - -/** Mark the monitor as internal - * - * This is used for embedded screens, like laptop panels. - * - * \param head The head to mark as internal. - * - * By default a head is external. The type is often inferred from the physical - * connector type. - * - * \ingroup head - * \internal - */ -WL_EXPORT void -weston_head_set_internal(struct weston_head *head) -{ - head->connection_internal = true; -} - -/** Store connector status - * - * \param head The head to modify. - * \param connected Whether the head is connected. - * - * Connectors are created as disconnected. This function can be used to - * set the connector status. - * - * The status should be set to true when a physical connector is connected to - * a video sink device like a monitor and to false when the connector is - * disconnected. For nested backends, the connection status should reflect the - * connection to the parent display server. - * - * When the connection status changes, it schedules a call to the heads_changed - * hook and sets the device_changed flag. - * - * \sa weston_compositor_set_heads_changed_cb - * \ingroup head - * \internal - */ -WL_EXPORT void -weston_head_set_connection_status(struct weston_head *head, bool connected) -{ - if (head->connected == connected) - return; - - head->connected = connected; - - weston_head_set_device_changed(head); -} - -static void -weston_output_compute_protection(struct weston_output *output) -{ - struct weston_head *head; - enum weston_hdcp_protection op_protection; - bool op_protection_valid = false; - struct weston_compositor *wc = output->compositor; - - wl_list_for_each(head, &output->head_list, output_link) { - if (!op_protection_valid) { - op_protection = head->current_protection; - op_protection_valid = true; - } - if (head->current_protection < op_protection) - op_protection = head->current_protection; - } - - if (!op_protection_valid) - op_protection = WESTON_HDCP_DISABLE; - - if (output->current_protection != op_protection) { - output->current_protection = op_protection; - weston_output_damage(output); - weston_schedule_surface_protection_update(wc); - } -} - -WL_EXPORT void -weston_head_set_content_protection_status(struct weston_head *head, - enum weston_hdcp_protection status) -{ - head->current_protection = status; - if (head->output) - weston_output_compute_protection(head->output); -} - -/** Is the head currently connected? - * - * \param head The head to query. - * \return Connection status. - * - * Returns true if the head is physically connected to a monitor, or in - * case of a nested backend returns true when there is a connection to the - * parent display server. - * - * This is independent from the head being enabled. - * - * \sa weston_head_is_enabled - * \ingroup head - */ -WL_EXPORT bool -weston_head_is_connected(struct weston_head *head) -{ - return head->connected; -} - -/** Is the head currently enabled? - * - * \param head The head to query. - * \return Video status. - * - * Returns true if the head is currently transmitting a video stream. - * - * This is independent of the head being connected. - * - * \sa weston_head_is_connected - * \ingroup head - */ -WL_EXPORT bool -weston_head_is_enabled(struct weston_head *head) -{ - if (!head->output) - return false; - - return head->output->enabled; -} - -/** Has the device information changed? - * - * \param head The head to query. - * \return True if the device information has changed since last reset. - * - * The information about the connected display device, e.g. a monitor, may - * change without being disconnected in between. Changing information - * causes a call to the heads_changed hook. - * - * The information includes make, model, serial number, physical size, - * and sub-pixel type. The connection status is also included. - * - * \sa weston_head_reset_device_changed, weston_compositor_set_heads_changed_cb - * \ingroup head - */ -WL_EXPORT bool -weston_head_is_device_changed(struct weston_head *head) -{ - return head->device_changed; -} - -/** Does the head represent a non-desktop display? - * - * \param head The head to query. - * \return True if the device is a non-desktop display. - * - * Non-desktop heads are not attached to outputs by default. - * This stops weston from extending the desktop onto head mounted displays. - * - * \ingroup head - */ -WL_EXPORT bool -weston_head_is_non_desktop(struct weston_head *head) -{ - return head->non_desktop; -} - -/** Acknowledge device information change - * - * \param head The head to acknowledge. - * - * Clears the device changed flag on this head. When a compositor has processed - * device information, it should call this to be able to notice further - * changes. - * - * \sa weston_head_is_device_changed - * \ingroup head - */ -WL_EXPORT void -weston_head_reset_device_changed(struct weston_head *head) -{ - head->device_changed = false; -} - -/** Get the name of a head - * - * \param head The head to query. - * \return The head's name, not NULL. - * - * The name depends on the backend. The DRM backend uses connector names, - * other backends may use hardcoded names or user-given names. - * - * \ingroup head - */ -WL_EXPORT const char * -weston_head_get_name(struct weston_head *head) -{ - return head->name; -} - -/** Get the output the head is attached to - * - * \param head The head to query. - * \return The output the head is attached to, or NULL if detached. - * \ingroup head - */ -WL_EXPORT struct weston_output * -weston_head_get_output(struct weston_head *head) -{ - return head->output; -} - -/** Get the head's native transformation - * - * \param head The head to query. - * \return The head's native transform, as a WL_OUTPUT_TRANSFORM_* value - * - * A weston_head may have a 'native' transform provided by the backend. - * Examples include panels which are physically rotated, where the rotation - * is recorded and described as part of the system configuration. This call - * will return any known native transform for the head. - * - * \ingroup head - */ -WL_EXPORT uint32_t -weston_head_get_transform(struct weston_head *head) -{ - return head->transform; -} - -/** Add destroy callback for a head - * - * \param head The head to watch for. - * \param listener The listener to add. The \c notify member must be set. - * - * Heads may get destroyed for various reasons by the backends. If a head is - * attached to an output, the compositor should listen for head destruction - * and reconfigure or destroy the output if necessary. - * - * The destroy callbacks will be called on weston_head destruction before any - * automatic detaching from an associated weston_output and before any - * weston_head information is lost. - * - * The \c data argument to the notify callback is the weston_head being - * destroyed. - * - * \ingroup head - */ -WL_EXPORT void -weston_head_add_destroy_listener(struct weston_head *head, - struct wl_listener *listener) -{ - wl_signal_add(&head->destroy_signal, listener); -} - -/** Look up destroy listener for a head - * - * \param head The head to query. - * \param notify The notify function used used for the added destroy listener. - * \return The listener, or NULL if not found. - * - * This looks up the previously added destroy listener struct based on the - * notify function it has. The listener can be used to access user data - * through \c container_of(). - * - * \sa wl_signal_get() - * \ingroup head - */ -WL_EXPORT struct wl_listener * -weston_head_get_destroy_listener(struct weston_head *head, - wl_notify_func_t notify) -{ - return wl_signal_get(&head->destroy_signal, notify); -} - -/* Move other outputs when one is resized so the space remains contiguous. */ -static void -weston_compositor_reflow_outputs(struct weston_compositor *compositor, - struct weston_output *resized_output, int delta_width) -{ - struct weston_output *output; - bool start_resizing = false; - - if (!delta_width) - return; - - wl_list_for_each(output, &compositor->output_list, link) { - if (output == resized_output) { - start_resizing = true; - continue; - } - - if (start_resizing) { - weston_output_move(output, output->x + delta_width, output->y); - output->dirty = 1; - } - } -} - -static void -weston_output_update_matrix(struct weston_output *output) -{ - float magnification; - - weston_matrix_init(&output->matrix); - weston_matrix_translate(&output->matrix, -output->x, -output->y, 0); - - if (output->zoom.active) { - magnification = 1 / (1 - output->zoom.spring_z.current); - weston_output_update_zoom(output); - weston_matrix_translate(&output->matrix, -output->zoom.trans_x, - -output->zoom.trans_y, 0); - weston_matrix_scale(&output->matrix, magnification, - magnification, 1.0); - } - - 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: - weston_matrix_translate(&output->matrix, -output->width, 0, 0); - weston_matrix_scale(&output->matrix, -1, 1, 1); - break; - } - - 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: - weston_matrix_translate(&output->matrix, -output->width, 0, 0); - weston_matrix_rotate_xy(&output->matrix, 0, -1); - break; - case WL_OUTPUT_TRANSFORM_180: - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - weston_matrix_translate(&output->matrix, - -output->width, -output->height, 0); - weston_matrix_rotate_xy(&output->matrix, -1, 0); - break; - case WL_OUTPUT_TRANSFORM_270: - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - weston_matrix_translate(&output->matrix, 0, -output->height, 0); - weston_matrix_rotate_xy(&output->matrix, 0, 1); - break; - } - - if (output->current_scale != 1) - weston_matrix_scale(&output->matrix, - output->current_scale, - output->current_scale, 1); - - output->dirty = 0; - - weston_matrix_invert(&output->inverse_matrix, &output->matrix); -} - -static void -weston_output_transform_scale_init(struct weston_output *output, uint32_t transform, uint32_t scale) -{ - output->transform = transform; - output->native_scale = scale; - output->current_scale = scale; - - convert_size_by_transform_scale(&output->width, &output->height, - output->current_mode->width, - output->current_mode->height, - transform, scale); -} - -static void -weston_output_init_geometry(struct weston_output *output, int x, int y) -{ - output->x = x; - output->y = y; - - pixman_region32_fini(&output->region); - pixman_region32_init_rect(&output->region, x, y, - output->width, - output->height); -} - -/** - * \ingroup output - */ -WL_EXPORT void -weston_output_move(struct weston_output *output, int x, int y) -{ - struct weston_head *head; - struct wl_resource *resource; - int ver; - - output->move_x = x - output->x; - output->move_y = y - output->y; - - if (output->move_x == 0 && output->move_y == 0) - return; - - weston_output_init_geometry(output, x, y); - - output->dirty = 1; - - /* Move views on this output. */ - wl_signal_emit(&output->compositor->output_moved_signal, output); - - /* Notify clients of the change for output position. */ - wl_list_for_each(head, &output->head_list, output_link) { - wl_resource_for_each(resource, &head->resource_list) { - wl_output_send_geometry(resource, - output->x, - output->y, - head->mm_width, - head->mm_height, - head->subpixel, - head->make, - head->model, - output->transform); - - ver = wl_resource_get_version(resource); - if (ver >= WL_OUTPUT_DONE_SINCE_VERSION) - wl_output_send_done(resource); - } - - wl_resource_for_each(resource, &head->xdg_output_resource_list) { - zxdg_output_v1_send_logical_position(resource, - output->x, - output->y); - zxdg_output_v1_send_done(resource); - } - } -} - -/** Signal that a pending output is taken into use. - * - * Removes the output from the pending list and adds it to the compositor's - * list of enabled outputs. The output created signal is emitted. - * - * The output gets an internal ID assigned, and the wl_output global is - * created. - * - * \param compositor The compositor instance. - * \param output The output to be added. - * - * \internal - * \ingroup compositor - */ -static void -weston_compositor_add_output(struct weston_compositor *compositor, - struct weston_output *output) -{ - struct weston_view *view, *next; - struct weston_head *head; - - assert(!output->enabled); - - /* Verify we haven't reached the limit of 32 available output IDs */ - assert(ffs(~compositor->output_id_pool) > 0); - - /* Invert the output id pool and look for the lowest numbered - * switch (the least significant bit). Take that bit's position - * as our ID, and mark it used in the compositor's output_id_pool. - */ - output->id = ffs(~compositor->output_id_pool) - 1; - compositor->output_id_pool |= 1u << output->id; - - wl_list_remove(&output->link); - wl_list_insert(compositor->output_list.prev, &output->link); - output->enabled = true; - - wl_list_for_each(head, &output->head_list, output_link) - weston_head_add_global(head); - - wl_signal_emit(&compositor->output_created_signal, output); - - wl_list_for_each_safe(view, next, &compositor->view_list, link) - weston_view_geometry_dirty(view); -} - -/** Transform device coordinates into global coordinates - * - * \param output the weston_output object - * \param[in] device_x X coordinate in device units. - * \param[in] device_y Y coordinate in device units. - * \param[out] x X coordinate in the global space. - * \param[out] y Y coordinate in the global space. - * - * Transforms coordinates from the device coordinate space (physical pixel - * units) to the global coordinate space (logical pixel units). This takes - * into account output transform and scale. - * - * \ingroup output - * \internal - */ -WL_EXPORT void -weston_output_transform_coordinate(struct weston_output *output, - double device_x, double device_y, - double *x, double *y) -{ - struct weston_vector p = { { - device_x, - device_y, - 0.0, - 1.0 } }; - - weston_matrix_transform(&output->inverse_matrix, &p); - - *x = p.f[0] / p.f[3]; - *y = p.f[1] / p.f[3]; -} - -/** Removes output from compositor's list of enabled outputs - * - * \param output The weston_output object that is being removed. - * - * The following happens: - * - * - The output assignments of all views in the current scenegraph are - * recomputed. - * - * - Presentation feedback is discarded. - * - * - Compositor is notified that outputs were changed and - * applies the necessary changes to re-layout outputs. - * - * - The output is put back in the pending outputs list. - * - * - Signal is emitted to notify all users of the weston_output - * object that the output is being destroyed. - * - * - wl_output protocol objects referencing this weston_output - * are made inert, and the wl_output global is removed. - * - * - The output's internal ID is released. - * - * \ingroup compositor - * \internal - */ -static void -weston_compositor_remove_output(struct weston_output *output) -{ - struct weston_compositor *compositor = output->compositor; - struct weston_view *view; - struct weston_head *head; - - assert(output->destroying); - assert(output->enabled); - - wl_list_for_each(view, &compositor->view_list, link) { - if (view->output_mask & (1u << output->id)) - weston_view_assign_output(view); - } - - weston_presentation_feedback_discard_list(&output->feedback_list); - - weston_compositor_reflow_outputs(compositor, output, -output->width); - - wl_list_remove(&output->link); - wl_list_insert(compositor->pending_output_list.prev, &output->link); - output->enabled = false; - - wl_signal_emit(&compositor->output_destroyed_signal, output); - wl_signal_emit(&output->destroy_signal, output); - - wl_list_for_each(head, &output->head_list, output_link) - weston_head_remove_global(head); - - compositor->output_id_pool &= ~(1u << output->id); - output->id = 0xffffffff; /* invalid */ -} - -/** Sets the output scale for a given output. - * - * \param output The weston_output object that the scale is set for. - * \param scale Scale factor for the given output. - * - * It only supports setting scale for an output that - * is not enabled and it can only be ran once. - * - * \ingroup ouput - */ -WL_EXPORT void -weston_output_set_scale(struct weston_output *output, - int32_t scale) -{ - /* We can only set scale on a disabled output */ - assert(!output->enabled); - - /* We only want to set scale once */ - assert(!output->scale); - - output->scale = scale; -} - -/** Sets the output transform for a given output. - * - * \param output The weston_output object that the transform is set for. - * \param transform Transform value for the given output. - * - * Refer to wl_output::transform section located at - * https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_output - * for list of values that can be passed to this function. - * - * \ingroup output - */ -WL_EXPORT void -weston_output_set_transform(struct weston_output *output, - uint32_t transform) -{ - struct weston_pointer_motion_event ev; - struct wl_resource *resource; - struct weston_seat *seat; - pixman_region32_t old_region; - int mid_x, mid_y; - struct weston_head *head; - int ver; - - if (!output->enabled && output->transform == UINT32_MAX) { - output->transform = transform; - return; - } - - weston_output_transform_scale_init(output, transform, output->scale); - - pixman_region32_init(&old_region); - pixman_region32_copy(&old_region, &output->region); - - weston_output_init_geometry(output, output->x, output->y); - - output->dirty = 1; - - /* Notify clients of the change for output transform. */ - wl_list_for_each(head, &output->head_list, output_link) { - wl_resource_for_each(resource, &head->resource_list) { - wl_output_send_geometry(resource, - output->x, - output->y, - head->mm_width, - head->mm_height, - head->subpixel, - head->make, - head->model, - output->transform); - - ver = wl_resource_get_version(resource); - if (ver >= WL_OUTPUT_DONE_SINCE_VERSION) - wl_output_send_done(resource); - } - wl_resource_for_each(resource, &head->xdg_output_resource_list) { - zxdg_output_v1_send_logical_position(resource, - output->x, - output->y); - zxdg_output_v1_send_logical_size(resource, - output->width, - output->height); - zxdg_output_v1_send_done(resource); - } - } - - /* we must ensure that pointers are inside output, otherwise they disappear */ - mid_x = output->x + output->width / 2; - mid_y = output->y + output->height / 2; - - ev.mask = WESTON_POINTER_MOTION_ABS; - ev.x = wl_fixed_to_double(wl_fixed_from_int(mid_x)); - ev.y = wl_fixed_to_double(wl_fixed_from_int(mid_y)); - - wl_list_for_each(seat, &output->compositor->seat_list, link) { - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - if (pointer && pixman_region32_contains_point(&old_region, - wl_fixed_to_int(pointer->x), - wl_fixed_to_int(pointer->y), - NULL)) - weston_pointer_move(pointer, &ev); - } -} - -/** Initializes a weston_output object with enough data so - ** an output can be configured. - * - * \param output The weston_output object to initialize - * \param compositor The compositor instance. - * \param name Name for the output (the string is copied). - * - * Sets initial values for fields that are expected to be - * configured either by compositors or backends. - * - * The name is used in logs, and can be used by compositors as a configuration - * identifier. - * - * \ingroup output - * \internal - */ -WL_EXPORT void -weston_output_init(struct weston_output *output, - struct weston_compositor *compositor, - const char *name) -{ - output->compositor = compositor; - output->destroying = 0; - output->name = strdup(name); - wl_list_init(&output->link); - wl_signal_init(&output->user_destroy_signal); - output->enabled = false; - output->desired_protection = WESTON_HDCP_DISABLE; - output->allow_protection = true; - - wl_list_init(&output->head_list); - - /* Add some (in)sane defaults which can be used - * for checking if an output was properly configured - */ - output->scale = 0; - /* Can't use -1 on uint32_t and 0 is valid enum value */ - output->transform = UINT32_MAX; - - pixman_region32_init(&output->region); - wl_list_init(&output->mode_list); -} - -/** Adds weston_output object to pending output list. - * - * \param output The weston_output object to add - * \param compositor The compositor instance. - * - * The opposite of this operation is built into weston_output_release(). - * - * \ingroup compositor - * \internal - */ -WL_EXPORT void -weston_compositor_add_pending_output(struct weston_output *output, - struct weston_compositor *compositor) -{ - assert(output->disable); - assert(output->enable); - - wl_list_remove(&output->link); - wl_list_insert(compositor->pending_output_list.prev, &output->link); -} - -/** Create a string with the attached heads' names. - * - * The string must be free()'d. - * - * \ingroup output - */ -static char * -weston_output_create_heads_string(struct weston_output *output) -{ - FILE *fp; - char *str = NULL; - size_t size = 0; - struct weston_head *head; - const char *sep = ""; - - fp = open_memstream(&str, &size); - if (!fp) - return NULL; - - wl_list_for_each(head, &output->head_list, output_link) { - fprintf(fp, "%s%s", sep, head->name); - sep = ", "; - } - fclose(fp); - - return str; -} - -/** Constructs a weston_output object that can be used by the compositor. - * - * \param output The weston_output object that needs to be enabled. Must not - * be enabled already. Must have at least one head attached. - * - * Output coordinates are calculated and each new output is by default - * assigned to the right of previous one. - * - * Sets up the transformation, zoom, and geometry of the output using - * the properties that need to be configured by the compositor. - * - * Establishes a repaint timer for the output with the relevant display - * object's event loop. See output_repaint_timer_handler(). - * - * The output is assigned an ID. Weston can support up to 32 distinct - * outputs, with IDs numbered from 0-31; the compositor's output_id_pool - * is referred to and used to find the first available ID number, and - * then this ID is marked as used in output_id_pool. - * - * The output is also assigned a Wayland global with the wl_output - * external interface. - * - * Backend specific function is called to set up the output output. - * - * Output is added to the compositor's output list - * - * If the backend specific function fails, the weston_output object - * is returned to a state it was before calling this function and - * is added to the compositor's pending_output_list in case it needs - * to be reconfigured or just so it can be destroyed at shutdown. - * - * 0 is returned on success, -1 on failure. - * - * \ingroup output - */ -WL_EXPORT int -weston_output_enable(struct weston_output *output) -{ - struct weston_compositor *c = output->compositor; - struct weston_output *iterator; - struct weston_head *head; - char *head_names; - int x = 0, y = 0; - - if (output->enabled) { - weston_log("Error: attempt to enable an enabled output '%s'\n", - output->name); - return -1; - } - - if (wl_list_empty(&output->head_list)) { - weston_log("Error: cannot enable output '%s' without heads.\n", - output->name); - return -1; - } - - if (wl_list_empty(&output->mode_list) || !output->current_mode) { - weston_log("Error: no video mode for output '%s'.\n", - output->name); - return -1; - } - - wl_list_for_each(head, &output->head_list, output_link) { - assert(head->make); - assert(head->model); - } - - iterator = container_of(c->output_list.prev, - struct weston_output, link); - - if (!wl_list_empty(&c->output_list)) - x = iterator->x + iterator->width; - - /* Make sure the scale is set up */ - assert(output->scale); - - /* Make sure we have a transform set */ - assert(output->transform != UINT32_MAX); - - output->x = x; - output->y = y; - output->dirty = 1; - output->original_scale = output->scale; - - wl_signal_init(&output->frame_signal); - wl_signal_init(&output->destroy_signal); - - weston_output_transform_scale_init(output, output->transform, output->scale); - weston_output_init_zoom(output); - - weston_output_init_geometry(output, x, y); - weston_output_damage(output); - - wl_list_init(&output->animation_list); - wl_list_init(&output->feedback_list); - - /* Enable the output (set up the crtc or create a - * window representing the output, set up the - * renderer, etc) - */ - if (output->enable(output) < 0) { - weston_log("Enabling output \"%s\" failed.\n", output->name); - return -1; - } - - weston_compositor_add_output(output->compositor, output); - - head_names = weston_output_create_heads_string(output); - weston_log("Output '%s' enabled with head(s) %s\n", - output->name, head_names); - free(head_names); - - return 0; -} - -/** Converts a weston_output object to a pending output state, so it - ** can be configured again or destroyed. - * - * \param output The weston_output object that needs to be disabled. - * - * Calls a backend specific function to disable an output, in case - * such function exists. - * - * The backend specific disable function may choose to postpone the disabling - * by returning a negative value, in which case this function returns early. - * In that case the backend will guarantee the output will be disabled soon - * by the backend calling this function again. One must not attempt to re-enable - * the output until that happens. - * - * Otherwise, if the output is being used by the compositor, it is removed - * from weston's output_list (see weston_compositor_remove_output()) - * and is returned to a state it was before weston_output_enable() - * was ran (see weston_output_enable_undo()). - * - * See weston_output_init() for more information on the - * state output is returned to. - * - * If the output has never been enabled yet, this function can still be - * called to ensure that the output is actually turned off rather than left - * in the state it was discovered in. - * - * \ingroup output - */ -WL_EXPORT void -weston_output_disable(struct weston_output *output) -{ - /* Should we rename this? */ - output->destroying = 1; - - /* Disable is called unconditionally also for not-enabled outputs, - * because at compositor start-up, if there is an output that is - * already on but the compositor wants to turn it off, we have to - * forward the turn-off to the backend so it knows to do it. - * The backend cannot initially turn off everything, because it - * would cause unnecessary mode-sets for all outputs the compositor - * wants to be on. - */ - if (output->disable(output) < 0) - return; - - if (output->enabled) - weston_compositor_remove_output(output); - - output->destroying = 0; -} - -/** Forces a synchronous call to heads_changed hook - * - * \param compositor The compositor instance - * - * If there are new or changed heads, calls the heads_changed hook and - * returns after the hook returns. - * - * \ingroup compositor - */ -WL_EXPORT void -weston_compositor_flush_heads_changed(struct weston_compositor *compositor) -{ - if (compositor->heads_changed_source) { - wl_event_source_remove(compositor->heads_changed_source); - weston_compositor_call_heads_changed(compositor); - } -} - -/** Add destroy callback for an output - * - * \param output The output to watch. - * \param listener The listener to add. The \c notify member must be set. - * - * The listener callback will be called when user destroys an output. This - * may be delayed by a backend in some cases. The main purpose of the - * listener is to allow hooking up custom data to the output. The custom data - * can be fetched via weston_output_get_destroy_listener() followed by - * container_of(). - * - * The \c data argument to the notify callback is the weston_output being - * destroyed. - * - * @note This is for the final destruction of an output, not when it gets - * disabled. If you want to keep track of enabled outputs, this is not it. - * - * \ingroup ouput - */ -WL_EXPORT void -weston_output_add_destroy_listener(struct weston_output *output, - struct wl_listener *listener) -{ - wl_signal_add(&output->user_destroy_signal, listener); -} - -/** Look up destroy listener for an output - * - * \param output The output to query. - * \param notify The notify function used used for the added destroy listener. - * \return The listener, or NULL if not found. - * - * This looks up the previously added destroy listener struct based on the - * notify function it has. The listener can be used to access user data - * through \c container_of(). - * - * \sa wl_signal_get() weston_output_add_destroy_listener() - * \ingroup output - */ -WL_EXPORT struct wl_listener * -weston_output_get_destroy_listener(struct weston_output *output, - wl_notify_func_t notify) -{ - return wl_signal_get(&output->user_destroy_signal, notify); -} - -/** Uninitialize an output - * - * Removes the output from the list of enabled outputs if necessary, but - * does not call the backend's output disable function. The output will no - * longer be in the list of pending outputs either. - * - * All fields of weston_output become uninitialized, i.e. should not be used - * anymore. The caller can free the memory after this. - * - * \ingroup ouput - * \internal - */ -WL_EXPORT void -weston_output_release(struct weston_output *output) -{ - struct weston_head *head, *tmp; - - output->destroying = 1; - - wl_signal_emit(&output->user_destroy_signal, output); - - if (output->idle_repaint_source) - wl_event_source_remove(output->idle_repaint_source); - - if (output->enabled) - weston_compositor_remove_output(output); - - pixman_region32_fini(&output->region); - wl_list_remove(&output->link); - - wl_list_for_each_safe(head, tmp, &output->head_list, output_link) - weston_head_detach(head); - - free(output->name); -} - -/** Find an output by its given name - * - * \param compositor The compositor to search in. - * \param name The output name to search for. - * \return An existing output with the given name, or NULL if not found. - * - * \ingroup compositor - */ -WL_EXPORT struct weston_output * -weston_compositor_find_output_by_name(struct weston_compositor *compositor, - const char *name) -{ - struct weston_output *output; - - wl_list_for_each(output, &compositor->output_list, link) - if (strcmp(output->name, name) == 0) - return output; - - wl_list_for_each(output, &compositor->pending_output_list, link) - if (strcmp(output->name, name) == 0) - return output; - - return NULL; -} - -/** Create a named output - * - * \param compositor The compositor. - * \param name The name for the output. - * \return A new \c weston_output, or NULL on failure. - * - * This creates a new weston_output that starts with no heads attached. - * - * An output must be configured and it must have at least one head before - * it can be enabled. - * - * \ingroup compositor - */ -WL_EXPORT struct weston_output * -weston_compositor_create_output(struct weston_compositor *compositor, - const char *name) -{ - assert(compositor->backend->create_output); - - if (weston_compositor_find_output_by_name(compositor, name)) { - weston_log("Warning: attempted to create an output with a " - "duplicate name '%s'.\n", name); - return NULL; - } - - return compositor->backend->create_output(compositor, name); -} - -/** Create an output for an unused head - * - * \param compositor The compositor. - * \param head The head to attach to the output. - * \return A new \c weston_output, or NULL on failure. - * - * This creates a new weston_output that starts with the given head attached. - * The output inherits the name of the head. The head must not be already - * attached to another output. - * - * An output must be configured before it can be enabled. - * - * \ingroup compositor - */ -WL_EXPORT struct weston_output * -weston_compositor_create_output_with_head(struct weston_compositor *compositor, - struct weston_head *head) -{ - struct weston_output *output; - - output = weston_compositor_create_output(compositor, head->name); - if (!output) - return NULL; - - if (weston_output_attach_head(output, head) < 0) { - weston_output_destroy(output); - return NULL; - } - - return output; -} - -/** Destroy an output - * - * \param output The output to destroy. - * - * The heads attached to the given output are detached and become unused again. - * - * It is not necessary to explicitly destroy all outputs at compositor exit. - * weston_compositor_destroy() will automatically destroy any remaining - * outputs. - * - * \ingroup ouput - */ -WL_EXPORT void -weston_output_destroy(struct weston_output *output) -{ - output->destroy(output); -} - -/** When you need a head... - * - * This function is a hack, used until all code has been converted to become - * multi-head aware. - * - * \param output The weston_output whose head to get. - * \return The first head in the output's list. - * - * \ingroup ouput - */ -WL_EXPORT struct weston_head * -weston_output_get_first_head(struct weston_output *output) -{ - if (wl_list_empty(&output->head_list)) - return NULL; - - return container_of(output->head_list.next, - struct weston_head, output_link); -} - -/** Allow/Disallow content-protection support for an output - * - * This function sets the allow_protection member for an output. Setting of - * this field will allow the compositor to attempt content-protection for this - * output, for a backend that supports the content-protection protocol. - * - * \param output The weston_output for whom the content-protection is to be - * allowed. - * \param allow_protection The bool value which is to be set. - */ -WL_EXPORT void -weston_output_allow_protection(struct weston_output *output, - bool allow_protection) -{ - output->allow_protection = allow_protection; -} - -static void -xdg_output_unlist(struct wl_resource *resource) -{ - wl_list_remove(wl_resource_get_link(resource)); -} - -static void -xdg_output_destroy(struct wl_client *client, struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static const struct zxdg_output_v1_interface xdg_output_interface = { - xdg_output_destroy -}; - -static void -xdg_output_manager_destroy(struct wl_client *client, - struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -xdg_output_manager_get_xdg_output(struct wl_client *client, - struct wl_resource *manager, - uint32_t id, - struct wl_resource *output_resource) -{ - int version = wl_resource_get_version(manager); - struct weston_head *head = wl_resource_get_user_data(output_resource); - struct weston_output *output = head->output; - struct wl_resource *resource; - - resource = wl_resource_create(client, &zxdg_output_v1_interface, - version, id); - if (resource == NULL) { - wl_client_post_no_memory(client); - return; - } - - wl_list_insert(&head->xdg_output_resource_list, - wl_resource_get_link(resource)); - - wl_resource_set_implementation(resource, &xdg_output_interface, - NULL, xdg_output_unlist); - - zxdg_output_v1_send_logical_position(resource, output->x, output->y); - zxdg_output_v1_send_logical_size(resource, - output->width, - output->height); - if (version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) - zxdg_output_v1_send_name(resource, head->name); - - zxdg_output_v1_send_done(resource); -} - -static const struct zxdg_output_manager_v1_interface xdg_output_manager_interface = { - xdg_output_manager_destroy, - xdg_output_manager_get_xdg_output -}; - -static void -bind_xdg_output_manager(struct wl_client *client, - void *data, uint32_t version, uint32_t id) -{ - struct wl_resource *resource; - - resource = wl_resource_create(client, &zxdg_output_manager_v1_interface, - version, id); - if (resource == NULL) { - wl_client_post_no_memory(client); - return; - } - - wl_resource_set_implementation(resource, &xdg_output_manager_interface, - NULL, NULL); -} - -static void -destroy_viewport(struct wl_resource *resource) -{ - struct weston_surface *surface = - wl_resource_get_user_data(resource); - - if (!surface) - return; - - surface->viewport_resource = NULL; - surface->pending.buffer_viewport.buffer.src_width = - wl_fixed_from_int(-1); - surface->pending.buffer_viewport.surface.width = -1; - surface->pending.buffer_viewport.changed = 1; -} - -static void -viewport_destroy(struct wl_client *client, - struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -viewport_set_source(struct wl_client *client, - struct wl_resource *resource, - wl_fixed_t src_x, - wl_fixed_t src_y, - wl_fixed_t src_width, - wl_fixed_t src_height) -{ - struct weston_surface *surface = - wl_resource_get_user_data(resource); - - if (!surface) { - wl_resource_post_error(resource, - WP_VIEWPORT_ERROR_NO_SURFACE, - "wl_surface for this viewport is no longer exists"); - return; - } - - assert(surface->viewport_resource == resource); - assert(surface->resource); - - if (src_width == wl_fixed_from_int(-1) && - src_height == wl_fixed_from_int(-1) && - src_x == wl_fixed_from_int(-1) && - src_y == wl_fixed_from_int(-1)) { - /* unset source rect */ - surface->pending.buffer_viewport.buffer.src_width = - wl_fixed_from_int(-1); - surface->pending.buffer_viewport.changed = 1; - return; - } - - if (src_width <= 0 || src_height <= 0 || src_x < 0 || src_y < 0) { - wl_resource_post_error(resource, - WP_VIEWPORT_ERROR_BAD_VALUE, - "wl_surface@%d viewport source " - "w=%f <= 0, h=%f <= 0, x=%f < 0, or y=%f < 0", - wl_resource_get_id(surface->resource), - wl_fixed_to_double(src_width), - wl_fixed_to_double(src_height), - wl_fixed_to_double(src_x), - wl_fixed_to_double(src_y)); - return; - } - - surface->pending.buffer_viewport.buffer.src_x = src_x; - surface->pending.buffer_viewport.buffer.src_y = src_y; - surface->pending.buffer_viewport.buffer.src_width = src_width; - surface->pending.buffer_viewport.buffer.src_height = src_height; - surface->pending.buffer_viewport.changed = 1; -} - -static void -viewport_set_destination(struct wl_client *client, - struct wl_resource *resource, - int32_t dst_width, - int32_t dst_height) -{ - struct weston_surface *surface = - wl_resource_get_user_data(resource); - - if (!surface) { - wl_resource_post_error(resource, - WP_VIEWPORT_ERROR_NO_SURFACE, - "wl_surface for this viewport no longer exists"); - return; - } - - assert(surface->viewport_resource == resource); - - if (dst_width == -1 && dst_height == -1) { - /* unset destination size */ - surface->pending.buffer_viewport.surface.width = -1; - surface->pending.buffer_viewport.changed = 1; - return; - } - - if (dst_width <= 0 || dst_height <= 0) { - wl_resource_post_error(resource, - WP_VIEWPORT_ERROR_BAD_VALUE, - "destination size must be positive (%dx%d)", - dst_width, dst_height); - return; - } - - surface->pending.buffer_viewport.surface.width = dst_width; - surface->pending.buffer_viewport.surface.height = dst_height; - surface->pending.buffer_viewport.changed = 1; -} - -static const struct wp_viewport_interface viewport_interface = { - viewport_destroy, - viewport_set_source, - viewport_set_destination -}; - -static void -viewporter_destroy(struct wl_client *client, - struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -viewporter_get_viewport(struct wl_client *client, - struct wl_resource *viewporter, - uint32_t id, - struct wl_resource *surface_resource) -{ - int version = wl_resource_get_version(viewporter); - struct weston_surface *surface = - wl_resource_get_user_data(surface_resource); - struct wl_resource *resource; - - if (surface->viewport_resource) { - wl_resource_post_error(viewporter, - WP_VIEWPORTER_ERROR_VIEWPORT_EXISTS, - "a viewport for that surface already exists"); - return; - } - - resource = wl_resource_create(client, &wp_viewport_interface, - version, id); - if (resource == NULL) { - wl_client_post_no_memory(client); - return; - } - - wl_resource_set_implementation(resource, &viewport_interface, - surface, destroy_viewport); - - surface->viewport_resource = resource; -} - -static const struct wp_viewporter_interface viewporter_interface = { - viewporter_destroy, - viewporter_get_viewport -}; - -static void -bind_viewporter(struct wl_client *client, - void *data, uint32_t version, uint32_t id) -{ - struct wl_resource *resource; - - resource = wl_resource_create(client, &wp_viewporter_interface, - version, id); - if (resource == NULL) { - wl_client_post_no_memory(client); - return; - } - - wl_resource_set_implementation(resource, &viewporter_interface, - NULL, NULL); -} - -static void -destroy_presentation_feedback(struct wl_resource *feedback_resource) -{ - struct weston_presentation_feedback *feedback; - - feedback = wl_resource_get_user_data(feedback_resource); - - wl_list_remove(&feedback->link); - free(feedback); -} - -static void -presentation_destroy(struct wl_client *client, struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -presentation_feedback(struct wl_client *client, - struct wl_resource *presentation_resource, - struct wl_resource *surface_resource, - uint32_t callback) -{ - struct weston_surface *surface; - struct weston_presentation_feedback *feedback; - - surface = wl_resource_get_user_data(surface_resource); - - feedback = zalloc(sizeof *feedback); - if (feedback == NULL) - goto err_calloc; - - feedback->resource = wl_resource_create(client, - &wp_presentation_feedback_interface, - 1, callback); - if (!feedback->resource) - goto err_create; - - wl_resource_set_implementation(feedback->resource, NULL, feedback, - destroy_presentation_feedback); - - wl_list_insert(&surface->pending.feedback_list, &feedback->link); - - return; - -err_create: - free(feedback); - -err_calloc: - wl_client_post_no_memory(client); -} - -static const struct wp_presentation_interface presentation_implementation = { - presentation_destroy, - presentation_feedback -}; - -static void -bind_presentation(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, &wp_presentation_interface, - version, id); - if (resource == NULL) { - wl_client_post_no_memory(client); - return; - } - - wl_resource_set_implementation(resource, &presentation_implementation, - compositor, NULL); - wp_presentation_send_clock_id(resource, compositor->presentation_clock); -} - -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, - version, id); - if (resource == NULL) { - wl_client_post_no_memory(client); - return; - } - - wl_resource_set_implementation(resource, &compositor_interface, - compositor, NULL); -} - -// OHOS remove logger -//static const char * -//output_repaint_status_text(struct weston_output *output) -//{ -// switch (output->repaint_status) { -// case REPAINT_NOT_SCHEDULED: -// return "no repaint"; -// case REPAINT_BEGIN_FROM_IDLE: -// return "start_repaint_loop scheduled"; -// case REPAINT_SCHEDULED: -// return "repaint scheduled"; -// case REPAINT_AWAITING_COMPLETION: -// return "awaiting completion"; -// } -// -// assert(!"output_repaint_status_text missing enum"); -// return NULL; -//} -// -//static void -//debug_scene_view_print_buffer(FILE *fp, struct weston_view *view) -//{ -// struct weston_buffer *buffer = view->surface->buffer_ref.buffer; -// struct wl_shm_buffer *shm; -// struct linux_dmabuf_buffer *dmabuf; -// const struct pixel_format_info *pixel_info = NULL; -// -// if (!buffer) { -// fprintf(fp, "\t\t[buffer not available]\n"); -// return; -// } -// -// shm = wl_shm_buffer_get(buffer->resource); -// if (shm) { -// uint32_t _format = wl_shm_buffer_get_format(shm); -// pixel_info = pixel_format_get_info_shm(_format); -// fprintf(fp, "\t\tSHM buffer\n"); -// fprintf(fp, "\t\t\tformat: 0x%lx %s\n", -// (unsigned long) _format, -// pixel_info ? pixel_info->drm_format_name : "UNKNOWN"); -// return; -// } -// -// dmabuf = linux_dmabuf_buffer_get(buffer->resource); -// if (dmabuf) { -// pixel_info = pixel_format_get_info(dmabuf->attributes.format); -// fprintf(fp, "\t\tdmabuf buffer\n"); -// fprintf(fp, "\t\t\tformat: 0x%lx %s\n", -// (unsigned long) dmabuf->attributes.format, -// pixel_info ? pixel_info->drm_format_name : "UNKNOWN"); -// fprintf(fp, "\t\t\tmodifier: 0x%llx\n", -// (unsigned long long) dmabuf->attributes.modifier[0]); -// return; -// } -// -// fprintf(fp, "\t\tEGL buffer\n"); -//} -// -//static void -//debug_scene_view_print(FILE *fp, struct weston_view *view, int view_idx) -//{ -// struct weston_compositor *ec = view->surface->compositor; -// struct weston_output *output; -// char desc[512]; -// pixman_box32_t *box; -// uint32_t surface_id = 0; -// pid_t pid = 0; -// -// if (view->surface->resource) { -// struct wl_resource *resource = view->surface->resource; -// wl_client_get_credentials(wl_resource_get_client(resource), -// &pid, NULL, NULL); -// surface_id = wl_resource_get_id(view->surface->resource); -// } -// -// if (!view->surface->get_label || -// view->surface->get_label(view->surface, desc, sizeof(desc)) < 0) { -// strcpy(desc, "[no description available]"); -// } -// fprintf(fp, "\tView %d (role %s, PID %d, surface ID %u, %s, %p):\n", -// view_idx, view->surface->role_name, pid, surface_id, -// desc, view); -// -// box = pixman_region32_extents(&view->transform.boundingbox); -// fprintf(fp, "\t\tposition: (%d, %d) -> (%d, %d)\n", -// box->x1, box->y1, box->x2, box->y2); -// box = pixman_region32_extents(&view->transform.opaque); -// -// if (weston_view_is_opaque(view, &view->transform.boundingbox)) { -// fprintf(fp, "\t\t[fully opaque]\n"); -// } else if (!pixman_region32_not_empty(&view->transform.opaque)) { -// fprintf(fp, "\t\t[not opaque]\n"); -// } else { -// fprintf(fp, "\t\t[opaque: (%d, %d) -> (%d, %d)]\n", -// box->x1, box->y1, box->x2, box->y2); -// } -// -// if (view->alpha < 1.0) -// fprintf(fp, "\t\talpha: %f\n", view->alpha); -// -// if (view->output_mask != 0) { -// bool first_output = true; -// fprintf(fp, "\t\toutputs: "); -// wl_list_for_each(output, &ec->output_list, link) { -// if (!(view->output_mask & (1 << output->id))) -// continue; -// fprintf(fp, "%s%d (%s)%s", -// (first_output) ? "" : ", ", -// output->id, output->name, -// (view->output == output) ? " (primary)" : ""); -// first_output = false; -// } -// } else { -// fprintf(fp, "\t\t[no outputs]"); -// } -// -// fprintf(fp, "\n"); -// -// debug_scene_view_print_buffer(fp, view); -//} -// -//static void -//debug_scene_view_print_tree(struct weston_view *view, -// FILE *fp, int *view_idx) -//{ -// struct weston_subsurface *sub; -// struct weston_view *ev; -// -// /* -// * print the view first, then we recursively go on printing -// * sub-surfaces. We bail out once no more sub-surfaces are available. -// */ -// debug_scene_view_print(fp, view, *view_idx); -// -// /* no more sub-surfaces */ -// if (wl_list_empty(&view->surface->subsurface_list)) -// return; -// -// wl_list_for_each(sub, &view->surface->subsurface_list, parent_link) { -// wl_list_for_each(ev, &sub->surface->views, surface_link) { -// /* only print the child views of the current view */ -// if (ev->parent_view != view) -// continue; -// -// (*view_idx)++; -// debug_scene_view_print_tree(ev, fp, view_idx); -// } -// } -//} - -/** - * Output information on how libweston is currently composing the scene - * graph. - * - * \ingroup compositor - */ -// OHOS remove logger -//WL_EXPORT char * -//weston_compositor_print_scene_graph(struct weston_compositor *ec) -//{ -// struct weston_output *output; -// struct weston_layer *layer; -// struct timespec now; -// int layer_idx = 0; -// FILE *fp; -// char *ret; -// size_t len; -// int err; -// -// fp = open_memstream(&ret, &len); -// assert(fp); -// -// weston_compositor_read_presentation_clock(ec, &now); -// fprintf(fp, "Weston scene graph at %ld.%09ld:\n\n", -// now.tv_sec, now.tv_nsec); -// -// wl_list_for_each(output, &ec->output_list, link) { -// struct weston_head *head; -// int head_idx = 0; -// -// fprintf(fp, "Output %d (%s):\n", output->id, output->name); -// assert(output->enabled); -// -// fprintf(fp, "\tposition: (%d, %d) -> (%d, %d)\n", -// output->x, output->y, -// output->x + output->width, -// output->y + output->height); -// fprintf(fp, "\tmode: %dx%d@%.3fHz\n", -// output->current_mode->width, -// output->current_mode->height, -// output->current_mode->refresh / 1000.0); -// fprintf(fp, "\tscale: %d\n", output->scale); -// -// fprintf(fp, "\trepaint status: %s\n", -// output_repaint_status_text(output)); -// if (output->repaint_status == REPAINT_SCHEDULED) -// fprintf(fp, "\tnext repaint: %ld.%09ld\n", -// output->next_repaint.tv_sec, -// output->next_repaint.tv_nsec); -// -// wl_list_for_each(head, &output->head_list, output_link) { -// fprintf(fp, "\tHead %d (%s): %sconnected\n", -// head_idx++, head->name, -// (head->connected) ? "" : "not "); -// } -// } -// -// fprintf(fp, "\n"); -// -// wl_list_for_each(layer, &ec->layer_list, link) { -// struct weston_view *view; -// int view_idx = 0; -// -// fprintf(fp, "Layer %d (pos 0x%lx):\n", layer_idx++, -// (unsigned long) layer->position); -// -// if (!weston_layer_mask_is_infinite(layer)) { -// fprintf(fp, "\t[mask: (%d, %d) -> (%d,%d)]\n\n", -// layer->mask.x1, layer->mask.y1, -// layer->mask.x2, layer->mask.y2); -// } -// -// wl_list_for_each(view, &layer->view_list.link, layer_link.link) { -// debug_scene_view_print_tree(view, fp, &view_idx); -// view_idx++; -// } -// -// if (wl_list_empty(&layer->view_list.link)) -// fprintf(fp, "\t[no views]\n"); -// -// fprintf(fp, "\n"); -// } -// -// err = fclose(fp); -// assert(err == 0); -// -// return ret; -//} - -/** - * Called when the 'scene-graph' debug scope is bound by a client. This - * one-shot weston-debug scope prints the current scene graph when bound, - * and then terminates the stream. - */ -// OHOS remove logger -//static void -//debug_scene_graph_cb(struct weston_log_subscription *sub, void *data) -//{ -// struct weston_compositor *ec = data; -// char *str = weston_compositor_print_scene_graph(ec); -// -// weston_log_subscription_printf(sub, "%s", str); -// free(str); -// weston_log_subscription_complete(sub); -//} - -/** Create the compositor. - * - * This functions creates and initializes a compositor instance. - * - * \param display The Wayland display to be used. - * \param user_data A pointer to an object that can later be retrieved - * \param log_ctx A pointer to weston_debug_compositor - * using the \ref weston_compositor_get_user_data function. - * \return The compositor instance on success or NULL on failure. - * - * \ingroup compositor - */ -WL_EXPORT struct weston_compositor * -weston_compositor_create(struct wl_display *display, -// OHOS remove logger -// struct weston_log_context *log_ctx, - void *user_data) -{ - struct weston_compositor *ec; - struct wl_event_loop *loop; - -// OHOS remove logger -// if (!log_ctx) -// return NULL; - - ec = zalloc(sizeof *ec); - if (!ec) - return NULL; - -// OHOS remove logger -// ec->weston_log_ctx = log_ctx; - ec->wl_display = display; - ec->user_data = user_data; - wl_signal_init(&ec->destroy_signal); - wl_signal_init(&ec->create_surface_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->output_destroyed_signal); - wl_signal_init(&ec->output_moved_signal); - wl_signal_init(&ec->output_resized_signal); - wl_signal_init(&ec->heads_changed_signal); - wl_signal_init(&ec->output_heads_changed_signal); - wl_signal_init(&ec->session_signal); - ec->session_active = true; - - ec->output_id_pool = 0; - ec->repaint_msec = DEFAULT_REPAINT_WINDOW; - - ec->activate_serial = 1; - - ec->touch_mode = WESTON_TOUCH_MODE_NORMAL; - - ec->content_protection = NULL; - - if (!wl_global_create(ec->wl_display, &wl_compositor_interface, 4, - ec, compositor_bind)) - goto fail; - - if (!wl_global_create(ec->wl_display, &wl_subcompositor_interface, 1, - ec, bind_subcompositor)) - goto fail; - - if (!wl_global_create(ec->wl_display, &wp_viewporter_interface, 1, - ec, bind_viewporter)) - goto fail; - - if (!wl_global_create(ec->wl_display, &zxdg_output_manager_v1_interface, 2, - ec, bind_xdg_output_manager)) - goto fail; - - if (!wl_global_create(ec->wl_display, &wp_presentation_interface, 1, - ec, bind_presentation)) - goto fail; - - if (weston_input_init(ec) != 0) - goto fail; - - wl_list_init(&ec->view_list); - wl_list_init(&ec->plane_list); - wl_list_init(&ec->layer_list); - wl_list_init(&ec->seat_list); - wl_list_init(&ec->pending_output_list); - wl_list_init(&ec->output_list); - wl_list_init(&ec->head_list); - wl_list_init(&ec->key_binding_list); - wl_list_init(&ec->modifier_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); - - wl_list_init(&ec->plugin_api_list); - - weston_plane_init(&ec->primary_plane, ec, 0, 0); - weston_compositor_stack_plane(ec, &ec->primary_plane, NULL); - - wl_data_device_manager_init(ec->wl_display); - - wl_display_init_shm(ec->wl_display); - - loop = wl_display_get_event_loop(ec->wl_display); - ec->idle_source = wl_event_loop_add_timer(loop, idle_handler, ec); - ec->repaint_timer = - wl_event_loop_add_timer(loop, output_repaint_timer_handler, - ec); - - weston_layer_init(&ec->fade_layer, ec); - weston_layer_init(&ec->cursor_layer, ec); - - weston_layer_set_position(&ec->fade_layer, WESTON_LAYER_POSITION_FADE); - weston_layer_set_position(&ec->cursor_layer, - WESTON_LAYER_POSITION_CURSOR); - -// OHOS remove debugger -// ec->debug_scene = -// weston_compositor_add_log_scope(ec, "scene-graph", -// "Scene graph details\n", -// debug_scene_graph_cb, NULL, -// ec); -// -// ec->timeline = -// weston_compositor_add_log_scope(ec, "timeline", -// "Timeline event points\n", -// weston_timeline_create_subscription, -// weston_timeline_destroy_subscription, -// ec); - return ec; - -fail: - free(ec); - return NULL; -} - -/** weston_compositor_shutdown - * \ingroup compositor - */ -WL_EXPORT void -weston_compositor_shutdown(struct weston_compositor *ec) -{ - struct weston_output *output, *next; - - wl_event_source_remove(ec->idle_source); - - /* Destroy all outputs associated with this compositor */ - wl_list_for_each_safe(output, next, &ec->output_list, link) - output->destroy(output); - - /* Destroy all pending outputs associated with this compositor */ - wl_list_for_each_safe(output, next, &ec->pending_output_list, link) - output->destroy(output); - - if (ec->renderer) - ec->renderer->destroy(ec); - - weston_binding_list_destroy_all(&ec->key_binding_list); - weston_binding_list_destroy_all(&ec->modifier_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); -} - -/** weston_compositor_exit_with_code - * \ingroup compositor - */ -WL_EXPORT void -weston_compositor_exit_with_code(struct weston_compositor *compositor, - int exit_code) -{ - if (compositor->exit_code == EXIT_SUCCESS) - compositor->exit_code = exit_code; - - weston_compositor_exit(compositor); -} - -/** weston_compositor_set_default_pointer_grab - * \ingroup compositor - */ -WL_EXPORT void -weston_compositor_set_default_pointer_grab(struct weston_compositor *ec, - const struct weston_pointer_grab_interface *interface) -{ - struct weston_seat *seat; - - ec->default_pointer_grab = interface; - wl_list_for_each(seat, &ec->seat_list, link) { - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - if (pointer) - weston_pointer_set_default_grab(pointer, interface); - } -} - -/** weston_compositor_set_presentation_clock - * \ingroup compositor - */ -WL_EXPORT int -weston_compositor_set_presentation_clock(struct weston_compositor *compositor, - clockid_t clk_id) -{ - struct timespec ts; - - if (clock_gettime(clk_id, &ts) < 0) - return -1; - - compositor->presentation_clock = clk_id; - - return 0; -} - -/** For choosing the software clock, when the display hardware or API - * does not expose a compatible presentation timestamp. - * - * \ingroup compositor - */ -WL_EXPORT int -weston_compositor_set_presentation_clock_software( - struct weston_compositor *compositor) -{ - /* In order of preference */ - static const clockid_t clocks[] = { - CLOCK_MONOTONIC_RAW, /* no jumps, no crawling */ - CLOCK_MONOTONIC_COARSE, /* no jumps, may crawl, fast & coarse */ - CLOCK_MONOTONIC, /* no jumps, may crawl */ - CLOCK_REALTIME_COARSE, /* may jump and crawl, fast & coarse */ - CLOCK_REALTIME /* may jump and crawl */ - }; - unsigned i; - - for (i = 0; i < ARRAY_LENGTH(clocks); i++) - if (weston_compositor_set_presentation_clock(compositor, - clocks[i]) == 0) - return 0; - - weston_log("Error: no suitable presentation clock available.\n"); - - return -1; -} - -/** Read the current time from the Presentation clock - * - * \param compositor - * \param[out] ts The current time. - * - * \note Reading the current time in user space is always imprecise to some - * degree. - * - * This function is never meant to fail. If reading the clock does fail, - * an error message is logged and a zero time is returned. Callers are not - * supposed to detect or react to failures. - * - * \ingroup compositor - */ -WL_EXPORT void -weston_compositor_read_presentation_clock( - const struct weston_compositor *compositor, - struct timespec *ts) -{ - static bool warned; - int ret; - - ret = clock_gettime(compositor->presentation_clock, ts); - if (ret < 0) { - ts->tv_sec = 0; - ts->tv_nsec = 0; - - if (!warned) - weston_log("Error: failure to read " - "the presentation clock %#x: '%s' (%d)\n", - compositor->presentation_clock, - strerror(errno), errno); - warned = true; - } -} - -/** Import dmabuf buffer into current renderer - * - * \param compositor - * \param buffer the dmabuf buffer to import - * \return true on usable buffers, false otherwise - * - * This function tests that the linux_dmabuf_buffer is usable - * for the current renderer. Returns false on unusable buffers. Usually - * usability is tested by importing the dmabufs for composition. - * - * This hook is also used for detecting if the renderer supports - * dmabufs at all. If the renderer hook is NULL, dmabufs are not - * supported. - * - * \ingroup compositor - */ -WL_EXPORT bool -weston_compositor_import_dmabuf(struct weston_compositor *compositor, - struct linux_dmabuf_buffer *buffer) -{ - struct weston_renderer *renderer; - - renderer = compositor->renderer; - - if (renderer->import_dmabuf == NULL) - return false; - - return renderer->import_dmabuf(compositor, buffer); -} - -WL_EXPORT bool -weston_compositor_dmabuf_can_scanout(struct weston_compositor *compositor, - struct linux_dmabuf_buffer *buffer) -{ - struct weston_backend *backend = compositor->backend; - - if (backend->can_scanout_dmabuf == NULL) - return false; - - return backend->can_scanout_dmabuf(compositor, buffer); -} - -WL_EXPORT void -weston_version(int *major, int *minor, int *micro) -{ - *major = WESTON_VERSION_MAJOR; - *minor = WESTON_VERSION_MINOR; - *micro = WESTON_VERSION_MICRO; -} - -/** - * Attempts to find a module path from the module map specified in the - * environment. If found, writes the full path into the path variable. - * - * The module map is a string in environment variable WESTON_MODULE_MAP, where - * each entry is of the form "name=path" and entries are separated by - * semicolons. Whitespace is significant. - * - * \param name The name to search for. - * \param path Where the path is written to if found. - * \param path_len Allocated bytes at \c path . - * \returns The length of the string written to path on success, or 0 if the - * module was not specified in the environment map or path_len was too small. - */ -WL_EXPORT size_t -weston_module_path_from_env(const char *name, char *path, size_t path_len) -{ - const char *mapping = getenv("WESTON_MODULE_MAP"); - const char *end; - const int name_len = strlen(name); - - if (!mapping) - return 0; - - end = mapping + strlen(mapping); - while (mapping < end && *mapping) { - const char *filename, *next; - - /* early out: impossibly short string */ - if (end - mapping < name_len + 1) - return 0; - - filename = &mapping[name_len + 1]; - next = strchrnul(mapping, ';'); - - if (strncmp(mapping, name, name_len) == 0 && - mapping[name_len] == '=') { - size_t file_len = next - filename; /* no trailing NUL */ - if (file_len >= path_len) - return 0; - strncpy(path, filename, file_len); - path[file_len] = '\0'; - return file_len; - } - - mapping = next + 1; - } - - return 0; -} - -WL_EXPORT void * -weston_load_module(const char *name, const char *entrypoint) -{ - char path[PATH_MAX]; - void *module, *init; - size_t len; - - if (name == NULL) - return NULL; - - if (name[0] != '/') { - len = weston_module_path_from_env(name, path, sizeof path); - if (len == 0) - len = snprintf(path, sizeof path, "%s/%s", - LIBWESTON_MODULEDIR, name); - } else { - len = snprintf(path, sizeof path, "%s", name); - } - - /* snprintf returns the length of the string it would've written, - * _excluding_ the NUL byte. So even being equal to the size of - * our buffer is an error here. */ - if (len >= sizeof path) - return NULL; - - module = dlopen(path, RTLD_NOW | RTLD_NOLOAD); - if (module) { - weston_log("Module '%s' already loaded\n", path); - } else { - 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; -} - -/** Add a compositor destroy listener only once - * - * \param compositor The compositor whose destroy to watch for. - * \param listener The listener struct to initialize. - * \param destroy_handler The callback when compositor is destroyed. - * \return True if listener is added, or false if there already is a listener - * with the given \c destroy_handler. - * - * This function does nothing and returns false if the given callback function - * is already present in the weston_compositor destroy callbacks list. - * Otherwise, this function initializes the given listener with the given - * callback pointer and adds it to the compositor's destroy callbacks list. - * - * This can be used to ensure that plugin initialization is done only once - * in case the same plugin is loaded multiple times. If this function returns - * false, the plugin should be already initialized successfully. - * - * All plugins should register a destroy listener for cleaning up. Note, that - * the plugin destruction order is not guaranteed: plugins that depend on other - * plugins must be able to be torn down in arbitrary order. - * - * \sa weston_compositor_destroy - */ -WL_EXPORT bool -weston_compositor_add_destroy_listener_once(struct weston_compositor *compositor, - struct wl_listener *listener, - wl_notify_func_t destroy_handler) -{ - if (wl_signal_get(&compositor->destroy_signal, destroy_handler)) - return false; - - listener->notify = destroy_handler; - wl_signal_add(&compositor->destroy_signal, listener); - return true; -} - -/** Destroys the compositor. - * - * This function cleans up the compositor state and then destroys it. - * - * @param compositor The compositor to be destroyed. - * - * @ingroup compositor - */ -WL_EXPORT void -weston_compositor_destroy(struct weston_compositor *compositor) -{ - /* prevent further rendering while shutting down */ - compositor->state = WESTON_COMPOSITOR_OFFSCREEN; - - wl_signal_emit(&compositor->destroy_signal, compositor); - - weston_compositor_xkb_destroy(compositor); - - if (compositor->backend) - compositor->backend->destroy(compositor); - - /* The backend is responsible for destroying the heads. */ - assert(wl_list_empty(&compositor->head_list)); - - weston_plugin_api_destroy_list(compositor); - - if (compositor->heads_changed_source) - wl_event_source_remove(compositor->heads_changed_source); - -// OHOS remove logger -// weston_log_scope_destroy(compositor->debug_scene); -// compositor->debug_scene = NULL; -// -// weston_log_scope_destroy(compositor->timeline); -// compositor->timeline = NULL; - - free(compositor); -} - -/** Instruct the compositor to exit. - * - * This functions does not directly destroy the compositor object, it merely - * command it to start the tear down process. It is not guaranteed that the - * tear down will happen immediately. - * - * \param compositor The compositor to tear down. - * - * \ingroup compositor - */ -WL_EXPORT void -weston_compositor_exit(struct weston_compositor *compositor) -{ - compositor->exit(compositor); -} - -/** Return the user data stored in the compositor. - * - * This function returns the user data pointer set with user_data parameter - * to the \ref weston_compositor_create function. - * - * \ingroup compositor - */ -WL_EXPORT void * -weston_compositor_get_user_data(struct weston_compositor *compositor) -{ - return compositor->user_data; -} - -static const char * const backend_map[] = { - [WESTON_BACKEND_DRM] = "drm-backend.so", - [WESTON_BACKEND_FBDEV] = "fbdev-backend.so", - [WESTON_BACKEND_HEADLESS] = "headless-backend.so", - [WESTON_BACKEND_RDP] = "rdp-backend.so", - [WESTON_BACKEND_WAYLAND] = "wayland-backend.so", - [WESTON_BACKEND_X11] = "x11-backend.so", -}; - -/** Load a backend into a weston_compositor - * - * A backend must be loaded to make a weston_compositor work. A backend - * provides input and output capabilities, and determines the renderer to use. - * - * \param compositor A compositor that has not had a backend loaded yet. - * \param backend Name of the backend file. - * \param config_base A pointer to a backend-specific configuration - * structure's 'base' member. - * - * \return 0 on success, or -1 on error. - * - * \ingroup compositor - */ -WL_EXPORT int -weston_compositor_load_backend(struct weston_compositor *compositor, - enum weston_compositor_backend backend, - struct weston_backend_config *config_base) -{ - int (*backend_init)(struct weston_compositor *c, - struct weston_backend_config *config_base); - - if (compositor->backend) { - weston_log("Error: attempt to load a backend when one is already loaded\n"); - return -1; - } - - if (backend >= ARRAY_LENGTH(backend_map)) - return -1; - - backend_init = weston_load_module(backend_map[backend], "weston_backend_init"); - if (!backend_init) - return -1; - - if (backend_init(compositor, config_base) < 0) { - compositor->backend = NULL; - return -1; - } - - return 0; -} - -/** weston_compositor_load_xwayland - * \ingroup compositor - */ -WL_EXPORT int -weston_compositor_load_xwayland(struct weston_compositor *compositor) -{ - int (*module_init)(struct weston_compositor *ec); - - module_init = weston_load_module("xwayland.so", "weston_module_init"); - if (!module_init) - return -1; - if (module_init(compositor) < 0) - return -1; - return 0; -} - -/** Resolve an internal compositor error by disconnecting the client. - * - * This function is used in cases when the wl_buffer turns out - * unusable and there is no fallback path. - * - * It is possible the fault is caused by a compositor bug, the underlying - * graphics stack bug or normal behaviour, or perhaps a client mistake. - * In any case, the options are to either composite garbage or nothing, - * or disconnect the client. This is a helper function for the latter. - * - * The error is sent as an INVALID_OBJECT error on the client's wl_display. - * - * \param buffer The weston buffer that is unusable. - * \param msg A custom error message attached to the protocol error. - */ -WL_EXPORT void -weston_buffer_send_server_error(struct weston_buffer *buffer, - const char *msg) -{ - struct wl_client *client; - struct wl_resource *display_resource; - uint32_t id; - - assert(buffer->resource); - id = wl_resource_get_id(buffer->resource); - client = wl_resource_get_client(buffer->resource); - display_resource = wl_client_get_object(client, 1); - - assert(display_resource); - wl_resource_post_error(display_resource, - WL_DISPLAY_ERROR_INVALID_OBJECT, - "server error with " - "wl_buffer@%u: %s", id, msg); -} - -WL_EXPORT void -weston_output_disable_planes_incr(struct weston_output *output) -{ - output->disable_planes++; - /* - * If disable_planes changes from 0 to non-zero, it means some type of - * recording of content has started, and therefore protection level of - * the protected surfaces must be updated to avoid the recording of - * the protected content. - */ - if (output->disable_planes == 1) - weston_schedule_surface_protection_update(output->compositor); -} - -WL_EXPORT void -weston_output_disable_planes_decr(struct weston_output *output) -{ - output->disable_planes--; - /* - * If disable_planes changes from non-zero to 0, it means no content - * recording is going on any more, and the protected and surfaces can be - * shown without any apprehensions about content being recorded. - */ - if (output->disable_planes == 0) - weston_schedule_surface_protection_update(output->compositor); - -} diff --git a/libweston/content-protection.c b/libweston/content-protection.c deleted file mode 100755 index 0f5f893..0000000 --- a/libweston/content-protection.c +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright © 2019 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include "libweston-internal.h" -#include "weston-content-protection-server-protocol.h" -#include "shared/helpers.h" -#include "shared/timespec-util.h" - -// OHOS logcat -//#define content_protection_log(cp, ...) \ -// weston_log_scope_printf((cp)->debug, __VA_ARGS__) -#define content_protection_log(cp, ...) \ - weston_log(__VA_ARGS__) - -static const char * const content_type_name [] = { - [WESTON_PROTECTED_SURFACE_TYPE_UNPROTECTED] = "UNPROTECTED", - [WESTON_PROTECTED_SURFACE_TYPE_HDCP_0] = "TYPE-0", - [WESTON_PROTECTED_SURFACE_TYPE_HDCP_1] = "TYPE-1", -}; - -void -weston_protected_surface_send_event(struct protected_surface *psurface, - enum weston_hdcp_protection protection) -{ - struct wl_resource *p_resource; - enum weston_protected_surface_type protection_type; - struct content_protection *cp; - struct wl_resource *surface_resource; - - p_resource = psurface->protection_resource; - if (!p_resource) - return; - /* No event to be sent to client, in case of enforced mode */ - if (psurface->surface->protection_mode == WESTON_SURFACE_PROTECTION_MODE_ENFORCED) - return; - protection_type = (enum weston_protected_surface_type) protection; - weston_protected_surface_send_status(p_resource, protection_type); - - cp = psurface->cp_backptr; - surface_resource = psurface->surface->resource; - content_protection_log(cp, "wl_surface@%"PRIu32" Protection type set to %s\n", - wl_resource_get_id(surface_resource), - content_type_name[protection_type]); -} - -static void -set_type(struct wl_client *client, struct wl_resource *resource, - enum weston_protected_surface_type content_type) -{ - struct content_protection *cp; - struct protected_surface *psurface; - enum weston_hdcp_protection weston_cp; - struct wl_resource *surface_resource; - - psurface = wl_resource_get_user_data(resource); - if (!psurface) - return; - cp = psurface->cp_backptr; - surface_resource = psurface->surface->resource; - - if (content_type < WESTON_PROTECTED_SURFACE_TYPE_UNPROTECTED || - content_type > WESTON_PROTECTED_SURFACE_TYPE_HDCP_1) { - wl_resource_post_error(resource, - WESTON_PROTECTED_SURFACE_ERROR_INVALID_TYPE, - "wl_surface@%"PRIu32" Invalid content-type %d for request:set_type\n", - wl_resource_get_id(surface_resource), content_type); - - content_protection_log(cp, "wl_surface@%"PRIu32" Invalid content-type %d for resquest:set_type\n", - wl_resource_get_id(surface_resource), content_type); - return; - } - - content_protection_log(cp, "wl_surface@%"PRIu32" Request: Enable Content-Protection Type: %s\n", - wl_resource_get_id(surface_resource), - content_type_name[content_type]); - - weston_cp = (enum weston_hdcp_protection) content_type; - psurface->surface->pending.desired_protection = weston_cp; -} - -static void -protected_surface_destroy(struct wl_client *client, struct wl_resource *resource) -{ - struct protected_surface *psurface; - - psurface = wl_resource_get_user_data(resource); - if (!psurface) - return; - psurface->surface->pending.desired_protection = WESTON_HDCP_DISABLE; -} - -static void -set_enforce_mode(struct wl_client *client, struct wl_resource *resource) -{ - /* - * Enforce Censored-Visibility. Compositor censors the protected - * surface on an unsecured output. - * In case of a surface, being shown on an unprotected output, the - * compositor hides the surface, not allowing it to be displayed on - * the unprotected output, without bothering the client. No difference - * for the protected outputs. - * - * The member 'protection_mode' is "double-buffered", so setting it in - * pending_state will cause the setting of the corresponding - * 'protection_mode' in weston_surface, after the commit. - * - * This function sets the 'protection_mode' of the weston_surface_state - * to 'enfoced'. The renderers inspect the flag and compare the - * desired_protection of the surface, to the current_protection of the - * output, based on that the real surface or a place-holder content, - * (e.g. solid color) are shown. - */ - - struct protected_surface *psurface; - - psurface = wl_resource_get_user_data(resource); - if (!psurface) - return; - - psurface->surface->pending.protection_mode = - WESTON_SURFACE_PROTECTION_MODE_ENFORCED; -} - -static void -set_relax_mode(struct wl_client *client, struct wl_resource *resource) -{ - /* - * Relaxed mode. By default this mode will be activated. - * In case of a surface, being shown in unprotected output, - * compositor just sends the event for protection status changed. - * - * On setting the relaxed mode, the 'protection_mode' member is queued - * to be set to 'relax' from the existing 'enforce' mode. - */ - - struct protected_surface *psurface; - - psurface = wl_resource_get_user_data(resource); - if (!psurface) - return; - - psurface->surface->pending.protection_mode = - WESTON_SURFACE_PROTECTION_MODE_RELAXED; -} - -static const struct weston_protected_surface_interface protected_surface_implementation = { - protected_surface_destroy, - set_type, - set_enforce_mode, - set_relax_mode, -}; - -static void -cp_destroy_listener(struct wl_listener *listener, void *data) -{ - struct content_protection *cp; - - cp = container_of(listener, struct content_protection, - destroy_listener); - wl_list_remove(&cp->destroy_listener.link); - wl_list_remove(&cp->protected_list); - // OHOS remove logger - // weston_log_scope_destroy(cp->debug); - // cp->debug = NULL; - cp->surface_protection_update = NULL; - free(cp); -} - -static void -free_protected_surface(struct protected_surface *psurface) -{ - psurface->surface->pending.desired_protection = WESTON_HDCP_DISABLE; - wl_resource_set_user_data(psurface->protection_resource, NULL); - wl_list_remove(&psurface->surface_destroy_listener.link); - wl_list_remove(&psurface->link); - free(psurface); -} - -static void -surface_destroyed(struct wl_listener *listener, void *data) -{ - struct protected_surface *psurface; - - psurface = container_of(listener, struct protected_surface, - surface_destroy_listener); - free_protected_surface(psurface); -} - -static void -destroy_protected_surface(struct wl_resource *resource) -{ - struct protected_surface *psurface; - - psurface = wl_resource_get_user_data(resource); - if (!psurface) - return; - free_protected_surface(psurface); -} - -static void -get_protection(struct wl_client *client, struct wl_resource *cp_resource, - uint32_t id, struct wl_resource *surface_resource) -{ - struct wl_resource *resource; - struct weston_surface *surface; - struct content_protection *cp; - struct protected_surface *psurface; - struct wl_listener *listener; - - surface = wl_resource_get_user_data(surface_resource); - assert(surface); - cp = wl_resource_get_user_data(cp_resource); - assert(cp); - - /* - * Check if this client has a corresponding protected-surface - */ - - listener = wl_resource_get_destroy_listener(surface->resource, - surface_destroyed); - - if (listener) { - wl_resource_post_error(cp_resource, - WESTON_CONTENT_PROTECTION_ERROR_SURFACE_EXISTS, - "wl_surface@%"PRIu32" Protection already exists", - wl_resource_get_id(surface_resource)); - return; - } - - psurface = zalloc(sizeof(struct protected_surface)); - if (!psurface) { - wl_client_post_no_memory(client); - return; - } - psurface->cp_backptr = cp; - resource = wl_resource_create(client, &weston_protected_surface_interface, - 1, id); - if (!resource) { - free(psurface); - wl_client_post_no_memory(client); - return; - } - - wl_list_insert(&cp->protected_list, &psurface->link); - wl_resource_set_implementation(resource, &protected_surface_implementation, - psurface, - destroy_protected_surface); - - psurface->protection_resource = resource; - psurface->surface = surface; - psurface->surface_destroy_listener.notify = surface_destroyed; - wl_resource_add_destroy_listener(surface->resource, - &psurface->surface_destroy_listener); - weston_protected_surface_send_event(psurface, - psurface->surface->current_protection); -} - -static void -destroy_protection(struct wl_client *client, struct wl_resource *cp_resource) -{ - wl_resource_destroy(cp_resource); -} - -static const -struct weston_content_protection_interface content_protection_implementation = { - destroy_protection, - get_protection, -}; - -static void -bind_weston_content_protection(struct wl_client *client, void *data, - uint32_t version, uint32_t id) -{ - struct content_protection *cp = data; - struct wl_resource *resource; - - resource = wl_resource_create(client, - &weston_content_protection_interface, - 1, id); - if (!resource) { - wl_client_post_no_memory(client); - return; - } - - wl_resource_set_implementation(resource, - &content_protection_implementation, - cp, NULL); -} -/* Advertise the content-protection support. - * - * Calling this function sets up the content-protection support via HDCP. - * This exposes the global interface, visible to the client, enabling them to - * request for content-protection for their surfaces according to the type of - * content. - */ - -WL_EXPORT int -weston_compositor_enable_content_protection(struct weston_compositor *compositor) -{ - struct content_protection *cp; - - cp = zalloc(sizeof(*cp)); - if (cp == NULL) - return -1; - cp->compositor = compositor; - - compositor->content_protection = cp; - wl_list_init(&cp->protected_list); - if (wl_global_create(compositor->wl_display, - &weston_content_protection_interface, 1, cp, - bind_weston_content_protection) == NULL) - return -1; - - cp->destroy_listener.notify = cp_destroy_listener; - wl_signal_add(&compositor->destroy_signal, &cp->destroy_listener); -// OHOS remove logger -// cp->debug = weston_compositor_add_log_scope(compositor, "content-protection-debug", -// "debug-logs for content-protection", -// NULL, NULL, NULL); - return 0; -} diff --git a/libweston/data-device.c b/libweston/data-device.c deleted file mode 100644 index 8b0a282..0000000 --- a/libweston/data-device.c +++ /dev/null @@ -1,1370 +0,0 @@ -/* - * Copyright © 2011 Kristian Høgsberg - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include -#include "libweston-internal.h" -#include "shared/helpers.h" -#include "shared/timespec-util.h" - -struct weston_drag { - struct wl_client *client; - struct weston_data_source *data_source; - struct wl_listener data_source_listener; - struct weston_view *focus; - struct wl_resource *focus_resource; - struct wl_listener focus_listener; - struct weston_view *icon; - struct wl_listener icon_destroy_listener; - int32_t dx, dy; - struct weston_keyboard_grab keyboard_grab; -}; - -struct weston_pointer_drag { - struct weston_drag base; - struct weston_pointer_grab grab; -}; - -struct weston_touch_drag { - struct weston_drag base; - struct weston_touch_grab grab; -}; - -#define ALL_ACTIONS (WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | \ - WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | \ - WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) - -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); - - /* Protect against untimely calls from older data offers */ - if (!offer->source || offer != offer->source->offer) - return; - - /* 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? */ - - offer->source->accept(offer->source, serial, mime_type); - offer->source->accepted = mime_type != NULL; -} - -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 == offer->source->offer) - 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 void -data_source_notify_finish(struct weston_data_source *source) -{ - if (!source->actions_set) - return; - - if (source->offer->in_ask && - wl_resource_get_version(source->resource) >= - WL_DATA_SOURCE_ACTION_SINCE_VERSION) { - wl_data_source_send_action(source->resource, - source->current_dnd_action); - } - - if (wl_resource_get_version(source->resource) >= - WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION) { - wl_data_source_send_dnd_finished(source->resource); - } - - source->offer = NULL; -} - -static uint32_t -data_offer_choose_action(struct weston_data_offer *offer) -{ - uint32_t available_actions, preferred_action = 0; - uint32_t source_actions, offer_actions; - - if (wl_resource_get_version(offer->resource) >= - WL_DATA_OFFER_ACTION_SINCE_VERSION) { - offer_actions = offer->dnd_actions; - preferred_action = offer->preferred_dnd_action; - } else { - offer_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; - } - - if (wl_resource_get_version(offer->source->resource) >= - WL_DATA_SOURCE_ACTION_SINCE_VERSION) - source_actions = offer->source->dnd_actions; - else - source_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; - - available_actions = offer_actions & source_actions; - - if (!available_actions) - return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; - - if (offer->source->seat && - offer->source->compositor_action & available_actions) - return offer->source->compositor_action; - - /* If the dest side has a preferred DnD action, use it */ - if ((preferred_action & available_actions) != 0) - return preferred_action; - - /* Use the first found action, in bit order */ - return 1 << (ffs(available_actions) - 1); -} - -static void -data_offer_update_action(struct weston_data_offer *offer) -{ - uint32_t action; - - if (!offer->source) - return; - - action = data_offer_choose_action(offer); - - if (offer->source->current_dnd_action == action) - return; - - offer->source->current_dnd_action = action; - - if (offer->in_ask) - return; - - if (wl_resource_get_version(offer->source->resource) >= - WL_DATA_SOURCE_ACTION_SINCE_VERSION) - wl_data_source_send_action(offer->source->resource, action); - - if (wl_resource_get_version(offer->resource) >= - WL_DATA_OFFER_ACTION_SINCE_VERSION) - wl_data_offer_send_action(offer->resource, action); -} - -static void -data_offer_set_actions(struct wl_client *client, - struct wl_resource *resource, - uint32_t dnd_actions, uint32_t preferred_action) -{ - struct weston_data_offer *offer = wl_resource_get_user_data(resource); - - if (dnd_actions & ~ALL_ACTIONS) { - wl_resource_post_error(offer->resource, - WL_DATA_OFFER_ERROR_INVALID_ACTION_MASK, - "invalid action mask %x", dnd_actions); - return; - } - - if (preferred_action && - (!(preferred_action & dnd_actions) || - __builtin_popcount(preferred_action) > 1)) { - wl_resource_post_error(offer->resource, - WL_DATA_OFFER_ERROR_INVALID_ACTION, - "invalid action %x", preferred_action); - return; - } - - offer->dnd_actions = dnd_actions; - offer->preferred_dnd_action = preferred_action; - data_offer_update_action(offer); -} - -static void -data_offer_finish(struct wl_client *client, struct wl_resource *resource) -{ - struct weston_data_offer *offer = wl_resource_get_user_data(resource); - - if (!offer->source || offer->source->offer != offer) - return; - - if (offer->source->set_selection) { - wl_resource_post_error(offer->resource, - WL_DATA_OFFER_ERROR_INVALID_FINISH, - "finish only valid for drag n drop"); - return; - } - - /* Disallow finish while we have a grab driving drag-and-drop, or - * if the negotiation is not at the right stage - */ - if (offer->source->seat || - !offer->source->accepted) { - wl_resource_post_error(offer->resource, - WL_DATA_OFFER_ERROR_INVALID_FINISH, - "premature finish request"); - return; - } - - switch (offer->source->current_dnd_action) { - case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE: - case WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK: - wl_resource_post_error(offer->resource, - WL_DATA_OFFER_ERROR_INVALID_OFFER, - "offer finished with an invalid action"); - return; - default: - break; - } - - data_source_notify_finish(offer->source); -} - -static const struct wl_data_offer_interface data_offer_interface = { - data_offer_accept, - data_offer_receive, - data_offer_destroy, - data_offer_finish, - data_offer_set_actions, -}; - -static void -destroy_data_offer(struct wl_resource *resource) -{ - struct weston_data_offer *offer = wl_resource_get_user_data(resource); - - if (!offer->source) - goto out; - - wl_list_remove(&offer->source_destroy_listener.link); - - if (offer->source->offer != offer) - goto out; - - /* If the drag destination has version < 3, wl_data_offer.finish - * won't be called, so do this here as a safety net, because - * we still want the version >=3 drag source to be happy. - */ - if (wl_resource_get_version(offer->resource) < - WL_DATA_OFFER_ACTION_SINCE_VERSION) { - data_source_notify_finish(offer->source); - } else if (offer->source->resource && - wl_resource_get_version(offer->source->resource) >= - WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION) { - wl_data_source_send_cancelled(offer->source->resource); - } - - offer->source->offer = NULL; -out: - 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 weston_data_offer * -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, - wl_resource_get_version(target), 0); - if (offer->resource == NULL) { - free(offer); - return NULL; - } - - wl_resource_set_implementation(offer->resource, &data_offer_interface, - offer, destroy_data_offer); - - offer->in_ask = false; - offer->dnd_actions = 0; - offer->preferred_dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; - 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); - - source->offer = offer; - source->accepted = false; - - return offer; -} - -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 void -data_source_set_actions(struct wl_client *client, - struct wl_resource *resource, - uint32_t dnd_actions) -{ - struct weston_data_source *source = - wl_resource_get_user_data(resource); - - if (source->actions_set) { - wl_resource_post_error(source->resource, - WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK, - "cannot set actions more than once"); - return; - } - - if (dnd_actions & ~ALL_ACTIONS) { - wl_resource_post_error(source->resource, - WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK, - "invalid action mask %x", dnd_actions); - return; - } - - if (source->seat) { - wl_resource_post_error(source->resource, - WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK, - "invalid action change after " - "wl_data_device.start_drag"); - return; - } - - source->dnd_actions = dnd_actions; - source->actions_set = true; -} - -static struct wl_data_source_interface data_source_interface = { - data_source_offer, - data_source_destroy, - data_source_set_actions -}; - -static void -drag_surface_configure(struct weston_drag *drag, - struct weston_pointer *pointer, - struct weston_touch *touch, - struct weston_surface *es, - int32_t sx, int32_t sy) -{ - struct weston_layer_entry *list; - float fx, fy; - - assert((pointer != NULL && touch == NULL) || - (pointer == NULL && touch != NULL)); - - if (!weston_surface_is_mapped(es) && es->buffer_ref.buffer) { - if (pointer && pointer->sprite && - weston_view_is_mapped(pointer->sprite)) - list = &pointer->sprite->layer_link; - else - list = &es->compositor->cursor_layer.view_list; - - weston_layer_entry_remove(&drag->icon->layer_link); - weston_layer_entry_insert(list, &drag->icon->layer_link); - weston_view_update_transform(drag->icon); - pixman_region32_clear(&es->pending.input); - es->is_mapped = true; - drag->icon->is_mapped = true; - } - - drag->dx += sx; - drag->dy += sy; - - /* init to 0 for avoiding a compile warning */ - fx = fy = 0; - if (pointer) { - fx = wl_fixed_to_double(pointer->x) + drag->dx; - fy = wl_fixed_to_double(pointer->y) + drag->dy; - } else if (touch) { - fx = wl_fixed_to_double(touch->grab_x) + drag->dx; - fy = wl_fixed_to_double(touch->grab_y) + drag->dy; - } - weston_view_set_position(drag->icon, fx, fy); -} - -static int -pointer_drag_surface_get_label(struct weston_surface *surface, - char *buf, size_t len) -{ - return snprintf(buf, len, "pointer drag icon"); -} - -static void -pointer_drag_surface_committed(struct weston_surface *es, - int32_t sx, int32_t sy) -{ - struct weston_pointer_drag *drag = es->committed_private; - struct weston_pointer *pointer = drag->grab.pointer; - - assert(es->committed == pointer_drag_surface_committed); - - drag_surface_configure(&drag->base, pointer, NULL, es, sx, sy); -} - -static int -touch_drag_surface_get_label(struct weston_surface *surface, - char *buf, size_t len) -{ - return snprintf(buf, len, "touch drag icon"); -} - -static void -touch_drag_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) -{ - struct weston_touch_drag *drag = es->committed_private; - struct weston_touch *touch = drag->grab.touch; - - assert(es->committed == touch_drag_surface_committed); - - drag_surface_configure(&drag->base, NULL, touch, es, sx, sy); -} - -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_seat *seat, - struct weston_view *view, - wl_fixed_t sx, wl_fixed_t sy) -{ - struct wl_resource *resource, *offer_resource = NULL; - struct wl_display *display = seat->compositor->wl_display; - struct weston_data_offer *offer; - uint32_t serial; - - if (drag->focus && view && drag->focus->surface == view->surface) { - drag->focus = view; - return; - } - - 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 (!view || !view->surface->resource) - return; - - if (!drag->data_source && - wl_resource_get_client(view->surface->resource) != drag->client) - return; - - if (drag->data_source && - drag->data_source->offer) { - /* Unlink the offer from the source */ - offer = drag->data_source->offer; - offer->source = NULL; - drag->data_source->offer = NULL; - wl_list_remove(&offer->source_destroy_listener.link); - } - - resource = wl_resource_find_for_client(&seat->drag_resource_list, - wl_resource_get_client(view->surface->resource)); - if (!resource) - return; - - serial = wl_display_next_serial(display); - - if (drag->data_source) { - drag->data_source->accepted = false; - offer = weston_data_source_send_offer(drag->data_source, resource); - if (offer == NULL) - return; - - data_offer_update_action(offer); - - offer_resource = offer->resource; - if (wl_resource_get_version (offer_resource) >= - WL_DATA_OFFER_SOURCE_ACTIONS_SINCE_VERSION) { - wl_data_offer_send_source_actions (offer_resource, - drag->data_source->dnd_actions); - } - } - - wl_data_device_send_enter(resource, serial, view->surface->resource, - sx, sy, offer_resource); - - drag->focus = view; - 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_pointer_drag *drag = - container_of(grab, struct weston_pointer_drag, grab); - struct weston_pointer *pointer = grab->pointer; - struct weston_view *view; - wl_fixed_t sx, sy; - - view = weston_compositor_pick_view(pointer->seat->compositor, - pointer->x, pointer->y, - &sx, &sy); - if (drag->base.focus != view) - weston_drag_set_focus(&drag->base, pointer->seat, view, sx, sy); -} - -static void -drag_grab_motion(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_motion_event *event) -{ - struct weston_pointer_drag *drag = - container_of(grab, struct weston_pointer_drag, grab); - struct weston_pointer *pointer = drag->grab.pointer; - float fx, fy; - wl_fixed_t sx, sy; - uint32_t msecs; - - weston_pointer_move(pointer, event); - - if (drag->base.icon) { - fx = wl_fixed_to_double(pointer->x) + drag->base.dx; - fy = wl_fixed_to_double(pointer->y) + drag->base.dy; - weston_view_set_position(drag->base.icon, fx, fy); - weston_view_schedule_repaint(drag->base.icon); - } - - if (drag->base.focus_resource) { - msecs = timespec_to_msec(time); - weston_view_from_global_fixed(drag->base.focus, - pointer->x, pointer->y, - &sx, &sy); - - wl_data_device_send_motion(drag->base.focus_resource, msecs, sx, sy); - } -} - -static void -data_device_end_drag_grab(struct weston_drag *drag, - struct weston_seat *seat) -{ - if (drag->icon) { - if (weston_view_is_mapped(drag->icon)) - weston_view_unmap(drag->icon); - - drag->icon->surface->committed = NULL; - weston_surface_set_label_func(drag->icon->surface, NULL); - pixman_region32_clear(&drag->icon->surface->pending.input); - wl_list_remove(&drag->icon_destroy_listener.link); - weston_view_destroy(drag->icon); - } - - weston_drag_set_focus(drag, seat, NULL, 0, 0); -} - -static void -data_device_end_pointer_drag_grab(struct weston_pointer_drag *drag) -{ - struct weston_pointer *pointer = drag->grab.pointer; - struct weston_keyboard *keyboard = drag->base.keyboard_grab.keyboard; - - data_device_end_drag_grab(&drag->base, pointer->seat); - weston_pointer_end_grab(pointer); - weston_keyboard_end_grab(keyboard); - free(drag); -} - -static void -drag_grab_button(struct weston_pointer_grab *grab, - const struct timespec *time, - uint32_t button, uint32_t state_w) -{ - struct weston_pointer_drag *drag = - container_of(grab, struct weston_pointer_drag, grab); - struct weston_pointer *pointer = drag->grab.pointer; - enum wl_pointer_button_state state = state_w; - struct weston_data_source *data_source = drag->base.data_source; - - if (data_source && - pointer->grab_button == button && - state == WL_POINTER_BUTTON_STATE_RELEASED) { - if (drag->base.focus_resource && - data_source->accepted && - data_source->current_dnd_action) { - wl_data_device_send_drop(drag->base.focus_resource); - - if (wl_resource_get_version(data_source->resource) >= - WL_DATA_SOURCE_DND_DROP_PERFORMED_SINCE_VERSION) - wl_data_source_send_dnd_drop_performed(data_source->resource); - - data_source->offer->in_ask = - data_source->current_dnd_action == - WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; - - data_source->seat = NULL; - } else if (wl_resource_get_version(data_source->resource) >= - WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION) { - wl_data_source_send_cancelled(data_source->resource); - } - } - - if (pointer->button_count == 0 && - state == WL_POINTER_BUTTON_STATE_RELEASED) { - if (drag->base.data_source) - wl_list_remove(&drag->base.data_source_listener.link); - data_device_end_pointer_drag_grab(drag); - } -} - -static void -drag_grab_axis(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_axis_event *event) -{ -} - -static void -drag_grab_axis_source(struct weston_pointer_grab *grab, uint32_t source) -{ -} - -static void -drag_grab_frame(struct weston_pointer_grab *grab) -{ -} - -static void -drag_grab_cancel(struct weston_pointer_grab *grab) -{ - struct weston_pointer_drag *drag = - container_of(grab, struct weston_pointer_drag, grab); - - if (drag->base.data_source) - wl_list_remove(&drag->base.data_source_listener.link); - - data_device_end_pointer_drag_grab(drag); -} - -static const struct weston_pointer_grab_interface pointer_drag_grab_interface = { - drag_grab_focus, - drag_grab_motion, - drag_grab_button, - drag_grab_axis, - drag_grab_axis_source, - drag_grab_frame, - drag_grab_cancel, -}; - -static void -drag_grab_touch_down(struct weston_touch_grab *grab, - const struct timespec *time, int touch_id, - wl_fixed_t sx, wl_fixed_t sy) -{ -} - -static void -data_device_end_touch_drag_grab(struct weston_touch_drag *drag) -{ - struct weston_touch *touch = drag->grab.touch; - struct weston_keyboard *keyboard = drag->base.keyboard_grab.keyboard; - - data_device_end_drag_grab(&drag->base, touch->seat); - weston_touch_end_grab(touch); - weston_keyboard_end_grab(keyboard); - free(drag); -} - -static void -drag_grab_touch_up(struct weston_touch_grab *grab, - const struct timespec *time, int touch_id) -{ - struct weston_touch_drag *touch_drag = - container_of(grab, struct weston_touch_drag, grab); - struct weston_touch *touch = grab->touch; - - if (touch_id != touch->grab_touch_id) - return; - - if (touch_drag->base.focus_resource) - wl_data_device_send_drop(touch_drag->base.focus_resource); - if (touch_drag->base.data_source) - wl_list_remove(&touch_drag->base.data_source_listener.link); - data_device_end_touch_drag_grab(touch_drag); -} - -static void -drag_grab_touch_focus(struct weston_touch_drag *drag) -{ - struct weston_touch *touch = drag->grab.touch; - struct weston_view *view; - wl_fixed_t view_x, view_y; - - view = weston_compositor_pick_view(touch->seat->compositor, - touch->grab_x, touch->grab_y, - &view_x, &view_y); - if (drag->base.focus != view) - weston_drag_set_focus(&drag->base, touch->seat, - view, view_x, view_y); -} - -static void -drag_grab_touch_motion(struct weston_touch_grab *grab, - const struct timespec *time, - int touch_id, wl_fixed_t x, wl_fixed_t y) -{ - struct weston_touch_drag *touch_drag = - container_of(grab, struct weston_touch_drag, grab); - struct weston_touch *touch = grab->touch; - wl_fixed_t view_x, view_y; - float fx, fy; - uint32_t msecs; - - if (touch_id != touch->grab_touch_id) - return; - - drag_grab_touch_focus(touch_drag); - if (touch_drag->base.icon) { - fx = wl_fixed_to_double(touch->grab_x) + touch_drag->base.dx; - fy = wl_fixed_to_double(touch->grab_y) + touch_drag->base.dy; - weston_view_set_position(touch_drag->base.icon, fx, fy); - weston_view_schedule_repaint(touch_drag->base.icon); - } - - if (touch_drag->base.focus_resource) { - msecs = timespec_to_msec(time); - weston_view_from_global_fixed(touch_drag->base.focus, - touch->grab_x, touch->grab_y, - &view_x, &view_y); - wl_data_device_send_motion(touch_drag->base.focus_resource, - msecs, view_x, view_y); - } -} - -static void -drag_grab_touch_frame(struct weston_touch_grab *grab) -{ -} - -static void -drag_grab_touch_cancel(struct weston_touch_grab *grab) -{ - struct weston_touch_drag *touch_drag = - container_of(grab, struct weston_touch_drag, grab); - - if (touch_drag->base.data_source) - wl_list_remove(&touch_drag->base.data_source_listener.link); - data_device_end_touch_drag_grab(touch_drag); -} - -static const struct weston_touch_grab_interface touch_drag_grab_interface = { - drag_grab_touch_down, - drag_grab_touch_up, - drag_grab_touch_motion, - drag_grab_touch_frame, - drag_grab_touch_cancel -}; - -static void -drag_grab_keyboard_key(struct weston_keyboard_grab *grab, - const struct timespec *time, uint32_t key, uint32_t state) -{ -} - -static void -drag_grab_keyboard_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_drag *drag = - container_of(grab, struct weston_drag, keyboard_grab); - uint32_t compositor_action; - - if (mods_depressed & (1 << keyboard->xkb_info->shift_mod)) - compositor_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; - else if (mods_depressed & (1 << keyboard->xkb_info->ctrl_mod)) - compositor_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; - else - compositor_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; - - drag->data_source->compositor_action = compositor_action; - - if (drag->data_source->offer) - data_offer_update_action(drag->data_source->offer); -} - -static void -drag_grab_keyboard_cancel(struct weston_keyboard_grab *grab) -{ - struct weston_drag *drag = - container_of(grab, struct weston_drag, keyboard_grab); - struct weston_pointer *pointer = grab->keyboard->seat->pointer_state; - struct weston_touch *touch = grab->keyboard->seat->touch_state; - - if (pointer && pointer->grab->interface == &pointer_drag_grab_interface) { - struct weston_touch_drag *touch_drag = - (struct weston_touch_drag *) drag; - drag_grab_touch_cancel(&touch_drag->grab); - } else if (touch && touch->grab->interface == &touch_drag_grab_interface) { - struct weston_pointer_drag *pointer_drag = - (struct weston_pointer_drag *) drag; - drag_grab_cancel(&pointer_drag->grab); - } -} - -static const struct weston_keyboard_grab_interface keyboard_drag_grab_interface = { - drag_grab_keyboard_key, - drag_grab_keyboard_modifiers, - drag_grab_keyboard_cancel -}; - -static void -destroy_pointer_data_device_source(struct wl_listener *listener, void *data) -{ - struct weston_pointer_drag *drag = container_of(listener, - struct weston_pointer_drag, base.data_source_listener); - - data_device_end_pointer_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_pointer_start_drag(struct weston_pointer *pointer, - struct weston_data_source *source, - struct weston_surface *icon, - struct wl_client *client) -{ - struct weston_pointer_drag *drag; - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(pointer->seat); - - drag = zalloc(sizeof *drag); - if (drag == NULL) - return -1; - - drag->grab.interface = &pointer_drag_grab_interface; - drag->base.keyboard_grab.interface = &keyboard_drag_grab_interface; - drag->base.client = client; - drag->base.data_source = source; - - if (icon) { - drag->base.icon = weston_view_create(icon); - if (drag->base.icon == NULL) { - free(drag); - return -1; - } - - drag->base.icon_destroy_listener.notify = handle_drag_icon_destroy; - wl_signal_add(&icon->destroy_signal, - &drag->base.icon_destroy_listener); - - icon->committed = pointer_drag_surface_committed; - icon->committed_private = drag; - weston_surface_set_label_func(icon, - pointer_drag_surface_get_label); - } else { - drag->base.icon = NULL; - } - - if (source) { - drag->base.data_source_listener.notify = destroy_pointer_data_device_source; - wl_signal_add(&source->destroy_signal, - &drag->base.data_source_listener); - } - - weston_pointer_clear_focus(pointer); - weston_keyboard_set_focus(keyboard, NULL); - - weston_pointer_start_grab(pointer, &drag->grab); - weston_keyboard_start_grab(keyboard, &drag->base.keyboard_grab); - - return 0; -} - -static void -destroy_touch_data_device_source(struct wl_listener *listener, void *data) -{ - struct weston_touch_drag *drag = container_of(listener, - struct weston_touch_drag, base.data_source_listener); - - data_device_end_touch_drag_grab(drag); -} - -WL_EXPORT int -weston_touch_start_drag(struct weston_touch *touch, - struct weston_data_source *source, - struct weston_surface *icon, - struct wl_client *client) -{ - struct weston_touch_drag *drag; - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(touch->seat); - - drag = zalloc(sizeof *drag); - if (drag == NULL) - return -1; - - drag->grab.interface = &touch_drag_grab_interface; - drag->base.client = client; - drag->base.data_source = source; - - if (icon) { - drag->base.icon = weston_view_create(icon); - if (drag->base.icon == NULL) { - free(drag); - return -1; - } - - drag->base.icon_destroy_listener.notify = handle_drag_icon_destroy; - wl_signal_add(&icon->destroy_signal, - &drag->base.icon_destroy_listener); - - icon->committed = touch_drag_surface_committed; - icon->committed_private = drag; - weston_surface_set_label_func(icon, - touch_drag_surface_get_label); - } else { - drag->base.icon = NULL; - } - - if (source) { - drag->base.data_source_listener.notify = destroy_touch_data_device_source; - wl_signal_add(&source->destroy_signal, - &drag->base.data_source_listener); - } - - weston_keyboard_set_focus(keyboard, NULL); - - weston_touch_start_grab(touch, &drag->grab); - weston_keyboard_start_grab(keyboard, &drag->base.keyboard_grab); - - drag_grab_touch_focus(drag); - - 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_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_touch *touch = weston_seat_get_touch(seat); - struct weston_surface *origin = wl_resource_get_user_data(origin_resource); - struct weston_data_source *source = NULL; - struct weston_surface *icon = NULL; - int is_pointer_grab, is_touch_grab; - int32_t ret = 0; - - is_pointer_grab = pointer && - pointer->button_count == 1 && - pointer->grab_serial == serial && - pointer->focus && - pointer->focus->surface == origin; - - is_touch_grab = touch && - touch->num_tp == 1 && - touch->grab_serial == serial && - touch->focus && - touch->focus->surface == origin; - - if (!is_pointer_grab && !is_touch_grab) - 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) { - if (weston_surface_set_role(icon, "wl_data_device-icon", - resource, - WL_DATA_DEVICE_ERROR_ROLE) < 0) - return; - } - - if (is_pointer_grab) - ret = weston_pointer_start_drag(pointer, source, icon, client); - else if (is_touch_grab) - ret = weston_touch_start_drag(touch, source, icon, client); - - if (ret < 0) - wl_resource_post_no_memory(resource); - else - source->seat = seat; -} - -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 weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct wl_resource *data_device; - struct weston_surface *focus = NULL; - - seat->selection_data_source = NULL; - - if (keyboard) - focus = 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); -} - -/** \brief Send the selection to the specified client - * - * This function creates a new wl_data_offer if there is a wl_data_source - * currently set as the selection and sends it to the specified client, - * followed by the wl_data_device.selection() event. - * If there is no current selection the wl_data_device.selection() event - * will carry a NULL wl_data_offer. - * - * If the client does not have a wl_data_device for the specified seat - * nothing will be done. - * - * \param seat The seat owning the wl_data_device used to send the events. - * \param client The client to which to send the selection. - */ -WL_EXPORT void -weston_seat_send_selection(struct weston_seat *seat, struct wl_client *client) -{ - struct weston_data_offer *offer; - struct wl_resource *data_device; - - wl_resource_for_each(data_device, &seat->drag_resource_list) { - if (wl_resource_get_client(data_device) != client) - continue; - - if (seat->selection_data_source) { - offer = weston_data_source_send_offer(seat->selection_data_source, - data_device); - wl_data_device_send_selection(data_device, offer->resource); - } else { - wl_data_device_send_selection(data_device, NULL); - } - } -} - -WL_EXPORT void -weston_seat_set_selection(struct weston_seat *seat, - struct weston_data_source *source, uint32_t serial) -{ - struct weston_surface *focus = NULL; - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - - 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 (source) - source->set_selection = true; - - if (keyboard) - focus = keyboard->focus; - if (focus && focus->resource) { - weston_seat_send_selection(seat, wl_resource_get_client(focus->resource)); - } - - 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) -{ - struct weston_seat *seat = wl_resource_get_user_data(resource); - struct weston_data_source *source; - - if (!seat || !source_resource) - return; - - source = wl_resource_get_user_data(source_resource); - - if (source->actions_set) { - wl_resource_post_error(source_resource, - WL_DATA_SOURCE_ERROR_INVALID_SOURCE, - "cannot set drag-and-drop source as selection"); - return; - } - - /* FIXME: Store serial and check against incoming serial here. */ - weston_seat_set_selection(seat, source, serial); -} -static void -data_device_release(struct wl_client *client, struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static const struct wl_data_device_interface data_device_interface = { - data_device_start_drag, - data_device_set_selection, - data_device_release -}; - -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; - } - - source->resource = - wl_resource_create(client, &wl_data_source_interface, - wl_resource_get_version(resource), id); - if (source->resource == NULL) { - free(source); - 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; - source->offer = NULL; - source->accepted = false; - source->seat = NULL; - source->actions_set = false; - source->dnd_actions = 0; - source->current_dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; - source->compositor_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; - source->set_selection = false; - - wl_array_init(&source->mime_types); - - 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, - wl_resource_get_version(manager_resource), - id); - if (resource == NULL) { - wl_resource_post_no_memory(manager_resource); - return; - } - - if (seat) { - wl_list_insert(&seat->drag_resource_list, - wl_resource_get_link(resource)); - } else { - wl_list_init(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, - version, 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 weston_surface *focus; - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - - if (!keyboard) - return; - - focus = keyboard->focus; - if (!focus || !focus->resource) - return; - - weston_seat_send_selection(seat, wl_resource_get_client(focus->resource)); -} - -WL_EXPORT int -wl_data_device_manager_init(struct wl_display *display) -{ - if (wl_global_create(display, - &wl_data_device_manager_interface, 3, - NULL, bind_manager) == NULL) - return -1; - - return 0; -} diff --git a/libweston/dbus.c b/libweston/dbus.c deleted file mode 100644 index 91f2be7..0000000 --- a/libweston/dbus.c +++ /dev/null @@ -1,408 +0,0 @@ -/* - * Copyright © 2013 David Herrmann - * - * 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. - */ - -/* - * DBus Helpers - * This file contains the dbus mainloop integration and several helpers to - * make lowlevel libdbus access easier. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "dbus.h" - -/* - * DBus Mainloop Integration - * weston_dbus_bind() and weston_dbus_unbind() allow to bind an existing - * DBusConnection to an existing wl_event_loop object. All dbus dispatching - * is then nicely integrated into the wayland event loop. - * Note that this only provides basic watch and timeout dispatching. No - * remote thread wakeup, signal handling or other dbus insanity is supported. - * This is fine as long as you don't use any of the deprecated libdbus - * interfaces (like waking up remote threads..). There is really no rational - * reason to support these. - */ - -static int weston_dbus_dispatch_watch(int fd, uint32_t mask, void *data) -{ - DBusWatch *watch = data; - uint32_t flags = 0; - - if (dbus_watch_get_enabled(watch)) { - if (mask & WL_EVENT_READABLE) - flags |= DBUS_WATCH_READABLE; - if (mask & WL_EVENT_WRITABLE) - flags |= DBUS_WATCH_WRITABLE; - if (mask & WL_EVENT_HANGUP) - flags |= DBUS_WATCH_HANGUP; - if (mask & WL_EVENT_ERROR) - flags |= DBUS_WATCH_ERROR; - - dbus_watch_handle(watch, flags); - } - - return 0; -} - -static dbus_bool_t weston_dbus_add_watch(DBusWatch *watch, void *data) -{ - struct wl_event_loop *loop = data; - struct wl_event_source *s; - int fd; - uint32_t mask = 0, flags; - - if (dbus_watch_get_enabled(watch)) { - flags = dbus_watch_get_flags(watch); - if (flags & DBUS_WATCH_READABLE) - mask |= WL_EVENT_READABLE; - if (flags & DBUS_WATCH_WRITABLE) - mask |= WL_EVENT_WRITABLE; - } - - fd = dbus_watch_get_unix_fd(watch); - s = wl_event_loop_add_fd(loop, fd, mask, weston_dbus_dispatch_watch, - watch); - if (!s) - return FALSE; - - dbus_watch_set_data(watch, s, NULL); - return TRUE; -} - -static void weston_dbus_remove_watch(DBusWatch *watch, void *data) -{ - struct wl_event_source *s; - - s = dbus_watch_get_data(watch); - if (!s) - return; - - wl_event_source_remove(s); -} - -static void weston_dbus_toggle_watch(DBusWatch *watch, void *data) -{ - struct wl_event_source *s; - uint32_t mask = 0, flags; - - s = dbus_watch_get_data(watch); - if (!s) - return; - - if (dbus_watch_get_enabled(watch)) { - flags = dbus_watch_get_flags(watch); - if (flags & DBUS_WATCH_READABLE) - mask |= WL_EVENT_READABLE; - if (flags & DBUS_WATCH_WRITABLE) - mask |= WL_EVENT_WRITABLE; - } - - wl_event_source_fd_update(s, mask); -} - -static int weston_dbus_dispatch_timeout(void *data) -{ - DBusTimeout *timeout = data; - - if (dbus_timeout_get_enabled(timeout)) - dbus_timeout_handle(timeout); - - return 0; -} - -static int weston_dbus_adjust_timeout(DBusTimeout *timeout, - struct wl_event_source *s) -{ - int64_t t = 0; - - if (dbus_timeout_get_enabled(timeout)) - t = dbus_timeout_get_interval(timeout); - - return wl_event_source_timer_update(s, t); -} - -static dbus_bool_t weston_dbus_add_timeout(DBusTimeout *timeout, void *data) -{ - struct wl_event_loop *loop = data; - struct wl_event_source *s; - int r; - - s = wl_event_loop_add_timer(loop, weston_dbus_dispatch_timeout, - timeout); - if (!s) - return FALSE; - - r = weston_dbus_adjust_timeout(timeout, s); - if (r < 0) { - wl_event_source_remove(s); - return FALSE; - } - - dbus_timeout_set_data(timeout, s, NULL); - return TRUE; -} - -static void weston_dbus_remove_timeout(DBusTimeout *timeout, void *data) -{ - struct wl_event_source *s; - - s = dbus_timeout_get_data(timeout); - if (!s) - return; - - wl_event_source_remove(s); -} - -static void weston_dbus_toggle_timeout(DBusTimeout *timeout, void *data) -{ - struct wl_event_source *s; - - s = dbus_timeout_get_data(timeout); - if (!s) - return; - - weston_dbus_adjust_timeout(timeout, s); -} - -static int weston_dbus_dispatch(int fd, uint32_t mask, void *data) -{ - DBusConnection *c = data; - int r; - - do { - r = dbus_connection_dispatch(c); - if (r == DBUS_DISPATCH_COMPLETE) - r = 0; - else if (r == DBUS_DISPATCH_DATA_REMAINS) - r = -EAGAIN; - else if (r == DBUS_DISPATCH_NEED_MEMORY) - r = -ENOMEM; - else - r = -EIO; - } while (r == -EAGAIN); - - if (r) - weston_log("cannot dispatch dbus events: %d\n", r); - - return 0; -} - -static int weston_dbus_bind(struct wl_event_loop *loop, DBusConnection *c, - struct wl_event_source **ctx_out) -{ - bool b; - int r, fd; - - /* Idle events cannot reschedule themselves, therefore we use a dummy - * event-fd and mark it for post-dispatch. Hence, the dbus - * dispatcher is called after every dispatch-round. - * This is required as dbus doesn't allow dispatching events from - * within its own event sources. */ - fd = eventfd(0, EFD_CLOEXEC); - if (fd < 0) - return -errno; - - *ctx_out = wl_event_loop_add_fd(loop, fd, 0, weston_dbus_dispatch, c); - close(fd); - - if (!*ctx_out) - return -ENOMEM; - - wl_event_source_check(*ctx_out); - - b = dbus_connection_set_watch_functions(c, - weston_dbus_add_watch, - weston_dbus_remove_watch, - weston_dbus_toggle_watch, - loop, - NULL); - if (!b) { - r = -ENOMEM; - goto error; - } - - b = dbus_connection_set_timeout_functions(c, - weston_dbus_add_timeout, - weston_dbus_remove_timeout, - weston_dbus_toggle_timeout, - loop, - NULL); - if (!b) { - r = -ENOMEM; - goto error; - } - - dbus_connection_ref(c); - return 0; - -error: - dbus_connection_set_timeout_functions(c, NULL, NULL, NULL, - NULL, NULL); - dbus_connection_set_watch_functions(c, NULL, NULL, NULL, - NULL, NULL); - wl_event_source_remove(*ctx_out); - *ctx_out = NULL; - return r; -} - -static void weston_dbus_unbind(DBusConnection *c, struct wl_event_source *ctx) -{ - dbus_connection_set_timeout_functions(c, NULL, NULL, NULL, - NULL, NULL); - dbus_connection_set_watch_functions(c, NULL, NULL, NULL, - NULL, NULL); - dbus_connection_unref(c); - wl_event_source_remove(ctx); -} - -/* - * Convenience Helpers - * Several convenience helpers are provided to make using dbus in weston - * easier. We don't use any of the gdbus or qdbus helpers as they pull in - * huge dependencies and actually are quite awful to use. Instead, we only - * use the basic low-level libdbus library. - */ - -int weston_dbus_open(struct wl_event_loop *loop, DBusBusType bus, - DBusConnection **out, struct wl_event_source **ctx_out) -{ - DBusConnection *c; - int r; - - /* Ihhh, global state.. stupid dbus. */ - dbus_connection_set_change_sigpipe(FALSE); - - /* This is actually synchronous. It blocks for some authentication and - * setup. We just trust the dbus-server here and accept this blocking - * call. There is no real reason to complicate things further and make - * this asynchronous/non-blocking. A context should be created during - * thead/process/app setup, so blocking calls should be fine. */ - c = dbus_bus_get_private(bus, NULL); - if (!c) - return -EIO; - - dbus_connection_set_exit_on_disconnect(c, FALSE); - - r = weston_dbus_bind(loop, c, ctx_out); - if (r < 0) - goto error; - - *out = c; - return r; - -error: - dbus_connection_close(c); - dbus_connection_unref(c); - return r; -} - -void weston_dbus_close(DBusConnection *c, struct wl_event_source *ctx) -{ - weston_dbus_unbind(c, ctx); - dbus_connection_close(c); - dbus_connection_unref(c); -} - -int weston_dbus_add_match(DBusConnection *c, const char *format, ...) -{ - DBusError err; - int r; - va_list list; - char *str; - - va_start(list, format); - r = vasprintf(&str, format, list); - va_end(list); - - if (r < 0) - return -ENOMEM; - - dbus_error_init(&err); - dbus_bus_add_match(c, str, &err); - free(str); - if (dbus_error_is_set(&err)) { - dbus_error_free(&err); - return -EIO; - } - - return 0; -} - -int weston_dbus_add_match_signal(DBusConnection *c, const char *sender, - const char *iface, const char *member, - const char *path) -{ - return weston_dbus_add_match(c, - "type='signal'," - "sender='%s'," - "interface='%s'," - "member='%s'," - "path='%s'", - sender, iface, member, path); -} - -void weston_dbus_remove_match(DBusConnection *c, const char *format, ...) -{ - int r; - va_list list; - char *str; - - va_start(list, format); - r = vasprintf(&str, format, list); - va_end(list); - - if (r < 0) - return; - - dbus_bus_remove_match(c, str, NULL); - free(str); -} - -void weston_dbus_remove_match_signal(DBusConnection *c, const char *sender, - const char *iface, const char *member, - const char *path) -{ - weston_dbus_remove_match(c, - "type='signal'," - "sender='%s'," - "interface='%s'," - "member='%s'," - "path='%s'", - sender, iface, member, path); -} diff --git a/libweston/dbus.h b/libweston/dbus.h deleted file mode 100644 index 639946c..0000000 --- a/libweston/dbus.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright © 2013 David Herrmann - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _WESTON_DBUS_H_ -#define _WESTON_DBUS_H_ - -#include "config.h" - -#include -#include - -#include - -#ifdef HAVE_DBUS - -#include - -/* - * weston_dbus_open() - Open new dbus connection - * - * Opens a new dbus connection to the bus given as @bus. It automatically - * integrates the new connection into the main-loop @loop. The connection - * itself is returned in @out. - * This also returns a context source used for dbus dispatching. It is - * returned on success in @ctx_out and must be passed to weston_dbus_close() - * unchanged. You must not access it from outside of a dbus helper! - * - * Returns 0 on success, negative error code on failure. - */ -int weston_dbus_open(struct wl_event_loop *loop, DBusBusType bus, - DBusConnection **out, struct wl_event_source **ctx_out); - -/* - * weston_dbus_close() - Close dbus connection - * - * Closes a dbus connection that was previously opened via weston_dbus_open(). - * It unbinds the connection from the main-loop it was previously bound to, - * closes the dbus connection and frees all resources. If you want to access - * @c after this call returns, you must hold a dbus-reference to it. But - * notice that the connection is closed after this returns so it cannot be - * used to spawn new dbus requests. - * You must pass the context source returns by weston_dbus_open() as @ctx. - */ -void weston_dbus_close(DBusConnection *c, struct wl_event_source *ctx); - -/* - * weston_dbus_add_match() - Add dbus match - * - * Configure a dbus-match on the given dbus-connection. This match is saved - * on the dbus-server as long as the connection is open. See dbus-manual - * for information. Compared to the dbus_bus_add_match() this allows a - * var-arg formatted match-string. - */ -int weston_dbus_add_match(DBusConnection *c, const char *format, ...); - -/* - * weston_dbus_add_match_signal() - Add dbus signal match - * - * Same as weston_dbus_add_match() but does the dbus-match formatting for - * signals internally. - */ -int weston_dbus_add_match_signal(DBusConnection *c, const char *sender, - const char *iface, const char *member, - const char *path); - -/* - * weston_dbus_remove_match() - Remove dbus match - * - * Remove a previously configured dbus-match from the dbus server. There is - * no need to remove dbus-matches if you close the connection, anyway. - * Compared to dbus_bus_remove_match() this allows a var-arg formatted - * match string. - */ -void weston_dbus_remove_match(DBusConnection *c, const char *format, ...); - -/* - * weston_dbus_remove_match_signal() - Remove dbus signal match - * - * Same as weston_dbus_remove_match() but does the dbus-match formatting for - * signals internally. - */ -void weston_dbus_remove_match_signal(DBusConnection *c, const char *sender, - const char *iface, const char *member, - const char *path); - -#endif /* HAVE_DBUS */ - -#endif // _WESTON_DBUS_H_ diff --git a/libweston/git-version.h b/libweston/git-version.h deleted file mode 100644 index 09d67da..0000000 --- a/libweston/git-version.h +++ /dev/null @@ -1 +0,0 @@ -#define BUILD_ID "unknown (not built from git or tarball)" diff --git a/libweston/git-version.h.meson b/libweston/git-version.h.meson deleted file mode 100644 index d91f19c..0000000 --- a/libweston/git-version.h.meson +++ /dev/null @@ -1 +0,0 @@ -#define BUILD_ID "@VCS_TAG@" diff --git a/libweston/input.c b/libweston/input.c deleted file mode 100644 index 292b681..0000000 --- a/libweston/input.c +++ /dev/null @@ -1,5063 +0,0 @@ -/* - * Copyright © 2013 Intel Corporation - * Copyright 2017-2018 Collabora, Ltd. - * Copyright 2017-2018 General Electric Company - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -// OHOS no such file -//#include -#include -#include -#include - -#include "shared/helpers.h" -#include "shared/os-compatibility.h" -#include "shared/timespec-util.h" -#include -#include "backend.h" -#include "libweston-internal.h" -#include "relative-pointer-unstable-v1-server-protocol.h" -#include "pointer-constraints-unstable-v1-server-protocol.h" -#include "input-timestamps-unstable-v1-server-protocol.h" - -enum pointer_constraint_type { - POINTER_CONSTRAINT_TYPE_LOCK, - POINTER_CONSTRAINT_TYPE_CONFINE, -}; - -enum motion_direction { - MOTION_DIRECTION_POSITIVE_X = 1 << 0, - MOTION_DIRECTION_NEGATIVE_X = 1 << 1, - MOTION_DIRECTION_POSITIVE_Y = 1 << 2, - MOTION_DIRECTION_NEGATIVE_Y = 1 << 3, -}; - -struct vec2d { - double x, y; -}; - -struct line { - struct vec2d a; - struct vec2d b; -}; - -struct border { - struct line line; - enum motion_direction blocking_dir; -}; - -static void -maybe_warp_confined_pointer(struct weston_pointer_constraint *constraint); - -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 void -send_timestamp(struct wl_resource *resource, - const struct timespec *time) -{ - uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; - - timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); - zwp_input_timestamps_v1_send_timestamp(resource, tv_sec_hi, tv_sec_lo, - tv_nsec); -} - -static void -send_timestamps_for_input_resource(struct wl_resource *input_resource, - struct wl_list *list, - const struct timespec *time) -{ - struct wl_resource *resource; - - wl_resource_for_each(resource, list) { - if (wl_resource_get_user_data(resource) == input_resource) - send_timestamp(resource, time); - } -} - -static void -remove_input_resource_from_timestamps(struct wl_resource *input_resource, - struct wl_list *list) -{ - struct wl_resource *resource; - - wl_resource_for_each(resource, list) { - if (wl_resource_get_user_data(resource) == input_resource) - wl_resource_set_user_data(resource, NULL); - } -} - -/** Register a touchscreen input device - * - * \param touch The parent weston_touch that identifies the seat. - * \param syspath Unique device name. - * \param backend_data Backend private data if necessary. - * \param ops Calibration operations, or NULL for not able to run calibration. - * \return New touch device, or NULL on failure. - */ -WL_EXPORT struct weston_touch_device * -weston_touch_create_touch_device(struct weston_touch *touch, - const char *syspath, - void *backend_data, - const struct weston_touch_device_ops *ops) -{ - struct weston_touch_device *device; - - assert(syspath); - if (ops) { - assert(ops->get_output); - assert(ops->get_calibration_head_name); - assert(ops->get_calibration); - assert(ops->set_calibration); - } - - device = zalloc(sizeof *device); - if (!device) - return NULL; - - wl_signal_init(&device->destroy_signal); - - device->syspath = strdup(syspath); - if (!device->syspath) { - free(device); - return NULL; - } - - device->backend_data = backend_data; - device->ops = ops; - - device->aggregate = touch; - wl_list_insert(touch->device_list.prev, &device->link); - - return device; -} - -/** Destroy the touch device. */ -WL_EXPORT void -weston_touch_device_destroy(struct weston_touch_device *device) -{ - wl_list_remove(&device->link); - wl_signal_emit(&device->destroy_signal, device); - free(device->syspath); - free(device); -} - -/** Is it possible to run calibration on this touch device? */ -WL_EXPORT bool -weston_touch_device_can_calibrate(struct weston_touch_device *device) -{ - return !!device->ops; -} - -static enum weston_touch_mode -weston_touch_device_get_mode(struct weston_touch_device *device) -{ - return device->aggregate->seat->compositor->touch_mode; -} - -static struct weston_pointer_client * -weston_pointer_client_create(struct wl_client *client) -{ - struct weston_pointer_client *pointer_client; - - pointer_client = zalloc(sizeof *pointer_client); - if (!pointer_client) - return NULL; - - pointer_client->client = client; - wl_list_init(&pointer_client->pointer_resources); - wl_list_init(&pointer_client->relative_pointer_resources); - - return pointer_client; -} - -static void -weston_pointer_client_destroy(struct weston_pointer_client *pointer_client) -{ - struct wl_resource *resource; - - wl_resource_for_each(resource, &pointer_client->pointer_resources) { - wl_resource_set_user_data(resource, NULL); - } - - wl_resource_for_each(resource, - &pointer_client->relative_pointer_resources) { - wl_resource_set_user_data(resource, NULL); - } - - wl_list_remove(&pointer_client->pointer_resources); - wl_list_remove(&pointer_client->relative_pointer_resources); - free(pointer_client); -} - -static bool -weston_pointer_client_is_empty(struct weston_pointer_client *pointer_client) -{ - return (wl_list_empty(&pointer_client->pointer_resources) && - wl_list_empty(&pointer_client->relative_pointer_resources)); -} - -static struct weston_pointer_client * -weston_pointer_get_pointer_client(struct weston_pointer *pointer, - struct wl_client *client) -{ - struct weston_pointer_client *pointer_client; - - wl_list_for_each(pointer_client, &pointer->pointer_clients, link) { - if (pointer_client->client == client) - return pointer_client; - } - - return NULL; -} - -static struct weston_pointer_client * -weston_pointer_ensure_pointer_client(struct weston_pointer *pointer, - struct wl_client *client) -{ - struct weston_pointer_client *pointer_client; - - pointer_client = weston_pointer_get_pointer_client(pointer, client); - if (pointer_client) - return pointer_client; - - pointer_client = weston_pointer_client_create(client); - wl_list_insert(&pointer->pointer_clients, &pointer_client->link); - - if (pointer->focus && - pointer->focus->surface->resource && - wl_resource_get_client(pointer->focus->surface->resource) == client) { - pointer->focus_client = pointer_client; - } - - return pointer_client; -} - -static void -weston_pointer_cleanup_pointer_client(struct weston_pointer *pointer, - struct weston_pointer_client *pointer_client) -{ - if (weston_pointer_client_is_empty(pointer_client)) { - if (pointer->focus_client == pointer_client) - pointer->focus_client = NULL; - wl_list_remove(&pointer_client->link); - weston_pointer_client_destroy(pointer_client); - } -} - -static void -unbind_pointer_client_resource(struct wl_resource *resource) -{ - struct weston_pointer *pointer = wl_resource_get_user_data(resource); - struct wl_client *client = wl_resource_get_client(resource); - struct weston_pointer_client *pointer_client; - - wl_list_remove(wl_resource_get_link(resource)); - - if (pointer) { - pointer_client = weston_pointer_get_pointer_client(pointer, - client); - assert(pointer_client); - remove_input_resource_from_timestamps(resource, - &pointer->timestamps_list); - weston_pointer_cleanup_pointer_client(pointer, pointer_client); - } -} - -static void unbind_resource(struct wl_resource *resource) -{ - wl_list_remove(wl_resource_get_link(resource)); -} - -WL_EXPORT void -weston_pointer_motion_to_abs(struct weston_pointer *pointer, - struct weston_pointer_motion_event *event, - wl_fixed_t *x, wl_fixed_t *y) -{ - if (event->mask & WESTON_POINTER_MOTION_ABS) { - *x = wl_fixed_from_double(event->x); - *y = wl_fixed_from_double(event->y); - } else if (event->mask & WESTON_POINTER_MOTION_REL) { - *x = pointer->x + wl_fixed_from_double(event->dx); - *y = pointer->y + wl_fixed_from_double(event->dy); - } else { - assert(!"invalid motion event"); - *x = *y = 0; - } -} - -static bool -weston_pointer_motion_to_rel(struct weston_pointer *pointer, - struct weston_pointer_motion_event *event, - double *dx, double *dy, - double *dx_unaccel, double *dy_unaccel) -{ - if (event->mask & WESTON_POINTER_MOTION_REL && - event->mask & WESTON_POINTER_MOTION_REL_UNACCEL) { - *dx = event->dx; - *dy = event->dy; - *dx_unaccel = event->dx_unaccel; - *dy_unaccel = event->dy_unaccel; - return true; - } else if (event->mask & WESTON_POINTER_MOTION_REL) { - *dx_unaccel = *dx = event->dx; - *dy_unaccel = *dy = event->dy; - return true; - } else if (event->mask & WESTON_POINTER_MOTION_REL_UNACCEL) { - *dx_unaccel = *dx = event->dx_unaccel; - *dy_unaccel = *dy = event->dy_unaccel; - return true; - } else { - return false; - } -} - -WL_EXPORT void -weston_seat_repick(struct weston_seat *seat) -{ - const struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - if (!pointer) - return; - - pointer->grab->interface->focus(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 -pointer_focus_view_destroyed(struct wl_listener *listener, void *data) -{ - struct weston_pointer *pointer = - container_of(listener, struct weston_pointer, - focus_view_listener); - - weston_pointer_clear_focus(pointer); -} - -static void -pointer_focus_resource_destroyed(struct wl_listener *listener, void *data) -{ - struct weston_pointer *pointer = - container_of(listener, struct weston_pointer, - focus_resource_listener); - - weston_pointer_clear_focus(pointer); -} - -static void -keyboard_focus_resource_destroyed(struct wl_listener *listener, void *data) -{ - struct weston_keyboard *keyboard = - container_of(listener, struct weston_keyboard, - focus_resource_listener); - - weston_keyboard_set_focus(keyboard, NULL); -} - -static void -touch_focus_view_destroyed(struct wl_listener *listener, void *data) -{ - struct weston_touch *touch = - container_of(listener, struct weston_touch, - focus_view_listener); - - weston_touch_set_focus(touch, NULL); -} - -static void -touch_focus_resource_destroyed(struct wl_listener *listener, void *data) -{ - struct weston_touch *touch = - container_of(listener, struct weston_touch, - focus_resource_listener); - - weston_touch_set_focus(touch, NULL); -} - -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_pointer_focus(struct weston_pointer_grab *grab) -{ - struct weston_pointer *pointer = grab->pointer; - struct weston_view *view; - wl_fixed_t sx, sy; - - if (pointer->button_count > 0) - return; - - view = weston_compositor_pick_view(pointer->seat->compositor, - pointer->x, pointer->y, - &sx, &sy); - - if (pointer->focus != view || pointer->sx != sx || pointer->sy != sy) - weston_pointer_set_focus(pointer, view, sx, sy); -} - -static void -pointer_send_relative_motion(struct weston_pointer *pointer, - const struct timespec *time, - struct weston_pointer_motion_event *event) -{ - uint64_t time_usec; - double dx, dy, dx_unaccel, dy_unaccel; - wl_fixed_t dxf, dyf, dxf_unaccel, dyf_unaccel; - struct wl_list *resource_list; - struct wl_resource *resource; - - if (!pointer->focus_client) - return; - - if (!weston_pointer_motion_to_rel(pointer, event, - &dx, &dy, - &dx_unaccel, &dy_unaccel)) - return; - - resource_list = &pointer->focus_client->relative_pointer_resources; - time_usec = timespec_to_usec(&event->time); - if (time_usec == 0) - time_usec = timespec_to_usec(time); - - dxf = wl_fixed_from_double(dx); - dyf = wl_fixed_from_double(dy); - dxf_unaccel = wl_fixed_from_double(dx_unaccel); - dyf_unaccel = wl_fixed_from_double(dy_unaccel); - - wl_resource_for_each(resource, resource_list) { - zwp_relative_pointer_v1_send_relative_motion( - resource, - (uint32_t) (time_usec >> 32), - (uint32_t) time_usec, - dxf, dyf, - dxf_unaccel, dyf_unaccel); - } -} - -static void -pointer_send_motion(struct weston_pointer *pointer, - const struct timespec *time, - wl_fixed_t sx, wl_fixed_t sy) -{ - struct wl_list *resource_list; - struct wl_resource *resource; - uint32_t msecs; - - if (!pointer->focus_client) - return; - - resource_list = &pointer->focus_client->pointer_resources; - msecs = timespec_to_msec(time); - wl_resource_for_each(resource, resource_list) { - send_timestamps_for_input_resource(resource, - &pointer->timestamps_list, - time); - wl_pointer_send_motion(resource, msecs, sx, sy); - } -} - -WL_EXPORT void -weston_pointer_send_motion(struct weston_pointer *pointer, - const struct timespec *time, - struct weston_pointer_motion_event *event) -{ - wl_fixed_t x, y; - wl_fixed_t old_sx = pointer->sx; - wl_fixed_t old_sy = pointer->sy; - - if (pointer->focus) { - weston_pointer_motion_to_abs(pointer, event, &x, &y); - weston_view_from_global_fixed(pointer->focus, x, y, - &pointer->sx, &pointer->sy); - } - - weston_pointer_move(pointer, event); - - if (old_sx != pointer->sx || old_sy != pointer->sy) { - pointer_send_motion(pointer, time, - pointer->sx, pointer->sy); - } - - pointer_send_relative_motion(pointer, time, event); -} - -static void -default_grab_pointer_motion(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_motion_event *event) -{ - weston_pointer_send_motion(grab->pointer, time, event); -} - -/** Check if the pointer has focused resources. - * - * \param pointer The pointer to check for focused resources. - * \return Whether or not this pointer has focused resources - */ -WL_EXPORT bool -weston_pointer_has_focus_resource(struct weston_pointer *pointer) -{ - if (!pointer->focus_client) - return false; - - if (wl_list_empty(&pointer->focus_client->pointer_resources)) - return false; - - return true; -} - -/** Send wl_pointer.button events to focused resources. - * - * \param pointer The pointer where the button events originates from. - * \param time The timestamp of the event - * \param button The button value of the event - * \param state The state enum value of the event - * - * For every resource that is currently in focus, send a wl_pointer.button event - * with the passed parameters. The focused resources are the wl_pointer - * resources of the client which currently has the surface with pointer focus. - */ -WL_EXPORT void -weston_pointer_send_button(struct weston_pointer *pointer, - const struct timespec *time, uint32_t button, - enum wl_pointer_button_state state) -{ - struct wl_display *display = pointer->seat->compositor->wl_display; - struct wl_list *resource_list; - struct wl_resource *resource; - uint32_t serial; - uint32_t msecs; - - if (!weston_pointer_has_focus_resource(pointer)) - return; - - resource_list = &pointer->focus_client->pointer_resources; - serial = wl_display_next_serial(display); - msecs = timespec_to_msec(time); - wl_resource_for_each(resource, resource_list) { - send_timestamps_for_input_resource(resource, - &pointer->timestamps_list, - time); - wl_pointer_send_button(resource, serial, msecs, button, state); - } -} - -static void -default_grab_pointer_button(struct weston_pointer_grab *grab, - const struct timespec *time, uint32_t button, - enum wl_pointer_button_state state) -{ - struct weston_pointer *pointer = grab->pointer; - struct weston_compositor *compositor = pointer->seat->compositor; - struct weston_view *view; - wl_fixed_t sx, sy; - - weston_pointer_send_button(pointer, time, button, state); - - if (pointer->button_count == 0 && - state == WL_POINTER_BUTTON_STATE_RELEASED) { - view = weston_compositor_pick_view(compositor, - pointer->x, pointer->y, - &sx, &sy); - - weston_pointer_set_focus(pointer, view, sx, sy); - } -} - -/** Send wl_pointer.axis events to focused resources. - * - * \param pointer The pointer where the axis events originates from. - * \param time The timestamp of the event - * \param event The axis value of the event - * - * For every resource that is currently in focus, send a wl_pointer.axis event - * with the passed parameters. The focused resources are the wl_pointer - * resources of the client which currently has the surface with pointer focus. - */ -WL_EXPORT void -weston_pointer_send_axis(struct weston_pointer *pointer, - const struct timespec *time, - struct weston_pointer_axis_event *event) -{ - struct wl_resource *resource; - struct wl_list *resource_list; - uint32_t msecs; - - if (!weston_pointer_has_focus_resource(pointer)) - return; - - resource_list = &pointer->focus_client->pointer_resources; - msecs = timespec_to_msec(time); - wl_resource_for_each(resource, resource_list) { - if (event->has_discrete && - wl_resource_get_version(resource) >= - WL_POINTER_AXIS_DISCRETE_SINCE_VERSION) - wl_pointer_send_axis_discrete(resource, event->axis, - event->discrete); - - if (event->value) { - send_timestamps_for_input_resource(resource, - &pointer->timestamps_list, - time); - wl_pointer_send_axis(resource, msecs, - event->axis, - wl_fixed_from_double(event->value)); - } else if (wl_resource_get_version(resource) >= - WL_POINTER_AXIS_STOP_SINCE_VERSION) { - send_timestamps_for_input_resource(resource, - &pointer->timestamps_list, - time); - wl_pointer_send_axis_stop(resource, msecs, - event->axis); - } - } -} - -/** Send wl_pointer.axis_source events to focused resources. - * - * \param pointer The pointer where the axis_source events originates from. - * \param source The axis_source enum value of the event - * - * For every resource that is currently in focus, send a wl_pointer.axis_source - * event with the passed parameter. The focused resources are the wl_pointer - * resources of the client which currently has the surface with pointer focus. - */ -WL_EXPORT void -weston_pointer_send_axis_source(struct weston_pointer *pointer, - enum wl_pointer_axis_source source) -{ - struct wl_resource *resource; - struct wl_list *resource_list; - - if (!weston_pointer_has_focus_resource(pointer)) - return; - - resource_list = &pointer->focus_client->pointer_resources; - wl_resource_for_each(resource, resource_list) { - if (wl_resource_get_version(resource) >= - WL_POINTER_AXIS_SOURCE_SINCE_VERSION) { - wl_pointer_send_axis_source(resource, source); - } - } -} - -static void -pointer_send_frame(struct wl_resource *resource) -{ - if (wl_resource_get_version(resource) >= - WL_POINTER_FRAME_SINCE_VERSION) { - wl_pointer_send_frame(resource); - } -} - -/** Send wl_pointer.frame events to focused resources. - * - * \param pointer The pointer where the frame events originates from. - * - * For every resource that is currently in focus, send a wl_pointer.frame event. - * The focused resources are the wl_pointer resources of the client which - * currently has the surface with pointer focus. - */ -WL_EXPORT void -weston_pointer_send_frame(struct weston_pointer *pointer) -{ - struct wl_resource *resource; - struct wl_list *resource_list; - - if (!weston_pointer_has_focus_resource(pointer)) - return; - - resource_list = &pointer->focus_client->pointer_resources; - wl_resource_for_each(resource, resource_list) - pointer_send_frame(resource); -} - -static void -default_grab_pointer_axis(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_axis_event *event) -{ - weston_pointer_send_axis(grab->pointer, time, event); -} - -static void -default_grab_pointer_axis_source(struct weston_pointer_grab *grab, - enum wl_pointer_axis_source source) -{ - weston_pointer_send_axis_source(grab->pointer, source); -} - -static void -default_grab_pointer_frame(struct weston_pointer_grab *grab) -{ - weston_pointer_send_frame(grab->pointer); -} - -static void -default_grab_pointer_cancel(struct weston_pointer_grab *grab) -{ -} - -static const struct weston_pointer_grab_interface - default_pointer_grab_interface = { - default_grab_pointer_focus, - default_grab_pointer_motion, - default_grab_pointer_button, - default_grab_pointer_axis, - default_grab_pointer_axis_source, - default_grab_pointer_frame, - default_grab_pointer_cancel, -}; - -/** Check if the touch has focused resources. - * - * \param touch The touch to check for focused resources. - * \return Whether or not this touch has focused resources - */ -WL_EXPORT bool -weston_touch_has_focus_resource(struct weston_touch *touch) -{ - if (!touch->focus) - return false; - - if (wl_list_empty(&touch->focus_resource_list)) - return false; - - return true; -} - -/** Send wl_touch.down events to focused resources. - * - * \param touch The touch where the down events originates from. - * \param time The timestamp of the event - * \param touch_id The touch_id value of the event - * \param x The x value of the event - * \param y The y value of the event - * - * For every resource that is currently in focus, send a wl_touch.down event - * with the passed parameters. The focused resources are the wl_touch - * resources of the client which currently has the surface with touch focus. - */ -WL_EXPORT void -weston_touch_send_down(struct weston_touch *touch, const struct timespec *time, - int touch_id, wl_fixed_t x, wl_fixed_t y) -{ - struct wl_display *display = touch->seat->compositor->wl_display; - uint32_t serial; - struct wl_resource *resource; - struct wl_list *resource_list; - wl_fixed_t sx, sy; - uint32_t msecs; - - if (!weston_touch_has_focus_resource(touch)) - return; - - weston_view_from_global_fixed(touch->focus, x, y, &sx, &sy); - - resource_list = &touch->focus_resource_list; - serial = wl_display_next_serial(display); - msecs = timespec_to_msec(time); - wl_resource_for_each(resource, resource_list) { - send_timestamps_for_input_resource(resource, - &touch->timestamps_list, - time); - wl_touch_send_down(resource, serial, msecs, - touch->focus->surface->resource, - touch_id, sx, sy); - } -} - -static void -default_grab_touch_down(struct weston_touch_grab *grab, - const struct timespec *time, int touch_id, - wl_fixed_t x, wl_fixed_t y) -{ - weston_touch_send_down(grab->touch, time, touch_id, x, y); -} - -/** Send wl_touch.up events to focused resources. - * - * \param touch The touch where the up events originates from. - * \param time The timestamp of the event - * \param touch_id The touch_id value of the event - * - * For every resource that is currently in focus, send a wl_touch.up event - * with the passed parameters. The focused resources are the wl_touch - * resources of the client which currently has the surface with touch focus. - */ -WL_EXPORT void -weston_touch_send_up(struct weston_touch *touch, const struct timespec *time, - int touch_id) -{ - struct wl_display *display = touch->seat->compositor->wl_display; - uint32_t serial; - struct wl_resource *resource; - struct wl_list *resource_list; - uint32_t msecs; - - if (!weston_touch_has_focus_resource(touch)) - return; - - resource_list = &touch->focus_resource_list; - serial = wl_display_next_serial(display); - msecs = timespec_to_msec(time); - wl_resource_for_each(resource, resource_list) { - send_timestamps_for_input_resource(resource, - &touch->timestamps_list, - time); - wl_touch_send_up(resource, serial, msecs, touch_id); - } -} - -static void -default_grab_touch_up(struct weston_touch_grab *grab, - const struct timespec *time, int touch_id) -{ - weston_touch_send_up(grab->touch, time, touch_id); -} - -/** Send wl_touch.motion events to focused resources. - * - * \param touch The touch where the motion events originates from. - * \param time The timestamp of the event - * \param touch_id The touch_id value of the event - * \param x The x value of the event - * \param y The y value of the event - * - * For every resource that is currently in focus, send a wl_touch.motion event - * with the passed parameters. The focused resources are the wl_touch - * resources of the client which currently has the surface with touch focus. - */ -WL_EXPORT void -weston_touch_send_motion(struct weston_touch *touch, - const struct timespec *time, int touch_id, - wl_fixed_t x, wl_fixed_t y) -{ - struct wl_resource *resource; - struct wl_list *resource_list; - wl_fixed_t sx, sy; - uint32_t msecs; - - if (!weston_touch_has_focus_resource(touch)) - return; - - weston_view_from_global_fixed(touch->focus, x, y, &sx, &sy); - - resource_list = &touch->focus_resource_list; - msecs = timespec_to_msec(time); - wl_resource_for_each(resource, resource_list) { - send_timestamps_for_input_resource(resource, - &touch->timestamps_list, - time); - wl_touch_send_motion(resource, msecs, - touch_id, sx, sy); - } -} - -static void -default_grab_touch_motion(struct weston_touch_grab *grab, - const struct timespec *time, int touch_id, - wl_fixed_t x, wl_fixed_t y) -{ - weston_touch_send_motion(grab->touch, time, touch_id, x, y); -} - - -/** Send wl_touch.frame events to focused resources. - * - * \param touch The touch where the frame events originates from. - * - * For every resource that is currently in focus, send a wl_touch.frame event. - * The focused resources are the wl_touch resources of the client which - * currently has the surface with touch focus. - */ -WL_EXPORT void -weston_touch_send_frame(struct weston_touch *touch) -{ - struct wl_resource *resource; - - if (!weston_touch_has_focus_resource(touch)) - return; - - wl_resource_for_each(resource, &touch->focus_resource_list) - wl_touch_send_frame(resource); -} - -static void -default_grab_touch_frame(struct weston_touch_grab *grab) -{ - weston_touch_send_frame(grab->touch); -} - -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_frame, - default_grab_touch_cancel, -}; - -/** Check if the keyboard has focused resources. - * - * \param keyboard The keyboard to check for focused resources. - * \return Whether or not this keyboard has focused resources - */ -WL_EXPORT bool -weston_keyboard_has_focus_resource(struct weston_keyboard *keyboard) -{ - if (!keyboard->focus) - return false; - - if (wl_list_empty(&keyboard->focus_resource_list)) - return false; - - return true; -} - -/** Send wl_keyboard.key events to focused resources. - * - * \param keyboard The keyboard where the key events originates from. - * \param time The timestamp of the event - * \param key The key value of the event - * \param state The state enum value of the event - * - * For every resource that is currently in focus, send a wl_keyboard.key event - * with the passed parameters. The focused resources are the wl_keyboard - * resources of the client which currently has the surface with keyboard focus. - */ -WL_EXPORT void -weston_keyboard_send_key(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, - enum wl_keyboard_key_state state) -{ - struct wl_resource *resource; - struct wl_display *display = keyboard->seat->compositor->wl_display; - uint32_t serial; - struct wl_list *resource_list; - uint32_t msecs; - - if (!weston_keyboard_has_focus_resource(keyboard)) - return; - - resource_list = &keyboard->focus_resource_list; - serial = wl_display_next_serial(display); - msecs = timespec_to_msec(time); - wl_resource_for_each(resource, resource_list) { - send_timestamps_for_input_resource(resource, - &keyboard->timestamps_list, - time); - wl_keyboard_send_key(resource, serial, msecs, key, state); - } -}; - -static void -default_grab_keyboard_key(struct weston_keyboard_grab *grab, - const struct timespec *time, uint32_t key, - uint32_t state) -{ - weston_keyboard_send_key(grab->keyboard, 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 weston_pointer_client * -find_pointer_client_for_surface(struct weston_pointer *pointer, - struct weston_surface *surface) -{ - struct wl_client *client; - - if (!surface) - return NULL; - - if (!surface->resource) - return NULL; - - client = wl_resource_get_client(surface->resource); - return weston_pointer_get_pointer_client(pointer, client); -} - -static struct weston_pointer_client * -find_pointer_client_for_view(struct weston_pointer *pointer, struct weston_view *view) -{ - if (!view) - return NULL; - - return find_pointer_client_for_surface(pointer, view->surface); -} - -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)); -} - -/** Send wl_keyboard.modifiers events to focused resources and pointer - * focused resources. - * - * \param keyboard The keyboard where the modifiers events originates from. - * \param serial The serial of the event - * \param mods_depressed The mods_depressed value of the event - * \param mods_latched The mods_latched value of the event - * \param mods_locked The mods_locked value of the event - * \param group The group value of the event - * - * For every resource that is currently in focus, send a wl_keyboard.modifiers - * event with the passed parameters. The focused resources are the wl_keyboard - * resources of the client which currently has the surface with keyboard focus. - * This also sends wl_keyboard.modifiers events to the wl_keyboard resources of - * the client having pointer focus (if different from the keyboard focus client). - */ -WL_EXPORT void -weston_keyboard_send_modifiers(struct weston_keyboard *keyboard, - uint32_t serial, uint32_t mods_depressed, - uint32_t mods_latched, - uint32_t mods_locked, uint32_t group) -{ - struct weston_pointer *pointer = - weston_seat_get_pointer(keyboard->seat); - - if (weston_keyboard_has_focus_resource(keyboard)) { - struct wl_list *resource_list; - struct wl_resource *resource; - - 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->surface->resource && - pointer->focus->surface != keyboard->focus) { - struct wl_client *pointer_client = - wl_resource_get_client(pointer->focus->surface->resource); - - send_modifiers_to_client_in_list(pointer_client, - &keyboard->resource_list, - serial, - keyboard); - } -} - -static void -default_grab_keyboard_modifiers(struct weston_keyboard_grab *grab, - uint32_t serial, uint32_t mods_depressed, - uint32_t mods_latched, - uint32_t mods_locked, uint32_t group) -{ - weston_keyboard_send_modifiers(grab->keyboard, serial, mods_depressed, - mods_latched, mods_locked, group); -} - -static void -default_grab_keyboard_cancel(struct weston_keyboard_grab *grab) -{ -} - -static const struct weston_keyboard_grab_interface - default_keyboard_grab_interface = { - default_grab_keyboard_key, - default_grab_keyboard_modifiers, - default_grab_keyboard_cancel, -}; - -static void -pointer_unmap_sprite(struct weston_pointer *pointer) -{ - struct weston_surface *surface = pointer->sprite->surface; - - if (weston_surface_is_mapped(surface)) - weston_surface_unmap(surface); - - wl_list_remove(&pointer->sprite_destroy_listener.link); - surface->committed = NULL; - surface->committed_private = NULL; - weston_surface_set_label_func(surface, NULL); - weston_view_destroy(pointer->sprite); - 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; -} - -static void -weston_pointer_reset_state(struct weston_pointer *pointer) -{ - pointer->button_count = 0; -} - -static void -weston_pointer_handle_output_destroy(struct wl_listener *listener, void *data); - -static struct weston_pointer * -weston_pointer_create(struct weston_seat *seat) -{ - struct weston_pointer *pointer; - - pointer = zalloc(sizeof *pointer); - if (pointer == NULL) - return NULL; - - wl_list_init(&pointer->pointer_clients); - weston_pointer_set_default_grab(pointer, - seat->compositor->default_pointer_grab); - wl_list_init(&pointer->focus_resource_listener.link); - pointer->focus_resource_listener.notify = pointer_focus_resource_destroyed; - pointer->default_grab.pointer = pointer; - pointer->grab = &pointer->default_grab; - wl_signal_init(&pointer->motion_signal); - wl_signal_init(&pointer->focus_signal); - wl_list_init(&pointer->focus_view_listener.link); - wl_signal_init(&pointer->destroy_signal); - wl_list_init(&pointer->timestamps_list); - - 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); - - pointer->output_destroy_listener.notify = - weston_pointer_handle_output_destroy; - wl_signal_add(&seat->compositor->output_destroyed_signal, - &pointer->output_destroy_listener); - - pointer->sx = wl_fixed_from_int(-1000000); - pointer->sy = wl_fixed_from_int(-1000000); - - return pointer; -} - -static void -weston_pointer_destroy(struct weston_pointer *pointer) -{ - struct weston_pointer_client *pointer_client, *tmp; - - wl_signal_emit(&pointer->destroy_signal, pointer); - - if (pointer->sprite) - pointer_unmap_sprite(pointer); - - wl_list_for_each_safe(pointer_client, tmp, &pointer->pointer_clients, - link) { - wl_list_remove(&pointer_client->link); - weston_pointer_client_destroy(pointer_client); - } - - wl_list_remove(&pointer->focus_resource_listener.link); - wl_list_remove(&pointer->focus_view_listener.link); - wl_list_remove(&pointer->output_destroy_listener.link); - wl_list_remove(&pointer->timestamps_list); - free(pointer); -} - -void -weston_pointer_set_default_grab(struct weston_pointer *pointer, - const struct weston_pointer_grab_interface *interface) -{ - if (interface) - pointer->default_grab.interface = interface; - else - pointer->default_grab.interface = - &default_pointer_grab_interface; -} - -static 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_list_init(&keyboard->focus_resource_listener.link); - keyboard->focus_resource_listener.notify = keyboard_focus_resource_destroyed; - 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); - wl_list_init(&keyboard->timestamps_list); - - return keyboard; -} - -static void -weston_xkb_info_destroy(struct weston_xkb_info *xkb_info); - -static void -weston_keyboard_destroy(struct weston_keyboard *keyboard) -{ - struct wl_resource *resource; - - wl_resource_for_each(resource, &keyboard->resource_list) { - wl_resource_set_user_data(resource, NULL); - } - - wl_resource_for_each(resource, &keyboard->focus_resource_list) { - wl_resource_set_user_data(resource, NULL); - } - - wl_list_remove(&keyboard->resource_list); - wl_list_remove(&keyboard->focus_resource_list); - - xkb_state_unref(keyboard->xkb_state.state); - if (keyboard->xkb_info) - weston_xkb_info_destroy(keyboard->xkb_info); - xkb_keymap_unref(keyboard->pending_keymap); - - wl_array_release(&keyboard->keys); - wl_list_remove(&keyboard->focus_resource_listener.link); - wl_list_remove(&keyboard->timestamps_list); - free(keyboard); -} - -static void -weston_touch_reset_state(struct weston_touch *touch) -{ - touch->num_tp = 0; -} - -static struct weston_touch * -weston_touch_create(void) -{ - struct weston_touch *touch; - - touch = zalloc(sizeof *touch); - if (touch == NULL) - return NULL; - - wl_list_init(&touch->device_list); - wl_list_init(&touch->resource_list); - wl_list_init(&touch->focus_resource_list); - wl_list_init(&touch->focus_view_listener.link); - touch->focus_view_listener.notify = touch_focus_view_destroyed; - wl_list_init(&touch->focus_resource_listener.link); - touch->focus_resource_listener.notify = touch_focus_resource_destroyed; - touch->default_grab.interface = &default_touch_grab_interface; - touch->default_grab.touch = touch; - touch->grab = &touch->default_grab; - wl_signal_init(&touch->focus_signal); - wl_list_init(&touch->timestamps_list); - - return touch; -} - -static void -weston_touch_destroy(struct weston_touch *touch) -{ - struct wl_resource *resource; - - assert(wl_list_empty(&touch->device_list)); - - wl_resource_for_each(resource, &touch->resource_list) { - wl_resource_set_user_data(resource, NULL); - } - - wl_resource_for_each(resource, &touch->focus_resource_list) { - wl_resource_set_user_data(resource, NULL); - } - - wl_list_remove(&touch->resource_list); - wl_list_remove(&touch->focus_resource_list); - wl_list_remove(&touch->focus_view_listener.link); - wl_list_remove(&touch->focus_resource_listener.link); - wl_list_remove(&touch->timestamps_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_signal_emit(&seat->updated_caps_signal, seat); -} - - -/** Clear the pointer focus - * - * \param pointer the pointer to clear focus for. - * - * This can be used to unset pointer focus and set the co-ordinates to the - * arbitrary values we use for the no focus case. - * - * There's no requirement to use this function. For example, passing the - * results of a weston_compositor_pick_view() directly to - * weston_pointer_set_focus() will do the right thing when no view is found. - */ -WL_EXPORT void -weston_pointer_clear_focus(struct weston_pointer *pointer) -{ - weston_pointer_set_focus(pointer, NULL, - wl_fixed_from_int(-1000000), - wl_fixed_from_int(-1000000)); -} - -WL_EXPORT void -weston_pointer_set_focus(struct weston_pointer *pointer, - struct weston_view *view, - wl_fixed_t sx, wl_fixed_t sy) -{ - struct weston_pointer_client *pointer_client; - struct weston_keyboard *kbd = weston_seat_get_keyboard(pointer->seat); - struct wl_resource *resource; - struct wl_resource *surface_resource; - struct wl_display *display = pointer->seat->compositor->wl_display; - uint32_t serial; - struct wl_list *focus_resource_list; - int refocus = 0; - - if ((!pointer->focus && view) || - (pointer->focus && !view) || - (pointer->focus && pointer->focus->surface != view->surface) || - pointer->sx != sx || pointer->sy != sy) - refocus = 1; - - if (pointer->focus_client && refocus) { - focus_resource_list = &pointer->focus_client->pointer_resources; - if (!wl_list_empty(focus_resource_list)) { - serial = wl_display_next_serial(display); - surface_resource = pointer->focus->surface->resource; - wl_resource_for_each(resource, focus_resource_list) { - wl_pointer_send_leave(resource, serial, - surface_resource); - pointer_send_frame(resource); - } - } - - pointer->focus_client = NULL; - } - - pointer_client = find_pointer_client_for_view(pointer, view); - if (pointer_client && refocus) { - struct wl_client *surface_client = pointer_client->client; - - serial = wl_display_next_serial(display); - - if (kbd && kbd->focus != view->surface) - send_modifiers_to_client_in_list(surface_client, - &kbd->resource_list, - serial, - kbd); - - pointer->focus_client = pointer_client; - - focus_resource_list = &pointer->focus_client->pointer_resources; - wl_resource_for_each(resource, focus_resource_list) { - wl_pointer_send_enter(resource, - serial, - view->surface->resource, - sx, sy); - pointer_send_frame(resource); - } - - pointer->focus_serial = serial; - } - - wl_list_remove(&pointer->focus_view_listener.link); - wl_list_init(&pointer->focus_view_listener.link); - wl_list_remove(&pointer->focus_resource_listener.link); - wl_list_init(&pointer->focus_resource_listener.link); - if (view) - wl_signal_add(&view->destroy_signal, &pointer->focus_view_listener); - if (view && view->surface->resource) - wl_resource_add_destroy_listener(view->surface->resource, - &pointer->focus_resource_listener); - - pointer->focus = view; - pointer->focus_view_listener.notify = pointer_focus_view_destroyed; - pointer->sx = sx; - pointer->sy = sy; - - assert(view || sx == wl_fixed_from_int(-1000000)); - assert(view || sy == wl_fixed_from_int(-1000000)); - - 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) { - wl_keyboard_send_enter(resource, serial, - surface->resource, - &keyboard->keys); - send_modifiers_to_resource(keyboard, resource, serial); - } -} - -WL_EXPORT void -weston_keyboard_set_focus(struct weston_keyboard *keyboard, - struct weston_surface *surface) -{ - struct weston_seat *seat = keyboard->seat; - struct wl_resource *resource; - struct wl_display *display = keyboard->seat->compositor->wl_display; - uint32_t serial; - struct wl_list *focus_resource_list; - - /* Keyboard focus on a surface without a client is equivalent to NULL - * focus as nothing would react to the keyboard events anyway. - * Just set focus to NULL instead - the destroy listener hangs on the - * wl_resource anyway. - */ - if (surface && !surface->resource) - surface = NULL; - - 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; - } - - if (seat->saved_kbd_focus) { - wl_list_remove(&seat->saved_kbd_focus_listener.link); - seat->saved_kbd_focus = NULL; - } - - wl_list_remove(&keyboard->focus_resource_listener.link); - wl_list_init(&keyboard->focus_resource_listener.link); - if (surface) - wl_resource_add_destroy_listener(surface->resource, - &keyboard->focus_resource_listener); - - keyboard->focus = surface; - wl_signal_emit(&keyboard->focus_signal, keyboard); -} - -/* Users of this function must manually manage the keyboard focus */ -WL_EXPORT void -weston_keyboard_start_grab(struct weston_keyboard *keyboard, - struct weston_keyboard_grab *grab) -{ - keyboard->grab = grab; - grab->keyboard = keyboard; -} - -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); -} - -static void -weston_pointer_clamp_for_output(struct weston_pointer *pointer, - struct weston_output *output, - wl_fixed_t *fx, wl_fixed_t *fy) -{ - int x, y; - - x = wl_fixed_to_int(*fx); - y = wl_fixed_to_int(*fy); - - if (x < output->x) - *fx = wl_fixed_from_int(output->x); - else if (x >= output->x + output->width) - *fx = wl_fixed_from_int(output->x + - output->width - 1); - if (y < output->y) - *fy = wl_fixed_from_int(output->y); - else if (y >= output->y + output->height) - *fy = wl_fixed_from_int(output->y + - output->height - 1); -} - -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) - weston_pointer_clamp_for_output(pointer, prev, fx, fy); -} - -static void -weston_pointer_move_to(struct weston_pointer *pointer, - wl_fixed_t x, wl_fixed_t y) -{ - 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); - - if (pointer->sprite) { - weston_view_set_position(pointer->sprite, - ix - pointer->hotspot_x, - iy - pointer->hotspot_y); - weston_view_schedule_repaint(pointer->sprite); - } - - pointer->grab->interface->focus(pointer->grab); - wl_signal_emit(&pointer->motion_signal, pointer); -} - -WL_EXPORT void -weston_pointer_move(struct weston_pointer *pointer, - struct weston_pointer_motion_event *event) -{ - wl_fixed_t x, y; - - weston_pointer_motion_to_abs(pointer, event, &x, &y); - weston_pointer_move_to(pointer, x, y); -} - -/** Verify if the pointer is in a valid position and move it if it isn't. - */ -static void -weston_pointer_handle_output_destroy(struct wl_listener *listener, void *data) -{ - struct weston_pointer *pointer; - struct weston_compositor *ec; - struct weston_output *output, *closest = NULL; - int x, y, distance, min = INT_MAX; - wl_fixed_t fx, fy; - - pointer = container_of(listener, struct weston_pointer, - output_destroy_listener); - ec = pointer->seat->compositor; - - x = wl_fixed_to_int(pointer->x); - y = wl_fixed_to_int(pointer->y); - - wl_list_for_each(output, &ec->output_list, link) { - if (pixman_region32_contains_point(&output->region, - x, y, NULL)) - return; - - /* Aproximante the distance from the pointer to the center of - * the output. */ - distance = abs(output->x + output->width / 2 - x) + - abs(output->y + output->height / 2 - y); - if (distance < min) { - min = distance; - closest = output; - } - } - - /* Nothing to do if there's no output left. */ - if (!closest) - return; - - fx = pointer->x; - fy = pointer->y; - - weston_pointer_clamp_for_output(pointer, closest, &fx, &fy); - weston_pointer_move_to(pointer, fx, fy); -} - -WL_EXPORT void -notify_motion(struct weston_seat *seat, - const struct timespec *time, - struct weston_pointer_motion_event *event) -{ - struct weston_compositor *ec = seat->compositor; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - weston_compositor_wake(ec); - pointer->grab->interface->motion(pointer->grab, time, event); -} - -static void -run_modifier_bindings(struct weston_seat *seat, uint32_t old, uint32_t new) -{ - struct weston_compositor *compositor = seat->compositor; - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - uint32_t diff; - unsigned int i; - struct { - uint32_t xkb; - enum weston_keyboard_modifier weston; - } mods[] = { - { keyboard->xkb_info->ctrl_mod, MODIFIER_CTRL }, - { keyboard->xkb_info->alt_mod, MODIFIER_ALT }, - { keyboard->xkb_info->super_mod, MODIFIER_SUPER }, - { keyboard->xkb_info->shift_mod, MODIFIER_SHIFT }, - }; - - diff = new & ~old; - for (i = 0; i < ARRAY_LENGTH(mods); i++) { - if (diff & (1 << mods[i].xkb)) - weston_compositor_run_modifier_binding(compositor, - keyboard, - mods[i].weston, - WL_KEYBOARD_KEY_STATE_PRESSED); - } - - diff = old & ~new; - for (i = 0; i < ARRAY_LENGTH(mods); i++) { - if (diff & (1 << mods[i].xkb)) - weston_compositor_run_modifier_binding(compositor, - keyboard, - mods[i].weston, - WL_KEYBOARD_KEY_STATE_RELEASED); - } -} - -WL_EXPORT void -notify_motion_absolute(struct weston_seat *seat, const struct timespec *time, - double x, double y) -{ - struct weston_compositor *ec = seat->compositor; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_pointer_motion_event event = { 0 }; - - weston_compositor_wake(ec); - - event = (struct weston_pointer_motion_event) { - .mask = WESTON_POINTER_MOTION_ABS, - .x = x, - .y = y, - }; - - pointer->grab->interface->motion(pointer->grab, time, &event); -} - -static unsigned int -peek_next_activate_serial(struct weston_compositor *c) -{ - unsigned serial = c->activate_serial + 1; - - return serial == 0 ? 1 : serial; -} - -static void -inc_activate_serial(struct weston_compositor *c) -{ - c->activate_serial = peek_next_activate_serial (c); -} - -WL_EXPORT void -weston_view_activate(struct weston_view *view, - struct weston_seat *seat, - uint32_t flags) -{ - struct weston_compositor *compositor = seat->compositor; - - if (flags & WESTON_ACTIVATE_FLAG_CLICKED) { - view->click_to_activate_serial = - peek_next_activate_serial(compositor); - } - - weston_seat_set_keyboard_focus(seat, view->surface); -} - -WL_EXPORT void -notify_button(struct weston_seat *seat, const struct timespec *time, - int32_t button, enum wl_pointer_button_state state) -{ - struct weston_compositor *compositor = seat->compositor; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - 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, pointer, 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, const struct timespec *time, - struct weston_pointer_axis_event *event) -{ - struct weston_compositor *compositor = seat->compositor; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - weston_compositor_wake(compositor); - - if (weston_compositor_run_axis_binding(compositor, pointer, - time, event)) - return; - - pointer->grab->interface->axis(pointer->grab, time, event); -} - -WL_EXPORT void -notify_axis_source(struct weston_seat *seat, uint32_t source) -{ - struct weston_compositor *compositor = seat->compositor; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - weston_compositor_wake(compositor); - - pointer->grab->interface->axis_source(pointer->grab, source); -} - -WL_EXPORT void -notify_pointer_frame(struct weston_seat *seat) -{ - struct weston_compositor *compositor = seat->compositor; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - weston_compositor_wake(compositor); - - pointer->grab->interface->frame(pointer->grab); -} - -WL_EXPORT int -weston_keyboard_set_locks(struct weston_keyboard *keyboard, - uint32_t mask, uint32_t value) -{ - uint32_t serial; - xkb_mod_mask_t mods_depressed, mods_latched, mods_locked, group; - xkb_mod_mask_t num, caps; - - /* We don't want the leds to go out of sync with the actual state - * so if the backend has no way to change the leds don't try to - * change the state */ - if (!keyboard->seat->led_update) - return -1; - - mods_depressed = xkb_state_serialize_mods(keyboard->xkb_state.state, - XKB_STATE_DEPRESSED); - mods_latched = xkb_state_serialize_mods(keyboard->xkb_state.state, - XKB_STATE_LATCHED); - mods_locked = xkb_state_serialize_mods(keyboard->xkb_state.state, - XKB_STATE_LOCKED); - group = xkb_state_serialize_group(keyboard->xkb_state.state, - XKB_STATE_EFFECTIVE); - - num = (1 << keyboard->xkb_info->mod2_mod); - caps = (1 << keyboard->xkb_info->caps_mod); - if (mask & WESTON_NUM_LOCK) { - if (value & WESTON_NUM_LOCK) - mods_locked |= num; - else - mods_locked &= ~num; - } - if (mask & WESTON_CAPS_LOCK) { - if (value & WESTON_CAPS_LOCK) - mods_locked |= caps; - else - mods_locked &= ~caps; - } - - xkb_state_update_mask(keyboard->xkb_state.state, mods_depressed, - mods_latched, mods_locked, 0, 0, group); - - serial = wl_display_next_serial( - keyboard->seat->compositor->wl_display); - notify_modifiers(keyboard->seat, serial); - - return 0; -} - -WL_EXPORT void -notify_modifiers(struct weston_seat *seat, uint32_t serial) -{ - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - 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(keyboard->xkb_state.state, - XKB_STATE_MODS_DEPRESSED); - mods_latched = xkb_state_serialize_mods(keyboard->xkb_state.state, - XKB_STATE_MODS_LATCHED); - mods_locked = xkb_state_serialize_mods(keyboard->xkb_state.state, - XKB_STATE_MODS_LOCKED); - group = xkb_state_serialize_layout(keyboard->xkb_state.state, - XKB_STATE_LAYOUT_EFFECTIVE); - - if (mods_depressed != keyboard->modifiers.mods_depressed || - mods_latched != keyboard->modifiers.mods_latched || - mods_locked != keyboard->modifiers.mods_locked || - group != keyboard->modifiers.group) - changed = 1; - - run_modifier_bindings(seat, keyboard->modifiers.mods_depressed, - mods_depressed); - - keyboard->modifiers.mods_depressed = mods_depressed; - keyboard->modifiers.mods_latched = mods_latched; - keyboard->modifiers.mods_locked = mods_locked; - 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 << keyboard->xkb_info->ctrl_mod)) - seat->modifier_state |= MODIFIER_CTRL; - if (mods_lookup & (1 << keyboard->xkb_info->alt_mod)) - seat->modifier_state |= MODIFIER_ALT; - if (mods_lookup & (1 << keyboard->xkb_info->super_mod)) - seat->modifier_state |= MODIFIER_SUPER; - if (mods_lookup & (1 << keyboard->xkb_info->shift_mod)) - seat->modifier_state |= MODIFIER_SHIFT; - - /* Finally, notify the compositor that LEDs have changed. */ - if (xkb_state_led_index_is_active(keyboard->xkb_state.state, - keyboard->xkb_info->num_led)) - leds |= LED_NUM_LOCK; - if (xkb_state_led_index_is_active(keyboard->xkb_state.state, - keyboard->xkb_info->caps_led)) - leds |= LED_CAPS_LOCK; - if (xkb_state_led_index_is_active(keyboard->xkb_state.state, - keyboard->xkb_info->scroll_led)) - leds |= LED_SCROLL_LOCK; - if (leds != keyboard->xkb_state.leds && seat->led_update) - seat->led_update(seat, leds); - keyboard->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) -{ - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - enum xkb_key_direction direction; - - 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(keyboard->xkb_state.state, key + 8, direction); - - notify_modifiers(seat, serial); -} - -WL_EXPORT void -weston_keyboard_send_keymap(struct weston_keyboard *kbd, struct wl_resource *resource) -{ - struct weston_xkb_info *xkb_info = kbd->xkb_info; - int fd; - size_t size; - enum ro_anonymous_file_mapmode mapmode; - - if (wl_resource_get_version(resource) < 7) - mapmode = RO_ANONYMOUS_FILE_MAPMODE_SHARED; - else - mapmode = RO_ANONYMOUS_FILE_MAPMODE_PRIVATE; - - fd = os_ro_anonymous_file_get_fd(xkb_info->keymap_rofile, mapmode); - size = os_ro_anonymous_file_size(xkb_info->keymap_rofile); - - if (fd == -1) { - weston_log("creating a keymap file failed: %s\n", - strerror(errno)); - return; - } - - wl_keyboard_send_keymap(resource, - WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, - fd, - size); - - os_ro_anonymous_file_put_fd(fd); -} - -static void -send_modifiers(struct wl_resource *resource, uint32_t serial, struct weston_keyboard *keyboard) -{ - wl_keyboard_send_modifiers(resource, serial, - keyboard->modifiers.mods_depressed, - keyboard->modifiers.mods_latched, - keyboard->modifiers.mods_locked, - keyboard->modifiers.group); -} - -static struct weston_xkb_info * -weston_xkb_info_create(struct xkb_keymap *keymap); - -static void -update_keymap(struct weston_seat *seat) -{ - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct wl_resource *resource; - struct weston_xkb_info *xkb_info; - struct xkb_state *state; - xkb_mod_mask_t latched_mods; - xkb_mod_mask_t locked_mods; - - xkb_info = weston_xkb_info_create(keyboard->pending_keymap); - - xkb_keymap_unref(keyboard->pending_keymap); - keyboard->pending_keymap = NULL; - - if (!xkb_info) { - weston_log("failed to create XKB info\n"); - return; - } - - state = xkb_state_new(xkb_info->keymap); - if (!state) { - weston_log("failed to initialise XKB state\n"); - weston_xkb_info_destroy(xkb_info); - return; - } - - latched_mods = xkb_state_serialize_mods(keyboard->xkb_state.state, - XKB_STATE_MODS_LATCHED); - locked_mods = xkb_state_serialize_mods(keyboard->xkb_state.state, - XKB_STATE_MODS_LOCKED); - xkb_state_update_mask(state, - 0, /* depressed */ - latched_mods, - locked_mods, - 0, 0, 0); - - weston_xkb_info_destroy(keyboard->xkb_info); - keyboard->xkb_info = xkb_info; - - xkb_state_unref(keyboard->xkb_state.state); - keyboard->xkb_state.state = state; - - wl_resource_for_each(resource, &keyboard->resource_list) - weston_keyboard_send_keymap(keyboard, resource); - wl_resource_for_each(resource, &keyboard->focus_resource_list) - weston_keyboard_send_keymap(keyboard, resource); - - notify_modifiers(seat, wl_display_next_serial(seat->compositor->wl_display)); - - if (!latched_mods && !locked_mods) - return; - - wl_resource_for_each(resource, &keyboard->resource_list) - send_modifiers(resource, wl_display_get_serial(seat->compositor->wl_display), keyboard); - wl_resource_for_each(resource, &keyboard->focus_resource_list) - send_modifiers(resource, wl_display_get_serial(seat->compositor->wl_display), keyboard); -} - -WL_EXPORT void -notify_key(struct weston_seat *seat, const struct timespec *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 = weston_seat_get_keyboard(seat); - struct weston_keyboard_grab *grab = keyboard->grab; - uint32_t *k, *end; - - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { - weston_compositor_idle_inhibit(compositor); - } 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, keyboard, time, - key, state); - grab = keyboard->grab; - } - - grab->interface->key(grab, time, key, state); - - if (keyboard->pending_keymap && - keyboard->keys.size == 0) - update_keymap(seat); - - if (update_state == STATE_UPDATE_AUTOMATIC) { - update_modifier_state(seat, - wl_display_get_serial(compositor->wl_display), - key, - state); - } - - keyboard->grab_serial = wl_display_get_serial(compositor->wl_display); - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { - keyboard->grab_time = *time; - keyboard->grab_key = key; - } -} - -WL_EXPORT void -notify_pointer_focus(struct weston_seat *seat, struct weston_output *output, - double x, double y) -{ - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - if (output) { - weston_pointer_move_to(pointer, - wl_fixed_from_double(x), - wl_fixed_from_double(y)); - } else { - /* 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 = weston_seat_get_keyboard(seat); - 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); - } - - surface = seat->saved_kbd_focus; - if (surface) { - weston_keyboard_set_focus(keyboard, surface); - } -} - -WL_EXPORT void -notify_keyboard_focus_out(struct weston_seat *seat) -{ - struct weston_compositor *compositor = seat->compositor; - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_surface *focus = keyboard->focus; - 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; - - weston_keyboard_set_focus(keyboard, NULL); - weston_keyboard_cancel_grab(keyboard); - if (pointer) - weston_pointer_cancel_grab(pointer); - - if (focus) { - seat->saved_kbd_focus = focus; - seat->saved_kbd_focus_listener.notify = - destroy_device_saved_kbd_focus; - wl_signal_add(&focus->destroy_signal, - &seat->saved_kbd_focus_listener); - } -} - -WL_EXPORT void -weston_touch_set_focus(struct weston_touch *touch, struct weston_view *view) -{ - struct wl_list *focus_resource_list; - - focus_resource_list = &touch->focus_resource_list; - - if (view && touch->focus && - touch->focus->surface == view->surface) { - touch->focus = view; - return; - } - - wl_list_remove(&touch->focus_resource_listener.link); - wl_list_init(&touch->focus_resource_listener.link); - wl_list_remove(&touch->focus_view_listener.link); - wl_list_init(&touch->focus_view_listener.link); - - if (!wl_list_empty(focus_resource_list)) { - move_resources(&touch->resource_list, - focus_resource_list); - } - - if (view) { - struct wl_client *surface_client; - - if (!view->surface->resource) { - touch->focus = NULL; - return; - } - - surface_client = wl_resource_get_client(view->surface->resource); - move_resources_for_client(focus_resource_list, - &touch->resource_list, - surface_client); - wl_resource_add_destroy_listener(view->surface->resource, - &touch->focus_resource_listener); - wl_signal_add(&view->destroy_signal, &touch->focus_view_listener); - } - touch->focus = view; -} - -static void -process_touch_normal(struct weston_touch_device *device, - const struct timespec *time, int touch_id, - double double_x, double double_y, int touch_type) -{ - struct weston_touch *touch = device->aggregate; - struct weston_touch_grab *grab = device->aggregate->grab; - struct weston_compositor *ec = device->aggregate->seat->compositor; - struct weston_view *ev; - wl_fixed_t sx, sy; - wl_fixed_t x = wl_fixed_from_double(double_x); - wl_fixed_t y = wl_fixed_from_double(double_y); - - /* 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: - /* the first finger down picks the view, and all further go - * to that view for the remainder of the touch session i.e. - * until all touch points are up again. */ - if (touch->num_tp == 1) { - ev = weston_compositor_pick_view(ec, x, y, &sx, &sy); - weston_touch_set_focus(touch, ev); - } else if (!touch->focus) { - /* 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", touch->num_tp); - return; - } - - weston_compositor_run_touch_binding(ec, touch, - time, touch_type); - - grab->interface->down(grab, time, touch_id, x, y); - if (touch->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: - ev = touch->focus; - if (!ev) - break; - - grab->interface->motion(grab, time, touch_id, x, y); - break; - case WL_TOUCH_UP: - grab->interface->up(grab, time, touch_id); - if (touch->num_tp == 0) - weston_touch_set_focus(touch, NULL); - break; - } -} - -static enum weston_touch_mode -get_next_touch_mode(enum weston_touch_mode from) -{ - switch (from) { - case WESTON_TOUCH_MODE_PREP_NORMAL: - return WESTON_TOUCH_MODE_NORMAL; - - case WESTON_TOUCH_MODE_PREP_CALIB: - return WESTON_TOUCH_MODE_CALIB; - - case WESTON_TOUCH_MODE_NORMAL: - case WESTON_TOUCH_MODE_CALIB: - return from; - } - - return WESTON_TOUCH_MODE_NORMAL; -} - -/** Global touch mode update - * - * If no seat has a touch down and the compositor is in a PREP touch mode, - * set the compositor to the goal touch mode. - * - * Calls calibrator if touch mode changed. - */ -static void -weston_compositor_update_touch_mode(struct weston_compositor *compositor) -{ - struct weston_seat *seat; - struct weston_touch *touch; - enum weston_touch_mode goal; - - wl_list_for_each(seat, &compositor->seat_list, link) { - touch = weston_seat_get_touch(seat); - if (!touch) - continue; - - if (touch->num_tp > 0) - return; - } - - goal = get_next_touch_mode(compositor->touch_mode); - if (compositor->touch_mode != goal) { - compositor->touch_mode = goal; - touch_calibrator_mode_changed(compositor); - } -} - -/** Start transition to normal touch event handling - * - * The touch event mode changes when all touches on all touch devices have - * been lifted. If no touches are currently down, the transition is immediate. - * - * \sa weston_touch_mode - */ -void -weston_compositor_set_touch_mode_normal(struct weston_compositor *compositor) -{ - switch (compositor->touch_mode) { - case WESTON_TOUCH_MODE_PREP_NORMAL: - case WESTON_TOUCH_MODE_NORMAL: - return; - case WESTON_TOUCH_MODE_PREP_CALIB: - compositor->touch_mode = WESTON_TOUCH_MODE_NORMAL; - touch_calibrator_mode_changed(compositor); - return; - case WESTON_TOUCH_MODE_CALIB: - compositor->touch_mode = WESTON_TOUCH_MODE_PREP_NORMAL; - } - - weston_compositor_update_touch_mode(compositor); -} - -/** Start transition to calibrator touch event handling - * - * The touch event mode changes when all touches on all touch devices have - * been lifted. If no touches are currently down, the transition is immediate. - * - * \sa weston_touch_mode - */ -void -weston_compositor_set_touch_mode_calib(struct weston_compositor *compositor) -{ - switch (compositor->touch_mode) { - case WESTON_TOUCH_MODE_PREP_CALIB: - case WESTON_TOUCH_MODE_CALIB: - assert(0); - return; - case WESTON_TOUCH_MODE_PREP_NORMAL: - compositor->touch_mode = WESTON_TOUCH_MODE_CALIB; - touch_calibrator_mode_changed(compositor); - return; - case WESTON_TOUCH_MODE_NORMAL: - compositor->touch_mode = WESTON_TOUCH_MODE_PREP_CALIB; - } - - weston_compositor_update_touch_mode(compositor); -} - -/** Feed in touch down, motion, and up events, calibratable device. - * - * 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. - * - * \param device The physical device that generated the event. - * \param time The event timestamp. - * \param touch_id ID for the touch point of this event (multi-touch). - * \param x X coordinate in compositor global space. - * \param y Y coordinate in compositor global space. - * \param norm Normalized device X, Y coordinates in calibration space, or NULL. - * \param touch_type Either WL_TOUCH_DOWN, WL_TOUCH_UP, or WL_TOUCH_MOTION. - * - * Coordinates double_x and double_y are used for normal operation. - * - * Coordinates norm are only used for touch device calibration. If and only if - * the weston_touch_device does not support calibrating, norm must be NULL. - * - * The calibration space is the normalized coordinate space - * [0.0, 1.0]×[0.0, 1.0] of the weston_touch_device. This is assumed to - * map to the similar normalized coordinate space of the associated - * weston_output. - */ -WL_EXPORT void -notify_touch_normalized(struct weston_touch_device *device, - const struct timespec *time, - int touch_id, - double x, double y, - const struct weston_point2d_device_normalized *norm, - int touch_type) -{ - struct weston_seat *seat = device->aggregate->seat; - struct weston_touch *touch = device->aggregate; - - if (touch_type != WL_TOUCH_UP) { - if (weston_touch_device_can_calibrate(device)) - assert(norm != NULL); - else - assert(norm == NULL); - } - - /* Update touchpoints count regardless of the current mode. */ - switch (touch_type) { - case WL_TOUCH_DOWN: - weston_compositor_idle_inhibit(seat->compositor); - - touch->num_tp++; - break; - case WL_TOUCH_UP: - if (touch->num_tp == 0) { - /* This can happen if we start out with one or - * more fingers on the touch screen, in which - * case we didn't get the corresponding down - * event. */ - weston_log("Unmatched touch up event on seat %s, device %s\n", - seat->seat_name, device->syspath); - return; - } - weston_compositor_idle_release(seat->compositor); - - touch->num_tp--; - break; - default: - break; - } - - /* Properly forward the touch event */ - switch (weston_touch_device_get_mode(device)) { - case WESTON_TOUCH_MODE_NORMAL: - case WESTON_TOUCH_MODE_PREP_CALIB: - process_touch_normal(device, time, touch_id, x, y, touch_type); - break; - case WESTON_TOUCH_MODE_CALIB: - case WESTON_TOUCH_MODE_PREP_NORMAL: - notify_touch_calibrator(device, time, touch_id, - norm, touch_type); - break; - } -} - -WL_EXPORT void -notify_touch_frame(struct weston_touch_device *device) -{ - struct weston_touch_grab *grab; - - switch (weston_touch_device_get_mode(device)) { - case WESTON_TOUCH_MODE_NORMAL: - case WESTON_TOUCH_MODE_PREP_CALIB: - grab = device->aggregate->grab; - grab->interface->frame(grab); - break; - case WESTON_TOUCH_MODE_CALIB: - case WESTON_TOUCH_MODE_PREP_NORMAL: - notify_touch_calibrator_frame(device); - break; - } - - weston_compositor_update_touch_mode(device->aggregate->seat->compositor); -} - -WL_EXPORT void -notify_touch_cancel(struct weston_touch_device *device) -{ - struct weston_touch_grab *grab; - - switch (weston_touch_device_get_mode(device)) { - case WESTON_TOUCH_MODE_NORMAL: - case WESTON_TOUCH_MODE_PREP_CALIB: - grab = device->aggregate->grab; - grab->interface->cancel(grab); - break; - case WESTON_TOUCH_MODE_CALIB: - case WESTON_TOUCH_MODE_PREP_NORMAL: - notify_touch_calibrator_cancel(device); - break; - } - - weston_compositor_update_touch_mode(device->aggregate->seat->compositor); -} - -static int -pointer_cursor_surface_get_label(struct weston_surface *surface, - char *buf, size_t len) -{ - return snprintf(buf, len, "cursor"); -} - -static void -pointer_cursor_surface_committed(struct weston_surface *es, - int32_t dx, int32_t dy) -{ - struct weston_pointer *pointer = es->committed_private; - int x, y; - - if (es->width == 0) - return; - - assert(es == pointer->sprite->surface); - - 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_view_set_position(pointer->sprite, x, y); - - empty_region(&es->pending.input); - empty_region(&es->input); - - if (!weston_surface_is_mapped(es)) { - weston_layer_entry_insert(&es->compositor->cursor_layer.view_list, - &pointer->sprite->layer_link); - weston_view_update_transform(pointer->sprite); - es->is_mapped = true; - pointer->sprite->is_mapped = true; - } -} - -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 (!pointer) - return; - - if (surface_resource) - surface = wl_resource_get_user_data(surface_resource); - - if (pointer->focus == NULL) - return; - /* pointer->focus->surface->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->surface->resource == NULL) - return; - if (wl_resource_get_client(pointer->focus->surface->resource) != client) - return; - if (pointer->focus_serial - serial > UINT32_MAX / 2) - return; - - if (!surface) { - if (pointer->sprite) - pointer_unmap_sprite(pointer); - return; - } - - if (pointer->sprite && pointer->sprite->surface == surface && - pointer->hotspot_x == x && pointer->hotspot_y == y) - return; - - if (!pointer->sprite || pointer->sprite->surface != surface) { - if (weston_surface_set_role(surface, "wl_pointer-cursor", - resource, - WL_POINTER_ERROR_ROLE) < 0) - return; - - if (pointer->sprite) - pointer_unmap_sprite(pointer); - - wl_signal_add(&surface->destroy_signal, - &pointer->sprite_destroy_listener); - - surface->committed = pointer_cursor_surface_committed; - surface->committed_private = pointer; - weston_surface_set_label_func(surface, - pointer_cursor_surface_get_label); - pointer->sprite = weston_view_create(surface); - } - - pointer->hotspot_x = x; - pointer->hotspot_y = y; - - if (surface->buffer_ref.buffer) { - pointer_cursor_surface_committed(surface, 0, 0); - weston_view_schedule_repaint(pointer->sprite); - } -} - -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); - /* We use the pointer_state directly, which means we'll - * give a wl_pointer if the seat has ever had one - even though - * the spec explicitly states that this request only takes effect - * if the seat has the pointer capability. - * - * This prevents a race between the compositor sending new - * capabilities and the client trying to use the old ones. - */ - struct weston_pointer *pointer = seat ? seat->pointer_state : NULL; - struct wl_resource *cr; - struct weston_pointer_client *pointer_client; - - cr = wl_resource_create(client, &wl_pointer_interface, - wl_resource_get_version(resource), id); - if (cr == NULL) { - wl_client_post_no_memory(client); - return; - } - - wl_list_init(wl_resource_get_link(cr)); - wl_resource_set_implementation(cr, &pointer_interface, pointer, - unbind_pointer_client_resource); - - /* If we don't have a pointer_state, the resource is inert, so there - * is nothing more to set up */ - if (!pointer) - return; - - pointer_client = weston_pointer_ensure_pointer_client(pointer, client); - if (!pointer_client) { - wl_client_post_no_memory(client); - return; - } - - wl_list_insert(&pointer_client->pointer_resources, - wl_resource_get_link(cr)); - - if (pointer->focus && pointer->focus->surface->resource && - wl_resource_get_client(pointer->focus->surface->resource) == client) { - wl_fixed_t sx, sy; - - weston_view_from_global_fixed(pointer->focus, - pointer->x, - pointer->y, - &sx, &sy); - - wl_pointer_send_enter(cr, - pointer->focus_serial, - pointer->focus->surface->resource, - sx, sy); - pointer_send_frame(cr); - } -} - -static void -destroy_keyboard_resource(struct wl_resource *resource) -{ - struct weston_keyboard *keyboard = wl_resource_get_user_data(resource); - - wl_list_remove(wl_resource_get_link(resource)); - - if (keyboard) { - remove_input_resource_from_timestamps(resource, - &keyboard->timestamps_list); - } -} - -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 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); - /* We use the keyboard_state directly, which means we'll - * give a wl_keyboard if the seat has ever had one - even though - * the spec explicitly states that this request only takes effect - * if the seat has the keyboard capability. - * - * This prevents a race between the compositor sending new - * capabilities and the client trying to use the old ones. - */ - struct weston_keyboard *keyboard = seat ? seat->keyboard_state : NULL; - struct wl_resource *cr; - - cr = wl_resource_create(client, &wl_keyboard_interface, - wl_resource_get_version(resource), id); - if (cr == NULL) { - wl_client_post_no_memory(client); - return; - } - - wl_list_init(wl_resource_get_link(cr)); - wl_resource_set_implementation(cr, &keyboard_interface, - keyboard, destroy_keyboard_resource); - - /* If we don't have a keyboard_state, the resource is inert, so there - * is nothing more to set up */ - if (!keyboard) - 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(&keyboard->resource_list, wl_resource_get_link(cr)); - - if (wl_resource_get_version(cr) >= WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION) { - wl_keyboard_send_repeat_info(cr, - seat->compositor->kb_repeat_rate, - seat->compositor->kb_repeat_delay); - } - - weston_keyboard_send_keymap(keyboard, cr); - - if (keyboard->focus && keyboard->focus->resource && - wl_resource_get_client(keyboard->focus->resource) == client) { - struct weston_surface *surface = - (struct weston_surface *)keyboard->focus; - - wl_list_remove(wl_resource_get_link(cr)); - wl_list_insert(&keyboard->focus_resource_list, - wl_resource_get_link(cr)); - wl_keyboard_send_enter(cr, - keyboard->focus_serial, - surface->resource, - &keyboard->keys); - - send_modifiers_to_resource(keyboard, - cr, - keyboard->focus_serial); - - /* If this is the first keyboard resource for this - * client... */ - if (keyboard->focus_resource_list.prev == - wl_resource_get_link(cr)) - wl_data_device_set_keyboard_focus(seat); - } -} - -static void -destroy_touch_resource(struct wl_resource *resource) -{ - struct weston_touch *touch = wl_resource_get_user_data(resource); - - wl_list_remove(wl_resource_get_link(resource)); - - if (touch) { - remove_input_resource_from_timestamps(resource, - &touch->timestamps_list); - } -} - -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); - /* We use the touch_state directly, which means we'll - * give a wl_touch if the seat has ever had one - even though - * the spec explicitly states that this request only takes effect - * if the seat has the touch capability. - * - * This prevents a race between the compositor sending new - * capabilities and the client trying to use the old ones. - */ - struct weston_touch *touch = seat ? seat->touch_state : NULL; - struct wl_resource *cr; - - cr = wl_resource_create(client, &wl_touch_interface, - wl_resource_get_version(resource), id); - if (cr == NULL) { - wl_client_post_no_memory(client); - return; - } - - wl_list_init(wl_resource_get_link(cr)); - wl_resource_set_implementation(cr, &touch_interface, - touch, destroy_touch_resource); - - /* If we don't have a touch_state, the resource is inert, so there - * is nothing more to set up */ - if (!touch) - return; - - if (touch->focus && - wl_resource_get_client(touch->focus->surface->resource) == client) { - wl_list_insert(&touch->focus_resource_list, - wl_resource_get_link(cr)); - } else { - wl_list_insert(&touch->resource_list, - wl_resource_get_link(cr)); - } -} - -static void -seat_release(struct wl_client *client, struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static const struct wl_seat_interface seat_interface = { - seat_get_pointer, - seat_get_keyboard, - seat_get_touch, - seat_release, -}; - -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, version, id); - wl_list_insert(&seat->base_resource_list, wl_resource_get_link(resource)); - wl_resource_set_implementation(resource, &seat_interface, data, - unbind_resource); - - if (weston_seat_get_pointer(seat)) - caps |= WL_SEAT_CAPABILITY_POINTER; - if (weston_seat_get_keyboard(seat)) - caps |= WL_SEAT_CAPABILITY_KEYBOARD; - if (weston_seat_get_touch(seat)) - caps |= WL_SEAT_CAPABILITY_TOUCH; - - wl_seat_send_capabilities(resource, caps); - if (version >= WL_SEAT_NAME_SINCE_VERSION) - wl_seat_send_name(resource, seat->seat_name); -} - -static void -relative_pointer_destroy(struct wl_client *client, - struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static const struct zwp_relative_pointer_v1_interface relative_pointer_interface = { - relative_pointer_destroy -}; - -static void -relative_pointer_manager_destroy(struct wl_client *client, - struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -relative_pointer_manager_get_relative_pointer(struct wl_client *client, - struct wl_resource *resource, - uint32_t id, - struct wl_resource *pointer_resource) -{ - struct weston_pointer *pointer = - wl_resource_get_user_data(pointer_resource); - struct weston_pointer_client *pointer_client; - struct wl_resource *cr; - - cr = wl_resource_create(client, &zwp_relative_pointer_v1_interface, - wl_resource_get_version(resource), id); - if (cr == NULL) { - wl_client_post_no_memory(client); - return; - } - - pointer_client = weston_pointer_ensure_pointer_client(pointer, client); - if (!pointer_client) { - wl_client_post_no_memory(client); - return; - } - - wl_list_insert(&pointer_client->relative_pointer_resources, - wl_resource_get_link(cr)); - wl_resource_set_implementation(cr, &relative_pointer_interface, - pointer, - unbind_pointer_client_resource); -} - -static const struct zwp_relative_pointer_manager_v1_interface relative_pointer_manager = { - relative_pointer_manager_destroy, - relative_pointer_manager_get_relative_pointer, -}; - -static void -bind_relative_pointer_manager(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, - &zwp_relative_pointer_manager_v1_interface, - 1, id); - - wl_resource_set_implementation(resource, &relative_pointer_manager, - compositor, - NULL); -} - -WL_EXPORT int -weston_compositor_set_xkb_rule_names(struct weston_compositor *ec, - struct xkb_rule_names *names) -{ - if (ec->xkb_context == NULL) { - ec->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - 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; - - xkb_keymap_unref(xkb_info->keymap); - - os_ro_anonymous_file_destroy(xkb_info->keymap_rofile); - free(xkb_info); -} - -void -weston_compositor_xkb_destroy(struct weston_compositor *ec) -{ - 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) -{ - char *keymap_string; - size_t keymap_size; - struct weston_xkb_info *xkb_info = zalloc(sizeof *xkb_info); - if (xkb_info == NULL) - return NULL; - - xkb_info->keymap = xkb_keymap_ref(keymap); - xkb_info->ref_count = 1; - - xkb_info->shift_mod = xkb_keymap_mod_get_index(xkb_info->keymap, - XKB_MOD_NAME_SHIFT); - xkb_info->caps_mod = xkb_keymap_mod_get_index(xkb_info->keymap, - XKB_MOD_NAME_CAPS); - xkb_info->ctrl_mod = xkb_keymap_mod_get_index(xkb_info->keymap, - XKB_MOD_NAME_CTRL); - xkb_info->alt_mod = xkb_keymap_mod_get_index(xkb_info->keymap, - XKB_MOD_NAME_ALT); - xkb_info->mod2_mod = xkb_keymap_mod_get_index(xkb_info->keymap, - "Mod2"); - xkb_info->mod3_mod = xkb_keymap_mod_get_index(xkb_info->keymap, - "Mod3"); - xkb_info->super_mod = xkb_keymap_mod_get_index(xkb_info->keymap, - XKB_MOD_NAME_LOGO); - xkb_info->mod5_mod = xkb_keymap_mod_get_index(xkb_info->keymap, - "Mod5"); - - xkb_info->num_led = xkb_keymap_led_get_index(xkb_info->keymap, - XKB_LED_NAME_NUM); - xkb_info->caps_led = xkb_keymap_led_get_index(xkb_info->keymap, - XKB_LED_NAME_CAPS); - xkb_info->scroll_led = xkb_keymap_led_get_index(xkb_info->keymap, - XKB_LED_NAME_SCROLL); - - keymap_string = xkb_keymap_get_as_string(xkb_info->keymap, - XKB_KEYMAP_FORMAT_TEXT_V1); - if (keymap_string == NULL) { - weston_log("failed to get string version of keymap\n"); - goto err_keymap; - } - keymap_size = strlen(keymap_string) + 1; - - xkb_info->keymap_rofile = os_ro_anonymous_file_create(keymap_size, - keymap_string); - free(keymap_string); - - if (!xkb_info->keymap_rofile) { - weston_log("failed to create anonymous file for keymap\n"); - goto err_keymap; - } - - return xkb_info; - -err_keymap: - xkb_keymap_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_keymap_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); - xkb_keymap_unref(keymap); - if (ec->xkb_info == NULL) - return -1; - - return 0; -} - -WL_EXPORT void -weston_seat_update_keymap(struct weston_seat *seat, struct xkb_keymap *keymap) -{ - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - - if (!keyboard || !keymap) - return; - - xkb_keymap_unref(keyboard->pending_keymap); - keyboard->pending_keymap = xkb_keymap_ref(keymap); - - if (keyboard->keys.size == 0) - update_keymap(seat); -} - -WL_EXPORT int -weston_seat_init_keyboard(struct weston_seat *seat, struct xkb_keymap *keymap) -{ - struct weston_keyboard *keyboard; - - if (seat->keyboard_state) { - seat->keyboard_device_count += 1; - if (seat->keyboard_device_count == 1) - seat_send_updated_caps(seat); - return 0; - } - - keyboard = weston_keyboard_create(); - if (keyboard == NULL) { - weston_log("failed to allocate weston keyboard struct\n"); - return -1; - } - - if (keymap != NULL) { - keyboard->xkb_info = weston_xkb_info_create(keymap); - if (keyboard->xkb_info == NULL) - goto err; - } else { - if (weston_compositor_build_global_keymap(seat->compositor) < 0) - goto err; - keyboard->xkb_info = seat->compositor->xkb_info; - keyboard->xkb_info->ref_count++; - } - - keyboard->xkb_state.state = xkb_state_new(keyboard->xkb_info->keymap); - if (keyboard->xkb_state.state == NULL) { - weston_log("failed to initialise XKB state\n"); - goto err; - } - - keyboard->xkb_state.leds = 0; - - seat->keyboard_state = keyboard; - seat->keyboard_device_count = 1; - keyboard->seat = seat; - - seat_send_updated_caps(seat); - - return 0; - -err: - if (keyboard->xkb_info) - weston_xkb_info_destroy(keyboard->xkb_info); - free(keyboard); - - return -1; -} - -static void -weston_keyboard_reset_state(struct weston_keyboard *keyboard) -{ - struct weston_seat *seat = keyboard->seat; - struct xkb_state *state; - - state = xkb_state_new(keyboard->xkb_info->keymap); - if (!state) { - weston_log("failed to reset XKB state\n"); - return; - } - xkb_state_unref(keyboard->xkb_state.state); - keyboard->xkb_state.state = state; - - keyboard->xkb_state.leds = 0; - - seat->modifier_state = 0; -} - -WL_EXPORT void -weston_seat_release_keyboard(struct weston_seat *seat) -{ - seat->keyboard_device_count--; - assert(seat->keyboard_device_count >= 0); - if (seat->keyboard_device_count == 0) { - weston_keyboard_set_focus(seat->keyboard_state, NULL); - weston_keyboard_cancel_grab(seat->keyboard_state); - weston_keyboard_reset_state(seat->keyboard_state); - seat_send_updated_caps(seat); - } -} - -WL_EXPORT void -weston_seat_init_pointer(struct weston_seat *seat) -{ - struct weston_pointer *pointer; - - if (seat->pointer_state) { - seat->pointer_device_count += 1; - if (seat->pointer_device_count == 1) - seat_send_updated_caps(seat); - return; - } - - pointer = weston_pointer_create(seat); - if (pointer == NULL) - return; - - seat->pointer_state = 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_state; - - seat->pointer_device_count--; - if (seat->pointer_device_count == 0) { - weston_pointer_clear_focus(pointer); - weston_pointer_cancel_grab(pointer); - - if (pointer->sprite) - pointer_unmap_sprite(pointer); - - weston_pointer_reset_state(pointer); - seat_send_updated_caps(seat); - - /* seat->pointer is intentionally not destroyed so that - * a newly attached pointer on this seat will retain - * the previous cursor co-ordinates. - */ - } -} - -WL_EXPORT void -weston_seat_init_touch(struct weston_seat *seat) -{ - struct weston_touch *touch; - - if (seat->touch_state) { - 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_state = 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->touch_state, NULL); - weston_touch_cancel_grab(seat->touch_state); - weston_touch_reset_state(seat->touch_state); - 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); - wl_signal_init(&seat->updated_caps_signal); - - seat->global = wl_global_create(ec->wl_display, &wl_seat_interface, - MIN(wl_seat_interface.version, 7), - seat, bind_seat); - - seat->compositor = ec; - seat->modifier_state = 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) -{ - struct wl_resource *resource; - - wl_resource_for_each(resource, &seat->base_resource_list) { - wl_resource_set_user_data(resource, NULL); - } - - wl_resource_for_each(resource, &seat->drag_resource_list) { - wl_resource_set_user_data(resource, NULL); - } - - wl_list_remove(&seat->base_resource_list); - wl_list_remove(&seat->drag_resource_list); - - wl_list_remove(&seat->link); - - if (seat->saved_kbd_focus) - wl_list_remove(&seat->saved_kbd_focus_listener.link); - - if (seat->pointer_state) - weston_pointer_destroy(seat->pointer_state); - if (seat->keyboard_state) - weston_keyboard_destroy(seat->keyboard_state); - if (seat->touch_state) - weston_touch_destroy(seat->touch_state); - - free (seat->seat_name); - - wl_global_destroy(seat->global); - - wl_signal_emit(&seat->destroy_signal, seat); -} - -/** Get a seat's keyboard pointer - * - * \param seat The seat to query - * \return The seat's keyboard pointer, or NULL if no keyboard is present - * - * The keyboard pointer for a seat isn't freed when all keyboards are removed, - * so it should only be used when the seat's keyboard_device_count is greater - * than zero. This function does that test and only returns a pointer - * when a keyboard is present. - */ -WL_EXPORT struct weston_keyboard * -weston_seat_get_keyboard(struct weston_seat *seat) -{ - if (!seat) - return NULL; - - if (seat->keyboard_device_count) - return seat->keyboard_state; - - return NULL; -} - -/** Get a seat's pointer pointer - * - * \param seat The seat to query - * \return The seat's pointer pointer, or NULL if no pointer device is present - * - * The pointer pointer for a seat isn't freed when all mice are removed, - * so it should only be used when the seat's pointer_device_count is greater - * than zero. This function does that test and only returns a pointer - * when a pointing device is present. - */ -WL_EXPORT struct weston_pointer * -weston_seat_get_pointer(struct weston_seat *seat) -{ - if (!seat) - return NULL; - - if (seat->pointer_device_count) - return seat->pointer_state; - - return NULL; -} - -static const struct zwp_locked_pointer_v1_interface locked_pointer_interface; -static const struct zwp_confined_pointer_v1_interface confined_pointer_interface; - -static enum pointer_constraint_type -pointer_constraint_get_type(struct weston_pointer_constraint *constraint) -{ - if (wl_resource_instance_of(constraint->resource, - &zwp_locked_pointer_v1_interface, - &locked_pointer_interface)) { - return POINTER_CONSTRAINT_TYPE_LOCK; - } else if (wl_resource_instance_of(constraint->resource, - &zwp_confined_pointer_v1_interface, - &confined_pointer_interface)) { - return POINTER_CONSTRAINT_TYPE_CONFINE; - } - - abort(); - return 0; -} - -static void -pointer_constraint_notify_activated(struct weston_pointer_constraint *constraint) -{ - struct wl_resource *resource = constraint->resource; - - switch (pointer_constraint_get_type(constraint)) { - case POINTER_CONSTRAINT_TYPE_LOCK: - zwp_locked_pointer_v1_send_locked(resource); - break; - case POINTER_CONSTRAINT_TYPE_CONFINE: - zwp_confined_pointer_v1_send_confined(resource); - break; - } -} - -static void -pointer_constraint_notify_deactivated(struct weston_pointer_constraint *constraint) -{ - struct wl_resource *resource = constraint->resource; - - switch (pointer_constraint_get_type(constraint)) { - case POINTER_CONSTRAINT_TYPE_LOCK: - zwp_locked_pointer_v1_send_unlocked(resource); - break; - case POINTER_CONSTRAINT_TYPE_CONFINE: - zwp_confined_pointer_v1_send_unconfined(resource); - break; - } -} - -static struct weston_pointer_constraint * -get_pointer_constraint_for_pointer(struct weston_surface *surface, - struct weston_pointer *pointer) -{ - struct weston_pointer_constraint *constraint; - - wl_list_for_each(constraint, &surface->pointer_constraints, link) { - if (constraint->pointer == pointer) - return constraint; - } - - return NULL; -} - -/** Get a seat's touch pointer - * - * \param seat The seat to query - * \return The seat's touch pointer, or NULL if no touch device is present - * - * The touch pointer for a seat isn't freed when all touch devices are removed, - * so it should only be used when the seat's touch_device_count is greater - * than zero. This function does that test and only returns a pointer - * when a touch device is present. - */ -WL_EXPORT struct weston_touch * -weston_seat_get_touch(struct weston_seat *seat) -{ - if (!seat) - return NULL; - - if (seat->touch_device_count) - return seat->touch_state; - - return NULL; -} - -/** Sets the keyboard focus to the given surface - * - * \param surface the surface to focus on - * \param seat The seat to query - */ -WL_EXPORT void -weston_seat_set_keyboard_focus(struct weston_seat *seat, - struct weston_surface *surface) -{ - struct weston_compositor *compositor = seat->compositor; - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct weston_surface_activation_data activation_data; - - if (keyboard && keyboard->focus != surface) { - weston_keyboard_set_focus(keyboard, surface); - wl_data_device_set_keyboard_focus(seat); - } - - inc_activate_serial(compositor); - - activation_data = (struct weston_surface_activation_data) { - .surface = surface, - .seat = seat, - }; - wl_signal_emit(&compositor->activate_signal, &activation_data); -} - -static void -enable_pointer_constraint(struct weston_pointer_constraint *constraint, - struct weston_view *view) -{ - assert(constraint->view == NULL); - constraint->view = view; - pointer_constraint_notify_activated(constraint); - weston_pointer_start_grab(constraint->pointer, &constraint->grab); - wl_list_remove(&constraint->surface_destroy_listener.link); - wl_list_init(&constraint->surface_destroy_listener.link); -} - -static bool -is_pointer_constraint_enabled(struct weston_pointer_constraint *constraint) -{ - return constraint->view != NULL; -} - -static void -weston_pointer_constraint_disable(struct weston_pointer_constraint *constraint) -{ - constraint->view = NULL; - pointer_constraint_notify_deactivated(constraint); - weston_pointer_end_grab(constraint->grab.pointer); -} - -void -weston_pointer_constraint_destroy(struct weston_pointer_constraint *constraint) -{ - if (is_pointer_constraint_enabled(constraint)) - weston_pointer_constraint_disable(constraint); - - wl_list_remove(&constraint->pointer_destroy_listener.link); - wl_list_remove(&constraint->surface_destroy_listener.link); - wl_list_remove(&constraint->surface_commit_listener.link); - wl_list_remove(&constraint->surface_activate_listener.link); - - wl_resource_set_user_data(constraint->resource, NULL); - pixman_region32_fini(&constraint->region); - wl_list_remove(&constraint->link); - free(constraint); -} - -static void -disable_pointer_constraint(struct weston_pointer_constraint *constraint) -{ - switch (constraint->lifetime) { - case ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT: - weston_pointer_constraint_destroy(constraint); - break; - case ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT: - weston_pointer_constraint_disable(constraint); - break; - } -} - -static bool -is_within_constraint_region(struct weston_pointer_constraint *constraint, - wl_fixed_t sx, wl_fixed_t sy) -{ - struct weston_surface *surface = constraint->surface; - pixman_region32_t constraint_region; - bool result; - - pixman_region32_init(&constraint_region); - pixman_region32_intersect(&constraint_region, - &surface->input, - &constraint->region); - result = pixman_region32_contains_point(&constraint_region, - wl_fixed_to_int(sx), - wl_fixed_to_int(sy), - NULL); - pixman_region32_fini(&constraint_region); - - return result; -} - -static void -maybe_enable_pointer_constraint(struct weston_pointer_constraint *constraint) -{ - struct weston_surface *surface = constraint->surface; - struct weston_view *vit; - struct weston_view *view = NULL; - struct weston_pointer *pointer = constraint->pointer; - struct weston_keyboard *keyboard; - struct weston_seat *seat = pointer->seat; - int32_t x, y; - - /* Postpone if no view of the surface was most recently clicked. */ - wl_list_for_each(vit, &surface->views, surface_link) { - if (vit->click_to_activate_serial == - surface->compositor->activate_serial) { - view = vit; - } - } - if (view == NULL) - return; - - /* Postpone if surface doesn't have keyboard focus. */ - keyboard = weston_seat_get_keyboard(seat); - if (!keyboard || keyboard->focus != surface) - return; - - /* Postpone constraint if the pointer is not within the - * constraint region. - */ - weston_view_from_global(view, - wl_fixed_to_int(pointer->x), - wl_fixed_to_int(pointer->y), - &x, &y); - if (!is_within_constraint_region(constraint, - wl_fixed_from_int(x), - wl_fixed_from_int(y))) - return; - - enable_pointer_constraint(constraint, view); -} - -static void -locked_pointer_grab_pointer_focus(struct weston_pointer_grab *grab) -{ -} - -static void -locked_pointer_grab_pointer_motion(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_motion_event *event) -{ - pointer_send_relative_motion(grab->pointer, time, event); -} - -static void -locked_pointer_grab_pointer_button(struct weston_pointer_grab *grab, - const struct timespec *time, - uint32_t button, - uint32_t state_w) -{ - weston_pointer_send_button(grab->pointer, time, button, state_w); -} - -static void -locked_pointer_grab_pointer_axis(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_axis_event *event) -{ - weston_pointer_send_axis(grab->pointer, time, event); -} - -static void -locked_pointer_grab_pointer_axis_source(struct weston_pointer_grab *grab, - uint32_t source) -{ - weston_pointer_send_axis_source(grab->pointer, source); -} - -static void -locked_pointer_grab_pointer_frame(struct weston_pointer_grab *grab) -{ - weston_pointer_send_frame(grab->pointer); -} - -static void -locked_pointer_grab_pointer_cancel(struct weston_pointer_grab *grab) -{ - struct weston_pointer_constraint *constraint = - container_of(grab, struct weston_pointer_constraint, grab); - - disable_pointer_constraint(constraint); -} - -static const struct weston_pointer_grab_interface - locked_pointer_grab_interface = { - locked_pointer_grab_pointer_focus, - locked_pointer_grab_pointer_motion, - locked_pointer_grab_pointer_button, - locked_pointer_grab_pointer_axis, - locked_pointer_grab_pointer_axis_source, - locked_pointer_grab_pointer_frame, - locked_pointer_grab_pointer_cancel, -}; - -static void -pointer_constraint_constrain_resource_destroyed(struct wl_resource *resource) -{ - struct weston_pointer_constraint *constraint = - wl_resource_get_user_data(resource); - - if (!constraint) - return; - - weston_pointer_constraint_destroy(constraint); -} - -static void -pointer_constraint_surface_activate(struct wl_listener *listener, void *data) -{ - struct weston_surface_activation_data *activation = data; - struct weston_pointer *pointer; - struct weston_surface *focus = activation->surface; - struct weston_pointer_constraint *constraint = - container_of(listener, struct weston_pointer_constraint, - surface_activate_listener); - bool is_constraint_surface; - - pointer = weston_seat_get_pointer(activation->seat); - if (!pointer) - return; - - is_constraint_surface = - get_pointer_constraint_for_pointer(focus, pointer) == constraint; - - if (is_constraint_surface && - !is_pointer_constraint_enabled(constraint)) - maybe_enable_pointer_constraint(constraint); - else if (!is_constraint_surface && - is_pointer_constraint_enabled(constraint)) - disable_pointer_constraint(constraint); -} - -static void -pointer_constraint_pointer_destroyed(struct wl_listener *listener, void *data) -{ - struct weston_pointer_constraint *constraint = - container_of(listener, struct weston_pointer_constraint, - pointer_destroy_listener); - - weston_pointer_constraint_destroy(constraint); -} - -static void -pointer_constraint_surface_destroyed(struct wl_listener *listener, void *data) -{ - struct weston_pointer_constraint *constraint = - container_of(listener, struct weston_pointer_constraint, - surface_destroy_listener); - - weston_pointer_constraint_destroy(constraint); -} - -static void -pointer_constraint_surface_committed(struct wl_listener *listener, void *data) -{ - struct weston_pointer_constraint *constraint = - container_of(listener, struct weston_pointer_constraint, - surface_commit_listener); - - if (constraint->region_is_pending) { - constraint->region_is_pending = false; - pixman_region32_copy(&constraint->region, - &constraint->region_pending); - pixman_region32_fini(&constraint->region_pending); - pixman_region32_init(&constraint->region_pending); - } - - if (constraint->hint_is_pending) { - constraint->hint_is_pending = false; - - constraint->hint_is_pending = true; - constraint->hint_x = constraint->hint_x_pending; - constraint->hint_y = constraint->hint_y_pending; - } - - if (pointer_constraint_get_type(constraint) == - POINTER_CONSTRAINT_TYPE_CONFINE && - is_pointer_constraint_enabled(constraint)) - maybe_warp_confined_pointer(constraint); -} - -static struct weston_pointer_constraint * -weston_pointer_constraint_create(struct weston_surface *surface, - struct weston_pointer *pointer, - struct weston_region *region, - enum zwp_pointer_constraints_v1_lifetime lifetime, - struct wl_resource *cr, - const struct weston_pointer_grab_interface *grab_interface) -{ - struct weston_pointer_constraint *constraint; - - constraint = zalloc(sizeof *constraint); - if (!constraint) - return NULL; - - constraint->lifetime = lifetime; - pixman_region32_init(&constraint->region); - pixman_region32_init(&constraint->region_pending); - wl_list_insert(&surface->pointer_constraints, &constraint->link); - constraint->surface = surface; - constraint->pointer = pointer; - constraint->resource = cr; - constraint->grab.interface = grab_interface; - if (region) { - pixman_region32_copy(&constraint->region, - ®ion->region); - } else { - pixman_region32_fini(&constraint->region); - region_init_infinite(&constraint->region); - } - - constraint->surface_activate_listener.notify = - pointer_constraint_surface_activate; - constraint->surface_destroy_listener.notify = - pointer_constraint_surface_destroyed; - constraint->surface_commit_listener.notify = - pointer_constraint_surface_committed; - constraint->pointer_destroy_listener.notify = - pointer_constraint_pointer_destroyed; - - wl_signal_add(&surface->compositor->activate_signal, - &constraint->surface_activate_listener); - wl_signal_add(&pointer->destroy_signal, - &constraint->pointer_destroy_listener); - wl_signal_add(&surface->destroy_signal, - &constraint->surface_destroy_listener); - wl_signal_add(&surface->commit_signal, - &constraint->surface_commit_listener); - - return constraint; -} - -static void -init_pointer_constraint(struct wl_resource *pointer_constraints_resource, - uint32_t id, - struct weston_surface *surface, - struct weston_pointer *pointer, - struct weston_region *region, - enum zwp_pointer_constraints_v1_lifetime lifetime, - const struct wl_interface *interface, - const void *implementation, - const struct weston_pointer_grab_interface *grab_interface) -{ - struct wl_client *client = - wl_resource_get_client(pointer_constraints_resource); - struct wl_resource *cr; - struct weston_pointer_constraint *constraint; - - if (pointer && get_pointer_constraint_for_pointer(surface, pointer)) { - wl_resource_post_error(pointer_constraints_resource, - ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED, - "the pointer has a lock/confine request on this surface"); - return; - } - - cr = wl_resource_create(client, interface, - wl_resource_get_version(pointer_constraints_resource), - id); - if (cr == NULL) { - wl_client_post_no_memory(client); - return; - } - - if (pointer) { - constraint = weston_pointer_constraint_create(surface, pointer, - region, lifetime, - cr, grab_interface); - if (constraint == NULL) { - wl_client_post_no_memory(client); - return; - } - } else { - constraint = NULL; - } - - wl_resource_set_implementation(cr, implementation, constraint, - pointer_constraint_constrain_resource_destroyed); - - if (constraint) - maybe_enable_pointer_constraint(constraint); -} - -static void -pointer_constraints_destroy(struct wl_client *client, - struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -locked_pointer_destroy(struct wl_client *client, - struct wl_resource *resource) -{ - struct weston_pointer_constraint *constraint = - wl_resource_get_user_data(resource); - wl_fixed_t x, y; - - if (constraint && constraint->view && constraint->hint_is_pending && - is_within_constraint_region(constraint, - constraint->hint_x, - constraint->hint_y)) { - weston_view_to_global_fixed(constraint->view, - constraint->hint_x, - constraint->hint_y, - &x, &y); - weston_pointer_move_to(constraint->pointer, x, y); - } - wl_resource_destroy(resource); -} - -static void -locked_pointer_set_cursor_position_hint(struct wl_client *client, - struct wl_resource *resource, - wl_fixed_t surface_x, - wl_fixed_t surface_y) -{ - struct weston_pointer_constraint *constraint = - wl_resource_get_user_data(resource); - - /* Ignore a set cursor hint that was sent after the lock was cancelled. - */ - if (!constraint || - !constraint->resource || - constraint->resource != resource) - return; - - constraint->hint_is_pending = true; - constraint->hint_x_pending = surface_x; - constraint->hint_y_pending = surface_y; -} - -static void -locked_pointer_set_region(struct wl_client *client, - struct wl_resource *resource, - struct wl_resource *region_resource) -{ - struct weston_pointer_constraint *constraint = - wl_resource_get_user_data(resource); - struct weston_region *region = region_resource ? - wl_resource_get_user_data(region_resource) : NULL; - - if (!constraint) - return; - - if (region) { - pixman_region32_copy(&constraint->region_pending, - ®ion->region); - } else { - pixman_region32_fini(&constraint->region_pending); - region_init_infinite(&constraint->region_pending); - } - constraint->region_is_pending = true; -} - - -static const struct zwp_locked_pointer_v1_interface locked_pointer_interface = { - locked_pointer_destroy, - locked_pointer_set_cursor_position_hint, - locked_pointer_set_region, -}; - -static void -pointer_constraints_lock_pointer(struct wl_client *client, - struct wl_resource *resource, - uint32_t id, - struct wl_resource *surface_resource, - struct wl_resource *pointer_resource, - struct wl_resource *region_resource, - uint32_t lifetime) -{ - struct weston_surface *surface = - wl_resource_get_user_data(surface_resource); - struct weston_pointer *pointer = wl_resource_get_user_data(pointer_resource); - struct weston_region *region = region_resource ? - wl_resource_get_user_data(region_resource) : NULL; - - init_pointer_constraint(resource, id, surface, pointer, region, lifetime, - &zwp_locked_pointer_v1_interface, - &locked_pointer_interface, - &locked_pointer_grab_interface); -} - -static void -confined_pointer_grab_pointer_focus(struct weston_pointer_grab *grab) -{ -} - -static double -vec2d_cross_product(struct vec2d a, struct vec2d b) -{ - return a.x * b.y - a.y * b.x; -} - -static struct vec2d -vec2d_add(struct vec2d a, struct vec2d b) -{ - return (struct vec2d) { - .x = a.x + b.x, - .y = a.y + b.y, - }; -} - -static struct vec2d -vec2d_subtract(struct vec2d a, struct vec2d b) -{ - return (struct vec2d) { - .x = a.x - b.x, - .y = a.y - b.y, - }; -} - -static struct vec2d -vec2d_multiply_constant(double c, struct vec2d a) -{ - return (struct vec2d) { - .x = c * a.x, - .y = c * a.y, - }; -} - -static bool -lines_intersect(struct line *line1, struct line *line2, - struct vec2d *intersection) -{ - struct vec2d p = line1->a; - struct vec2d r = vec2d_subtract(line1->b, line1->a); - struct vec2d q = line2->a; - struct vec2d s = vec2d_subtract(line2->b, line2->a); - double rxs; - double sxr; - double t; - double u; - - /* - * The line (p, r) and (q, s) intersects where - * - * p + t r = q + u s - * - * Calculate t: - * - * (p + t r) × s = (q + u s) × s - * p × s + t (r × s) = q × s + u (s × s) - * p × s + t (r × s) = q × s - * t (r × s) = q × s - p × s - * t (r × s) = (q - p) × s - * t = ((q - p) × s) / (r × s) - * - * Using the same method, for u we get: - * - * u = ((p - q) × r) / (s × r) - */ - - rxs = vec2d_cross_product(r, s); - sxr = vec2d_cross_product(s, r); - - /* If r × s = 0 then the lines are either parallel or collinear. */ - if (fabs(rxs) < DBL_MIN) - return false; - - t = vec2d_cross_product(vec2d_subtract(q, p), s) / rxs; - u = vec2d_cross_product(vec2d_subtract(p, q), r) / sxr; - - /* The lines only intersect if 0 ≤ t ≤ 1 and 0 ≤ u ≤ 1. */ - if (t < 0.0 || t > 1.0 || u < 0.0 || u > 1.0) - return false; - - *intersection = vec2d_add(p, vec2d_multiply_constant(t, r)); - return true; -} - -static struct border * -add_border(struct wl_array *array, - double x1, double y1, - double x2, double y2, - enum motion_direction blocking_dir) -{ - struct border *border = wl_array_add(array, sizeof *border); - - *border = (struct border) { - .line = (struct line) { - .a = (struct vec2d) { - .x = x1, - .y = y1, - }, - .b = (struct vec2d) { - .x = x2, - .y = y2, - }, - }, - .blocking_dir = blocking_dir, - }; - - return border; -} - -static int -compare_lines_x(const void *a, const void *b) -{ - const struct border *border_a = a; - const struct border *border_b = b; - - - if (border_a->line.a.x == border_b->line.a.x) - return border_a->line.b.x < border_b->line.b.x; - else - return border_a->line.a.x > border_b->line.a.x; -} - -static void -add_non_overlapping_edges(pixman_box32_t *boxes, - int band_above_start, - int band_below_start, - int band_below_end, - struct wl_array *borders) -{ - int i; - struct wl_array band_merge; - struct border *border; - struct border *prev_border; - struct border *new_border; - - wl_array_init(&band_merge); - - /* Add bottom band of previous row, and top band of current row, and - * sort them so lower left x coordinate comes first. If there are two - * borders with the same left x coordinate, the wider one comes first. - */ - for (i = band_above_start; i < band_below_start; i++) { - pixman_box32_t *box = &boxes[i]; - add_border(&band_merge, box->x1, box->y2, box->x2, box->y2, - MOTION_DIRECTION_POSITIVE_Y); - } - for (i = band_below_start; i < band_below_end; i++) { - pixman_box32_t *box= &boxes[i]; - add_border(&band_merge, box->x1, box->y1, box->x2, box->y1, - MOTION_DIRECTION_NEGATIVE_Y); - } - qsort(band_merge.data, - band_merge.size / sizeof *border, - sizeof *border, - compare_lines_x); - - /* Combine the two combined bands so that any overlapping border is - * eliminated. */ - prev_border = NULL; - wl_array_for_each(border, &band_merge) { - assert(border->line.a.y == border->line.b.y); - assert(!prev_border || - prev_border->line.a.y == border->line.a.y); - assert(!prev_border || - (prev_border->line.a.x != border->line.a.x || - prev_border->line.b.x != border->line.b.x)); - assert(!prev_border || - prev_border->line.a.x <= border->line.a.x); - - if (prev_border && - prev_border->line.a.x == border->line.a.x) { - /* - * ------------ + - * ------- = - * [ ]----- - */ - prev_border->line.a.x = border->line.b.x; - } else if (prev_border && - prev_border->line.b.x == border->line.b.x) { - /* - * ------------ + - * ------ = - * ------[ ] - */ - prev_border->line.b.x = border->line.a.x; - } else if (prev_border && - prev_border->line.b.x == border->line.a.x) { - /* - * -------- + - * ------ = - * -------------- - */ - prev_border->line.b.x = border->line.b.x; - } else if (prev_border && - prev_border->line.b.x >= border->line.a.x) { - /* - * --------------- + - * ------ = - * -----[ ]---- - */ - new_border = add_border(borders, - border->line.b.x, - border->line.b.y, - prev_border->line.b.x, - prev_border->line.b.y, - prev_border->blocking_dir); - prev_border->line.b.x = border->line.a.x; - prev_border = new_border; - } else { - assert(!prev_border || - prev_border->line.b.x < border->line.a.x); - /* - * First border or non-overlapping. - * - * ----- + - * ----- = - * ----- ----- - */ - new_border = wl_array_add(borders, sizeof *border); - *new_border = *border; - prev_border = new_border; - } - } - - wl_array_release(&band_merge); -} - -static void -add_band_bottom_edges(pixman_box32_t *boxes, - int band_start, - int band_end, - struct wl_array *borders) -{ - int i; - - for (i = band_start; i < band_end; i++) { - add_border(borders, - boxes[i].x1, boxes[i].y2, - boxes[i].x2, boxes[i].y2, - MOTION_DIRECTION_POSITIVE_Y); - } -} - -static void -region_to_outline(pixman_region32_t *region, struct wl_array *borders) -{ - pixman_box32_t *boxes; - int num_boxes; - int i; - int top_most, bottom_most; - int current_roof; - int prev_top; - int band_start, prev_band_start; - - /* - * Remove any overlapping lines from the set of rectangles. Note that - * pixman regions are grouped as rows of rectangles, where rectangles - * in one row never touch or overlap and are all of the same height. - * - * -------- --- -------- --- - * | | | | | | | | - * ----------====---- --- ----------- ----- --- - * | | => | | - * ----==========--------- ----- ---------- - * | | | | - * ------------------- ------------------- - * - */ - - boxes = pixman_region32_rectangles(region, &num_boxes); - prev_top = 0; - top_most = boxes[0].y1; - current_roof = top_most; - bottom_most = boxes[num_boxes - 1].y2; - band_start = 0; - prev_band_start = 0; - for (i = 0; i < num_boxes; i++) { - /* Detect if there is a vertical empty space, and add the lower - * level of the previous band if so was the case. */ - if (i > 0 && - boxes[i].y1 != prev_top && - boxes[i].y1 != boxes[i - 1].y2) { - current_roof = boxes[i].y1; - add_band_bottom_edges(boxes, - band_start, - i, - borders); - } - - /* Special case adding the last band, since it won't be handled - * by the band change detection below. */ - if (boxes[i].y1 != current_roof && i == num_boxes - 1) { - if (boxes[i].y1 != prev_top) { - /* The last band is a single box, so we don't - * have a prev_band_start to tell us when the - * previous band started. */ - add_non_overlapping_edges(boxes, - band_start, - i, - i + 1, - borders); - } else { - add_non_overlapping_edges(boxes, - prev_band_start, - band_start, - i + 1, - borders); - } - } - - /* Detect when passing a band and combine the top border of the - * just passed band with the bottom band of the previous band. - */ - if (boxes[i].y1 != top_most && boxes[i].y1 != prev_top) { - /* Combine the two passed bands. */ - if (prev_top != current_roof) { - add_non_overlapping_edges(boxes, - prev_band_start, - band_start, - i, - borders); - } - - prev_band_start = band_start; - band_start = i; - } - - /* Add the top border if the box is part of the current roof. */ - if (boxes[i].y1 == current_roof) { - add_border(borders, - boxes[i].x1, boxes[i].y1, - boxes[i].x2, boxes[i].y1, - MOTION_DIRECTION_NEGATIVE_Y); - } - - /* Add the bottom border of the last band. */ - if (boxes[i].y2 == bottom_most) { - add_border(borders, - boxes[i].x1, boxes[i].y2, - boxes[i].x2, boxes[i].y2, - MOTION_DIRECTION_POSITIVE_Y); - } - - /* Always add the left border. */ - add_border(borders, - boxes[i].x1, boxes[i].y1, - boxes[i].x1, boxes[i].y2, - MOTION_DIRECTION_NEGATIVE_X); - - /* Always add the right border. */ - add_border(borders, - boxes[i].x2, boxes[i].y1, - boxes[i].x2, boxes[i].y2, - MOTION_DIRECTION_POSITIVE_X); - - prev_top = boxes[i].y1; - } -} - -static bool -is_border_horizontal (struct border *border) -{ - return border->line.a.y == border->line.b.y; -} - -static bool -is_border_blocking_directions(struct border *border, - uint32_t directions) -{ - /* Don't block parallel motions. */ - if (is_border_horizontal(border)) { - if ((directions & (MOTION_DIRECTION_POSITIVE_Y | - MOTION_DIRECTION_NEGATIVE_Y)) == 0) - return false; - } else { - if ((directions & (MOTION_DIRECTION_POSITIVE_X | - MOTION_DIRECTION_NEGATIVE_X)) == 0) - return false; - } - - return (~border->blocking_dir & directions) != directions; -} - -static struct border * -get_closest_border(struct wl_array *borders, - struct line *motion, - uint32_t directions) -{ - struct border *border; - struct vec2d intersection; - struct vec2d delta; - double distance_2; - struct border *closest_border = NULL; - double closest_distance_2 = DBL_MAX; - - wl_array_for_each(border, borders) { - if (!is_border_blocking_directions(border, directions)) - continue; - - if (!lines_intersect(&border->line, motion, &intersection)) - continue; - - delta = vec2d_subtract(intersection, motion->a); - distance_2 = delta.x*delta.x + delta.y*delta.y; - if (distance_2 < closest_distance_2) { - closest_border = border; - closest_distance_2 = distance_2; - } - } - - return closest_border; -} - -static void -clamp_to_border(struct border *border, - struct line *motion, - uint32_t *motion_dir) -{ - /* - * When clamping either rightward or downward motions, the motion needs - * to be clamped so that the destination coordinate does not end up on - * the border (see weston_pointer_clamp_event_to_region). Do this by - * clamping such motions to the border minus the smallest possible - * wl_fixed_t value. - */ - if (is_border_horizontal(border)) { - if (*motion_dir & MOTION_DIRECTION_POSITIVE_Y) - motion->b.y = border->line.a.y - wl_fixed_to_double(1); - else - motion->b.y = border->line.a.y; - *motion_dir &= ~(MOTION_DIRECTION_POSITIVE_Y | - MOTION_DIRECTION_NEGATIVE_Y); - } else { - if (*motion_dir & MOTION_DIRECTION_POSITIVE_X) - motion->b.x = border->line.a.x - wl_fixed_to_double(1); - else - motion->b.x = border->line.a.x; - *motion_dir &= ~(MOTION_DIRECTION_POSITIVE_X | - MOTION_DIRECTION_NEGATIVE_X); - } -} - -static uint32_t -get_motion_directions(struct line *motion) -{ - uint32_t directions = 0; - - if (motion->a.x < motion->b.x) - directions |= MOTION_DIRECTION_POSITIVE_X; - else if (motion->a.x > motion->b.x) - directions |= MOTION_DIRECTION_NEGATIVE_X; - if (motion->a.y < motion->b.y) - directions |= MOTION_DIRECTION_POSITIVE_Y; - else if (motion->a.y > motion->b.y) - directions |= MOTION_DIRECTION_NEGATIVE_Y; - - return directions; -} - -static void -weston_pointer_clamp_event_to_region(struct weston_pointer *pointer, - struct weston_pointer_motion_event *event, - pixman_region32_t *region, - wl_fixed_t *clamped_x, - wl_fixed_t *clamped_y) -{ - wl_fixed_t x, y; - wl_fixed_t sx, sy; - wl_fixed_t old_sx = pointer->sx; - wl_fixed_t old_sy = pointer->sy; - struct wl_array borders; - struct line motion; - struct border *closest_border; - float new_x_f, new_y_f; - uint32_t directions; - - weston_pointer_motion_to_abs(pointer, event, &x, &y); - weston_view_from_global_fixed(pointer->focus, x, y, &sx, &sy); - - wl_array_init(&borders); - - /* - * Generate borders given the confine region we are to use. The borders - * are defined to be the outer region of the allowed area. This means - * top/left borders are "within" the allowed area, while bottom/right - * borders are outside. This needs to be considered when clamping - * confined motion vectors. - */ - region_to_outline(region, &borders); - - motion = (struct line) { - .a = (struct vec2d) { - .x = wl_fixed_to_double(old_sx), - .y = wl_fixed_to_double(old_sy), - }, - .b = (struct vec2d) { - .x = wl_fixed_to_double(sx), - .y = wl_fixed_to_double(sy), - }, - }; - directions = get_motion_directions(&motion); - - while (directions) { - closest_border = get_closest_border(&borders, - &motion, - directions); - if (closest_border) - clamp_to_border(closest_border, &motion, &directions); - else - break; - } - - weston_view_to_global_float(pointer->focus, - (float) motion.b.x, (float) motion.b.y, - &new_x_f, &new_y_f); - *clamped_x = wl_fixed_from_double(new_x_f); - *clamped_y = wl_fixed_from_double(new_y_f); - - wl_array_release(&borders); -} - -static double -point_to_border_distance_2(struct border *border, double x, double y) -{ - double orig_x, orig_y; - double dx, dy; - - if (is_border_horizontal(border)) { - if (x < border->line.a.x) - orig_x = border->line.a.x; - else if (x > border->line.b.x) - orig_x = border->line.b.x; - else - orig_x = x; - orig_y = border->line.a.y; - } else { - if (y < border->line.a.y) - orig_y = border->line.a.y; - else if (y > border->line.b.y) - orig_y = border->line.b.y; - else - orig_y = y; - orig_x = border->line.a.x; - } - - - dx = fabs(orig_x - x); - dy = fabs(orig_y - y); - return dx*dx + dy*dy; -} - -static void -warp_to_behind_border(struct border *border, wl_fixed_t *sx, wl_fixed_t *sy) -{ - switch (border->blocking_dir) { - case MOTION_DIRECTION_POSITIVE_X: - case MOTION_DIRECTION_NEGATIVE_X: - if (border->blocking_dir == MOTION_DIRECTION_POSITIVE_X) - *sx = wl_fixed_from_double(border->line.a.x) - 1; - else - *sx = wl_fixed_from_double(border->line.a.x) + 1; - if (*sy < wl_fixed_from_double(border->line.a.y)) - *sy = wl_fixed_from_double(border->line.a.y) + 1; - else if (*sy > wl_fixed_from_double(border->line.b.y)) - *sy = wl_fixed_from_double(border->line.b.y) - 1; - break; - case MOTION_DIRECTION_POSITIVE_Y: - case MOTION_DIRECTION_NEGATIVE_Y: - if (border->blocking_dir == MOTION_DIRECTION_POSITIVE_Y) - *sy = wl_fixed_from_double(border->line.a.y) - 1; - else - *sy = wl_fixed_from_double(border->line.a.y) + 1; - if (*sx < wl_fixed_from_double(border->line.a.x)) - *sx = wl_fixed_from_double(border->line.a.x) + 1; - else if (*sx > wl_fixed_from_double(border->line.b.x)) - *sx = wl_fixed_from_double(border->line.b.x) - 1; - break; - } -} - -static void -maybe_warp_confined_pointer(struct weston_pointer_constraint *constraint) -{ - wl_fixed_t x; - wl_fixed_t y; - wl_fixed_t sx; - wl_fixed_t sy; - - weston_view_from_global_fixed(constraint->view, - constraint->pointer->x, - constraint->pointer->y, - &sx, - &sy); - - if (!is_within_constraint_region(constraint, sx, sy)) { - double xf = wl_fixed_to_double(sx); - double yf = wl_fixed_to_double(sy); - pixman_region32_t confine_region; - struct wl_array borders; - struct border *border; - double closest_distance_2 = DBL_MAX; - struct border *closest_border = NULL; - - wl_array_init(&borders); - - pixman_region32_init(&confine_region); - pixman_region32_intersect(&confine_region, - &constraint->view->surface->input, - &constraint->region); - region_to_outline(&confine_region, &borders); - pixman_region32_fini(&confine_region); - - wl_array_for_each(border, &borders) { - double distance_2; - - distance_2 = point_to_border_distance_2(border, xf, yf); - if (distance_2 < closest_distance_2) { - closest_border = border; - closest_distance_2 = distance_2; - } - } - assert(closest_border); - - warp_to_behind_border(closest_border, &sx, &sy); - - wl_array_release(&borders); - - weston_view_to_global_fixed(constraint->view, sx, sy, &x, &y); - weston_pointer_move_to(constraint->pointer, x, y); - } -} - -static void -confined_pointer_grab_pointer_motion(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_motion_event *event) -{ - struct weston_pointer_constraint *constraint = - container_of(grab, struct weston_pointer_constraint, grab); - struct weston_pointer *pointer = grab->pointer; - struct weston_surface *surface; - wl_fixed_t x, y; - wl_fixed_t old_sx = pointer->sx; - wl_fixed_t old_sy = pointer->sy; - pixman_region32_t confine_region; - - assert(pointer->focus); - assert(pointer->focus->surface == constraint->surface); - - surface = pointer->focus->surface; - - pixman_region32_init(&confine_region); - pixman_region32_intersect(&confine_region, - &surface->input, - &constraint->region); - weston_pointer_clamp_event_to_region(pointer, event, - &confine_region, &x, &y); - weston_pointer_move_to(pointer, x, y); - pixman_region32_fini(&confine_region); - - weston_view_from_global_fixed(pointer->focus, x, y, - &pointer->sx, &pointer->sy); - - if (old_sx != pointer->sx || old_sy != pointer->sy) { - pointer_send_motion(pointer, time, - pointer->sx, pointer->sy); - } - - pointer_send_relative_motion(pointer, time, event); -} - -static void -confined_pointer_grab_pointer_button(struct weston_pointer_grab *grab, - const struct timespec *time, - uint32_t button, - uint32_t state_w) -{ - weston_pointer_send_button(grab->pointer, time, button, state_w); -} - -static void -confined_pointer_grab_pointer_axis(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_axis_event *event) -{ - weston_pointer_send_axis(grab->pointer, time, event); -} - -static void -confined_pointer_grab_pointer_axis_source(struct weston_pointer_grab *grab, - uint32_t source) -{ - weston_pointer_send_axis_source(grab->pointer, source); -} - -static void -confined_pointer_grab_pointer_frame(struct weston_pointer_grab *grab) -{ - weston_pointer_send_frame(grab->pointer); -} - -static void -confined_pointer_grab_pointer_cancel(struct weston_pointer_grab *grab) -{ - struct weston_pointer_constraint *constraint = - container_of(grab, struct weston_pointer_constraint, grab); - - disable_pointer_constraint(constraint); -} - -static const struct weston_pointer_grab_interface - confined_pointer_grab_interface = { - confined_pointer_grab_pointer_focus, - confined_pointer_grab_pointer_motion, - confined_pointer_grab_pointer_button, - confined_pointer_grab_pointer_axis, - confined_pointer_grab_pointer_axis_source, - confined_pointer_grab_pointer_frame, - confined_pointer_grab_pointer_cancel, -}; - -static void -confined_pointer_destroy(struct wl_client *client, - struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -confined_pointer_set_region(struct wl_client *client, - struct wl_resource *resource, - struct wl_resource *region_resource) -{ - struct weston_pointer_constraint *constraint = - wl_resource_get_user_data(resource); - struct weston_region *region = region_resource ? - wl_resource_get_user_data(region_resource) : NULL; - - if (!constraint) - return; - - if (region) { - pixman_region32_copy(&constraint->region_pending, - ®ion->region); - } else { - pixman_region32_fini(&constraint->region_pending); - region_init_infinite(&constraint->region_pending); - } - constraint->region_is_pending = true; -} - -static const struct zwp_confined_pointer_v1_interface confined_pointer_interface = { - confined_pointer_destroy, - confined_pointer_set_region, -}; - -static void -pointer_constraints_confine_pointer(struct wl_client *client, - struct wl_resource *resource, - uint32_t id, - struct wl_resource *surface_resource, - struct wl_resource *pointer_resource, - struct wl_resource *region_resource, - uint32_t lifetime) -{ - struct weston_surface *surface = - wl_resource_get_user_data(surface_resource); - struct weston_pointer *pointer = wl_resource_get_user_data(pointer_resource); - struct weston_region *region = region_resource ? - wl_resource_get_user_data(region_resource) : NULL; - - init_pointer_constraint(resource, id, surface, pointer, region, lifetime, - &zwp_confined_pointer_v1_interface, - &confined_pointer_interface, - &confined_pointer_grab_interface); -} - -static const struct zwp_pointer_constraints_v1_interface pointer_constraints_interface = { - pointer_constraints_destroy, - pointer_constraints_lock_pointer, - pointer_constraints_confine_pointer, -}; - -static void -bind_pointer_constraints(struct wl_client *client, void *data, - uint32_t version, uint32_t id) -{ - struct wl_resource *resource; - - resource = wl_resource_create(client, - &zwp_pointer_constraints_v1_interface, - 1, id); - - wl_resource_set_implementation(resource, &pointer_constraints_interface, - NULL, NULL); -} - -static void -input_timestamps_destroy(struct wl_client *client, - struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static const struct zwp_input_timestamps_v1_interface - input_timestamps_interface = { - input_timestamps_destroy, -}; - -static void -input_timestamps_manager_destroy(struct wl_client *client, - struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -input_timestamps_manager_get_keyboard_timestamps(struct wl_client *client, - struct wl_resource *resource, - uint32_t id, - struct wl_resource *keyboard_resource) -{ - struct weston_keyboard *keyboard = - wl_resource_get_user_data(keyboard_resource); - struct wl_resource *input_ts; - - input_ts = wl_resource_create(client, - &zwp_input_timestamps_v1_interface, - 1, id); - if (!input_ts) { - wl_client_post_no_memory(client); - return; - } - - if (keyboard) { - wl_list_insert(&keyboard->timestamps_list, - wl_resource_get_link(input_ts)); - } else { - wl_list_init(wl_resource_get_link(input_ts)); - } - - wl_resource_set_implementation(input_ts, - &input_timestamps_interface, - keyboard_resource, - unbind_resource); -} - -static void -input_timestamps_manager_get_pointer_timestamps(struct wl_client *client, - struct wl_resource *resource, - uint32_t id, - struct wl_resource *pointer_resource) -{ - struct weston_pointer *pointer = - wl_resource_get_user_data(pointer_resource); - struct wl_resource *input_ts; - - input_ts = wl_resource_create(client, - &zwp_input_timestamps_v1_interface, - 1, id); - if (!input_ts) { - wl_client_post_no_memory(client); - return; - } - - if (pointer) { - wl_list_insert(&pointer->timestamps_list, - wl_resource_get_link(input_ts)); - } else { - wl_list_init(wl_resource_get_link(input_ts)); - } - - wl_resource_set_implementation(input_ts, - &input_timestamps_interface, - pointer_resource, - unbind_resource); -} - -static void -input_timestamps_manager_get_touch_timestamps(struct wl_client *client, - struct wl_resource *resource, - uint32_t id, - struct wl_resource *touch_resource) -{ - struct weston_touch *touch = wl_resource_get_user_data(touch_resource); - struct wl_resource *input_ts; - - input_ts = wl_resource_create(client, - &zwp_input_timestamps_v1_interface, - 1, id); - if (!input_ts) { - wl_client_post_no_memory(client); - return; - } - - if (touch) { - wl_list_insert(&touch->timestamps_list, - wl_resource_get_link(input_ts)); - } else { - wl_list_init(wl_resource_get_link(input_ts)); - } - - wl_resource_set_implementation(input_ts, - &input_timestamps_interface, - touch_resource, - unbind_resource); -} - -static const struct zwp_input_timestamps_manager_v1_interface - input_timestamps_manager_interface = { - input_timestamps_manager_destroy, - input_timestamps_manager_get_keyboard_timestamps, - input_timestamps_manager_get_pointer_timestamps, - input_timestamps_manager_get_touch_timestamps, -}; - -static void -bind_input_timestamps_manager(struct wl_client *client, void *data, - uint32_t version, uint32_t id) -{ - struct wl_resource *resource = - wl_resource_create(client, - &zwp_input_timestamps_manager_v1_interface, - 1, id); - - if (resource == NULL) { - wl_client_post_no_memory(client); - return; - } - - wl_resource_set_implementation(resource, - &input_timestamps_manager_interface, - NULL, NULL); -} - -int -weston_input_init(struct weston_compositor *compositor) -{ - if (!wl_global_create(compositor->wl_display, - &zwp_relative_pointer_manager_v1_interface, 1, - compositor, bind_relative_pointer_manager)) - return -1; - - if (!wl_global_create(compositor->wl_display, - &zwp_pointer_constraints_v1_interface, 1, - NULL, bind_pointer_constraints)) - return -1; - - if (!wl_global_create(compositor->wl_display, - &zwp_input_timestamps_manager_v1_interface, 1, - NULL, bind_input_timestamps_manager)) - return -1; - - return 0; -} diff --git a/libweston/launcher-direct.c b/libweston/launcher-direct.c deleted file mode 100755 index f83f567..0000000 --- a/libweston/launcher-direct.c +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright © 2012 Benjamin Franzke - * Copyright © 2013 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "launcher-impl.h" - -#define DRM_MAJOR 226 - -#ifndef KDSKBMUTE -#define KDSKBMUTE 0x4B51 -#endif - -/* major()/minor() */ -#ifdef MAJOR_IN_MKDEV -#include -#endif -#ifdef MAJOR_IN_SYSMACROS -#include -#endif - -#ifdef BUILD_DRM_COMPOSITOR - -#include - -static inline int -is_drm_master(int drm_fd) -{ - drm_magic_t magic; - - return drmGetMagic(drm_fd, &magic) == 0 && - drmAuthMagic(drm_fd, magic) == 0; -} - -#else - -static inline int -drmDropMaster(int drm_fd) -{ - return 0; -} - -static inline int -drmSetMaster(int drm_fd) -{ - return 0; -} - -static inline int -is_drm_master(int drm_fd) -{ - return 0; -} - -#endif - -struct launcher_direct { - struct weston_launcher base; - struct weston_compositor *compositor; - int kb_mode, tty, drm_fd; - struct wl_event_source *vt_source; -}; - -static int -vt_handler(int signal_number, void *data) -{ - struct launcher_direct *launcher = data; - struct weston_compositor *compositor = launcher->compositor; - - if (compositor->session_active) { - compositor->session_active = false; - wl_signal_emit(&compositor->session_signal, compositor); - drmDropMaster(launcher->drm_fd); - ioctl(launcher->tty, VT_RELDISP, 1); - } else { - ioctl(launcher->tty, VT_RELDISP, VT_ACKACQ); - drmSetMaster(launcher->drm_fd); - compositor->session_active = true; - wl_signal_emit(&compositor->session_signal, compositor); - } - - return 1; -} - -static int -setup_tty(struct launcher_direct *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: %s\n", - strerror(errno)); - 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: %s\n", tty_device, - strerror(errno)); - 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: %s\n", strerror(errno)); - return -1; - } - if (kd_mode != KD_TEXT) { - weston_log("%s is already in graphics mode, " - "is another display server running?\n", tty_device); - } - - 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: %s\n", - strerror(errno)); - goto err_close; - } - - if (ioctl(launcher->tty, KDSKBMUTE, 1) && - ioctl(launcher->tty, KDSKBMODE, K_OFF)) { - weston_log("failed to set K_OFF keyboard mode: %s\n", - strerror(errno)); - goto err_close; - } - - ret = ioctl(launcher->tty, KDSETMODE, KD_GRAPHICS); - if (ret) { - weston_log("failed to set KD_GRAPHICS mode on tty: %s\n", - strerror(errno)); - goto err_close; - } - - /* - * SIGRTMIN is used as global VT-acquire+release signal. Note that - * SIGRT* must be tested on runtime, as their exact values are not - * known at compile-time. POSIX requires 32 of them to be available. - */ - if (SIGRTMIN > SIGRTMAX) { - weston_log("not enough RT signals available: %u-%u\n", - SIGRTMIN, SIGRTMAX); - ret = -EINVAL; - goto err_close; - } - - mode.mode = VT_PROCESS; - mode.relsig = SIGRTMIN; - mode.acqsig = SIGRTMIN; - 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, SIGRTMIN, vt_handler, launcher); - if (!launcher->vt_source) - goto err_close; - - return 0; - - err_close: - close(launcher->tty); - return -1; -} - -static int -launcher_direct_open(struct weston_launcher *launcher_base, const char *path, int flags) -{ - struct launcher_direct *launcher = wl_container_of(launcher_base, launcher, base); - struct stat s; - int fd; - - fd = open(path, flags | O_CLOEXEC); - if (fd == -1) - return -1; - - if (fstat(fd, &s) == -1) { - close(fd); - return -1; - } - // OHOS - weston_log("launcher_direct_open %s\n", path); - - if (major(s.st_rdev) == DRM_MAJOR) { - launcher->drm_fd = fd; -// OHOS -// if (!is_drm_master(fd)) { -// weston_log("drm fd not master %s\n", path); -// close(fd); -// return -1; -// } - } - - return fd; -} - -static void -launcher_direct_close(struct weston_launcher *launcher_base, int fd) -{ - close(fd); -} - -static void -launcher_direct_restore(struct weston_launcher *launcher_base) -{ - struct launcher_direct *launcher = wl_container_of(launcher_base, launcher, base); - 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: %s\n", - strerror(errno)); - - if (ioctl(launcher->tty, KDSETMODE, KD_TEXT)) - weston_log("failed to set KD_TEXT mode on tty: %s\n", - strerror(errno)); - - /* 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(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 -launcher_direct_activate_vt(struct weston_launcher *launcher_base, int vt) -{ - struct launcher_direct *launcher = wl_container_of(launcher_base, launcher, base); - return ioctl(launcher->tty, VT_ACTIVATE, vt); -} - -static int -launcher_direct_connect(struct weston_launcher **out, struct weston_compositor *compositor, - int tty, const char *seat_id, bool sync_drm) -{ - struct launcher_direct *launcher; - - if (geteuid() != 0) - return -EINVAL; - - launcher = zalloc(sizeof(*launcher)); - if (launcher == NULL) - return -ENOMEM; - - launcher->base.iface = &launcher_direct_iface; - launcher->compositor = compositor; - - if (strcmp("seat0", seat_id) == 0) { - if (setup_tty(launcher, tty) == -1) { - free(launcher); - return -1; - } - } else { - launcher->tty = -1; - } - - * (struct launcher_direct **) out = launcher; - return 0; -} - -static void -launcher_direct_destroy(struct weston_launcher *launcher_base) -{ - struct launcher_direct *launcher = wl_container_of(launcher_base, launcher, base); - - if (launcher->tty >= 0) { - launcher_direct_restore(&launcher->base); - wl_event_source_remove(launcher->vt_source); - close(launcher->tty); - } - - free(launcher); -} - -static int -launcher_direct_get_vt(struct weston_launcher *base) -{ - struct launcher_direct *launcher = wl_container_of(base, launcher, base); - struct stat s; - if (fstat(launcher->tty, &s) < 0) - return -1; - - return minor(s.st_rdev); -} - -const struct launcher_interface launcher_direct_iface = { - .connect = launcher_direct_connect, - .destroy = launcher_direct_destroy, - .open = launcher_direct_open, - .close = launcher_direct_close, - .activate_vt = launcher_direct_activate_vt, - .get_vt = launcher_direct_get_vt, -}; diff --git a/libweston/launcher-impl.h b/libweston/launcher-impl.h deleted file mode 100644 index 4161caf..0000000 --- a/libweston/launcher-impl.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright © 2015 Jasper St. Pierre - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include - -struct weston_launcher; - -struct launcher_interface { - int (* connect) (struct weston_launcher **launcher_out, struct weston_compositor *compositor, - int tty, const char *seat_id, bool sync_drm); - void (* destroy) (struct weston_launcher *launcher); - int (* open) (struct weston_launcher *launcher, const char *path, int flags); - void (* close) (struct weston_launcher *launcher, int fd); - int (* activate_vt) (struct weston_launcher *launcher, int vt); - /* Get the number of the VT weston is running in */ - int (* get_vt) (struct weston_launcher *launcher); -}; - -struct weston_launcher { - const struct launcher_interface *iface; -}; - -extern const struct launcher_interface launcher_logind_iface; -extern const struct launcher_interface launcher_weston_launch_iface; -extern const struct launcher_interface launcher_direct_iface; diff --git a/libweston/launcher-logind.c b/libweston/launcher-logind.c deleted file mode 100644 index 9b3c52f..0000000 --- a/libweston/launcher-logind.c +++ /dev/null @@ -1,856 +0,0 @@ -/* - * Copyright © 2013 David Herrmann - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "backend.h" -#include "dbus.h" -#include "launcher-impl.h" - -#define DRM_MAJOR 226 - -/* major()/minor() */ -#ifdef MAJOR_IN_MKDEV -#include -#endif -#ifdef MAJOR_IN_SYSMACROS -#include -#endif - -struct launcher_logind { - struct weston_launcher base; - struct weston_compositor *compositor; - bool sync_drm; - char *seat; - char *sid; - unsigned int vtnr; - int vt; - int kb_mode; - - DBusConnection *dbus; - struct wl_event_source *dbus_ctx; - char *spath; - DBusPendingCall *pending_active; -}; - -static int -launcher_logind_take_device(struct launcher_logind *wl, uint32_t major, - uint32_t minor, bool *paused_out) -{ - DBusMessage *m, *reply; - bool b; - int r, fd; - dbus_bool_t paused; - - m = dbus_message_new_method_call("org.freedesktop.login1", - wl->spath, - "org.freedesktop.login1.Session", - "TakeDevice"); - if (!m) - return -ENOMEM; - - b = dbus_message_append_args(m, - DBUS_TYPE_UINT32, &major, - DBUS_TYPE_UINT32, &minor, - DBUS_TYPE_INVALID); - if (!b) { - r = -ENOMEM; - goto err_unref; - } - - reply = dbus_connection_send_with_reply_and_block(wl->dbus, m, - -1, NULL); - if (!reply) { - r = -ENODEV; - goto err_unref; - } - - b = dbus_message_get_args(reply, NULL, - DBUS_TYPE_UNIX_FD, &fd, - DBUS_TYPE_BOOLEAN, &paused, - DBUS_TYPE_INVALID); - if (!b) { - r = -ENODEV; - goto err_reply; - } - - r = fd; - if (paused_out) - *paused_out = paused; - -err_reply: - dbus_message_unref(reply); -err_unref: - dbus_message_unref(m); - return r; -} - -static void -launcher_logind_release_device(struct launcher_logind *wl, uint32_t major, - uint32_t minor) -{ - DBusMessage *m; - bool b; - - m = dbus_message_new_method_call("org.freedesktop.login1", - wl->spath, - "org.freedesktop.login1.Session", - "ReleaseDevice"); - if (m) { - b = dbus_message_append_args(m, - DBUS_TYPE_UINT32, &major, - DBUS_TYPE_UINT32, &minor, - DBUS_TYPE_INVALID); - if (b) - dbus_connection_send(wl->dbus, m, NULL); - dbus_message_unref(m); - } -} - -static void -launcher_logind_pause_device_complete(struct launcher_logind *wl, uint32_t major, - uint32_t minor) -{ - DBusMessage *m; - bool b; - - m = dbus_message_new_method_call("org.freedesktop.login1", - wl->spath, - "org.freedesktop.login1.Session", - "PauseDeviceComplete"); - if (m) { - b = dbus_message_append_args(m, - DBUS_TYPE_UINT32, &major, - DBUS_TYPE_UINT32, &minor, - DBUS_TYPE_INVALID); - if (b) - dbus_connection_send(wl->dbus, m, NULL); - dbus_message_unref(m); - } -} - -static int -launcher_logind_open(struct weston_launcher *launcher, const char *path, int flags) -{ - struct launcher_logind *wl = wl_container_of(launcher, wl, base); - struct stat st; - int fl, r, fd; - - r = stat(path, &st); - if (r < 0) - return -1; - if (!S_ISCHR(st.st_mode)) { - errno = ENODEV; - return -1; - } - - fd = launcher_logind_take_device(wl, major(st.st_rdev), - minor(st.st_rdev), NULL); - if (fd < 0) - return fd; - - /* Compared to weston_launcher_open() we cannot specify the open-mode - * directly. Instead, logind passes us an fd with sane default modes. - * For DRM and evdev this means O_RDWR | O_CLOEXEC. If we want - * something else, we need to change it afterwards. We currently - * only support setting O_NONBLOCK. Changing access-modes is not - * possible so accept whatever logind passes us. */ - - fl = fcntl(fd, F_GETFL); - if (fl < 0) { - r = -errno; - goto err_close; - } - - if (flags & O_NONBLOCK) - fl |= O_NONBLOCK; - - r = fcntl(fd, F_SETFL, fl); - if (r < 0) { - r = -errno; - goto err_close; - } - return fd; - -err_close: - close(fd); - launcher_logind_release_device(wl, major(st.st_rdev), - minor(st.st_rdev)); - errno = -r; - return -1; -} - -static void -launcher_logind_close(struct weston_launcher *launcher, int fd) -{ - struct launcher_logind *wl = wl_container_of(launcher, wl, base); - struct stat st; - int r; - - r = fstat(fd, &st); - close(fd); - if (r < 0) { - weston_log("logind: cannot fstat fd: %s\n", strerror(errno)); - return; - } - - if (!S_ISCHR(st.st_mode)) { - weston_log("logind: invalid device passed\n"); - return; - } - - launcher_logind_release_device(wl, major(st.st_rdev), - minor(st.st_rdev)); -} - -static int -launcher_logind_activate_vt(struct weston_launcher *launcher, int vt) -{ - struct launcher_logind *wl = wl_container_of(launcher, wl, base); - DBusMessage *m; - bool b; - int r; - - m = dbus_message_new_method_call("org.freedesktop.login1", - "/org/freedesktop/login1/seat/self", - "org.freedesktop.login1.Seat", - "SwitchTo"); - if (!m) - return -ENOMEM; - - b = dbus_message_append_args(m, - DBUS_TYPE_UINT32, &vt, - DBUS_TYPE_INVALID); - if (!b) { - r = -ENOMEM; - goto err_unref; - } - - dbus_connection_send(wl->dbus, m, NULL); - r = 0; - - err_unref: - dbus_message_unref(m); - return r; -} - -static void -launcher_logind_set_active(struct launcher_logind *wl, bool active) -{ - if (wl->compositor->session_active == active) - return; - - wl->compositor->session_active = active; - - wl_signal_emit(&wl->compositor->session_signal, - wl->compositor); -} - -static void -parse_active(struct launcher_logind *wl, DBusMessage *m, DBusMessageIter *iter) -{ - DBusMessageIter sub; - dbus_bool_t b; - - if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT) - return; - - dbus_message_iter_recurse(iter, &sub); - - if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) - return; - - dbus_message_iter_get_basic(&sub, &b); - - /* If the backend requested DRM master-device synchronization, we only - * wake-up the compositor once the master-device is up and running. For - * other backends, we immediately forward the Active-change event. */ - if (!wl->sync_drm || !b) - launcher_logind_set_active(wl, b); -} - -static void -get_active_cb(DBusPendingCall *pending, void *data) -{ - struct launcher_logind *wl = data; - DBusMessageIter iter; - DBusMessage *m; - int type; - - dbus_pending_call_unref(wl->pending_active); - wl->pending_active = NULL; - - m = dbus_pending_call_steal_reply(pending); - if (!m) - return; - - type = dbus_message_get_type(m); - if (type == DBUS_MESSAGE_TYPE_METHOD_RETURN && - dbus_message_iter_init(m, &iter)) - parse_active(wl, m, &iter); - - dbus_message_unref(m); -} - -static void -launcher_logind_get_active(struct launcher_logind *wl) -{ - DBusPendingCall *pending; - DBusMessage *m; - bool b; - const char *iface, *name; - - m = dbus_message_new_method_call("org.freedesktop.login1", - wl->spath, - "org.freedesktop.DBus.Properties", - "Get"); - if (!m) - return; - - iface = "org.freedesktop.login1.Session"; - name = "Active"; - b = dbus_message_append_args(m, - DBUS_TYPE_STRING, &iface, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_INVALID); - if (!b) - goto err_unref; - - b = dbus_connection_send_with_reply(wl->dbus, m, &pending, -1); - if (!b) - goto err_unref; - - b = dbus_pending_call_set_notify(pending, get_active_cb, wl, NULL); - if (!b) { - dbus_pending_call_cancel(pending); - dbus_pending_call_unref(pending); - goto err_unref; - } - - if (wl->pending_active) { - dbus_pending_call_cancel(wl->pending_active); - dbus_pending_call_unref(wl->pending_active); - } - wl->pending_active = pending; - return; - -err_unref: - dbus_message_unref(m); -} - -static void -disconnected_dbus(struct launcher_logind *wl) -{ - weston_log("logind: dbus connection lost, exiting..\n"); - exit(-1); -} - -static void -session_removed(struct launcher_logind *wl, DBusMessage *m) -{ - const char *name, *obj; - bool r; - - r = dbus_message_get_args(m, NULL, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_OBJECT_PATH, &obj, - DBUS_TYPE_INVALID); - if (!r) { - weston_log("logind: cannot parse SessionRemoved dbus signal\n"); - return; - } - - if (!strcmp(name, wl->sid)) { - weston_log("logind: our session got closed, exiting..\n"); - exit(-1); - } -} - -static void -property_changed(struct launcher_logind *wl, DBusMessage *m) -{ - DBusMessageIter iter, sub, entry; - const char *interface, *name; - - if (!dbus_message_iter_init(m, &iter) || - dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) - goto error; - - dbus_message_iter_get_basic(&iter, &interface); - - if (!dbus_message_iter_next(&iter) || - dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) - goto error; - - dbus_message_iter_recurse(&iter, &sub); - - while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY) { - dbus_message_iter_recurse(&sub, &entry); - - if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) - goto error; - - dbus_message_iter_get_basic(&entry, &name); - if (!dbus_message_iter_next(&entry)) - goto error; - - if (!strcmp(name, "Active")) { - parse_active(wl, m, &entry); - return; - } - - dbus_message_iter_next(&sub); - } - - if (!dbus_message_iter_next(&iter) || - dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) - goto error; - - dbus_message_iter_recurse(&iter, &sub); - - while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) { - dbus_message_iter_get_basic(&sub, &name); - - if (!strcmp(name, "Active")) { - launcher_logind_get_active(wl); - return; - } - - dbus_message_iter_next(&sub); - } - - return; - -error: - weston_log("logind: cannot parse PropertiesChanged dbus signal\n"); -} - -static void -device_paused(struct launcher_logind *wl, DBusMessage *m) -{ - bool r; - const char *type; - uint32_t major, minor; - - r = dbus_message_get_args(m, NULL, - DBUS_TYPE_UINT32, &major, - DBUS_TYPE_UINT32, &minor, - DBUS_TYPE_STRING, &type, - DBUS_TYPE_INVALID); - if (!r) { - weston_log("logind: cannot parse PauseDevice dbus signal\n"); - return; - } - - /* "pause" means synchronous pausing. Acknowledge it unconditionally - * as we support asynchronous device shutdowns, anyway. - * "force" means asynchronous pausing. - * "gone" means the device is gone. We handle it the same as "force" as - * a following udev event will be caught, too. - * - * If it's our main DRM device, tell the compositor to go asleep. */ - - if (!strcmp(type, "pause")) - launcher_logind_pause_device_complete(wl, major, minor); - - if (wl->sync_drm && wl->compositor->backend->device_changed) - wl->compositor->backend->device_changed(wl->compositor, - makedev(major,minor), - false); -} - -static void -device_resumed(struct launcher_logind *wl, DBusMessage *m) -{ - bool r; - uint32_t major, minor; - - r = dbus_message_get_args(m, NULL, - DBUS_TYPE_UINT32, &major, - DBUS_TYPE_UINT32, &minor, - DBUS_TYPE_INVALID); - if (!r) { - weston_log("logind: cannot parse ResumeDevice dbus signal\n"); - return; - } - - /* DeviceResumed messages provide us a new file-descriptor for - * resumed devices. For DRM devices it's the same as before, for evdev - * devices it's a new open-file. As we reopen evdev devices, anyway, - * there is no need for us to handle this event for evdev. For DRM, we - * notify the compositor to wake up. */ - - if (wl->sync_drm && wl->compositor->backend->device_changed) - wl->compositor->backend->device_changed(wl->compositor, - makedev(major,minor), - true); -} - -static DBusHandlerResult -filter_dbus(DBusConnection *c, DBusMessage *m, void *data) -{ - struct launcher_logind *wl = data; - - if (dbus_message_is_signal(m, DBUS_INTERFACE_LOCAL, "Disconnected")) { - disconnected_dbus(wl); - } else if (dbus_message_is_signal(m, "org.freedesktop.login1.Manager", - "SessionRemoved")) { - session_removed(wl, m); - } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.Properties", - "PropertiesChanged")) { - property_changed(wl, m); - } else if (dbus_message_is_signal(m, "org.freedesktop.login1.Session", - "PauseDevice")) { - device_paused(wl, m); - } else if (dbus_message_is_signal(m, "org.freedesktop.login1.Session", - "ResumeDevice")) { - device_resumed(wl, m); - } - - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; -} - -static int -launcher_logind_setup_dbus(struct launcher_logind *wl) -{ - bool b; - int r; - - r = asprintf(&wl->spath, "/org/freedesktop/login1/session/%s", - wl->sid); - if (r < 0) - return -ENOMEM; - - b = dbus_connection_add_filter(wl->dbus, filter_dbus, wl, NULL); - if (!b) { - weston_log("logind: cannot add dbus filter\n"); - r = -ENOMEM; - goto err_spath; - } - - r = weston_dbus_add_match_signal(wl->dbus, - "org.freedesktop.login1", - "org.freedesktop.login1.Manager", - "SessionRemoved", - "/org/freedesktop/login1"); - if (r < 0) { - weston_log("logind: cannot add dbus match\n"); - goto err_spath; - } - - r = weston_dbus_add_match_signal(wl->dbus, - "org.freedesktop.login1", - "org.freedesktop.login1.Session", - "PauseDevice", - wl->spath); - if (r < 0) { - weston_log("logind: cannot add dbus match\n"); - goto err_spath; - } - - r = weston_dbus_add_match_signal(wl->dbus, - "org.freedesktop.login1", - "org.freedesktop.login1.Session", - "ResumeDevice", - wl->spath); - if (r < 0) { - weston_log("logind: cannot add dbus match\n"); - goto err_spath; - } - - r = weston_dbus_add_match_signal(wl->dbus, - "org.freedesktop.login1", - "org.freedesktop.DBus.Properties", - "PropertiesChanged", - wl->spath); - if (r < 0) { - weston_log("logind: cannot add dbus match\n"); - goto err_spath; - } - - return 0; - -err_spath: - /* don't remove any dbus-match as the connection is closed, anyway */ - free(wl->spath); - return r; -} - -static void -launcher_logind_destroy_dbus(struct launcher_logind *wl) -{ - /* don't remove any dbus-match as the connection is closed, anyway */ - free(wl->spath); -} - -static int -launcher_logind_take_control(struct launcher_logind *wl) -{ - DBusError err; - DBusMessage *m, *reply; - dbus_bool_t force; - bool b; - int r; - - dbus_error_init(&err); - - m = dbus_message_new_method_call("org.freedesktop.login1", - wl->spath, - "org.freedesktop.login1.Session", - "TakeControl"); - if (!m) - return -ENOMEM; - - force = false; - b = dbus_message_append_args(m, - DBUS_TYPE_BOOLEAN, &force, - DBUS_TYPE_INVALID); - if (!b) { - r = -ENOMEM; - goto err_unref; - } - - reply = dbus_connection_send_with_reply_and_block(wl->dbus, - m, -1, &err); - if (!reply) { - if (dbus_error_has_name(&err, DBUS_ERROR_UNKNOWN_METHOD)) - weston_log("logind: old systemd version detected\n"); - else - weston_log("logind: cannot take control over session %s\n", wl->sid); - - dbus_error_free(&err); - r = -EIO; - goto err_unref; - } - - dbus_message_unref(reply); - dbus_message_unref(m); - return 0; - -err_unref: - dbus_message_unref(m); - return r; -} - -static void -launcher_logind_release_control(struct launcher_logind *wl) -{ - DBusMessage *m; - - m = dbus_message_new_method_call("org.freedesktop.login1", - wl->spath, - "org.freedesktop.login1.Session", - "ReleaseControl"); - if (m) { - dbus_connection_send(wl->dbus, m, NULL); - dbus_message_unref(m); - } -} - -static int -weston_sd_session_get_vt(const char *sid, unsigned int *out) -{ -#ifdef HAVE_SYSTEMD_LOGIN_209 - return sd_session_get_vt(sid, out); -#else - int r; - char *tty; - - r = sd_session_get_tty(sid, &tty); - if (r < 0) - return r; - - r = sscanf(tty, "tty%u", out); - free(tty); - - if (r != 1) - return -EINVAL; - - return 0; -#endif -} - -static int -launcher_logind_activate(struct launcher_logind *wl) -{ - DBusMessage *m; - - m = dbus_message_new_method_call("org.freedesktop.login1", - wl->spath, - "org.freedesktop.login1.Session", - "Activate"); - if (!m) - return -ENOMEM; - - dbus_connection_send(wl->dbus, m, NULL); - return 0; -} - -static int -launcher_logind_connect(struct weston_launcher **out, struct weston_compositor *compositor, - int tty, const char *seat_id, bool sync_drm) -{ - struct launcher_logind *wl; - struct wl_event_loop *loop; - char *t; - int r; - - wl = zalloc(sizeof(*wl)); - if (wl == NULL) { - r = -ENOMEM; - goto err_out; - } - - wl->base.iface = &launcher_logind_iface; - wl->compositor = compositor; - wl->sync_drm = sync_drm; - - wl->seat = strdup(seat_id); - if (!wl->seat) { - r = -ENOMEM; - goto err_wl; - } - - r = sd_pid_get_session(getpid(), &wl->sid); - if (r < 0) { - weston_log("logind: not running in a systemd session\n"); - goto err_seat; - } - - t = NULL; - r = sd_session_get_seat(wl->sid, &t); - if (r < 0) { - weston_log("logind: failed to get session seat\n"); - free(t); - goto err_session; - } else if (strcmp(seat_id, t)) { - weston_log("logind: weston's seat '%s' differs from session-seat '%s'\n", - seat_id, t); - r = -EINVAL; - free(t); - goto err_session; - } - - r = strcmp(t, "seat0"); - free(t); - if (r == 0) { - r = weston_sd_session_get_vt(wl->sid, &wl->vtnr); - if (r < 0) { - weston_log("logind: session not running on a VT\n"); - goto err_session; - } else if (tty > 0 && wl->vtnr != (unsigned int )tty) { - weston_log("logind: requested VT --tty=%d differs from real session VT %u\n", - tty, wl->vtnr); - r = -EINVAL; - goto err_session; - } - } - - loop = wl_display_get_event_loop(compositor->wl_display); - r = weston_dbus_open(loop, DBUS_BUS_SYSTEM, &wl->dbus, &wl->dbus_ctx); - if (r < 0) { - weston_log("logind: cannot connect to system dbus\n"); - goto err_session; - } - - r = launcher_logind_setup_dbus(wl); - if (r < 0) - goto err_dbus; - - r = launcher_logind_take_control(wl); - if (r < 0) - goto err_dbus_cleanup; - - r = launcher_logind_activate(wl); - if (r < 0) - goto err_dbus_cleanup; - - weston_log("logind: session control granted\n"); - * (struct launcher_logind **) out = wl; - return 0; - -err_dbus_cleanup: - launcher_logind_destroy_dbus(wl); -err_dbus: - weston_dbus_close(wl->dbus, wl->dbus_ctx); -err_session: - free(wl->sid); -err_seat: - free(wl->seat); -err_wl: - free(wl); -err_out: - weston_log("logind: cannot setup systemd-logind helper (%d), using legacy fallback\n", r); - errno = -r; - return -1; -} - -static void -launcher_logind_destroy(struct weston_launcher *launcher) -{ - struct launcher_logind *wl = wl_container_of(launcher, wl, base); - - if (wl->pending_active) { - dbus_pending_call_cancel(wl->pending_active); - dbus_pending_call_unref(wl->pending_active); - } - - launcher_logind_release_control(wl); - launcher_logind_destroy_dbus(wl); - weston_dbus_close(wl->dbus, wl->dbus_ctx); - free(wl->sid); - free(wl->seat); - free(wl); -} - -static int -launcher_logind_get_vt(struct weston_launcher *launcher) -{ - struct launcher_logind *wl = wl_container_of(launcher, wl, base); - return wl->vtnr; -} - -const struct launcher_interface launcher_logind_iface = { - .connect = launcher_logind_connect, - .destroy = launcher_logind_destroy, - .open = launcher_logind_open, - .close = launcher_logind_close, - .activate_vt = launcher_logind_activate_vt, - .get_vt = launcher_logind_get_vt, -}; diff --git a/libweston/launcher-util.c b/libweston/launcher-util.c deleted file mode 100644 index 5cbb0ab..0000000 --- a/libweston/launcher-util.c +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright © 2012 Benjamin Franzke - * Copyright © 2013 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include - -#include "launcher-util.h" -#include "launcher-impl.h" - -#include -#include -#include - -static const struct launcher_interface *ifaces[] = { -#ifdef HAVE_SYSTEMD_LOGIN - &launcher_logind_iface, -#endif - &launcher_weston_launch_iface, - &launcher_direct_iface, - NULL, -}; - -WL_EXPORT struct weston_launcher * -weston_launcher_connect(struct weston_compositor *compositor, int tty, - const char *seat_id, bool sync_drm) -{ - const struct launcher_interface **it; - - for (it = ifaces; *it != NULL; it++) { - const struct launcher_interface *iface = *it; - struct weston_launcher *launcher; - - if (iface->connect(&launcher, compositor, tty, seat_id, sync_drm) == 0) - return launcher; - } - - return NULL; -} - -WL_EXPORT void -weston_launcher_destroy(struct weston_launcher *launcher) -{ - launcher->iface->destroy(launcher); -} - -WL_EXPORT int -weston_launcher_open(struct weston_launcher *launcher, - const char *path, int flags) -{ - return launcher->iface->open(launcher, path, flags); -} - -WL_EXPORT void -weston_launcher_close(struct weston_launcher *launcher, int fd) -{ - launcher->iface->close(launcher, fd); -} - -WL_EXPORT int -weston_launcher_activate_vt(struct weston_launcher *launcher, int vt) -{ - return launcher->iface->activate_vt(launcher, vt); -} - -static void -switch_vt_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data) -{ - struct weston_compositor *compositor = data; - struct weston_launcher *launcher = compositor->launcher; - int vt = key - KEY_F1 + 1; - - if (vt == launcher->iface->get_vt(launcher)) - return; - - weston_launcher_activate_vt(launcher, vt); -} - -WL_EXPORT void -weston_setup_vt_switch_bindings(struct weston_compositor *compositor) -{ - uint32_t key; - struct weston_launcher *launcher = compositor->launcher; - - if (launcher->iface->get_vt(launcher) <= 0) - return; - - if (compositor->vt_switching == false) - return; - - for (key = KEY_F1; key < KEY_F9; key++) - weston_compositor_add_key_binding(compositor, key, - MODIFIER_CTRL | MODIFIER_ALT, - switch_vt_binding, - compositor); -} diff --git a/libweston/launcher-util.h b/libweston/launcher-util.h deleted file mode 100644 index dd7b770..0000000 --- a/libweston/launcher-util.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright © 2012 Benjamin Franzke - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _WESTON_LAUNCHER_UTIL_H_ -#define _WESTON_LAUNCHER_UTIL_H_ - -#include "config.h" - -#include - -struct weston_launcher; - -struct weston_launcher * -weston_launcher_connect(struct weston_compositor *compositor, int tty, - const char *seat_id, bool sync_drm); - -void -weston_launcher_destroy(struct weston_launcher *launcher); - -int -weston_launcher_open(struct weston_launcher *launcher, - const char *path, int flags); - -void -weston_launcher_close(struct weston_launcher *launcher, int fd); - -int -weston_launcher_activate_vt(struct weston_launcher *launcher, int vt); - -void -weston_setup_vt_switch_bindings(struct weston_compositor *compositor); - -#endif diff --git a/libweston/launcher-weston-launch.c b/libweston/launcher-weston-launch.c deleted file mode 100644 index e3b4e1a..0000000 --- a/libweston/launcher-weston-launch.c +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright © 2012 Benjamin Franzke - * Copyright © 2013 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "weston-launch.h" -#include "launcher-impl.h" -#include "shared/string-helpers.h" - -#define DRM_MAJOR 226 - -#ifndef KDSKBMUTE -#define KDSKBMUTE 0x4B51 -#endif - -#ifdef BUILD_DRM_COMPOSITOR - -#include - -#else - -static inline int -drmDropMaster(int drm_fd) -{ - return 0; -} - -static inline int -drmSetMaster(int drm_fd) -{ - return 0; -} - -#endif - -/* major()/minor() */ -#ifdef MAJOR_IN_MKDEV -#include -#endif -#ifdef MAJOR_IN_SYSMACROS -#include -#endif - -union cmsg_data { unsigned char b[4]; int fd; }; - -struct launcher_weston_launch { - struct weston_launcher base; - struct weston_compositor *compositor; - struct wl_event_loop *loop; - int fd; - struct wl_event_source *source; - - int kb_mode, tty, drm_fd; -}; - -static ssize_t -launcher_weston_launch_send(int sockfd, void *buf, size_t buflen) -{ - ssize_t len; - - do { - len = send(sockfd, buf, buflen, 0); - } while (len < 0 && errno == EINTR); - - return len; -} - -static int -launcher_weston_launch_open(struct weston_launcher *launcher_base, - const char *path, int flags) -{ - struct launcher_weston_launch *launcher = wl_container_of(launcher_base, launcher, base); - int n, ret; - 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; - - 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); - - launcher_weston_launch_send(launcher->fd, message, n); - 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; -} - -static void -launcher_weston_launch_close(struct weston_launcher *launcher_base, int fd) -{ - close(fd); -} - -static void -launcher_weston_launch_restore(struct weston_launcher *launcher_base) -{ - struct launcher_weston_launch *launcher = wl_container_of(launcher_base, launcher, base); - 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: %s\n", - strerror(errno)); - - if (ioctl(launcher->tty, KDSETMODE, KD_TEXT)) - weston_log("failed to set KD_TEXT mode on tty: %s\n", - strerror(errno)); - - /* 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(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 -launcher_weston_launch_data(int fd, uint32_t mask, void *data) -{ - struct launcher_weston_launch *launcher = data; - int len, ret, reply; - - 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. */ - launcher_weston_launch_restore(&launcher->base); - 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 = true; - wl_signal_emit(&launcher->compositor->session_signal, - launcher->compositor); - break; - case WESTON_LAUNCHER_DEACTIVATE: - launcher->compositor->session_active = false; - wl_signal_emit(&launcher->compositor->session_signal, - launcher->compositor); - - reply = WESTON_LAUNCHER_DEACTIVATE_DONE; - launcher_weston_launch_send(launcher->fd, &reply, sizeof reply); - - break; - default: - weston_log("unexpected event from weston-launch\n"); - break; - } - - return 1; -} - -static int -launcher_weston_launch_activate_vt(struct weston_launcher *launcher_base, int vt) -{ - struct launcher_weston_launch *launcher = wl_container_of(launcher_base, launcher, base); - return ioctl(launcher->tty, VT_ACTIVATE, vt); -} - -static int -launcher_weston_environment_get_fd(const char *env) -{ - char *e; - int fd, flags; - - e = getenv(env); - if (!e || !safe_strtoint(e, &fd)) - return -1; - - flags = fcntl(fd, F_GETFD); - if (flags == -1) - return -1; - - fcntl(fd, F_SETFD, flags | FD_CLOEXEC); - unsetenv(env); - - return fd; -} - - -static int -launcher_weston_launch_connect(struct weston_launcher **out, struct weston_compositor *compositor, - int tty, const char *seat_id, bool sync_drm) -{ - struct launcher_weston_launch *launcher; - struct wl_event_loop *loop; - - launcher = malloc(sizeof *launcher); - if (launcher == NULL) - return -ENOMEM; - - launcher->base.iface = &launcher_weston_launch_iface; - * (struct launcher_weston_launch **) out = launcher; - launcher->compositor = compositor; - launcher->drm_fd = -1; - launcher->fd = launcher_weston_environment_get_fd("WESTON_LAUNCHER_SOCK"); - if (launcher->fd != -1) { - launcher->tty = launcher_weston_environment_get_fd("WESTON_TTY_FD"); - /* We don't get a chance to read out the original kb - * mode for the tty, so just hard code K_UNICODE here - * in case we have to clean if weston-launch dies. */ - launcher->kb_mode = K_UNICODE; - - loop = wl_display_get_event_loop(compositor->wl_display); - launcher->source = wl_event_loop_add_fd(loop, launcher->fd, - WL_EVENT_READABLE, - launcher_weston_launch_data, - launcher); - if (launcher->source == NULL) { - free(launcher); - return -ENOMEM; - } - - return 0; - } else { - return -1; - } -} - -static void -launcher_weston_launch_destroy(struct weston_launcher *launcher_base) -{ - struct launcher_weston_launch *launcher = wl_container_of(launcher_base, launcher, base); - - if (launcher->fd != -1) { - close(launcher->fd); - wl_event_source_remove(launcher->source); - } else { - launcher_weston_launch_restore(&launcher->base); - } - - if (launcher->tty >= 0) - close(launcher->tty); - - free(launcher); -} - -static int -launcher_weston_launch_get_vt(struct weston_launcher *base) -{ - struct launcher_weston_launch *launcher = wl_container_of(base, launcher, base); - struct stat s; - if (fstat(launcher->tty, &s) < 0) - return -1; - - return minor(s.st_rdev); -} - -const struct launcher_interface launcher_weston_launch_iface = { - .connect = launcher_weston_launch_connect, - .destroy = launcher_weston_launch_destroy, - .open = launcher_weston_launch_open, - .close = launcher_weston_launch_close, - .activate_vt = launcher_weston_launch_activate_vt, - .get_vt = launcher_weston_launch_get_vt, -}; diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c deleted file mode 100644 index 9a084cb..0000000 --- a/libweston/libinput-device.c +++ /dev/null @@ -1,761 +0,0 @@ -/* - * Copyright © 2010 Intel Corporation - * Copyright © 2013 Jonas Ådahl - * Copyright 2017-2018 Collabora, Ltd. - * Copyright 2017-2018 General Electric Company - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "backend.h" -#include "libweston-internal.h" -#include "libinput-device.h" -#include "shared/helpers.h" -#include "shared/timespec-util.h" - -void -evdev_led_update(struct evdev_device *device, enum weston_led weston_leds) -{ - enum libinput_led leds = 0; - - if (weston_leds & LED_NUM_LOCK) - leds |= LIBINPUT_LED_NUM_LOCK; - if (weston_leds & LED_CAPS_LOCK) - leds |= LIBINPUT_LED_CAPS_LOCK; - if (weston_leds & LED_SCROLL_LOCK) - leds |= LIBINPUT_LED_SCROLL_LOCK; - - libinput_device_led_update(device->device, leds); -} - -static void -handle_keyboard_key(struct libinput_device *libinput_device, - struct libinput_event_keyboard *keyboard_event) -{ - struct evdev_device *device = - libinput_device_get_user_data(libinput_device); - int key_state = - libinput_event_keyboard_get_key_state(keyboard_event); - int seat_key_count = - libinput_event_keyboard_get_seat_key_count(keyboard_event); - struct timespec time; - - /* Ignore key events that are not seat wide state changes. */ - if ((key_state == LIBINPUT_KEY_STATE_PRESSED && - seat_key_count != 1) || - (key_state == LIBINPUT_KEY_STATE_RELEASED && - seat_key_count != 0)) - return; - - timespec_from_usec(&time, - libinput_event_keyboard_get_time_usec(keyboard_event)); - - notify_key(device->seat, &time, - libinput_event_keyboard_get_key(keyboard_event), - key_state, STATE_UPDATE_AUTOMATIC); -} - -static bool -handle_pointer_motion(struct libinput_device *libinput_device, - struct libinput_event_pointer *pointer_event) -{ - struct evdev_device *device = - libinput_device_get_user_data(libinput_device); - struct weston_pointer_motion_event event = { 0 }; - struct timespec time; - double dx_unaccel, dy_unaccel; - - timespec_from_usec(&time, - libinput_event_pointer_get_time_usec(pointer_event)); - dx_unaccel = libinput_event_pointer_get_dx_unaccelerated(pointer_event); - dy_unaccel = libinput_event_pointer_get_dy_unaccelerated(pointer_event); - - event = (struct weston_pointer_motion_event) { - .mask = WESTON_POINTER_MOTION_REL | - WESTON_POINTER_MOTION_REL_UNACCEL, - .time = time, - .dx = libinput_event_pointer_get_dx(pointer_event), - .dy = libinput_event_pointer_get_dy(pointer_event), - .dx_unaccel = dx_unaccel, - .dy_unaccel = dy_unaccel, - }; - - notify_motion(device->seat, &time, &event); - - return true; -} - -static bool -handle_pointer_motion_absolute( - struct libinput_device *libinput_device, - struct libinput_event_pointer *pointer_event) -{ - struct evdev_device *device = - libinput_device_get_user_data(libinput_device); - struct weston_output *output = device->output; - struct timespec time; - double x, y; - uint32_t width, height; - - if (!output) - return false; - - timespec_from_usec(&time, - libinput_event_pointer_get_time_usec(pointer_event)); - width = device->output->current_mode->width; - height = device->output->current_mode->height; - - x = libinput_event_pointer_get_absolute_x_transformed(pointer_event, - width); - y = libinput_event_pointer_get_absolute_y_transformed(pointer_event, - height); - - weston_output_transform_coordinate(device->output, x, y, &x, &y); - notify_motion_absolute(device->seat, &time, x, y); - - return true; -} - -static bool -handle_pointer_button(struct libinput_device *libinput_device, - struct libinput_event_pointer *pointer_event) -{ - struct evdev_device *device = - libinput_device_get_user_data(libinput_device); - int button_state = - libinput_event_pointer_get_button_state(pointer_event); - int seat_button_count = - libinput_event_pointer_get_seat_button_count(pointer_event); - struct timespec time; - - /* Ignore button events that are not seat wide state changes. */ - if ((button_state == LIBINPUT_BUTTON_STATE_PRESSED && - seat_button_count != 1) || - (button_state == LIBINPUT_BUTTON_STATE_RELEASED && - seat_button_count != 0)) - return false; - - timespec_from_usec(&time, - libinput_event_pointer_get_time_usec(pointer_event)); - - notify_button(device->seat, &time, - libinput_event_pointer_get_button(pointer_event), - button_state); - - return true; -} - -static double -normalize_scroll(struct libinput_event_pointer *pointer_event, - enum libinput_pointer_axis axis) -{ - enum libinput_pointer_axis_source source; - double value = 0.0; - - source = libinput_event_pointer_get_axis_source(pointer_event); - /* libinput < 0.8 sent wheel click events with value 10. Since 0.8 - the value is the angle of the click in degrees. To keep - backwards-compat with existing clients, we just send multiples of - the click count. - */ - switch (source) { - case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL: - value = 10 * libinput_event_pointer_get_axis_value_discrete( - pointer_event, - axis); - break; - case LIBINPUT_POINTER_AXIS_SOURCE_FINGER: - case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS: - value = libinput_event_pointer_get_axis_value(pointer_event, - axis); - break; - default: - assert(!"unhandled event source in normalize_scroll"); - } - - return value; -} - -static int32_t -get_axis_discrete(struct libinput_event_pointer *pointer_event, - enum libinput_pointer_axis axis) -{ - enum libinput_pointer_axis_source source; - - source = libinput_event_pointer_get_axis_source(pointer_event); - - if (source != LIBINPUT_POINTER_AXIS_SOURCE_WHEEL) - return 0; - - return libinput_event_pointer_get_axis_value_discrete(pointer_event, - axis); -} - -static bool -handle_pointer_axis(struct libinput_device *libinput_device, - struct libinput_event_pointer *pointer_event) -{ - static int warned; - struct evdev_device *device = - libinput_device_get_user_data(libinput_device); - double vert, horiz; - int32_t vert_discrete, horiz_discrete; - enum libinput_pointer_axis axis; - struct weston_pointer_axis_event weston_event; - enum libinput_pointer_axis_source source; - uint32_t wl_axis_source; - bool has_vert, has_horiz; - struct timespec time; - - has_vert = libinput_event_pointer_has_axis(pointer_event, - LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); - has_horiz = libinput_event_pointer_has_axis(pointer_event, - LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL); - - if (!has_vert && !has_horiz) - return false; - - source = libinput_event_pointer_get_axis_source(pointer_event); - switch (source) { - case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL: - wl_axis_source = WL_POINTER_AXIS_SOURCE_WHEEL; - break; - case LIBINPUT_POINTER_AXIS_SOURCE_FINGER: - wl_axis_source = WL_POINTER_AXIS_SOURCE_FINGER; - break; - case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS: - wl_axis_source = WL_POINTER_AXIS_SOURCE_CONTINUOUS; - break; - default: - if (warned < 5) { - weston_log("Unknown scroll source %d.\n", source); - warned++; - } - return false; - } - - notify_axis_source(device->seat, wl_axis_source); - - timespec_from_usec(&time, - libinput_event_pointer_get_time_usec(pointer_event)); - - if (has_vert) { - axis = LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL; - vert_discrete = get_axis_discrete(pointer_event, axis); - vert = normalize_scroll(pointer_event, axis); - - weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; - weston_event.value = vert; - weston_event.discrete = vert_discrete; - weston_event.has_discrete = (vert_discrete != 0); - - notify_axis(device->seat, &time, &weston_event); - } - - if (has_horiz) { - axis = LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL; - horiz_discrete = get_axis_discrete(pointer_event, axis); - horiz = normalize_scroll(pointer_event, axis); - - weston_event.axis = WL_POINTER_AXIS_HORIZONTAL_SCROLL; - weston_event.value = horiz; - weston_event.discrete = horiz_discrete; - weston_event.has_discrete = (horiz_discrete != 0); - - notify_axis(device->seat, &time, &weston_event); - } - - return true; -} - -static struct weston_output * -touch_get_output(struct weston_touch_device *device) -{ - struct evdev_device *evdev_device = device->backend_data; - - return evdev_device->output; -} - -static const char * -touch_get_calibration_head_name(struct weston_touch_device *device) -{ - struct evdev_device *evdev_device = device->backend_data; - struct weston_output *output = evdev_device->output; - struct weston_head *head; - - if (!output) - return NULL; - - assert(output->enabled); - if (evdev_device->output_name) - return evdev_device->output_name; - - /* No specific head was configured, so the association was made by - * the default rule. Just grab whatever head's name. - */ - wl_list_for_each(head, &output->head_list, output_link) - return head->name; - - assert(0); - return NULL; -} - -static void -touch_get_calibration(struct weston_touch_device *device, - struct weston_touch_device_matrix *cal) -{ - struct evdev_device *evdev_device = device->backend_data; - - libinput_device_config_calibration_get_matrix(evdev_device->device, - cal->m); -} - -static void -do_set_calibration(struct evdev_device *evdev_device, - const struct weston_touch_device_matrix *cal) -{ - enum libinput_config_status status; - - weston_log("input device %s: applying calibration:\n", - libinput_device_get_sysname(evdev_device->device)); - weston_log_continue(STAMP_SPACE " %f %f %f\n", - cal->m[0], cal->m[1], cal->m[2]); - weston_log_continue(STAMP_SPACE " %f %f %f\n", - cal->m[3], cal->m[4], cal->m[5]); - - status = libinput_device_config_calibration_set_matrix(evdev_device->device, - cal->m); - if (status != LIBINPUT_CONFIG_STATUS_SUCCESS) - weston_log("Error: Failed to apply calibration.\n"); -} - -static void -touch_set_calibration(struct weston_touch_device *device, - const struct weston_touch_device_matrix *cal) -{ - struct evdev_device *evdev_device = device->backend_data; - - /* Stop output hotplug from reloading the WL_CALIBRATION values. - * libinput will maintain the latest calibration for us. - */ - evdev_device->override_wl_calibration = true; - - do_set_calibration(evdev_device, cal); -} - -static const struct weston_touch_device_ops touch_calibration_ops = { - .get_output = touch_get_output, - .get_calibration_head_name = touch_get_calibration_head_name, - .get_calibration = touch_get_calibration, - .set_calibration = touch_set_calibration -}; - -static struct weston_touch_device * -create_touch_device(struct evdev_device *device) -{ - const struct weston_touch_device_ops *ops = NULL; - struct weston_touch_device *touch_device; - struct udev_device *udev_device; - - if (libinput_device_config_calibration_has_matrix(device->device)) - ops = &touch_calibration_ops; - - udev_device = libinput_device_get_udev_device(device->device); - if (!udev_device) - return NULL; - - touch_device = weston_touch_create_touch_device(device->seat->touch_state, - udev_device_get_syspath(udev_device), - device, ops); - - udev_device_unref(udev_device); - - if (!touch_device) - return NULL; - - weston_log("Touchscreen - %s - %s\n", - libinput_device_get_name(device->device), - touch_device->syspath); - - return touch_device; -} - -static void -handle_touch_with_coords(struct libinput_device *libinput_device, - struct libinput_event_touch *touch_event, - int touch_type) -{ - struct evdev_device *device = - libinput_device_get_user_data(libinput_device); - double x; - double y; - struct weston_point2d_device_normalized norm; - uint32_t width, height; - struct timespec time; - int32_t slot; - - if (!device->output) - return; - - timespec_from_usec(&time, - libinput_event_touch_get_time_usec(touch_event)); - slot = libinput_event_touch_get_seat_slot(touch_event); - - width = device->output->current_mode->width; - height = device->output->current_mode->height; - x = libinput_event_touch_get_x_transformed(touch_event, width); - y = libinput_event_touch_get_y_transformed(touch_event, height); - - weston_output_transform_coordinate(device->output, - x, y, &x, &y); - - if (weston_touch_device_can_calibrate(device->touch_device)) { - norm.x = libinput_event_touch_get_x_transformed(touch_event, 1); - norm.y = libinput_event_touch_get_y_transformed(touch_event, 1); - notify_touch_normalized(device->touch_device, &time, slot, - x, y, &norm, touch_type); - } else { - notify_touch(device->touch_device, &time, slot, x, y, touch_type); - } -} - -static void -handle_touch_down(struct libinput_device *device, - struct libinput_event_touch *touch_event) -{ - handle_touch_with_coords(device, touch_event, WL_TOUCH_DOWN); -} - -static void -handle_touch_motion(struct libinput_device *device, - struct libinput_event_touch *touch_event) -{ - handle_touch_with_coords(device, touch_event, WL_TOUCH_MOTION); -} - -static void -handle_touch_up(struct libinput_device *libinput_device, - struct libinput_event_touch *touch_event) -{ - struct evdev_device *device = - libinput_device_get_user_data(libinput_device); - struct timespec time; - int32_t slot = libinput_event_touch_get_seat_slot(touch_event); - - timespec_from_usec(&time, - libinput_event_touch_get_time_usec(touch_event)); - - notify_touch(device->touch_device, &time, slot, 0, 0, WL_TOUCH_UP); -} - -static void -handle_touch_frame(struct libinput_device *libinput_device, - struct libinput_event_touch *touch_event) -{ - struct evdev_device *device = - libinput_device_get_user_data(libinput_device); - - notify_touch_frame(device->touch_device); -} - -int -evdev_device_process_event(struct libinput_event *event) -{ - struct libinput_device *libinput_device = - libinput_event_get_device(event); - struct evdev_device *device = - libinput_device_get_user_data(libinput_device); - int handled = 1; - bool need_frame = false; - - switch (libinput_event_get_type(event)) { - case LIBINPUT_EVENT_KEYBOARD_KEY: - handle_keyboard_key(libinput_device, - libinput_event_get_keyboard_event(event)); - break; - case LIBINPUT_EVENT_POINTER_MOTION: - need_frame = handle_pointer_motion(libinput_device, - libinput_event_get_pointer_event(event)); - break; - case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: - need_frame = handle_pointer_motion_absolute( - libinput_device, - libinput_event_get_pointer_event(event)); - break; - case LIBINPUT_EVENT_POINTER_BUTTON: - need_frame = handle_pointer_button(libinput_device, - libinput_event_get_pointer_event(event)); - break; - case LIBINPUT_EVENT_POINTER_AXIS: - need_frame = handle_pointer_axis( - libinput_device, - libinput_event_get_pointer_event(event)); - break; - case LIBINPUT_EVENT_TOUCH_DOWN: - handle_touch_down(libinput_device, - libinput_event_get_touch_event(event)); - break; - case LIBINPUT_EVENT_TOUCH_MOTION: - handle_touch_motion(libinput_device, - libinput_event_get_touch_event(event)); - break; - case LIBINPUT_EVENT_TOUCH_UP: - handle_touch_up(libinput_device, - libinput_event_get_touch_event(event)); - break; - case LIBINPUT_EVENT_TOUCH_FRAME: - handle_touch_frame(libinput_device, - libinput_event_get_touch_event(event)); - break; - default: - handled = 0; - weston_log("unknown libinput event %d\n", - libinput_event_get_type(event)); - } - - if (need_frame) - notify_pointer_frame(device->seat); - - return handled; -} - -static void -notify_output_destroy(struct wl_listener *listener, void *data) -{ - struct evdev_device *device = - container_of(listener, - struct evdev_device, output_destroy_listener); - - evdev_device_set_output(device, NULL); -} - -/** - * The WL_CALIBRATION property requires a pixel-specific matrix to be - * applied after scaling device coordinates to screen coordinates. libinput - * can't do that, so we need to convert the calibration to the normalized - * format libinput expects. - */ -void -evdev_device_set_calibration(struct evdev_device *device) -{ - struct udev *udev; - struct udev_device *udev_device = NULL; - const char *sysname = libinput_device_get_sysname(device->device); - const char *calibration_values; - uint32_t width, height; - struct weston_touch_device_matrix calibration; - - if (!libinput_device_config_calibration_has_matrix(device->device)) - return; - - /* If LIBINPUT_CALIBRATION_MATRIX was set to non-identity, we will not - * override it with WL_CALIBRATION. It also means we don't need an - * output to load a calibration. */ - if (libinput_device_config_calibration_get_default_matrix( - device->device, - calibration.m) != 0) - return; - - /* touch_set_calibration() has updated the values, do not load old - * values from WL_CALIBRATION. - */ - if (device->override_wl_calibration) - return; - - if (!device->output) { - weston_log("input device %s has no enabled output associated " - "(%s named), skipping calibration for now.\n", - sysname, device->output_name ?: "none"); - return; - } - - width = device->output->width; - height = device->output->height; - if (width == 0 || height == 0) - return; - - udev = udev_new(); - if (!udev) - return; - - udev_device = udev_device_new_from_subsystem_sysname(udev, - "input", - sysname); - if (!udev_device) - goto out; - - calibration_values = - udev_device_get_property_value(udev_device, - "WL_CALIBRATION"); - - if (calibration_values) { - weston_log("Warning: input device %s has WL_CALIBRATION property set. " - "Support for it will be removed in the future. " - "Please use LIBINPUT_CALIBRATION_MATRIX instead.\n", - sysname); - } - - if (!calibration_values || sscanf(calibration_values, - "%f %f %f %f %f %f", - &calibration.m[0], - &calibration.m[1], - &calibration.m[2], - &calibration.m[3], - &calibration.m[4], - &calibration.m[5]) != 6) - goto out; - - /* normalize to a format libinput can use. There is a chance of - this being wrong if the width/height don't match the device - width/height but I'm not sure how to fix that */ - calibration.m[2] /= width; - calibration.m[5] /= height; - - do_set_calibration(device, &calibration); - - weston_log_continue(STAMP_SPACE " raw translation %f %f for output %s\n", - calibration.m[2] * width, - calibration.m[5] * height, - device->output->name); - -out: - if (udev_device) - udev_device_unref(udev_device); - udev_unref(udev); -} - -void -evdev_device_set_output(struct evdev_device *device, - struct weston_output *output) -{ - if (device->output == output) - return; - - if (device->output_destroy_listener.notify) { - wl_list_remove(&device->output_destroy_listener.link); - device->output_destroy_listener.notify = NULL; - } - - if (!output) { - weston_log("output for input device %s removed\n", - libinput_device_get_sysname(device->device)); - - device->output = NULL; - return; - } - - weston_log("associating input device %s with output %s " - "(%s by udev)\n", - libinput_device_get_sysname(device->device), - output->name, - device->output_name ?: "none"); - - device->output = output; - device->output_destroy_listener.notify = notify_output_destroy; - wl_signal_add(&output->destroy_signal, - &device->output_destroy_listener); - evdev_device_set_calibration(device); -} - -struct evdev_device * -evdev_device_create(struct libinput_device *libinput_device, - struct weston_seat *seat) -{ - struct evdev_device *device; - - device = zalloc(sizeof *device); - if (device == NULL) - return NULL; - - device->seat = seat; - wl_list_init(&device->link); - device->device = libinput_device; - - if (libinput_device_has_capability(libinput_device, - LIBINPUT_DEVICE_CAP_KEYBOARD)) { - weston_seat_init_keyboard(seat, NULL); - device->seat_caps |= EVDEV_SEAT_KEYBOARD; - } - if (libinput_device_has_capability(libinput_device, - LIBINPUT_DEVICE_CAP_POINTER)) { - weston_seat_init_pointer(seat); - device->seat_caps |= EVDEV_SEAT_POINTER; - } - if (libinput_device_has_capability(libinput_device, - LIBINPUT_DEVICE_CAP_TOUCH)) { - weston_seat_init_touch(seat); - device->seat_caps |= EVDEV_SEAT_TOUCH; - device->touch_device = create_touch_device(device); - } - - libinput_device_set_user_data(libinput_device, device); - libinput_device_ref(libinput_device); - - return device; -} - -void -evdev_device_destroy(struct evdev_device *device) -{ - 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_touch_device_destroy(device->touch_device); - weston_seat_release_touch(device->seat); - } - - if (device->output) - wl_list_remove(&device->output_destroy_listener.link); - wl_list_remove(&device->link); - libinput_device_unref(device->device); - free(device->output_name); - free(device); -} - -void -evdev_notify_keyboard_focus(struct weston_seat *seat, - struct wl_list *evdev_devices) -{ - struct wl_array keys; - - if (seat->keyboard_device_count == 0) - return; - - wl_array_init(&keys); - notify_keyboard_focus_in(seat, &keys, STATE_UPDATE_AUTOMATIC); - wl_array_release(&keys); -} diff --git a/libweston/libinput-device.h b/libweston/libinput-device.h deleted file mode 100644 index d3fc645..0000000 --- a/libweston/libinput-device.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright © 2011, 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _LIBINPUT_DEVICE_H_ -#define _LIBINPUT_DEVICE_H_ - -#include "config.h" - -#include -#include -#include -#include - -#include - -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; - enum evdev_device_seat_capability seat_caps; - struct libinput_device *device; - struct weston_touch_device *touch_device; - struct wl_list link; - struct weston_output *output; - struct wl_listener output_destroy_listener; - char *output_name; - int fd; - bool override_wl_calibration; -}; - -void -evdev_led_update(struct evdev_device *device, enum weston_led leds); - -struct evdev_device * -evdev_device_create(struct libinput_device *libinput_device, - struct weston_seat *seat); - -int -evdev_device_process_event(struct libinput_event *event); - -void -evdev_device_set_output(struct evdev_device *device, - struct weston_output *output); -void -evdev_device_destroy(struct evdev_device *device); - -void -evdev_notify_keyboard_focus(struct weston_seat *seat, - struct wl_list *evdev_devices); -void -evdev_device_set_calibration(struct evdev_device *device); - -int -dispatch_libinput(struct libinput *libinput); - -#endif /* _LIBINPUT_DEVICE_H_ */ diff --git a/libweston/libinput-seat.c b/libweston/libinput-seat.c deleted file mode 100644 index 656f5c4..0000000 --- a/libweston/libinput-seat.c +++ /dev/null @@ -1,490 +0,0 @@ -/* - * Copyright © 2013 Intel Corporation - * Copyright © 2013 Jonas Ådahl - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include "backend.h" -#include "libweston-internal.h" -// OHOS remove logger -//#include "weston-log-internal.h" -#include "launcher-util.h" -#include "libinput-seat.h" -#include "libinput-device.h" -#include "shared/helpers.h" - -static void -process_events(struct udev_input *input); -static struct udev_seat * -udev_seat_create(struct udev_input *input, const char *seat_name); -static void -udev_seat_destroy(struct udev_seat *seat); - -static struct udev_seat * -get_udev_seat(struct udev_input *input, struct libinput_device *device) -{ - struct libinput_seat *libinput_seat; - const char *seat_name; - - libinput_seat = libinput_device_get_seat(device); - seat_name = libinput_seat_get_logical_name(libinput_seat); - return udev_seat_get_named(input, seat_name); -} - -static struct weston_output * -output_find_by_head_name(struct weston_compositor *compositor, - const char *head_name) -{ - struct weston_output *output; - struct weston_head *head; - - if (!head_name) - return NULL; - - /* Only enabled outputs with connected heads. - * This means force-enabled outputs but with disconnected heads - * will be ignored; if the touchscreen doesn't have a video signal, - * touching it is meaningless. - */ - wl_list_for_each(output, &compositor->output_list, link) { - wl_list_for_each(head, &output->head_list, output_link) { - if (!weston_head_is_connected(head)) - continue; - - if (strcmp(head_name, head->name) == 0) - return output; - } - } - - return NULL; -} - -static void -device_added(struct udev_input *input, struct libinput_device *libinput_device) -{ - struct weston_compositor *c; - struct evdev_device *device; - struct weston_output *output; - const char *output_name; - struct weston_seat *seat; - struct udev_seat *udev_seat; - struct weston_pointer *pointer; - - c = input->compositor; - - udev_seat = get_udev_seat(input, libinput_device); - if (!udev_seat) - return; - - seat = &udev_seat->base; - device = evdev_device_create(libinput_device, seat); - if (device == NULL) - return; - - if (input->configure_device != NULL) - input->configure_device(c, device->device); - evdev_device_set_calibration(device); - udev_seat = (struct udev_seat *) seat; - wl_list_insert(udev_seat->devices_list.prev, &device->link); - - pointer = weston_seat_get_pointer(seat); - if (seat->output && pointer) - weston_pointer_clamp(pointer, - &pointer->x, - &pointer->y); - - output_name = libinput_device_get_output_name(libinput_device); - if (output_name) { - device->output_name = strdup(output_name); - output = output_find_by_head_name(c, output_name); - evdev_device_set_output(device, output); - } else if (!wl_list_empty(&c->output_list)) { - /* default assignment to an arbitrary output */ - output = container_of(c->output_list.next, - struct weston_output, link); - evdev_device_set_output(device, output); - } - - if (!input->suspended) - weston_seat_repick(seat); -} - -static void -device_removed(struct udev_input *input, struct libinput_device *libinput_device) -{ - struct evdev_device *device; - - device = libinput_device_get_user_data(libinput_device); - evdev_device_destroy(device); -} - -static void -udev_seat_remove_devices(struct udev_seat *seat) -{ - struct evdev_device *device, *next; - - wl_list_for_each_safe(device, next, &seat->devices_list, link) { - evdev_device_destroy(device); - } -} - -void -udev_input_disable(struct udev_input *input) -{ - if (input->suspended) - return; - - wl_event_source_remove(input->libinput_source); - input->libinput_source = NULL; - libinput_suspend(input->libinput); - process_events(input); - input->suspended = 1; -} - -static int -udev_input_process_event(struct libinput_event *event) -{ - struct libinput *libinput = libinput_event_get_context(event); - struct libinput_device *libinput_device = - libinput_event_get_device(event); - struct udev_input *input = libinput_get_user_data(libinput); - int handled = 1; - - switch (libinput_event_get_type(event)) { - case LIBINPUT_EVENT_DEVICE_ADDED: - device_added(input, libinput_device); - break; - case LIBINPUT_EVENT_DEVICE_REMOVED: - device_removed(input, libinput_device); - break; - default: - handled = 0; - } - - return handled; -} - -static void -process_event(struct libinput_event *event) -{ - if (udev_input_process_event(event)) - return; - if (evdev_device_process_event(event)) - return; -} - -static void -process_events(struct udev_input *input) -{ - struct libinput_event *event; - - while ((event = libinput_get_event(input->libinput))) { - process_event(event); - libinput_event_destroy(event); - } -} - -static int -udev_input_dispatch(struct udev_input *input) -{ - if (libinput_dispatch(input->libinput) != 0) - weston_log("libinput: Failed to dispatch libinput\n"); - - process_events(input); - - return 0; -} - -static int -libinput_source_dispatch(int fd, uint32_t mask, void *data) -{ - struct udev_input *input = data; - - return udev_input_dispatch(input) != 0; -} - -static int -open_restricted(const char *path, int flags, void *user_data) -{ - struct udev_input *input = user_data; - struct weston_launcher *launcher = input->compositor->launcher; - - return weston_launcher_open(launcher, path, flags); -} - -static void -close_restricted(int fd, void *user_data) -{ - struct udev_input *input = user_data; - struct weston_launcher *launcher = input->compositor->launcher; - - weston_launcher_close(launcher, fd); -} - -const struct libinput_interface libinput_interface = { - open_restricted, - close_restricted, -}; - -int -udev_input_enable(struct udev_input *input) -{ - struct wl_event_loop *loop; - struct weston_compositor *c = input->compositor; - int fd; - struct udev_seat *seat; - int devices_found = 0; - - loop = wl_display_get_event_loop(c->wl_display); - fd = libinput_get_fd(input->libinput); - input->libinput_source = - wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, - libinput_source_dispatch, input); - if (!input->libinput_source) { - return -1; - } - - if (input->suspended) { - if (libinput_resume(input->libinput) != 0) { - wl_event_source_remove(input->libinput_source); - input->libinput_source = NULL; - return -1; - } - input->suspended = 0; - process_events(input); - } - - 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 && !c->require_input) { - weston_log("warning: no input devices found, but none required " - "as per configuration.\n"); - return 0; - } - - 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 void -libinput_log_func(struct libinput *libinput, - enum libinput_log_priority priority, - const char *format, va_list args) -{ - weston_vlog(format, args); -} - -int -udev_input_init(struct udev_input *input, struct weston_compositor *c, - struct udev *udev, const char *seat_id, - udev_configure_device_t configure_device) -{ - enum libinput_log_priority priority = LIBINPUT_LOG_PRIORITY_INFO; - const char *log_priority = NULL; - - memset(input, 0, sizeof *input); - - input->compositor = c; - input->configure_device = configure_device; - - log_priority = getenv("WESTON_LIBINPUT_LOG_PRIORITY"); - - input->libinput = libinput_udev_create_context(&libinput_interface, - input, udev); - if (!input->libinput) { - return -1; - } - - libinput_log_set_handler(input->libinput, &libinput_log_func); - - if (log_priority) { - if (strcmp(log_priority, "debug") == 0) { - priority = LIBINPUT_LOG_PRIORITY_DEBUG; - } else if (strcmp(log_priority, "info") == 0) { - priority = LIBINPUT_LOG_PRIORITY_INFO; - } else if (strcmp(log_priority, "error") == 0) { - priority = LIBINPUT_LOG_PRIORITY_ERROR; - } - } - - libinput_log_set_priority(input->libinput, priority); - - if (libinput_udev_assign_seat(input->libinput, seat_id) != 0) { - libinput_unref(input->libinput); - return -1; - } - - process_events(input); - - return udev_input_enable(input); -} - -void -udev_input_destroy(struct udev_input *input) -{ - struct udev_seat *seat, *next; - - if (input->libinput_source) - wl_event_source_remove(input->libinput_source); - wl_list_for_each_safe(seat, next, &input->compositor->seat_list, base.link) - udev_seat_destroy(seat); - libinput_unref(input->libinput); -} - -static void -udev_seat_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 void -udev_seat_output_changed(struct udev_seat *seat, struct weston_output *output) -{ - struct evdev_device *device; - struct weston_output *found; - - wl_list_for_each(device, &seat->devices_list, link) { - /* If we find any input device without an associated output - * or an output name to associate with, just tie it with the - * output we got here - the default assignment. - */ - if (!device->output_name) { - if (!device->output) - evdev_device_set_output(device, output); - - continue; - } - - /* Update all devices' output associations, may they gain or - * lose it. - */ - found = output_find_by_head_name(output->compositor, - device->output_name); - evdev_device_set_output(device, found); - } -} - -static void -notify_output_create(struct wl_listener *listener, void *data) -{ - struct udev_seat *seat = container_of(listener, struct udev_seat, - output_create_listener); - struct weston_output *output = data; - - udev_seat_output_changed(seat, output); -} - -static void -notify_output_heads_changed(struct wl_listener *listener, void *data) -{ - struct udev_seat *seat = container_of(listener, struct udev_seat, - output_heads_listener); - struct weston_output *output = data; - - udev_seat_output_changed(seat, output); -} - -static struct udev_seat * -udev_seat_create(struct udev_input *input, const char *seat_name) -{ - struct weston_compositor *c = input->compositor; - struct udev_seat *seat; - - seat = zalloc(sizeof *seat); - if (!seat) - return NULL; - - weston_seat_init(&seat->base, c, seat_name); - seat->base.led_update = udev_seat_led_update; - - seat->output_create_listener.notify = notify_output_create; - wl_signal_add(&c->output_created_signal, - &seat->output_create_listener); - - seat->output_heads_listener.notify = notify_output_heads_changed; - wl_signal_add(&c->output_heads_changed_signal, - &seat->output_heads_listener); - - wl_list_init(&seat->devices_list); - - return seat; -} - -static void -udev_seat_destroy(struct udev_seat *seat) -{ - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(&seat->base); - - if (keyboard) - notify_keyboard_focus_out(&seat->base); - - udev_seat_remove_devices(seat); - weston_seat_release(&seat->base); - wl_list_remove(&seat->output_create_listener.link); - wl_list_remove(&seat->output_heads_listener.link); - free(seat); -} - -struct udev_seat * -udev_seat_get_named(struct udev_input *input, const char *seat_name) -{ - struct udev_seat *seat; - - wl_list_for_each(seat, &input->compositor->seat_list, base.link) { - if (strcmp(seat->base.seat_name, seat_name) == 0) - return seat; - } - - return udev_seat_create(input, seat_name); -} diff --git a/libweston/libinput-seat.h b/libweston/libinput-seat.h deleted file mode 100644 index 315980d..0000000 --- a/libweston/libinput-seat.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright © 2013 Intel Corporation - * Copyright © 2013 Jonas Ådahl - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _LIBINPUT_SEAT_H_ -#define _LIBINPUT_SEAT_H_ - -#include "config.h" - -#include - -#include - -struct libinput_device; - -struct udev_seat { - struct weston_seat base; - struct wl_list devices_list; - struct wl_listener output_create_listener; - struct wl_listener output_heads_listener; -}; - -typedef void (*udev_configure_device_t)(struct weston_compositor *compositor, - struct libinput_device *device); - -struct udev_input { - struct libinput *libinput; - struct wl_event_source *libinput_source; - struct weston_compositor *compositor; - int suspended; - udev_configure_device_t configure_device; -}; - -int -udev_input_enable(struct udev_input *input); -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, - udev_configure_device_t configure_device); -void -udev_input_destroy(struct udev_input *input); - -struct udev_seat * -udev_seat_get_named(struct udev_input *u, - const char *seat_name); - -#endif diff --git a/libweston/libweston-internal.h b/libweston/libweston-internal.h deleted file mode 100755 index 66c1a70..0000000 --- a/libweston/libweston-internal.h +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2017, 2018 General Electric Company - * Copyright © 2012, 2017-2019 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef LIBWESTON_INTERNAL_H -#define LIBWESTON_INTERNAL_H - -/* - * This is the internal (private) part of libweston. All symbols found here - * are, and should be only (with a few exceptions) used within the internal - * parts of libweston. Notable exception(s) include a few files in tests/ that - * need access to these functions, screen-share file from compositor/ and those - * remoting/. Those will require some further fixing as to avoid including this - * private header. - * - * Eventually, these symbols should reside naturally into their own scope. New - * features should either provide their own (internal) header or use this one. - */ - - -/* weston_buffer */ - -void -weston_buffer_send_server_error(struct weston_buffer *buffer, - const char *msg); -void -weston_buffer_reference(struct weston_buffer_reference *ref, - struct weston_buffer *buffer); - -void -weston_buffer_release_move(struct weston_buffer_release_reference *dest, - struct weston_buffer_release_reference *src); - -void -weston_buffer_release_reference(struct weston_buffer_release_reference *ref, - struct weston_buffer_release *buf_release); - -/* weston_bindings */ -void -weston_binding_list_destroy_all(struct wl_list *list); - -/* weston_compositor */ - -void -touch_calibrator_mode_changed(struct weston_compositor *compositor); - -// OHOS remove noop -// int -// noop_renderer_init(struct weston_compositor *ec); - -void -weston_compositor_add_head(struct weston_compositor *compositor, - struct weston_head *head); -void -weston_compositor_add_pending_output(struct weston_output *output, - struct weston_compositor *compositor); -bool -weston_compositor_import_dmabuf(struct weston_compositor *compositor, - struct linux_dmabuf_buffer *buffer); -bool -weston_compositor_dmabuf_can_scanout(struct weston_compositor *compositor, - struct linux_dmabuf_buffer *buffer); -void -weston_compositor_offscreen(struct weston_compositor *compositor); - -// OHOS remove logger -// char * -// weston_compositor_print_scene_graph(struct weston_compositor *ec); - -void -weston_compositor_read_presentation_clock( - const struct weston_compositor *compositor, - struct timespec *ts); - -int -weston_compositor_run_axis_binding(struct weston_compositor *compositor, - struct weston_pointer *pointer, - const struct timespec *time, - struct weston_pointer_axis_event *event); -void -weston_compositor_run_button_binding(struct weston_compositor *compositor, - struct weston_pointer *pointer, - const struct timespec *time, - uint32_t button, - enum wl_pointer_button_state value); -int -weston_compositor_run_debug_binding(struct weston_compositor *compositor, - struct weston_keyboard *keyboard, - const struct timespec *time, - uint32_t key, - enum wl_keyboard_key_state state); -void -weston_compositor_run_key_binding(struct weston_compositor *compositor, - struct weston_keyboard *keyboard, - const struct timespec *time, - uint32_t key, - enum wl_keyboard_key_state state); -void -weston_compositor_run_modifier_binding(struct weston_compositor *compositor, - struct weston_keyboard *keyboard, - enum weston_keyboard_modifier modifier, - enum wl_keyboard_key_state state); -void -weston_compositor_run_touch_binding(struct weston_compositor *compositor, - struct weston_touch *touch, - const struct timespec *time, - int touch_type); -void -weston_compositor_stack_plane(struct weston_compositor *ec, - struct weston_plane *plane, - struct weston_plane *above); -void -weston_compositor_set_touch_mode_normal(struct weston_compositor *compositor); - -void -weston_compositor_set_touch_mode_calib(struct weston_compositor *compositor); - -int -weston_compositor_set_presentation_clock(struct weston_compositor *compositor, - clockid_t clk_id); -int -weston_compositor_set_presentation_clock_software( - struct weston_compositor *compositor); -void -weston_compositor_shutdown(struct weston_compositor *ec); - -void -weston_compositor_xkb_destroy(struct weston_compositor *ec); - -int -weston_input_init(struct weston_compositor *compositor); - -/* weston_output */ - -void -weston_output_disable_planes_incr(struct weston_output *output); - -void -weston_output_disable_planes_decr(struct weston_output *output); - -/* weston_plane */ - -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); - -/* weston_seat */ - -struct clipboard * -clipboard_create(struct weston_seat *seat); - -void -weston_seat_init(struct weston_seat *seat, struct weston_compositor *ec, - const char *seat_name); - -void -weston_seat_repick(struct weston_seat *seat); - -void -weston_seat_release(struct weston_seat *seat); - -void -weston_seat_send_selection(struct weston_seat *seat, struct wl_client *client); - -void -weston_seat_init_pointer(struct weston_seat *seat); - -int -weston_seat_init_keyboard(struct weston_seat *seat, struct xkb_keymap *keymap); - -void -weston_seat_init_touch(struct weston_seat *seat); - -void -weston_seat_release_keyboard(struct weston_seat *seat); - -void -weston_seat_release_pointer(struct weston_seat *seat); - -void -weston_seat_release_touch(struct weston_seat *seat); - -void -weston_seat_update_keymap(struct weston_seat *seat, struct xkb_keymap *keymap); - -void -wl_data_device_set_keyboard_focus(struct weston_seat *seat); - -/* weston_pointer */ - -void -weston_pointer_clamp(struct weston_pointer *pointer, - wl_fixed_t *fx, wl_fixed_t *fy); -void -weston_pointer_set_default_grab(struct weston_pointer *pointer, - const struct weston_pointer_grab_interface *interface); - -void -weston_pointer_constraint_destroy(struct weston_pointer_constraint *constraint); - -/* weston_keyboard */ -bool -weston_keyboard_has_focus_resource(struct weston_keyboard *keyboard); - -/* weston_touch */ - -struct weston_touch_device * -weston_touch_create_touch_device(struct weston_touch *touch, - const char *syspath, - void *backend_data, - const struct weston_touch_device_ops *ops); - -void -weston_touch_device_destroy(struct weston_touch_device *device); - -bool -weston_touch_has_focus_resource(struct weston_touch *touch); - -int -weston_touch_start_drag(struct weston_touch *touch, - struct weston_data_source *source, - struct weston_surface *icon, - struct wl_client *client); - - -/* weston_touch_device */ - -bool -weston_touch_device_can_calibrate(struct weston_touch_device *device); - -/* weston_surface */ -void -weston_surface_to_buffer_float(struct weston_surface *surface, - float x, float y, float *bx, float *by); -pixman_box32_t -weston_surface_to_buffer_rect(struct weston_surface *surface, - pixman_box32_t rect); - -void -weston_surface_to_buffer_region(struct weston_surface *surface, - pixman_region32_t *surface_region, - pixman_region32_t *buffer_region); -void -weston_surface_schedule_repaint(struct weston_surface *surface); - -/* weston_spring */ - -void -weston_spring_init(struct weston_spring *spring, - double k, double current, double target); -int -weston_spring_done(struct weston_spring *spring); - -void -weston_spring_update(struct weston_spring *spring, const struct timespec *time); - -/* weston_view */ - -void -weston_view_to_global_fixed(struct weston_view *view, - wl_fixed_t sx, wl_fixed_t sy, - wl_fixed_t *x, wl_fixed_t *y); -void -weston_view_from_global_float(struct weston_view *view, - float x, float y, float *vx, float *vy); -bool -weston_view_is_opaque(struct weston_view *ev, pixman_region32_t *region); - -bool -weston_view_has_valid_buffer(struct weston_view *ev); - -bool -weston_view_matches_output_entirely(struct weston_view *ev, - struct weston_output *output); -void -weston_view_move_to_plane(struct weston_view *view, - struct weston_plane *plane); - -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); -void -weston_transformed_region(int width, int height, - enum wl_output_transform transform, - int32_t scale, - pixman_region32_t *src, pixman_region32_t *dest); -void -weston_matrix_transform_region(pixman_region32_t *dest, - struct weston_matrix *matrix, - pixman_region32_t *src); - -/* protected_surface */ -void -weston_protected_surface_send_event(struct protected_surface *psurface, - enum weston_hdcp_protection protection); - -/* others */ -int -wl_data_device_manager_init(struct wl_display *display); - -#endif diff --git a/libweston/linux-dmabuf.c b/libweston/linux-dmabuf.c deleted file mode 100644 index 796e982..0000000 --- a/libweston/linux-dmabuf.c +++ /dev/null @@ -1,591 +0,0 @@ -/* - * Copyright © 2014, 2015 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include - -#include -#include "linux-dmabuf.h" -#include "linux-dmabuf-unstable-v1-server-protocol.h" -#include "libweston-internal.h" - -static void -linux_dmabuf_buffer_destroy(struct linux_dmabuf_buffer *buffer) -{ - int i; - - for (i = 0; i < buffer->attributes.n_planes; i++) { - close(buffer->attributes.fd[i]); - buffer->attributes.fd[i] = -1; - } - - buffer->attributes.n_planes = 0; - free(buffer); -} - -static void -destroy_params(struct wl_resource *params_resource) -{ - struct linux_dmabuf_buffer *buffer; - - buffer = wl_resource_get_user_data(params_resource); - - if (!buffer) - return; - - linux_dmabuf_buffer_destroy(buffer); -} - -static void -params_destroy(struct wl_client *client, struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -params_add(struct wl_client *client, - struct wl_resource *params_resource, - int32_t name_fd, - uint32_t plane_idx, - uint32_t offset, - uint32_t stride, - uint32_t modifier_hi, - uint32_t modifier_lo) -{ - struct linux_dmabuf_buffer *buffer; - - buffer = wl_resource_get_user_data(params_resource); - if (!buffer) { - wl_resource_post_error(params_resource, - ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, - "params was already used to create a wl_buffer"); - close(name_fd); - return; - } - - assert(buffer->params_resource == params_resource); - assert(!buffer->buffer_resource); - - if (plane_idx >= MAX_DMABUF_PLANES) { - wl_resource_post_error(params_resource, - ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX, - "plane index %u is too high", plane_idx); - close(name_fd); - return; - } - - if (buffer->attributes.fd[plane_idx] != -1) { - wl_resource_post_error(params_resource, - ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET, - "a dmabuf has already been added for plane %u", - plane_idx); - close(name_fd); - return; - } - - buffer->attributes.fd[plane_idx] = name_fd; - buffer->attributes.offset[plane_idx] = offset; - buffer->attributes.stride[plane_idx] = stride; - - if (wl_resource_get_version(params_resource) < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) - buffer->attributes.modifier[plane_idx] = DRM_FORMAT_MOD_INVALID; - else - buffer->attributes.modifier[plane_idx] = ((uint64_t)modifier_hi << 32) | - modifier_lo; - - buffer->attributes.n_planes++; -} - -static void -linux_dmabuf_wl_buffer_destroy(struct wl_client *client, - struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static const struct wl_buffer_interface linux_dmabuf_buffer_implementation = { - linux_dmabuf_wl_buffer_destroy -}; - -static void -destroy_linux_dmabuf_wl_buffer(struct wl_resource *resource) -{ - struct linux_dmabuf_buffer *buffer; - - buffer = wl_resource_get_user_data(resource); - assert(buffer->buffer_resource == resource); - assert(!buffer->params_resource); - - if (buffer->user_data_destroy_func) - buffer->user_data_destroy_func(buffer); - - linux_dmabuf_buffer_destroy(buffer); -} - -static void -params_create_common(struct wl_client *client, - struct wl_resource *params_resource, - uint32_t buffer_id, - int32_t width, - int32_t height, - uint32_t format, - uint32_t flags) -{ - struct linux_dmabuf_buffer *buffer; - int i; - - buffer = wl_resource_get_user_data(params_resource); - - if (!buffer) { - wl_resource_post_error(params_resource, - ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, - "params was already used to create a wl_buffer"); - return; - } - - assert(buffer->params_resource == params_resource); - assert(!buffer->buffer_resource); - - /* Switch the linux_dmabuf_buffer object from params resource to - * eventually wl_buffer resource. - */ - wl_resource_set_user_data(buffer->params_resource, NULL); - buffer->params_resource = NULL; - - if (!buffer->attributes.n_planes) { - wl_resource_post_error(params_resource, - ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, - "no dmabuf has been added to the params"); - goto err_out; - } - - /* Check for holes in the dmabufs set (e.g. [0, 1, 3]) */ - for (i = 0; i < buffer->attributes.n_planes; i++) { - if (buffer->attributes.fd[i] == -1) { - wl_resource_post_error(params_resource, - ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, - "no dmabuf has been added for plane %i", i); - goto err_out; - } - } - - buffer->attributes.width = width; - buffer->attributes.height = height; - buffer->attributes.format = format; - buffer->attributes.flags = flags; - - if (width < 1 || height < 1) { - wl_resource_post_error(params_resource, - ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS, - "invalid width %d or height %d", width, height); - goto err_out; - } - - for (i = 0; i < buffer->attributes.n_planes; i++) { - off_t size; - - if ((uint64_t) buffer->attributes.offset[i] + buffer->attributes.stride[i] > UINT32_MAX) { - wl_resource_post_error(params_resource, - ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, - "size overflow for plane %i", i); - goto err_out; - } - - if (i == 0 && - (uint64_t) buffer->attributes.offset[i] + - (uint64_t) buffer->attributes.stride[i] * height > UINT32_MAX) { - wl_resource_post_error(params_resource, - ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, - "size overflow for plane %i", i); - goto err_out; - } - - /* Don't report an error as it might be caused - * by the kernel not supporting seeking on dmabuf */ - size = lseek(buffer->attributes.fd[i], 0, SEEK_END); - if (size == -1) - continue; - - if (buffer->attributes.offset[i] >= size) { - wl_resource_post_error(params_resource, - ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, - "invalid offset %i for plane %i", - buffer->attributes.offset[i], i); - goto err_out; - } - - if (buffer->attributes.offset[i] + buffer->attributes.stride[i] > size) { - wl_resource_post_error(params_resource, - ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, - "invalid stride %i for plane %i", - buffer->attributes.stride[i], i); - goto err_out; - } - - /* Only valid for first plane as other planes might be - * sub-sampled according to fourcc format */ - if (i == 0 && - buffer->attributes.offset[i] + buffer->attributes.stride[i] * height > size) { - wl_resource_post_error(params_resource, - ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, - "invalid buffer stride or height for plane %i", i); - goto err_out; - } - } - - if (buffer->direct_display) { - if (!weston_compositor_dmabuf_can_scanout(buffer->compositor, - buffer)) - goto err_failed; - - goto avoid_gpu_import; - } - - if (!weston_compositor_import_dmabuf(buffer->compositor, buffer)) - goto err_failed; - -avoid_gpu_import: - buffer->buffer_resource = wl_resource_create(client, - &wl_buffer_interface, - 1, buffer_id); - if (!buffer->buffer_resource) { - wl_resource_post_no_memory(params_resource); - goto err_buffer; - } - - wl_resource_set_implementation(buffer->buffer_resource, - &linux_dmabuf_buffer_implementation, - buffer, destroy_linux_dmabuf_wl_buffer); - - /* send 'created' event when the request is not for an immediate - * import, ie buffer_id is zero */ - if (buffer_id == 0) - zwp_linux_buffer_params_v1_send_created(params_resource, - buffer->buffer_resource); - - return; - -err_buffer: - if (buffer->user_data_destroy_func) - buffer->user_data_destroy_func(buffer); - -err_failed: - if (buffer_id == 0) - zwp_linux_buffer_params_v1_send_failed(params_resource); - else - /* since the behavior is left implementation defined by the - * protocol in case of create_immed failure due to an unknown cause, - * we choose to treat it as a fatal error and immediately kill the - * client instead of creating an invalid handle and waiting for it - * to be used. - */ - wl_resource_post_error(params_resource, - ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER, - "importing the supplied dmabufs failed"); - -err_out: - linux_dmabuf_buffer_destroy(buffer); -} - -static void -params_create(struct wl_client *client, - struct wl_resource *params_resource, - int32_t width, - int32_t height, - uint32_t format, - uint32_t flags) -{ - params_create_common(client, params_resource, 0, width, height, format, - flags); -} - -static void -params_create_immed(struct wl_client *client, - struct wl_resource *params_resource, - uint32_t buffer_id, - int32_t width, - int32_t height, - uint32_t format, - uint32_t flags) -{ - params_create_common(client, params_resource, buffer_id, width, height, - format, flags); -} - -static const struct zwp_linux_buffer_params_v1_interface -zwp_linux_buffer_params_implementation = { - params_destroy, - params_add, - params_create, - params_create_immed -}; - -static void -linux_dmabuf_destroy(struct wl_client *client, struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -linux_dmabuf_create_params(struct wl_client *client, - struct wl_resource *linux_dmabuf_resource, - uint32_t params_id) -{ - struct weston_compositor *compositor; - struct linux_dmabuf_buffer *buffer; - uint32_t version; - int i; - - version = wl_resource_get_version(linux_dmabuf_resource); - compositor = wl_resource_get_user_data(linux_dmabuf_resource); - - buffer = zalloc(sizeof *buffer); - if (!buffer) - goto err_out; - - for (i = 0; i < MAX_DMABUF_PLANES; i++) - buffer->attributes.fd[i] = -1; - - buffer->compositor = compositor; - buffer->params_resource = - wl_resource_create(client, - &zwp_linux_buffer_params_v1_interface, - version, params_id); - buffer->direct_display = false; - if (!buffer->params_resource) - goto err_dealloc; - - wl_resource_set_implementation(buffer->params_resource, - &zwp_linux_buffer_params_implementation, - buffer, destroy_params); - - return; - -err_dealloc: - free(buffer); - -err_out: - wl_resource_post_no_memory(linux_dmabuf_resource); -} - -/** Get the linux_dmabuf_buffer from a wl_buffer resource - * - * If the given wl_buffer resource was created through the linux_dmabuf - * protocol interface, returns the linux_dmabuf_buffer object. This can - * be used as a type check for a wl_buffer. - * - * \param resource A wl_buffer resource. - * \return The linux_dmabuf_buffer if it exists, or NULL otherwise. - */ -WL_EXPORT struct linux_dmabuf_buffer * -linux_dmabuf_buffer_get(struct wl_resource *resource) -{ - struct linux_dmabuf_buffer *buffer; - - if (!resource) - return NULL; - - if (!wl_resource_instance_of(resource, &wl_buffer_interface, - &linux_dmabuf_buffer_implementation)) - return NULL; - - buffer = wl_resource_get_user_data(resource); - assert(buffer); - assert(!buffer->params_resource); - assert(buffer->buffer_resource == resource); - - return buffer; -} - -/** Set renderer-private data - * - * Set the user data for the linux_dmabuf_buffer. It is invalid to overwrite - * a non-NULL user data with a new non-NULL pointer. This is meant to - * protect against renderers fighting over linux_dmabuf_buffer user data - * ownership. - * - * The renderer-private data is usually set from the - * weston_renderer::import_dmabuf hook. - * - * \param buffer The linux_dmabuf_buffer object to set for. - * \param data The new renderer-private data pointer. - * \param func Destructor function to be called for the renderer-private - * data when the linux_dmabuf_buffer gets destroyed. - * - * \sa weston_compositor_import_dmabuf - */ -WL_EXPORT void -linux_dmabuf_buffer_set_user_data(struct linux_dmabuf_buffer *buffer, - void *data, - dmabuf_user_data_destroy_func func) -{ - assert(data == NULL || buffer->user_data == NULL); - - buffer->user_data = data; - buffer->user_data_destroy_func = func; -} - -/** Get renderer-private data - * - * Get the user data from the linux_dmabuf_buffer. - * - * \param buffer The linux_dmabuf_buffer to query. - * \return Renderer-private data pointer. - * - * \sa linux_dmabuf_buffer_set_user_data - */ -WL_EXPORT void * -linux_dmabuf_buffer_get_user_data(struct linux_dmabuf_buffer *buffer) -{ - return buffer->user_data; -} - -static const struct zwp_linux_dmabuf_v1_interface linux_dmabuf_implementation = { - linux_dmabuf_destroy, - linux_dmabuf_create_params -}; - -static void -bind_linux_dmabuf(struct wl_client *client, - void *data, uint32_t version, uint32_t id) -{ - struct weston_compositor *compositor = data; - struct wl_resource *resource; - int *formats = NULL; - uint64_t *modifiers = NULL; - int num_formats, num_modifiers; - uint64_t modifier_invalid = DRM_FORMAT_MOD_INVALID; - int i, j; - - resource = wl_resource_create(client, &zwp_linux_dmabuf_v1_interface, - version, id); - if (resource == NULL) { - wl_client_post_no_memory(client); - return; - } - - wl_resource_set_implementation(resource, &linux_dmabuf_implementation, - compositor, NULL); - - /* - * Use EGL_EXT_image_dma_buf_import_modifiers to query and advertise - * format/modifier codes. - */ - compositor->renderer->query_dmabuf_formats(compositor, &formats, - &num_formats); - - for (i = 0; i < num_formats; i++) { - compositor->renderer->query_dmabuf_modifiers(compositor, - formats[i], - &modifiers, - &num_modifiers); - - /* send DRM_FORMAT_MOD_INVALID token when no modifiers are supported - * for this format */ - if (num_modifiers == 0) { - num_modifiers = 1; - modifiers = &modifier_invalid; - } - for (j = 0; j < num_modifiers; j++) { - if (version >= ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) { - uint32_t modifier_lo = modifiers[j] & 0xFFFFFFFF; - uint32_t modifier_hi = modifiers[j] >> 32; - zwp_linux_dmabuf_v1_send_modifier(resource, - formats[i], - modifier_hi, - modifier_lo); - } else if (modifiers[j] == DRM_FORMAT_MOD_LINEAR || - modifiers == &modifier_invalid) { - zwp_linux_dmabuf_v1_send_format(resource, - formats[i]); - } - } - if (modifiers != &modifier_invalid) - free(modifiers); - } - free(formats); -} - -/** Advertise linux_dmabuf support - * - * Calling this initializes the zwp_linux_dmabuf protocol support, so that - * the interface will be advertised to clients. Essentially it creates a - * global. Do not call this function multiple times in the compositor's - * lifetime. There is no way to deinit explicitly, globals will be reaped - * when the wl_display gets destroyed. - * - * \param compositor The compositor to init for. - * \return Zero on success, -1 on failure. - */ -WL_EXPORT int -linux_dmabuf_setup(struct weston_compositor *compositor) -{ - if (!wl_global_create(compositor->wl_display, - &zwp_linux_dmabuf_v1_interface, 3, - compositor, bind_linux_dmabuf)) - return -1; - - return 0; -} - -/** Resolve an internal compositor error by disconnecting the client. - * - * This function is used in cases when the dmabuf-based wl_buffer - * turns out unusable and there is no fallback path. This is used by - * renderers which are the fallback path in the first place. - * - * It is possible the fault is caused by a compositor bug, the underlying - * graphics stack bug or normal behaviour, or perhaps a client mistake. - * In any case, the options are to either composite garbage or nothing, - * or disconnect the client. This is a helper function for the latter. - * - * The error is sent as an INVALID_OBJECT error on the client's wl_display. - * - * \param buffer The linux_dmabuf_buffer that is unusable. - * \param msg A custom error message attached to the protocol error. - */ -WL_EXPORT void -linux_dmabuf_buffer_send_server_error(struct linux_dmabuf_buffer *buffer, - const char *msg) -{ - struct wl_client *client; - struct wl_resource *display_resource; - uint32_t id; - - assert(buffer->buffer_resource); - id = wl_resource_get_id(buffer->buffer_resource); - client = wl_resource_get_client(buffer->buffer_resource); - display_resource = wl_client_get_object(client, 1); - - assert(display_resource); - wl_resource_post_error(display_resource, - WL_DISPLAY_ERROR_INVALID_OBJECT, - "linux_dmabuf server error with " - "wl_buffer@%u: %s", id, msg); -} diff --git a/libweston/linux-dmabuf.h b/libweston/linux-dmabuf.h deleted file mode 100644 index 926dd9e..0000000 --- a/libweston/linux-dmabuf.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright © 2014, 2015 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_LINUX_DMABUF_H -#define WESTON_LINUX_DMABUF_H - -#include - -#define MAX_DMABUF_PLANES 4 -#ifndef DRM_FORMAT_MOD_INVALID -#define DRM_FORMAT_MOD_INVALID ((1ULL<<56) - 1) -#endif -#ifndef DRM_FORMAT_MOD_LINEAR -#define DRM_FORMAT_MOD_LINEAR 0 -#endif - -struct linux_dmabuf_buffer; -typedef void (*dmabuf_user_data_destroy_func)( - struct linux_dmabuf_buffer *buffer); - -struct dmabuf_attributes { - int32_t width; - int32_t height; - uint32_t format; - uint32_t flags; /* enum zlinux_buffer_params_flags */ - int n_planes; - int fd[MAX_DMABUF_PLANES]; - uint32_t offset[MAX_DMABUF_PLANES]; - uint32_t stride[MAX_DMABUF_PLANES]; - uint64_t modifier[MAX_DMABUF_PLANES]; -}; - -struct linux_dmabuf_buffer { - struct wl_resource *buffer_resource; - struct wl_resource *params_resource; - struct weston_compositor *compositor; - struct dmabuf_attributes attributes; - - void *user_data; - dmabuf_user_data_destroy_func user_data_destroy_func; - - /* XXX: - * - * Add backend private data. This would be for the backend - * to do all additional imports it might ever use in advance. - * The basic principle, even if not implemented in drivers today, - * is that dmabufs are first attached, but the actual allocation - * is deferred to first use. This would allow the exporter and all - * attachers to agree on how to allocate. - * - * The DRM backend would use this to create drmFBs for each - * dmabuf_buffer, just in case at some point it would become - * feasible to scan it out directly. This would improve the - * possibilities to successfully scan out, avoiding compositing. - */ - - /**< marked as scan-out capable, avoids any composition */ - bool direct_display; -}; - -int -linux_dmabuf_setup(struct weston_compositor *compositor); - -int -weston_direct_display_setup(struct weston_compositor *compositor); - -struct linux_dmabuf_buffer * -linux_dmabuf_buffer_get(struct wl_resource *resource); - -void -linux_dmabuf_buffer_set_user_data(struct linux_dmabuf_buffer *buffer, - void *data, - dmabuf_user_data_destroy_func func); -void * -linux_dmabuf_buffer_get_user_data(struct linux_dmabuf_buffer *buffer); - -void -linux_dmabuf_buffer_send_server_error(struct linux_dmabuf_buffer *buffer, - const char *msg); - -#endif /* WESTON_LINUX_DMABUF_H */ diff --git a/libweston/linux-explicit-synchronization.c b/libweston/linux-explicit-synchronization.c deleted file mode 100644 index 4b47383..0000000 --- a/libweston/linux-explicit-synchronization.c +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright © 2018 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include -#include "linux-explicit-synchronization.h" -#include "linux-explicit-synchronization-unstable-v1-server-protocol.h" -#include "linux-sync-file.h" -#include "shared/fd-util.h" -#include "libweston-internal.h" - -static void -destroy_linux_buffer_release(struct wl_resource *resource) -{ - struct weston_buffer_release *buffer_release = - wl_resource_get_user_data(resource); - - fd_clear(&buffer_release->fence_fd); - free(buffer_release); -} - -static void -destroy_linux_surface_synchronization(struct wl_resource *resource) -{ - struct weston_surface *surface = - wl_resource_get_user_data(resource); - - if (surface) { - fd_clear(&surface->pending.acquire_fence_fd); - surface->synchronization_resource = NULL; - } -} - -static void -linux_surface_synchronization_destroy(struct wl_client *client, - struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -linux_surface_synchronization_set_acquire_fence(struct wl_client *client, - struct wl_resource *resource, - int32_t fd) -{ - struct weston_surface *surface = wl_resource_get_user_data(resource); - - if (!surface) { - wl_resource_post_error( - resource, - ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_SURFACE, - "surface no longer exists"); - goto err; - } - - if (!linux_sync_file_is_valid(fd)) { - wl_resource_post_error( - resource, - ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_INVALID_FENCE, - "invalid fence fd"); - goto err; - } - - if (surface->pending.acquire_fence_fd != -1) { - wl_resource_post_error( - resource, - ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_DUPLICATE_FENCE, - "already have a fence fd"); - goto err; - } - - fd_update(&surface->pending.acquire_fence_fd, fd); - - return; - -err: - close(fd); -} - -static void -linux_surface_synchronization_get_release(struct wl_client *client, - struct wl_resource *resource, - uint32_t id) -{ - struct weston_surface *surface = - wl_resource_get_user_data(resource); - struct weston_buffer_release *buffer_release; - - if (!surface) { - wl_resource_post_error( - resource, - ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_SURFACE, - "surface no longer exists"); - return; - } - - if (surface->pending.buffer_release_ref.buffer_release) { - wl_resource_post_error( - resource, - ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_DUPLICATE_RELEASE, - "already has a buffer release"); - return; - } - - buffer_release = zalloc(sizeof *buffer_release); - if (buffer_release == NULL) - goto err_alloc; - - buffer_release->fence_fd = -1; - buffer_release->resource = - wl_resource_create(client, - &zwp_linux_buffer_release_v1_interface, - wl_resource_get_version(resource), id); - if (!buffer_release->resource) - goto err_create; - - wl_resource_set_implementation(buffer_release->resource, NULL, - buffer_release, - destroy_linux_buffer_release); - - weston_buffer_release_reference(&surface->pending.buffer_release_ref, - buffer_release); - - return; - -err_create: - free(buffer_release); - -err_alloc: - wl_client_post_no_memory(client); - -} - -const struct zwp_linux_surface_synchronization_v1_interface -linux_surface_synchronization_implementation = { - linux_surface_synchronization_destroy, - linux_surface_synchronization_set_acquire_fence, - linux_surface_synchronization_get_release, -}; - -static void -linux_explicit_synchronization_destroy(struct wl_client *client, - struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -linux_explicit_synchronization_get_synchronization(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); - - if (surface->synchronization_resource) { - wl_resource_post_error( - resource, - ZWP_LINUX_EXPLICIT_SYNCHRONIZATION_V1_ERROR_SYNCHRONIZATION_EXISTS, - "wl_surface@%"PRIu32" already has a synchronization object", - wl_resource_get_id(surface_resource)); - return; - } - - surface->synchronization_resource = - wl_resource_create(client, - &zwp_linux_surface_synchronization_v1_interface, - wl_resource_get_version(resource), id); - if (!surface->synchronization_resource) { - wl_client_post_no_memory(client); - return; - } - - wl_resource_set_implementation(surface->synchronization_resource, - &linux_surface_synchronization_implementation, - surface, - destroy_linux_surface_synchronization); -} - -static const struct zwp_linux_explicit_synchronization_v1_interface -linux_explicit_synchronization_implementation = { - linux_explicit_synchronization_destroy, - linux_explicit_synchronization_get_synchronization -}; - -static void -bind_linux_explicit_synchronization(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, - &zwp_linux_explicit_synchronization_v1_interface, - version, id); - if (resource == NULL) { - wl_client_post_no_memory(client); - return; - } - - wl_resource_set_implementation(resource, - &linux_explicit_synchronization_implementation, - compositor, NULL); -} - -/** Advertise linux_explicit_synchronization support - * - * Calling this initializes the zwp_linux_explicit_synchronization_v1 - * protocol support, so that the interface will be advertised to clients. - * Essentially it creates a global. Do not call this function multiple times - * in the compositor's lifetime. There is no way to deinit explicitly, globals - * will be reaped when the wl_display gets destroyed. - * - * \param compositor The compositor to init for. - * \return Zero on success, -1 on failure. - */ -WL_EXPORT int -linux_explicit_synchronization_setup(struct weston_compositor *compositor) -{ - if (!wl_global_create(compositor->wl_display, - &zwp_linux_explicit_synchronization_v1_interface, - 2, compositor, - bind_linux_explicit_synchronization)) - return -1; - - return 0; -} - -/** Resolve an internal compositor error by disconnecting the client. - * - * This function is used in cases when explicit synchronization - * turns out to be unusable and there is no fallback path. - * - * It is possible the fault is caused by a compositor bug, the underlying - * graphics stack bug or normal behaviour, or perhaps a client mistake. - * In any case, the options are to either composite garbage or nothing, - * or disconnect the client. This is a helper function for the latter. - * - * The error is sent as an INVALID_OBJECT error on the client's wl_display. - * - * \param resource The explicit synchronization related resource that is unusable. - * \param msg A custom error message attached to the protocol error. - */ -WL_EXPORT void -linux_explicit_synchronization_send_server_error(struct wl_resource *resource, - const char *msg) -{ - uint32_t id = wl_resource_get_id(resource); - const char *class = wl_resource_get_class(resource); - struct wl_client *client = wl_resource_get_client(resource); - struct wl_resource *display_resource = wl_client_get_object(client, 1); - - assert(display_resource); - wl_resource_post_error(display_resource, - WL_DISPLAY_ERROR_INVALID_OBJECT, - "linux_explicit_synchronization server error " - "with %s@%"PRIu32": %s", - class, id, msg); -} diff --git a/libweston/linux-explicit-synchronization.h b/libweston/linux-explicit-synchronization.h deleted file mode 100644 index 5502287..0000000 --- a/libweston/linux-explicit-synchronization.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright © 2018 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_LINUX_EXPLICIT_SYNCHRONIZATION_H -#define WESTON_LINUX_EXPLICIT_SYNCHRONIZATION_H - -struct weston_compositor; -struct wl_resource; - -int -linux_explicit_synchronization_setup(struct weston_compositor *compositor); - -void -linux_explicit_synchronization_send_server_error(struct wl_resource *resource, - const char *msg); - -#endif /* WESTON_LINUX_EXPLICIT_SYNCHRONIZATION */ diff --git a/libweston/linux-sync-file-uapi.h b/libweston/linux-sync-file-uapi.h deleted file mode 100644 index cd30665..0000000 --- a/libweston/linux-sync-file-uapi.h +++ /dev/null @@ -1,30 +0,0 @@ -/* Sync file Linux kernel UAPI */ - -#ifndef WESTON_LINUX_SYNC_FILE_UAPI_H -#define WESTON_LINUX_SYNC_FILE_UAPI_H - -#include -#include - -struct sync_fence_info { - char obj_name[32]; - char driver_name[32]; - __s32 status; - __u32 flags; - __u64 timestamp_ns; -}; - -struct sync_file_info { - char name[32]; - __s32 status; - __u32 flags; - __u32 num_fences; - __u32 pad; - - __u64 sync_fence_info; -}; - -#define SYNC_IOC_MAGIC '>' -#define SYNC_IOC_FILE_INFO _IOWR(SYNC_IOC_MAGIC, 4, struct sync_file_info) - -#endif /* WESTON_LINUX_SYNC_FILE_UAPI_H */ diff --git a/libweston/linux-sync-file.c b/libweston/linux-sync-file.c deleted file mode 100644 index 9f5313c..0000000 --- a/libweston/linux-sync-file.c +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright © 2018 Collabora, Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_LINUX_SYNC_FILE_H -#include -#else -#include "linux-sync-file-uapi.h" -#endif - -#include "linux-sync-file.h" -#include "shared/timespec-util.h" - -/* Check that a file descriptor represents a valid sync file - * - * \param fd[in] a file descriptor - * \return true if fd is a valid sync file, false otherwise - */ -bool -linux_sync_file_is_valid(int fd) -{ - struct sync_file_info file_info = { { 0 } }; - - if (ioctl(fd, SYNC_IOC_FILE_INFO, &file_info) < 0) - return false; - - return file_info.num_fences > 0; -} - -/* Read the timestamp stored in a sync file - * - * \param fd[in] fd a file descriptor for a sync file - * \param ts[out] the timespec struct to fill with the timestamp - * \return 0 if a timestamp was read, -1 on error - */ -WL_EXPORT int -weston_linux_sync_file_read_timestamp(int fd, struct timespec *ts) -{ - struct sync_file_info file_info = { { 0 } }; - struct sync_fence_info fence_info = { { 0 } }; - - assert(ts != NULL); - - file_info.sync_fence_info = (uint64_t)(uintptr_t)&fence_info; - file_info.num_fences = 1; - - if (ioctl(fd, SYNC_IOC_FILE_INFO, &file_info) < 0) - return -1; - - timespec_from_nsec(ts, fence_info.timestamp_ns); - - return 0; -} diff --git a/libweston/linux-sync-file.h b/libweston/linux-sync-file.h deleted file mode 100644 index 9746d7b..0000000 --- a/libweston/linux-sync-file.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright © 2018 Collabora, Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_LINUX_SYNC_FILE_H -#define WESTON_LINUX_SYNC_FILE_H - -#include -#include - -bool -linux_sync_file_is_valid(int fd); - -int -weston_linux_sync_file_read_timestamp(int fd, struct timespec *ts); - -#endif /* WESTON_LINUX_SYNC_FILE_H */ diff --git a/libweston/log.c b/libweston/log.c deleted file mode 100644 index da57e03..0000000 --- a/libweston/log.c +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright © 2012 Martin Minarik - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include "weston-log-internal.h" - -/** - * \defgroup wlog weston-logging - */ - -static int -default_log_handler(const char *fmt, va_list ap); - -/** Needs to be set, defaults to default_log_handler - * - * \ingroup wlog - */ -static log_func_t log_handler = default_log_handler; - -/** Needs to be set, defaults to default_log_handler - * - * \ingroup wlog - */ -static log_func_t log_continue_handler = default_log_handler; - -/** Sentinel log message handler - * - * This function is used as the default handler for log messages. It - * exists only to issue a noisy reminder to the user that a real handler - * must be installed prior to issuing logging calls. The process is - * immediately aborted after the reminder is printed. - * - * \param fmt The format string. Ignored. - * \param ap The variadic argument list. Ignored. - * - * \ingroup wlog - */ -static int -default_log_handler(const char *fmt, va_list ap) -{ - fprintf(stderr, "weston_log_set_handler() must be called before using of weston_log().\n"); - abort(); -} - -/** Install the log handler - * - * The given functions will be called to output text as passed to the - * \a weston_log and \a weston_log_continue functions. - * - * \param log The log function. This function will be called when - * \a weston_log is called, and should begin a new line, - * with user defined line headers, if any. - * \param cont The continue log function. This function will be called - * when \a weston_log_continue is called, and should append - * its output to the current line, without any header or - * other content in between. - * - * \ingroup wlog - */ -WL_EXPORT void -weston_log_set_handler(log_func_t log, log_func_t cont) -{ - log_handler = log; - log_continue_handler = cont; -} - -/** weston_vlog calls log_handler - * \ingroup wlog - */ -WL_EXPORT int -weston_vlog(const char *fmt, va_list ap) -{ - return log_handler(fmt, ap); -} - -/** printf() equivalent in weston compositor. - * - * \rststar - * .. note:: - * - * Needs :var:`log_handler` to be set-up! - * \endrststar - * - * \ingroup wlog - */ -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; -} - -/** weston_vlog_continue calls log_continue_handler - * - * \ingroup wlog - */ -WL_EXPORT int -weston_vlog_continue(const char *fmt, va_list argp) -{ - return log_continue_handler(fmt, argp); -} - -/** weston_log_continue - * - * \ingroup wlog - */ -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/libweston/meson.build b/libweston/meson.build deleted file mode 100644 index 08d23ec..0000000 --- a/libweston/meson.build +++ /dev/null @@ -1,242 +0,0 @@ -deps_libweston = [ - dep_wayland_server, - dep_pixman, - dep_libm, - dep_libdl, - dep_libdrm_headers, - dep_xkbcommon, - dep_matrix_c -] -srcs_libweston = [ - git_version_h, - 'animation.c', - 'bindings.c', - 'clipboard.c', - 'compositor.c', - 'content-protection.c', - 'data-device.c', - 'input.c', - 'linux-dmabuf.c', - 'linux-explicit-synchronization.c', - 'linux-sync-file.c', - 'log.c', - 'noop-renderer.c', - 'pixel-formats.c', - 'pixman-renderer.c', - 'plugin-registry.c', - 'screenshooter.c', - 'timeline.c', - 'touch-calibration.c', - 'weston-log-wayland.c', - 'weston-log-file.c', - 'weston-log-flight-rec.c', - 'weston-log.c', - 'weston-direct-display.c', - 'zoom.c', - linux_dmabuf_unstable_v1_protocol_c, - linux_dmabuf_unstable_v1_server_protocol_h, - linux_explicit_synchronization_unstable_v1_protocol_c, - linux_explicit_synchronization_unstable_v1_server_protocol_h, - input_method_unstable_v1_protocol_c, - input_method_unstable_v1_server_protocol_h, - input_timestamps_unstable_v1_protocol_c, - input_timestamps_unstable_v1_server_protocol_h, - presentation_time_protocol_c, - presentation_time_server_protocol_h, - pointer_constraints_unstable_v1_protocol_c, - pointer_constraints_unstable_v1_server_protocol_h, - relative_pointer_unstable_v1_protocol_c, - relative_pointer_unstable_v1_server_protocol_h, - weston_screenshooter_protocol_c, - weston_screenshooter_server_protocol_h, - text_cursor_position_protocol_c, - text_cursor_position_server_protocol_h, - text_input_unstable_v1_protocol_c, - text_input_unstable_v1_server_protocol_h, - weston_touch_calibration_protocol_c, - weston_touch_calibration_server_protocol_h, - weston_content_protection_protocol_c, - weston_content_protection_server_protocol_h, - viewporter_protocol_c, - viewporter_server_protocol_h, - xdg_output_unstable_v1_protocol_c, - xdg_output_unstable_v1_server_protocol_h, - weston_debug_protocol_c, - weston_debug_server_protocol_h, - weston_direct_display_protocol_c, - weston_direct_display_server_protocol_h, -] - -if get_option('renderer-gl') - dep_egl = dependency('egl', required: false) - if not dep_egl.found() - error('libweston + gl-renderer requires egl which was not found. Or, you can use \'-Drenderer-gl=false\'.') - endif - deps_libweston += dep_egl -endif - -lib_weston = shared_library( - 'weston-@0@'.format(libweston_major), - srcs_libweston, - include_directories: common_inc, - install: true, - version: '0.0.@0@'.format(libweston_revision), - link_whole: lib_libshared, - dependencies: deps_libweston -) - -deps_for_libweston_users = [ - dep_wayland_server, - dep_pixman, - dep_xkbcommon, -] - -# For external users, like Weston. -dep_libweston_public = declare_dependency( - link_with: lib_weston, - include_directories: public_inc, - dependencies: deps_for_libweston_users -) - -# For internal users, like the backends. -dep_libweston_private = declare_dependency( - link_with: lib_weston, - include_directories: [ include_directories('.'), public_inc ], - dependencies: deps_for_libweston_users -) - -# XXX: We should be able to use dep_libweston_private.partial_dependency() instead -# of this, but a Meson bug makes it not work. It will be fixed with -# https://github.com/mesonbuild/meson/pull/5167 -# in hopefully Meson 0.51. -dep_libweston_private_h_deps = [] -foreach d : deps_for_libweston_users - dep_libweston_private_h_deps += d.partial_dependency(compile_args: true) -endforeach -dep_libweston_private_h = declare_dependency( - include_directories: [ include_directories('.'), public_inc ], - dependencies: dep_libweston_private_h_deps -) - -pkgconfig.generate( - lib_weston, - filebase: 'libweston-@0@'.format(libweston_major), - name: 'libweston API', - version: version_weston, - description: 'Header files for libweston compositors development', - requires_private: deps_for_libweston_users, - subdirs: dir_include_libweston -) - -pkgconfig.generate( - filebase: 'libweston-@0@-protocols'.format(libweston_major), - name: 'libWeston Protocols', - version: version_weston, - description: 'libWeston protocol files', - variables: [ - 'datarootdir=' + join_paths('${prefix}', get_option('datadir')), - 'pkgdatadir=' + join_paths('${pc_sysrootdir}${datarootdir}', dir_protocol_libweston) - ], - install_dir: dir_data_pc -) - -srcs_session_helper = [ - 'launcher-direct.c', - 'launcher-util.c', - 'launcher-weston-launch.c', -] -deps_session_helper = [ dep_libweston_private_h ] - -if get_option('backend-drm') - deps_session_helper += dep_libdrm -endif - -systemd_dep = dependency('', required: false) -if get_option('launcher-logind') - systemd_dep = dependency('libsystemd', version: '>= 209', required: false) - if systemd_dep.found() - config_h.set('HAVE_SYSTEMD_LOGIN_209', '1') - else - systemd_dep = dependency('libsystemd-login', version: '>= 198', required: false) - if not systemd_dep.found() - error('logind support requires libsystemd or libsystemd-login but neither was found. Or, you can use \'-Dlauncher-logind=false\'') - endif - endif - - dbus_dep = dependency('dbus-1', version: '>= 1.6', required: false) - if not dbus_dep.found() - error('logind support requires dbus-1 >= 1.6 which was not found. Or, you can use \'-Dlauncher-logind=false\'') - endif - - config_h.set('HAVE_DBUS', '1') - config_h.set('HAVE_SYSTEMD_LOGIN', '1') - - srcs_session_helper += [ - 'dbus.c', - 'launcher-logind.c', - ] - deps_session_helper += [ - dbus_dep, - systemd_dep, - ] -endif - -lib_session_helper = static_library( - 'session-helper', - srcs_session_helper, - include_directories: common_inc, - dependencies: deps_session_helper, - install: false -) -dep_session_helper = declare_dependency(link_with: lib_session_helper) - - -lib_libinput_backend = static_library( - 'libinput-backend', - [ - 'libinput-device.c', - 'libinput-seat.c' - ], - dependencies: [ - dep_libweston_private, - dep_libinput, - dependency('libudev', version: '>= 136') - ], - include_directories: common_inc, - install: false -) -dep_libinput_backend = declare_dependency( - link_with: lib_libinput_backend, - include_directories: include_directories('.') -) - -dep_vertex_clipping = declare_dependency( - sources: 'vertex-clipping.c', - include_directories: include_directories('.') -) - -if get_option('weston-launch') - dep_pam = cc.find_library('pam') - - if not cc.has_function('pam_open_session', dependencies: dep_pam) - error('pam_open_session not found for weston-launch') - endif - - executable( - 'weston-launch', - 'weston-launch.c', - dependencies: [dep_pam, systemd_dep, dep_libdrm], - include_directories: common_inc, - install: true - ) - - meson.add_install_script('echo', 'REMINDER: You are installing weston-launch, please make it setuid-root.') -endif - -subdir('renderer-gl') -subdir('backend-drm') -subdir('backend-fbdev') -subdir('backend-headless') -subdir('backend-rdp') -subdir('backend-wayland') -subdir('backend-x11') diff --git a/libweston/noop-renderer.c b/libweston/noop-renderer.c deleted file mode 100644 index d86e7f0..0000000 --- a/libweston/noop-renderer.c +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include -#include "libweston-internal.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) -{ - struct wl_shm_buffer *shm_buffer; - uint8_t *data; - uint32_t size, i, width, height, stride; - volatile unsigned char unused = 0; /* volatile so it's not optimized out */ - - if (!buffer) - return; - - shm_buffer = wl_shm_buffer_get(buffer->resource); - - if (!shm_buffer) { - weston_log("No-op renderer supports only SHM buffers\n"); - return; - } - - data = wl_shm_buffer_get_data(shm_buffer); - stride = wl_shm_buffer_get_stride(shm_buffer); - width = wl_shm_buffer_get_width(shm_buffer); - height = wl_shm_buffer_get_height(shm_buffer); - size = stride * height; - - /* Access the buffer data to make sure the buffer's client gets killed - * if the buffer size is invalid. This makes the bad_buffer test pass. - * This can be removed if we start reading the buffer contents - * somewhere else, e.g. in repaint_output(). */ - wl_shm_buffer_begin_access(shm_buffer); - for (i = 0; i < size; i++) - unused ^= data[i]; - wl_shm_buffer_end_access(shm_buffer); - - buffer->shm_buffer = shm_buffer; - buffer->width = width; - buffer->height = height; -} - -static void -noop_renderer_surface_set_color(struct weston_surface *surface, - float red, float green, float blue, float alpha) -{ -} - -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 = zalloc(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->surface_set_color = noop_renderer_surface_set_color; - renderer->destroy = noop_renderer_destroy; - ec->renderer = renderer; - - return 0; -} diff --git a/libweston/pixel-formats.c b/libweston/pixel-formats.c deleted file mode 100755 index 79dc709..0000000 --- a/libweston/pixel-formats.c +++ /dev/null @@ -1,516 +0,0 @@ -/* - * Copyright © 2016, 2019 Collabora, Ltd. - * Copyright (c) 2018 DisplayLink (UK) Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - * Author: Daniel Stone - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "shared/helpers.h" -#include "wayland-util.h" -#include "pixel-formats.h" - -#if ENABLE_EGL -#include -#include -#include -#include -#define GL_FORMAT(fmt) .gl_format = (fmt) -#define GL_TYPE(type) .gl_type = (type) -#define SAMPLER_TYPE(type) .sampler_type = (type) -#else -#define GL_FORMAT(fmt) .gl_format = 0 -#define GL_TYPE(type) .gl_type = 0 -#define SAMPLER_TYPE(type) .sampler_type = 0 -#endif - -#define DRM_FORMAT(f) .format = DRM_FORMAT_ ## f, .drm_format_name = #f -#define BITS_RGBA_FIXED(r_, g_, b_, a_) \ - .bits.r = r_, \ - .bits.g = g_, \ - .bits.b = b_, \ - .bits.a = a_, \ - .component_type = PIXEL_COMPONENT_TYPE_FIXED - -#include "shared/weston-egl-ext.h" - -/** - * Table of DRM formats supported by Weston; RGB, ARGB and YUV formats are - * supported. Indexed/greyscale formats, and formats not containing complete - * colour channels, are not supported. - */ -static const struct pixel_format_info pixel_format_table[] = { - { - DRM_FORMAT(XRGB4444), - BITS_RGBA_FIXED(4, 4, 4, 0), - }, - { - DRM_FORMAT(ARGB4444), - BITS_RGBA_FIXED(4, 4, 4, 4), - .opaque_substitute = DRM_FORMAT_XRGB4444, - }, - { - DRM_FORMAT(XBGR4444), - BITS_RGBA_FIXED(4, 4, 4, 0), - }, - { - DRM_FORMAT(ABGR4444), - BITS_RGBA_FIXED(4, 4, 4, 4), - .opaque_substitute = DRM_FORMAT_XBGR4444, - }, - { - DRM_FORMAT(RGBX4444), - BITS_RGBA_FIXED(4, 4, 4, 0), -# if __BYTE_ORDER == __LITTLE_ENDIAN - GL_FORMAT(GL_RGBA), - GL_TYPE(GL_UNSIGNED_SHORT_4_4_4_4), -#endif - }, - { - DRM_FORMAT(RGBA4444), - BITS_RGBA_FIXED(4, 4, 4, 4), - .opaque_substitute = DRM_FORMAT_RGBX4444, -# if __BYTE_ORDER == __LITTLE_ENDIAN - GL_FORMAT(GL_RGBA), - GL_TYPE(GL_UNSIGNED_SHORT_4_4_4_4), -#endif - }, - { - DRM_FORMAT(BGRX4444), - BITS_RGBA_FIXED(4, 4, 4, 0), - }, - { - DRM_FORMAT(BGRA4444), - BITS_RGBA_FIXED(4, 4, 4, 4), - .opaque_substitute = DRM_FORMAT_BGRX4444, - }, - { - DRM_FORMAT(XRGB1555), - BITS_RGBA_FIXED(5, 5, 5, 0), - .depth = 15, - .bpp = 16, - }, - { - DRM_FORMAT(ARGB1555), - BITS_RGBA_FIXED(5, 5, 5, 1), - .opaque_substitute = DRM_FORMAT_XRGB1555, - }, - { - DRM_FORMAT(XBGR1555), - BITS_RGBA_FIXED(5, 5, 5, 0), - }, - { - DRM_FORMAT(ABGR1555), - BITS_RGBA_FIXED(5, 5, 5, 1), - .opaque_substitute = DRM_FORMAT_XBGR1555, - }, - { - DRM_FORMAT(RGBX5551), - BITS_RGBA_FIXED(5, 5, 5, 0), -# if __BYTE_ORDER == __LITTLE_ENDIAN - GL_FORMAT(GL_RGBA), - GL_TYPE(GL_UNSIGNED_SHORT_5_5_5_1), -#endif - }, - { - DRM_FORMAT(RGBA5551), - BITS_RGBA_FIXED(5, 5, 5, 1), - .opaque_substitute = DRM_FORMAT_RGBX5551, -# if __BYTE_ORDER == __LITTLE_ENDIAN - GL_FORMAT(GL_RGBA), - GL_TYPE(GL_UNSIGNED_SHORT_5_5_5_1), -#endif - }, - { - DRM_FORMAT(BGRX5551), - BITS_RGBA_FIXED(5, 5, 5, 0), - }, - { - DRM_FORMAT(BGRA5551), - BITS_RGBA_FIXED(5, 5, 5, 1), - .opaque_substitute = DRM_FORMAT_BGRX5551, - }, - { - DRM_FORMAT(RGB565), - BITS_RGBA_FIXED(5, 6, 5, 0), - .depth = 16, - .bpp = 16, -# if __BYTE_ORDER == __LITTLE_ENDIAN - GL_FORMAT(GL_RGB), - GL_TYPE(GL_UNSIGNED_SHORT_5_6_5), -#endif - }, - { - DRM_FORMAT(BGR565), - BITS_RGBA_FIXED(5, 6, 5, 0), - }, - { - DRM_FORMAT(RGB888), - BITS_RGBA_FIXED(8, 8, 8, 0), - }, - { - DRM_FORMAT(BGR888), - BITS_RGBA_FIXED(8, 8, 8, 0), - GL_FORMAT(GL_RGB), - GL_TYPE(GL_UNSIGNED_BYTE), - }, - { - DRM_FORMAT(XRGB8888), - BITS_RGBA_FIXED(8, 8, 8, 0), - .depth = 24, - .bpp = 32, - GL_FORMAT(GL_BGRA_EXT), - GL_TYPE(GL_UNSIGNED_BYTE), - }, - { - DRM_FORMAT(ARGB8888), - BITS_RGBA_FIXED(8, 8, 8, 8), - .opaque_substitute = DRM_FORMAT_XRGB8888, - .depth = 32, - .bpp = 32, - GL_FORMAT(GL_BGRA_EXT), - GL_TYPE(GL_UNSIGNED_BYTE), - }, - { - DRM_FORMAT(XBGR8888), - BITS_RGBA_FIXED(8, 8, 8, 0), - GL_FORMAT(GL_RGBA), - GL_TYPE(GL_UNSIGNED_BYTE), - }, - { - DRM_FORMAT(ABGR8888), - BITS_RGBA_FIXED(8, 8, 8, 8), - .opaque_substitute = DRM_FORMAT_XBGR8888, - GL_FORMAT(GL_RGBA), - GL_TYPE(GL_UNSIGNED_BYTE), - }, - { - DRM_FORMAT(RGBX8888), - BITS_RGBA_FIXED(8, 8, 8, 0), - }, - { - DRM_FORMAT(RGBA8888), - BITS_RGBA_FIXED(8, 8, 8, 8), - .opaque_substitute = DRM_FORMAT_RGBX8888, - }, - { - DRM_FORMAT(BGRX8888), - BITS_RGBA_FIXED(8, 8, 8, 0), - }, - { - DRM_FORMAT(BGRA8888), - BITS_RGBA_FIXED(8, 8, 8, 8), - .opaque_substitute = DRM_FORMAT_BGRX8888, - }, - { - DRM_FORMAT(XRGB2101010), - BITS_RGBA_FIXED(10, 10, 10, 0), - .depth = 30, - .bpp = 32, - }, - { - DRM_FORMAT(ARGB2101010), - BITS_RGBA_FIXED(10, 10, 10, 2), - .opaque_substitute = DRM_FORMAT_XRGB2101010, - }, - { - DRM_FORMAT(XBGR2101010), - BITS_RGBA_FIXED(10, 10, 10, 0), -# if __BYTE_ORDER == __LITTLE_ENDIAN - GL_FORMAT(GL_RGBA), - GL_TYPE(GL_UNSIGNED_INT_2_10_10_10_REV_EXT), -#endif - }, - { - DRM_FORMAT(ABGR2101010), - BITS_RGBA_FIXED(10, 10, 10, 2), - .opaque_substitute = DRM_FORMAT_XBGR2101010, -# if __BYTE_ORDER == __LITTLE_ENDIAN - GL_FORMAT(GL_RGBA), - GL_TYPE(GL_UNSIGNED_INT_2_10_10_10_REV_EXT), -#endif - }, - { - DRM_FORMAT(RGBX1010102), - BITS_RGBA_FIXED(10, 10, 10, 0), - }, - { - DRM_FORMAT(RGBA1010102), - BITS_RGBA_FIXED(10, 10, 10, 2), - .opaque_substitute = DRM_FORMAT_RGBX1010102, - }, - { - DRM_FORMAT(BGRX1010102), - BITS_RGBA_FIXED(10, 10, 10, 0), - }, - { - DRM_FORMAT(BGRA1010102), - BITS_RGBA_FIXED(10, 10, 10, 2), - .opaque_substitute = DRM_FORMAT_BGRX1010102, - }, - { - DRM_FORMAT(YUYV), - SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), - .num_planes = 1, - .hsub = 2, - }, - { - DRM_FORMAT(YVYU), - SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), - .num_planes = 1, - .chroma_order = ORDER_VU, - .hsub = 2, - }, - { - DRM_FORMAT(UYVY), - SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), - .num_planes = 1, - .luma_chroma_order = ORDER_CHROMA_LUMA, - .hsub = 2, - }, - { - DRM_FORMAT(VYUY), - SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), - .num_planes = 1, - .luma_chroma_order = ORDER_CHROMA_LUMA, - .chroma_order = ORDER_VU, - .hsub = 2, - }, - { - DRM_FORMAT(NV12), - SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), - .num_planes = 2, - .hsub = 2, - .vsub = 2, - }, - { - DRM_FORMAT(NV21), - SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), - .num_planes = 2, - .chroma_order = ORDER_VU, - .hsub = 2, - .vsub = 2, - }, - { - DRM_FORMAT(NV16), - SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), - .num_planes = 2, - .hsub = 2, - .vsub = 1, - }, - { - DRM_FORMAT(NV61), - SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), - .num_planes = 2, - .chroma_order = ORDER_VU, - .hsub = 2, - .vsub = 1, - }, - { - DRM_FORMAT(NV24), - SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), - .num_planes = 2, - }, - { - DRM_FORMAT(NV42), - SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), - .num_planes = 2, - .chroma_order = ORDER_VU, - }, - { - DRM_FORMAT(YUV410), - SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), - .num_planes = 3, - .hsub = 4, - .vsub = 4, - }, - { - DRM_FORMAT(YVU410), - SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), - .num_planes = 3, - .chroma_order = ORDER_VU, - .hsub = 4, - .vsub = 4, - }, - { - DRM_FORMAT(YUV411), - SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), - .num_planes = 3, - .hsub = 4, - .vsub = 1, - }, - { - DRM_FORMAT(YVU411), - SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), - .num_planes = 3, - .chroma_order = ORDER_VU, - .hsub = 4, - .vsub = 1, - }, - { - DRM_FORMAT(YUV420), - SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), - .num_planes = 3, - .hsub = 2, - .vsub = 2, - }, - { - DRM_FORMAT(YVU420), - SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), - .num_planes = 3, - .chroma_order = ORDER_VU, - .hsub = 2, - .vsub = 2, - }, - { - DRM_FORMAT(YUV422), - SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), - .num_planes = 3, - .hsub = 2, - .vsub = 1, - }, - { - DRM_FORMAT(YVU422), - SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), - .num_planes = 3, - .chroma_order = ORDER_VU, - .hsub = 2, - .vsub = 1, - }, - { - DRM_FORMAT(YUV444), - SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), - .num_planes = 3, - }, - { - DRM_FORMAT(YVU444), - SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), - .num_planes = 3, - .chroma_order = ORDER_VU, - }, -}; - -WL_EXPORT const struct pixel_format_info * -pixel_format_get_info_shm(uint32_t format) -{ - if (format == WL_SHM_FORMAT_XRGB8888) - return pixel_format_get_info(DRM_FORMAT_XRGB8888); - else if (format == WL_SHM_FORMAT_ARGB8888) - return pixel_format_get_info(DRM_FORMAT_ARGB8888); - else - return pixel_format_get_info(format); -} - -WL_EXPORT const struct pixel_format_info * -pixel_format_get_info(uint32_t format) -{ - unsigned int i; - - for (i = 0; i < ARRAY_LENGTH(pixel_format_table); i++) { - if (pixel_format_table[i].format == format) - return &pixel_format_table[i]; - } - - return NULL; -} - -WL_EXPORT const struct pixel_format_info * -pixel_format_get_info_by_drm_name(const char *drm_format_name) -{ - const struct pixel_format_info *info; - unsigned int i; - - for (i = 0; i < ARRAY_LENGTH(pixel_format_table); i++) { - info = &pixel_format_table[i]; - if (strcasecmp(info->drm_format_name, drm_format_name) == 0) - return info; - } - - return NULL; -} - -WL_EXPORT unsigned int -pixel_format_get_plane_count(const struct pixel_format_info *info) -{ - return info->num_planes ? info->num_planes : 1; -} - -WL_EXPORT bool -pixel_format_is_opaque(const struct pixel_format_info *info) -{ - return !info->opaque_substitute; -} - -WL_EXPORT const struct pixel_format_info * -pixel_format_get_opaque_substitute(const struct pixel_format_info *info) -{ - if (!info->opaque_substitute) - return info; - else - return pixel_format_get_info(info->opaque_substitute); -} - -WL_EXPORT const struct pixel_format_info * -pixel_format_get_info_by_opaque_substitute(uint32_t format) -{ - unsigned int i; - - for (i = 0; i < ARRAY_LENGTH(pixel_format_table); i++) { - if (pixel_format_table[i].opaque_substitute == format) - return &pixel_format_table[i]; - } - - return NULL; -} - -WL_EXPORT unsigned int -pixel_format_width_for_plane(const struct pixel_format_info *info, - unsigned int plane, - unsigned int width) -{ - /* We don't support any formats where the first plane is subsampled. */ - if (plane == 0 || !info->hsub) - return width; - - return width / info->hsub; -} - -WL_EXPORT unsigned int -pixel_format_height_for_plane(const struct pixel_format_info *info, - unsigned int plane, - unsigned int height) -{ - /* We don't support any formats where the first plane is subsampled. */ - if (plane == 0 || !info->vsub) - return height; - - return height / info->vsub; -} diff --git a/libweston/pixel-formats.h b/libweston/pixel-formats.h deleted file mode 100644 index 3e12526..0000000 --- a/libweston/pixel-formats.h +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright © 2016, 2019 Collabora, Ltd. - * Copyright (c) 2018 DisplayLink (UK) Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - * Author: Daniel Stone - */ - -#include -#include - -/** - * Contains information about pixel formats, mapping format codes from - * wl_shm and drm_fourcc.h (which are deliberately identical, but for the - * special cases of WL_SHM_ARGB8888 and WL_SHM_XRGB8888) into various - * sets of information. Helper functions are provided for dealing with these - * raw structures. - */ -struct pixel_format_info { - /** DRM/wl_shm format code */ - uint32_t format; - - /** The DRM format name without the DRM_FORMAT_ prefix. */ - const char *drm_format_name; - - /** If non-zero, number of planes in base (non-modified) format. */ - int num_planes; - - /** If format contains alpha channel, opaque equivalent of format, - * i.e. alpha channel replaced with X. */ - uint32_t opaque_substitute; - - /** How the format should be sampled, expressed in terms of tokens - * from the EGL_WL_bind_wayland_display extension. If not set, - * assumed to be either RGB or RGBA, depending on whether or not - * the format contains an alpha channel. The samplers may still - * return alpha even for opaque formats; users must manually set - * the alpha channel to 1.0 (or ignore it) if the format is - * opaque. */ - uint32_t sampler_type; - - /** GL format, if data can be natively/directly uploaded. Note that - * whilst DRM formats are little-endian unless explicitly specified, - * (i.e. DRM_FORMAT_ARGB8888 is stored BGRA as sequential bytes in - * memory), GL uses the sequential byte order, so that format maps to - * GL_BGRA_EXT plus GL_UNSIGNED_BYTE. To add to the confusion, the - * explicitly-sized types (e.g. GL_UNSIGNED_SHORT_5_5_5_1) read in - * machine-endian order, so for these types, the correspondence - * depends on endianness. */ - int gl_format; - - /** GL data type, if data can be natively/directly uploaded. */ - int gl_type; - - /** If set, this format can be used with the legacy drmModeAddFB() - * function (not AddFB2), using this and the bpp member. */ - int depth; - - /** See 'depth' member above. */ - int bpp; - - /** Horizontal subsampling; if non-zero, divide the width by this - * member to obtain the number of columns in the source buffer for - * secondary planes only. Stride is not affected by horizontal - * subsampling. */ - int hsub; - - /** Vertical subsampling; if non-zero, divide the height by this - * member to obtain the number of rows in the source buffer for - * secondary planes only. */ - int vsub; - - /* Ordering of chroma components. */ - enum { - ORDER_UV = 0, - ORDER_VU, - } chroma_order; - - /* If packed YUV (num_planes == 1), ordering of luma/chroma - * components. */ - enum { - ORDER_LUMA_CHROMA = 0, - ORDER_CHROMA_LUMA, - } luma_chroma_order; - - /** How many significant bits each channel has, or zero if N/A. */ - struct { - int r; - int g; - int b; - int a; - } bits; - - /** How channel bits are interpreted, fixed (uint) or floating-point */ - enum { - PIXEL_COMPONENT_TYPE_FIXED = 0, - PIXEL_COMPONENT_TYPE_FLOAT, - } component_type; -}; - -/** - * Get pixel format information for a DRM format code - * - * Given a DRM format code, return a pixel format info structure describing - * the properties of that format. - * - * @param format DRM format code to get info for - * @returns A pixel format structure (must not be freed), or NULL if the - * format could not be found - */ -const struct pixel_format_info * -pixel_format_get_info(uint32_t format); - -/** - * Get pixel format information for a SHM format code - * - * Given a SHM format code, return a DRM pixel format info structure describing - * the properties of that format. - * - * @param format SHM format code to get info for. - * @returns A pixel format structure (must not be freed), or NULL if the - * format could not be found. - */ -const struct pixel_format_info * -pixel_format_get_info_shm(uint32_t format); - -/** - * Get pixel format information for a named DRM format - * - * Given a DRM format name, return a pixel format info structure describing - * the properties of that format. - * - * The DRM format name is the preprocessor token name from drm_fourcc.h - * without the DRM_FORMAT_ prefix. The search is also case-insensitive. - * Both "xrgb8888" and "XRGB8888" searches will find DRM_FORMAT_XRGB8888 - * for example. - * - * @param drm_format_name DRM format name to get info for (not NULL) - * @returns A pixel format structure (must not be freed), or NULL if the - * name could not be found - */ -const struct pixel_format_info * -pixel_format_get_info_by_drm_name(const char *drm_format_name); - -/** - * Get number of planes used by a pixel format - * - * Given a pixel format info structure, return the number of planes - * required for a buffer. Note that this is not necessarily identical to - * the number of samplers required to be bound, as two views into a single - * plane are sometimes required. - * - * @param format Pixel format info structure - * @returns Number of planes required for the format - */ -unsigned int -pixel_format_get_plane_count(const struct pixel_format_info *format); - -/** - * Determine if a pixel format is opaque or contains alpha - * - * Returns whether or not the pixel format is opaque, or contains a - * significant alpha channel. Note that the suggested EGL sampler type may - * still sample undefined data into the alpha channel; users must consider - * alpha as 1.0 if the format is opaque, and not rely on the sampler to - * return this when sampling from the alpha channel. - * - * @param format Pixel format info structure - * @returns True if the format is opaque, or false if it has significant alpha - */ -bool -pixel_format_is_opaque(const struct pixel_format_info *format); - -/** - * Get compatible opaque equivalent for a format - * - * Given a pixel format info structure, return a format which is wholly - * compatible with the input format, but opaque, ignoring the alpha channel. - * If an alpha format is provided, but the content is known to all be opaque, - * then this can be used as a substitute to avoid blending. - * - * If the input format is opaque, this function will return the input format. - * - * @param format Pixel format info structure - * @returns A pixel format info structure for the compatible opaque substitute - */ -const struct pixel_format_info * -pixel_format_get_opaque_substitute(const struct pixel_format_info *format); - -/** - * For an opaque format, get the equivalent format with alpha instead of an - * ignored channel - * - * This is the opposite lookup from pixel_format_get_opaque_substitute(). - * Finds the format whose opaque substitute is the given format. - * - * If the input format is not opaque or does not have ignored (X) bits, then - * the search cannot find a match. - * - * @param format DRM format code to search for - * @returns A pixel format info structure for the pixel format whose opaque - * substitute is the argument, or NULL if no match. - */ -const struct pixel_format_info * -pixel_format_get_info_by_opaque_substitute(uint32_t format); - -/** - * Return the effective sampling width for a given plane - * - * When horizontal subsampling is effective, a sampler bound to a secondary - * plane must bind the sampler with a smaller effective width. This function - * returns the effective width to use for the sampler, i.e. dividing by hsub. - * - * If horizontal subsampling is not in effect, this will be equal to the - * width. - * - * @param format Pixel format info structure - * @param plane Zero-indexed plane number - * @param width Width of the buffer - * @returns Effective width for sampling - */ -unsigned int -pixel_format_width_for_plane(const struct pixel_format_info *format, - unsigned int plane, - unsigned int width); - -/** - * Return the effective sampling height for a given plane - * - * When vertical subsampling is in effect, a sampler bound to a secondary - * plane must bind the sampler with a smaller effective height. This function - * returns the effective height to use for the sampler, i.e. dividing by vsub. - * - * If vertical subsampling is not in effect, this will be equal to the height. - * - * @param format Pixel format info structure - * @param plane Zero-indexed plane number - * @param height Height of the buffer - * @returns Effective width for sampling - */ -unsigned int -pixel_format_height_for_plane(const struct pixel_format_info *format, - unsigned int plane, - unsigned int height); diff --git a/libweston/pixman-renderer-protected.h b/libweston/pixman-renderer-protected.h deleted file mode 100644 index d8ac85f..0000000 --- a/libweston/pixman-renderer-protected.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2021 Huawei Device Co., Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "pixman-renderer.h" - -#ifndef LIBWESTON_PIXMAN_RENDERER_PROTECTED_H -#define LIBWESTON_PIXMAN_RENDERER_PROTECTED_H - -struct pixman_output_state { - void *shadow_buffer; - pixman_image_t *shadow_image; - pixman_image_t *hw_buffer; - pixman_region32_t *hw_extra_damage; - struct tde_output_state_t *tde; -}; - -struct pixman_surface_state { - struct weston_surface *surface; - - pixman_image_t *image; - struct weston_buffer_reference buffer_ref; - struct weston_buffer_release_reference buffer_release_ref; - - struct wl_listener buffer_destroy_listener; - struct wl_listener surface_destroy_listener; - struct wl_listener renderer_destroy_listener; - struct tde_surface_state_t *tde; -}; - -struct pixman_renderer { - struct weston_renderer base; - - int repaint_debug; - pixman_image_t *debug_color; - struct weston_binding *debug_binding; - - struct wl_signal destroy_signal; - struct tde_renderer_t *tde; -}; - -struct pixman_surface_state * get_surface_state(struct weston_surface *surface); -struct pixman_renderer * get_renderer(struct weston_compositor *ec); -struct pixman_output_state * get_output_state(struct weston_output *output); - -#endif // LIBWESTON_PIXMAN_RENDERER_PROTECTED_H diff --git a/libweston/pixman-renderer.c b/libweston/pixman-renderer.c deleted file mode 100755 index fbdce23..0000000 --- a/libweston/pixman-renderer.c +++ /dev/null @@ -1,998 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * Copyright © 2013 Vasily Khoruzhick - * Copyright © 2015 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include - -#include "pixman-renderer.h" -#include "pixman-renderer-protected.h" -#include "shared/helpers.h" - -#include - -#include "tde-render-part.h" - -static int -pixman_renderer_create_surface(struct weston_surface *surface); - -struct pixman_output_state * -get_output_state(struct weston_output *output) -{ - return (struct pixman_output_state *)output->renderer_state; -} - -static int -pixman_renderer_create_surface(struct weston_surface *surface); - -struct pixman_surface_state * -get_surface_state(struct weston_surface *surface) -{ - if (!surface->renderer_state) - pixman_renderer_create_surface(surface); - - return (struct pixman_surface_state *)surface->renderer_state; -} - -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_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); - - pixman_image_composite32(PIXMAN_OP_SRC, - po->hw_buffer, /* src */ - NULL /* mask */, - out_buf, /* dest */ - x, y, /* 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_unref(out_buf); - - return 0; -} - -static void -region_global_to_output(struct weston_output *output, pixman_region32_t *region) -{ - if (output->zoom.active) { - weston_matrix_transform_region(region, &output->matrix, region); - } else { - pixman_region32_translate(region, -output->x, -output->y); - weston_transformed_region(output->width, output->height, - output->transform, - output->current_scale, - region, region); - } -} - -#define D2F(v) pixman_double_to_fixed((double)v) - -static void -weston_matrix_to_pixman_transform(pixman_transform_t *pt, - const struct weston_matrix *wm) -{ - /* Pixman supports only 2D transform matrix, but Weston uses 3D, * - * so we're omitting Z coordinate here. */ - pt->matrix[0][0] = pixman_double_to_fixed(wm->d[0]); - pt->matrix[0][1] = pixman_double_to_fixed(wm->d[4]); - pt->matrix[0][2] = pixman_double_to_fixed(wm->d[12]); - pt->matrix[1][0] = pixman_double_to_fixed(wm->d[1]); - pt->matrix[1][1] = pixman_double_to_fixed(wm->d[5]); - pt->matrix[1][2] = pixman_double_to_fixed(wm->d[13]); - pt->matrix[2][0] = pixman_double_to_fixed(wm->d[3]); - pt->matrix[2][1] = pixman_double_to_fixed(wm->d[7]); - pt->matrix[2][2] = pixman_double_to_fixed(wm->d[15]); -} - -static void -pixman_renderer_compute_transform(pixman_transform_t *transform_out, - struct weston_view *ev, - struct weston_output *output) -{ - struct weston_matrix matrix; - - /* Set up the source transformation based on the surface - position, the output position/transform/scale and the client - specified buffer transform/scale */ - matrix = output->inverse_matrix; - - if (ev->transform.enabled) { - weston_matrix_multiply(&matrix, &ev->transform.inverse); - } else { - weston_matrix_translate(&matrix, - -ev->geometry.x, -ev->geometry.y, 0); - } - - weston_matrix_multiply(&matrix, &ev->surface->surface_to_buffer_matrix); - - weston_matrix_to_pixman_transform(transform_out, &matrix); -} - -static bool -view_transformation_is_translation(struct weston_view *view) -{ - if (!view->transform.enabled) - return true; - - if (view->transform.matrix.type <= WESTON_MATRIX_TRANSFORM_TRANSLATE) - return true; - - return false; -} - -static void -region_intersect_only_translation(pixman_region32_t *result_global, - pixman_region32_t *global, - pixman_region32_t *surf, - struct weston_view *view) -{ - float view_x, view_y; - - assert(view_transformation_is_translation(view)); - - /* Convert from surface to global coordinates */ - pixman_region32_copy(result_global, surf); - weston_view_to_global_float(view, 0, 0, &view_x, &view_y); - pixman_region32_translate(result_global, (int)view_x, (int)view_y); - - pixman_region32_intersect(result_global, result_global, global); -} - -static void -composite_whole(pixman_op_t op, - pixman_image_t *src, - pixman_image_t *mask, - pixman_image_t *dest, - const pixman_transform_t *transform, - pixman_filter_t filter) -{ - int32_t dest_width; - int32_t dest_height; - - dest_width = pixman_image_get_width(dest); - dest_height = pixman_image_get_height(dest); - - pixman_image_set_transform(src, transform); - pixman_image_set_filter(src, filter, NULL, 0); - - /* bilinear filtering needs the equivalent of OpenGL CLAMP_TO_EDGE */ - if (filter == PIXMAN_FILTER_NEAREST) - pixman_image_set_repeat(src, PIXMAN_REPEAT_NONE); - else - pixman_image_set_repeat(src, PIXMAN_REPEAT_PAD); - - pixman_image_composite32(op, src, mask, dest, - 0, 0, /* src_x, src_y */ - 0, 0, /* mask_x, mask_y */ - 0, 0, /* dest_x, dest_y */ - dest_width, dest_height); -} - -static void -composite_clipped(pixman_image_t *src, - pixman_image_t *mask, - pixman_image_t *dest, - const pixman_transform_t *transform, - pixman_filter_t filter, - pixman_region32_t *src_clip) -{ - int n_box; - pixman_box32_t *boxes; - int32_t dest_width; - int32_t dest_height; - int src_stride; - int bitspp; - pixman_format_code_t src_format; - void *src_data; - int i; - - /* - * Hardcoded to use PIXMAN_OP_OVER, because sampling outside of - * a Pixman image produces (0,0,0,0) instead of discarding the - * fragment. - * - * Also repeat mode must be PIXMAN_REPEAT_NONE (the default) to - * actually sample (0,0,0,0). This may cause issues for clients that - * expect OpenGL CLAMP_TO_EDGE sampling behavior on their buffer. - * Using temporary 'boximg' it is not possible to apply CLAMP_TO_EDGE - * correctly with bilinear filter. Maybe trapezoid rendering could be - * the answer instead of source clip? - */ - - dest_width = pixman_image_get_width(dest); - dest_height = pixman_image_get_height(dest); - src_format = pixman_image_get_format(src); - src_stride = pixman_image_get_stride(src); - bitspp = PIXMAN_FORMAT_BPP(src_format); - src_data = pixman_image_get_data(src); - - assert(src_format); - - /* This would be massive overdraw, except when n_box is 1. */ - boxes = pixman_region32_rectangles(src_clip, &n_box); - for (i = 0; i < n_box; i++) { - uint8_t *ptr = src_data; - pixman_image_t *boximg; - pixman_transform_t adj = *transform; - - ptr += boxes[i].y1 * src_stride; - ptr += boxes[i].x1 * bitspp / 8; - boximg = pixman_image_create_bits_no_clear(src_format, - boxes[i].x2 - boxes[i].x1, - boxes[i].y2 - boxes[i].y1, - (uint32_t *)ptr, src_stride); - - pixman_transform_translate(&adj, NULL, - pixman_int_to_fixed(-boxes[i].x1), - pixman_int_to_fixed(-boxes[i].y1)); - pixman_image_set_transform(boximg, &adj); - - pixman_image_set_filter(boximg, filter, NULL, 0); - pixman_image_composite32(PIXMAN_OP_OVER, boximg, mask, dest, - 0, 0, /* src_x, src_y */ - 0, 0, /* mask_x, mask_y */ - 0, 0, /* dest_x, dest_y */ - dest_width, dest_height); - - pixman_image_unref(boximg); - } - - if (n_box > 1) { - static bool warned = false; - - if (!warned) - weston_log("Pixman-renderer warning: %dx overdraw\n", - n_box); - warned = true; - } -} - -/** Paint an intersected region - * - * \param ev The view to be painted. - * \param output The output being painted. - * \param repaint_output The region to be painted in output coordinates. - * \param source_clip The region of the source image to use, in source image - * coordinates. If NULL, use the whole source image. - * \param pixman_op Compositing operator, either SRC or OVER. - */ -static void -repaint_region(struct weston_view *ev, struct weston_output *output, - pixman_region32_t *repaint_output, - pixman_region32_t *source_clip, - pixman_op_t pixman_op) -{ - struct pixman_renderer *pr = - (struct pixman_renderer *) output->compositor->renderer; - struct pixman_surface_state *ps = get_surface_state(ev->surface); - struct pixman_output_state *po = get_output_state(output); - struct weston_buffer_viewport *vp = &ev->surface->buffer_viewport; - pixman_image_t *target_image; - pixman_transform_t transform; - pixman_filter_t filter; - pixman_image_t *mask_image; - pixman_color_t mask = { 0, }; - - if (po->shadow_image) - target_image = po->shadow_image; - else - target_image = po->hw_buffer; - - /* Clip rendering to the damaged output region */ - pixman_image_set_clip_region32(target_image, repaint_output); - - pixman_renderer_compute_transform(&transform, ev, output); - - if (ev->transform.enabled || output->current_scale != vp->buffer.scale) - filter = PIXMAN_FILTER_BILINEAR; - else - filter = PIXMAN_FILTER_NEAREST; - - if (ps->buffer_ref.buffer && ps->buffer_ref.buffer->shm_buffer) - wl_shm_buffer_begin_access(ps->buffer_ref.buffer->shm_buffer); - - if (ev->alpha < 1.0) { - mask.alpha = 0xffff * ev->alpha; - mask_image = pixman_image_create_solid_fill(&mask); - } else { - mask_image = NULL; - } - - if (source_clip) - composite_clipped(ps->image, mask_image, target_image, - &transform, filter, source_clip); - else - composite_whole(pixman_op, ps->image, mask_image, - target_image, &transform, filter); - - if (mask_image) - pixman_image_unref(mask_image); - - if (ps->buffer_ref.buffer && ps->buffer_ref.buffer->shm_buffer) - wl_shm_buffer_end_access(ps->buffer_ref.buffer->shm_buffer); - - if (pr->repaint_debug) - pixman_image_composite32(PIXMAN_OP_OVER, - pr->debug_color, /* src */ - NULL /* mask */, - target_image, /* dest */ - 0, 0, /* src_x, src_y */ - 0, 0, /* mask_x, mask_y */ - 0, 0, /* dest_x, dest_y */ - pixman_image_get_width (target_image), /* width */ - pixman_image_get_height (target_image) /* height */); - - pixman_image_set_clip_region32(target_image, NULL); -} - -static void -draw_view_translated(struct weston_view *view, struct weston_output *output, - pixman_region32_t *repaint_global) -{ - struct weston_surface *surface = view->surface; - /* non-opaque region in surface coordinates: */ - pixman_region32_t surface_blend; - /* region to be painted in output coordinates: */ - pixman_region32_t repaint_output; - - pixman_region32_init(&repaint_output); - - /* Blended region is whole surface minus opaque region, - * unless surface alpha forces us to blend all. - */ - pixman_region32_init_rect(&surface_blend, 0, 0, - surface->width, surface->height); - - if (!(view->alpha < 1.0)) { - pixman_region32_subtract(&surface_blend, &surface_blend, - &surface->opaque); - - if (pixman_region32_not_empty(&surface->opaque)) { - region_intersect_only_translation(&repaint_output, - repaint_global, - &surface->opaque, - view); - region_global_to_output(output, &repaint_output); - - repaint_region(view, output, &repaint_output, NULL, - PIXMAN_OP_SRC); - } - } - - if (pixman_region32_not_empty(&surface_blend)) { - region_intersect_only_translation(&repaint_output, - repaint_global, - &surface_blend, view); - region_global_to_output(output, &repaint_output); - - // OHOS TDE - if (tde_repaint_region_hook(view, output, &surface_blend, &repaint_output) != 0) { - repaint_region(view, output, &repaint_output, NULL, - PIXMAN_OP_OVER); - } - } - - pixman_region32_fini(&surface_blend); - pixman_region32_fini(&repaint_output); -} - -static void -draw_view_source_clipped(struct weston_view *view, - struct weston_output *output, - pixman_region32_t *repaint_global) -{ - struct weston_surface *surface = view->surface; - pixman_region32_t surf_region; - pixman_region32_t buffer_region; - pixman_region32_t repaint_output; - - /* Do not bother separating the opaque region from non-opaque. - * Source clipping requires PIXMAN_OP_OVER in all cases, so painting - * opaque separately has no benefit. - */ - - pixman_region32_init_rect(&surf_region, 0, 0, - surface->width, surface->height); - if (view->geometry.scissor_enabled) - pixman_region32_intersect(&surf_region, &surf_region, - &view->geometry.scissor); - - pixman_region32_init(&buffer_region); - weston_surface_to_buffer_region(surface, &surf_region, &buffer_region); - - pixman_region32_init(&repaint_output); - pixman_region32_copy(&repaint_output, repaint_global); - region_global_to_output(output, &repaint_output); - - // OHOS TDE - if (tde_repaint_region_hook(view, output, &buffer_region, &repaint_output) != 0) { - repaint_region(view, output, &repaint_output, &buffer_region, - PIXMAN_OP_OVER); - } - - pixman_region32_fini(&repaint_output); - pixman_region32_fini(&buffer_region); - pixman_region32_fini(&surf_region); -} - -static void -draw_view(struct weston_view *ev, struct weston_output *output, - pixman_region32_t *damage) /* in global coordinates */ -{ - struct pixman_surface_state *ps = get_surface_state(ev->surface); - /* repaint bounding region in global coordinates: */ - pixman_region32_t repaint; - - /* No buffer attached */ - if (!ps->image) - return; - - pixman_region32_init(&repaint); - pixman_region32_intersect(&repaint, - &ev->transform.boundingbox, damage); - pixman_region32_subtract(&repaint, &repaint, &ev->clip); - - if (!pixman_region32_not_empty(&repaint)) - goto out; - - if (view_transformation_is_translation(ev)) { - /* The simple case: The surface regions opaque, non-opaque, - * etc. are convertible to global coordinate space. - * There is no need to use a source clip region. - * It is possible to paint opaque region as PIXMAN_OP_SRC. - * Also the boundingbox is accurate rather than an - * approximation. - */ - draw_view_translated(ev, output, &repaint); - } else { - /* The complex case: the view transformation does not allow - * converting opaque etc. regions into global coordinate space. - * Therefore we need source clipping to avoid sampling from - * unwanted source image areas, unless the source image is - * to be used whole. Source clipping does not work with - * PIXMAN_OP_SRC. - */ - draw_view_source_clipped(ev, output, &repaint); - } - -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_view *view; - - wl_list_for_each_reverse(view, &compositor->view_list, link) - if (view->plane == &compositor->primary_plane) - draw_view(view, 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_region32_fini(&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); - pixman_region32_t hw_damage; - - if (!po->hw_buffer) { - po->hw_extra_damage = NULL; - return; - } - - pixman_region32_init(&hw_damage); - if (po->hw_extra_damage) { - pixman_region32_union(&hw_damage, - po->hw_extra_damage, output_damage); - po->hw_extra_damage = NULL; - } else { - pixman_region32_copy(&hw_damage, output_damage); - } - - if (po->shadow_image) { - repaint_surfaces(output, output_damage); - copy_to_hw_buffer(output, &hw_damage); - } else { - repaint_surfaces(output, &hw_damage); - } - pixman_region32_fini(&hw_damage); - - wl_signal_emit(&output->frame_signal, output_damage); - - /* Actual flip should be done by caller */ -} - -static void -pixman_renderer_flush_damage(struct weston_surface *surface) -{ - /* No-op for pixman renderer */ -} - -static void -buffer_state_handle_buffer_destroy(struct wl_listener *listener, void *data) -{ - struct pixman_surface_state *ps; - - ps = container_of(listener, struct pixman_surface_state, - buffer_destroy_listener); - - if (ps->image) { - tde_unref_image_hook(ps->image); - pixman_image_unref(ps->image); - ps->image = NULL; - } - - ps->buffer_destroy_listener.notify = NULL; -} - -static void -pixman_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) -{ - // OHOS TDE - if (tde_render_attach_hook(es, buffer) == 0) { - return; - } - - 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); - weston_buffer_release_reference(&ps->buffer_release_ref, - es->buffer_release_ref.buffer_release); - - if (ps->buffer_destroy_listener.notify) { - wl_list_remove(&ps->buffer_destroy_listener.link); - ps->buffer_destroy_listener.notify = NULL; - } - - 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); - weston_buffer_release_reference(&ps->buffer_release_ref, NULL); - return; - } - - switch (wl_shm_buffer_get_format(shm_buffer)) { - case WL_SHM_FORMAT_XRGB8888: - pixman_format = PIXMAN_x8r8g8b8; - es->is_opaque = true; - break; - case WL_SHM_FORMAT_ARGB8888: - pixman_format = PIXMAN_a8r8g8b8; - es->is_opaque = false; - break; - case WL_SHM_FORMAT_RGB565: - pixman_format = PIXMAN_r5g6b5; - es->is_opaque = true; - break; - default: - weston_log("Unsupported SHM buffer format 0x%x\n", - wl_shm_buffer_get_format(shm_buffer)); - weston_buffer_reference(&ps->buffer_ref, NULL); - weston_buffer_release_reference(&ps->buffer_release_ref, NULL); - weston_buffer_send_server_error(buffer, - "disconnecting due to unhandled buffer type"); - 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)); - - ps->buffer_destroy_listener.notify = - buffer_state_handle_buffer_destroy; - wl_signal_add(&buffer->destroy_signal, - &ps->buffer_destroy_listener); -} - -static void -pixman_renderer_surface_state_destroy(struct pixman_surface_state *ps) -{ - wl_list_remove(&ps->surface_destroy_listener.link); - wl_list_remove(&ps->renderer_destroy_listener.link); - if (ps->buffer_destroy_listener.notify) { - wl_list_remove(&ps->buffer_destroy_listener.link); - ps->buffer_destroy_listener.notify = NULL; - } - - ps->surface->renderer_state = NULL; - - if (ps->image) { - tde_unref_image_hook(ps->image); - pixman_image_unref(ps->image); - ps->image = NULL; - } - weston_buffer_reference(&ps->buffer_ref, NULL); - weston_buffer_release_reference(&ps->buffer_release_ref, NULL); - tde_surface_state_free_hook(ps); - free(ps); -} - -static void -surface_state_handle_surface_destroy(struct wl_listener *listener, void *data) -{ - struct pixman_surface_state *ps; - - ps = container_of(listener, struct pixman_surface_state, - surface_destroy_listener); - - pixman_renderer_surface_state_destroy(ps); -} - -static void -surface_state_handle_renderer_destroy(struct wl_listener *listener, void *data) -{ - struct pixman_surface_state *ps; - - ps = container_of(listener, struct pixman_surface_state, - renderer_destroy_listener); - - pixman_renderer_surface_state_destroy(ps); -} - -int -pixman_renderer_create_surface(struct weston_surface *surface) -{ - struct pixman_surface_state *ps; - struct pixman_renderer *pr = get_renderer(surface->compositor); - - ps = zalloc(sizeof *ps); - if (ps == NULL) - return -1; - - tde_surface_state_alloc_hook(ps); - - surface->renderer_state = ps; - - ps->surface = surface; - - ps->surface_destroy_listener.notify = - surface_state_handle_surface_destroy; - wl_signal_add(&surface->destroy_signal, - &ps->surface_destroy_listener); - - ps->renderer_destroy_listener.notify = - surface_state_handle_renderer_destroy; - wl_signal_add(&pr->destroy_signal, - &ps->renderer_destroy_listener); - - 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) { - tde_unref_image_hook(ps->image); - pixman_image_unref(ps->image); - ps->image = NULL; - } - - ps->image = pixman_image_create_solid_fill(&color); -} - -static void -pixman_renderer_destroy(struct weston_compositor *ec) -{ - struct pixman_renderer *pr = get_renderer(ec); - - wl_signal_emit(&pr->destroy_signal, pr); - - // OHOS remove debugger - if (pr->debug_binding != NULL) - weston_binding_destroy(pr->debug_binding); - - // OHOS TDE - tde_renderer_free_hook(pr); - free(pr); - - ec->renderer = NULL; -} - -static void -pixman_renderer_surface_get_content_size(struct weston_surface *surface, - int *width, int *height) -{ - struct pixman_surface_state *ps = get_surface_state(surface); - - if (ps->image) { - *width = pixman_image_get_width(ps->image); - *height = pixman_image_get_height(ps->image); - } else { - *width = 0; - *height = 0; - } -} - -static int -pixman_renderer_surface_copy_content(struct weston_surface *surface, - void *target, size_t size, - int src_x, int src_y, - int width, int height) -{ - const pixman_format_code_t format = PIXMAN_a8b8g8r8; - const size_t bytespp = 4; /* PIXMAN_a8b8g8r8 */ - struct pixman_surface_state *ps = get_surface_state(surface); - pixman_image_t *out_buf; - - if (!ps->image) - return -1; - - out_buf = pixman_image_create_bits(format, width, height, - target, width * bytespp); - - pixman_image_set_transform(ps->image, NULL); - pixman_image_composite32(PIXMAN_OP_SRC, - ps->image, /* src */ - NULL, /* mask */ - out_buf, /* dest */ - src_x, src_y, /* src_x, src_y */ - 0, 0, /* mask_x, mask_y */ - 0, 0, /* dest_x, dest_y */ - width, height); - - pixman_image_unref(out_buf); - - return 0; -} - -// OHOS debugger -//static void -//debug_binding(struct weston_keyboard *keyboard, const struct timespec *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); -// } -//} - -static bool pixman_renderer_import_dmabuf(struct weston_compositor *ec, struct linux_dmabuf_buffer *dmabuf); -static void pixman_renderer_query_dmabuf_formats(struct weston_compositor *wc, int **formats, int *num_formats); -static void pixman_renderer_query_dmabuf_modifiers(struct weston_compositor *wc, int format, uint64_t **modifiers, int *num_modifiers); - -WL_EXPORT int -pixman_renderer_init(struct weston_compositor *ec) -{ - struct pixman_renderer *renderer; - - renderer = zalloc(sizeof *renderer); - if (renderer == NULL) - return -1; - - // OHOS TDE - tde_renderer_alloc_hook(renderer); - - 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.surface_set_color = pixman_renderer_surface_set_color; - renderer->base.destroy = pixman_renderer_destroy; - renderer->base.surface_get_content_size = - pixman_renderer_surface_get_content_size; - renderer->base.surface_copy_content = - pixman_renderer_surface_copy_content; - ec->renderer = &renderer->base; - ec->capabilities |= WESTON_CAP_ROTATION_ANY; - ec->capabilities |= WESTON_CAP_VIEW_CLIP_MASK; - - // OHOS remove debugger - //renderer->debug_binding = - // weston_compositor_add_debug_binding(ec, KEY_R, - // debug_binding, ec); - - wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_RGB565); - - wl_signal_init(&renderer->destroy_signal); - - 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 void -pixman_renderer_output_set_hw_extra_damage(struct weston_output *output, - pixman_region32_t *extra_damage) -{ - struct pixman_output_state *po = get_output_state(output); - - po->hw_extra_damage = extra_damage; -} - -WL_EXPORT int -pixman_renderer_output_create(struct weston_output *output, - const struct pixman_renderer_output_options *options) -{ - struct pixman_output_state *po; - int w, h; - - po = zalloc(sizeof *po); - if (po == NULL) - return -1; - - // OHOS TDE - tde_output_state_alloc_hook(po); - - if (options->use_shadow) { - /* 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) { - tde_output_state_free_hook(po); - 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); - tde_output_state_free_hook(po); - 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); - - if (po->shadow_image) - pixman_image_unref(po->shadow_image); - - if (po->hw_buffer) - pixman_image_unref(po->hw_buffer); - - free(po->shadow_buffer); - - po->shadow_buffer = NULL; - po->shadow_image = NULL; - po->hw_buffer = NULL; - - tde_output_state_free_hook(po); - free(po); -} diff --git a/libweston/pixman-renderer.h b/libweston/pixman-renderer.h deleted file mode 100644 index c0ebc83..0000000 --- a/libweston/pixman-renderer.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright © 2013 Vasily Khoruzhick - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include "backend.h" -#include "libweston-internal.h" - -#ifndef LIBWESTON_PIXMAN_RENDERER_H -#define LIBWESTON_PIXMAN_RENDERER_H - -int -pixman_renderer_init(struct weston_compositor *ec); - -struct pixman_renderer_output_options { - /** Composite into a shadow buffer, copying to the hardware buffer */ - bool use_shadow; -}; - -int -pixman_renderer_output_create(struct weston_output *output, - const struct pixman_renderer_output_options *options); - -void -pixman_renderer_output_set_buffer(struct weston_output *output, - pixman_image_t *buffer); - -void -pixman_renderer_output_set_hw_extra_damage(struct weston_output *output, - pixman_region32_t *extra_damage); - -void -pixman_renderer_output_destroy(struct weston_output *output); - -#endif // LIBWESTON_PIXMAN_RENDERER_H diff --git a/libweston/plugin-registry.c b/libweston/plugin-registry.c deleted file mode 100644 index f5ec6fa..0000000 --- a/libweston/plugin-registry.c +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2016 DENSO CORPORATION - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include - -#include -#include - -struct weston_plugin_api { - struct wl_list link; /**< in weston_compositor::plugin_api_list */ - - char *api_name; /**< The search key */ - const void *vtable; /**< The function table */ - size_t vtable_size; /**< Size of the function table in bytes */ -}; - -/** Register an implementation of an API - * - * \param compositor The compositor instance. - * \param api_name The API name which other plugins use to find the - * implementation. - * \param vtable Pointer to the function table of the API. - * \param vtable_size Size of the function table in bytes. - * \return 0 on success, -1 on error, -2 if api_name already registered - * - * This call makes the given vtable to be reachable via - * weston_plugin_api_get(). Calls through the vtable may start happening - * as soon as the caller returns after success. Argument vtable must not be - * NULL. Argument api_name must be non-NULL and non-zero length. - * - * You can increase the function table size without breaking the ABI. - * To cater for ABI breaks, it is recommended to have api_name include a - * version number. - * - * A registered API cannot be unregistered. However, calls through a - * registered API must not be made from the compositor destroy signal handlers. - */ -WL_EXPORT int -weston_plugin_api_register(struct weston_compositor *compositor, - const char *api_name, - const void *vtable, - size_t vtable_size) -{ - struct weston_plugin_api *wpa; - - assert(api_name); - assert(strlen(api_name) > 0); - assert(vtable); - - if (!api_name || !vtable || strlen(api_name) == 0) - return -1; - - wl_list_for_each(wpa, &compositor->plugin_api_list, link) - if (strcmp(wpa->api_name, api_name) == 0) - return -2; - - wpa = zalloc(sizeof(*wpa)); - if (!wpa) - return -1; - - wpa->api_name = strdup(api_name); - wpa->vtable = vtable; - wpa->vtable_size = vtable_size; - - if (!wpa->api_name) { - free(wpa); - - return -1; - } - - wl_list_insert(&compositor->plugin_api_list, &wpa->link); - weston_log("Registered plugin API '%s' of size %zd\n", - wpa->api_name, wpa->vtable_size); - - return 0; -} - -/** Internal function to free registered APIs - * - * \param compositor The compositor instance. - */ -void -weston_plugin_api_destroy_list(struct weston_compositor *compositor) -{ - struct weston_plugin_api *wpa, *tmp; - - wl_list_for_each_safe(wpa, tmp, &compositor->plugin_api_list, link) { - free(wpa->api_name); - wl_list_remove(&wpa->link); - free(wpa); - } -} - -/** Fetch the implementation of an API - * - * \param compositor The compositor instance. - * \param api_name The name of the API to search for. - * \param vtable_size The expected function table size in bytes. - * \return Pointer to the function table, or NULL on error. - * - * Find the function table corresponding to api_name. The vtable_size here - * must be less or equal to the vtable_size given in the corresponding - * weston_plugin_api_register() call made by the implementing plugin. - * - * Calls can be made through the function table immediately. However, calls - * must not be made from or after the compositor destroy signal handler. - */ -WL_EXPORT const void * -weston_plugin_api_get(struct weston_compositor *compositor, - const char *api_name, - size_t vtable_size) -{ - struct weston_plugin_api *wpa; - - assert(api_name); - if (!api_name) - return NULL; - - wl_list_for_each(wpa, &compositor->plugin_api_list, link) { - if (strcmp(wpa->api_name, api_name) != 0) - continue; - - if (vtable_size <= wpa->vtable_size) - return wpa->vtable; - - return NULL; - } - - return NULL; -} diff --git a/libweston/renderer-gl/egl-glue.c b/libweston/renderer-gl/egl-glue.c deleted file mode 100644 index 28be4ff..0000000 --- a/libweston/renderer-gl/egl-glue.c +++ /dev/null @@ -1,693 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * Copyright © 2015, 2019 Collabora, Ltd. - * Copyright © 2016 NVIDIA Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include - -#include "shared/helpers.h" -#include "shared/platform.h" - -#include "gl-renderer.h" -#include "gl-renderer-internal.h" -#include "pixel-formats.h" -#include "shared/weston-egl-ext.h" - -#include - -struct egl_config_print_info { - const EGLint *attrs; - unsigned attrs_count; - const char *prefix; - const char *separator; - int field_width; -}; - -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 -} - -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); -} - -static void -print_egl_surface_type_bits(FILE *fp, EGLint egl_surface_type) -{ - const char *sep = ""; - unsigned i; - - static const struct { - EGLint bit; - const char *str; - } egl_surf_bits[] = { - { EGL_WINDOW_BIT, "win" }, - { EGL_PIXMAP_BIT, "pix" }, - { EGL_PBUFFER_BIT, "pbf" }, - { EGL_MULTISAMPLE_RESOLVE_BOX_BIT, "ms_resolve_box" }, - { EGL_SWAP_BEHAVIOR_PRESERVED_BIT, "swap_preserved" }, - }; - - for (i = 0; i < ARRAY_LENGTH(egl_surf_bits); i++) { - if (egl_surface_type & egl_surf_bits[i].bit) { - fprintf(fp, "%s%s", sep, egl_surf_bits[i].str); - sep = "|"; - } - } -} - -static const struct egl_config_print_info config_info_ints[] = { -#define ARRAY(...) ((const EGLint[]) { __VA_ARGS__ }) - - { ARRAY(EGL_CONFIG_ID), 1, "id: ", "", 3 }, - { ARRAY(EGL_RED_SIZE, EGL_GREEN_SIZE, EGL_BLUE_SIZE, EGL_ALPHA_SIZE), 4, - "rgba: ", " ", 1 }, - { ARRAY(EGL_BUFFER_SIZE), 1, "buf: ", "", 2 }, - { ARRAY(EGL_DEPTH_SIZE), 1, "dep: ", "", 2 }, - { ARRAY(EGL_STENCIL_SIZE), 1, "stcl: ", "", 1 }, - { ARRAY(EGL_MIN_SWAP_INTERVAL, EGL_MAX_SWAP_INTERVAL), 2, - "int: ", "-", 1 }, - -#undef ARRAY -}; - -static void -print_egl_config_ints(FILE *fp, EGLDisplay egldpy, EGLConfig eglconfig) -{ - unsigned i; - - for (i = 0; i < ARRAY_LENGTH(config_info_ints); i++) { - const struct egl_config_print_info *info = &config_info_ints[i]; - unsigned j; - const char *sep = ""; - - fputs(info->prefix, fp); - for (j = 0; j < info->attrs_count; j++) { - EGLint value; - - if (eglGetConfigAttrib(egldpy, eglconfig, - info->attrs[j], &value)) { - fprintf(fp, "%s%*d", - sep, info->field_width, value); - } else { - fprintf(fp, "%s!", sep); - } - sep = info->separator; - } - - fputs(" ", fp); - } -} - -static void -print_egl_config_info(FILE *fp, EGLDisplay egldpy, EGLConfig eglconfig) -{ - EGLint value; - - print_egl_config_ints(fp, egldpy, eglconfig); - - fputs("type: ", fp); - if (eglGetConfigAttrib(egldpy, eglconfig, EGL_SURFACE_TYPE, &value)) - print_egl_surface_type_bits(fp, value); - else - fputs("-", fp); - - fputs(" vis_id: ", fp); - if (eglGetConfigAttrib(egldpy, eglconfig, EGL_NATIVE_VISUAL_ID, &value)) { - if (value != 0) { - const struct pixel_format_info *p; - - p = pixel_format_get_info(value); - if (p) { - fprintf(fp, "%s (0x%x)", - p->drm_format_name, (unsigned)value); - } else { - fprintf(fp, "0x%x", (unsigned)value); - } - } else { - fputs("0", fp); - } - } else { - fputs("-", fp); - } -} - -static void -log_all_egl_configs(EGLDisplay egldpy) -{ - EGLint count = 0; - EGLConfig *configs; - int i; - char *strbuf = NULL; - size_t strsize = 0; - FILE *fp; - - weston_log("All available EGLConfigs:\n"); - - if (!eglGetConfigs(egldpy, NULL, 0, &count) || count < 1) - return; - - configs = calloc(count, sizeof *configs); - if (!configs) - return; - - if (!eglGetConfigs(egldpy, configs, count, &count)) - return; - - fp = open_memstream(&strbuf, &strsize); - if (!fp) - goto out; - - for (i = 0; i < count; i++) { - print_egl_config_info(fp, egldpy, configs[i]); - fputc(0, fp); - fflush(fp); - weston_log_continue(STAMP_SPACE "%s\n", strbuf); - rewind(fp); - } - - fclose(fp); - free(strbuf); - -out: - free(configs); -} - -void -log_egl_config_info(EGLDisplay egldpy, EGLConfig eglconfig) -{ - char *strbuf = NULL; - size_t strsize = 0; - FILE *fp; - - fp = open_memstream(&strbuf, &strsize); - if (fp) { - print_egl_config_info(fp, egldpy, eglconfig); - fclose(fp); - } - - weston_log("Chosen EGL config details: %s\n", strbuf ? strbuf : "?"); - free(strbuf); -} - -static bool -egl_config_pixel_format_matches(struct gl_renderer *gr, - EGLConfig config, - const struct pixel_format_info *pinfo) -{ - static const EGLint attribs[4] = { - EGL_ALPHA_SIZE, EGL_RED_SIZE, EGL_GREEN_SIZE, EGL_BLUE_SIZE - }; - const int *argb[4] = { - &pinfo->bits.a, &pinfo->bits.r, &pinfo->bits.g, &pinfo->bits.b - }; - unsigned i; - EGLint value; - - if (gr->platform == EGL_PLATFORM_GBM_KHR) { - if (!eglGetConfigAttrib(gr->egl_display, config, - EGL_NATIVE_VISUAL_ID, &value)) - return false; - - return ((uint32_t)value) == pinfo->format; - } - - for (i = 0; i < 4; i++) { - if (!eglGetConfigAttrib(gr->egl_display, config, - attribs[i], &value)) - return false; - if (value != *argb[i]) - return false; - } - - return true; -} - -static int -egl_choose_config(struct gl_renderer *gr, - const EGLint *attribs, - const struct pixel_format_info *const *pinfo, - unsigned pinfo_count, - EGLConfig *config_out) -{ - EGLint count = 0; - EGLint matched = 0; - EGLConfig *configs; - unsigned i; - EGLint j; - int config_index = -1; - - if (!eglGetConfigs(gr->egl_display, NULL, 0, &count) || count < 1) { - weston_log("No EGL configs to choose from.\n"); - return -1; - } - configs = calloc(count, sizeof *configs); - if (!configs) - return -1; - - if (!eglChooseConfig(gr->egl_display, attribs, configs, - count, &matched) || !matched) { - weston_log("No EGL configs with appropriate attributes.\n"); - goto out; - } - - if (pinfo_count == 0) - config_index = 0; - - for (i = 0; config_index == -1 && i < pinfo_count; i++) - for (j = 0; config_index == -1 && j < matched; j++) - if (egl_config_pixel_format_matches(gr, configs[j], - pinfo[i])) - config_index = j; - - if (config_index != -1) - *config_out = configs[config_index]; - -out: - free(configs); - if (config_index == -1) - return -1; - - if (i > 1) - weston_log("Unable to use first choice EGL config with" - " %s, succeeded with alternate %s.\n", - pinfo[0]->drm_format_name, - pinfo[i - 1]->drm_format_name); - return 0; -} - -static bool -egl_config_is_compatible(struct gl_renderer *gr, - EGLConfig config, - EGLint egl_surface_type, - const struct pixel_format_info *const *pinfo, - unsigned pinfo_count) -{ - EGLint value; - unsigned i; - - if (config == EGL_NO_CONFIG_KHR) - return false; - - if (!eglGetConfigAttrib(gr->egl_display, config, - EGL_SURFACE_TYPE, &value)) - return false; - if ((value & egl_surface_type) != egl_surface_type) - return false; - - for (i = 0; i < pinfo_count; i++) { - if (egl_config_pixel_format_matches(gr, config, pinfo[i])) - return true; - } - return false; -} - -/* The caller must free() the string */ -static char * -explain_egl_config_criteria(EGLint egl_surface_type, - const struct pixel_format_info *const *pinfo, - unsigned pinfo_count) -{ - FILE *fp; - char *str = NULL; - size_t size = 0; - const char *sep; - unsigned i; - - fp = open_memstream(&str, &size); - if (!fp) - return NULL; - - fputs("{ ", fp); - - print_egl_surface_type_bits(fp, egl_surface_type); - fputs("; ", fp); - - sep = ""; - for (i = 0; i < pinfo_count; i++) { - fprintf(fp, "%s%s", sep, pinfo[i]->drm_format_name); - sep = ", "; - } - - fputs(" }", fp); - - fclose(fp); - - return str; -} - -EGLConfig -gl_renderer_get_egl_config(struct gl_renderer *gr, - EGLint egl_surface_type, - const uint32_t *drm_formats, - unsigned drm_formats_count) -{ - EGLConfig egl_config; - const struct pixel_format_info *pinfo[16]; - unsigned pinfo_count; - unsigned i; - char *what; - EGLint config_attribs[] = { - EGL_SURFACE_TYPE, egl_surface_type, - EGL_RED_SIZE, 1, - EGL_GREEN_SIZE, 1, - EGL_BLUE_SIZE, 1, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_NONE - }; - - assert(drm_formats_count < ARRAY_LENGTH(pinfo)); - drm_formats_count = MIN(drm_formats_count, ARRAY_LENGTH(pinfo)); - - for (pinfo_count = 0, i = 0; i < drm_formats_count; i++) { - pinfo[pinfo_count] = pixel_format_get_info(drm_formats[i]); - if (!pinfo[pinfo_count]) { - weston_log("Bad/unknown DRM format code 0x%08x.\n", - drm_formats[i]); - continue; - } - pinfo_count++; - } - - if (egl_config_is_compatible(gr, gr->egl_config, egl_surface_type, - pinfo, pinfo_count)) - return gr->egl_config; - - if (egl_choose_config(gr, config_attribs, pinfo, pinfo_count, - &egl_config) < 0) { - what = explain_egl_config_criteria(egl_surface_type, - pinfo, pinfo_count); - weston_log("No EGLConfig matches %s.\n", what); - free(what); - log_all_egl_configs(gr->egl_display); - return EGL_NO_CONFIG_KHR; - } - - /* - * If we do not have configless context support, all EGLConfigs must - * be the one and the same, because we use just one GL context for - * everything. - */ - if (gr->egl_config != EGL_NO_CONFIG_KHR && - egl_config != gr->egl_config) { - what = explain_egl_config_criteria(egl_surface_type, - pinfo, pinfo_count); - weston_log("Found an EGLConfig matching %s but it is not usable" - " because neither EGL_KHR_no_config_context nor " - "EGL_MESA_configless_context are supported by EGL.\n", - what); - free(what); - return EGL_NO_CONFIG_KHR; - } - - return egl_config; -} - -int -gl_renderer_setup_egl_display(struct gl_renderer *gr, - void *native_display) -{ - gr->egl_display = NULL; - - /* extension_suffix is supported */ - if (gr->has_platform_base) - gr->egl_display = gr->get_platform_display(gr->platform, - native_display, - NULL); - - if (!gr->egl_display) { - weston_log("warning: either no EGL_EXT_platform_base " - "support or specific platform support; " - "falling back to eglGetDisplay.\n"); - gr->egl_display = eglGetDisplay(native_display); - } - - if (!gr->egl_display) { - weston_log("failed to create display\n"); - return -1; - } - - if (!eglInitialize(gr->egl_display, NULL, NULL)) { - weston_log("failed to initialize display\n"); - goto fail; - } - - return 0; - -fail: - gl_renderer_print_egl_error_state(); - return -1; -} - -static const char * -platform_to_extension(EGLenum platform) -{ - switch (platform) { - case EGL_PLATFORM_GBM_KHR: - return "gbm"; - case EGL_PLATFORM_WAYLAND_KHR: - return "wayland"; - case EGL_PLATFORM_X11_KHR: - return "x11"; - case EGL_PLATFORM_SURFACELESS_MESA: - return "surfaceless"; - default: - assert(0 && "bad EGL platform enum"); - } -} - -/** Checks for EGL client extensions (i.e. independent of EGL display), - * loads the function pointers, and checks if the platform is supported. - * - * \param gr The OpenGL renderer - * \return 0 for success, -1 if platform is unsupported - * - * This function checks whether a specific platform_* extension is supported - * by EGL by checking in order "EGL_KHR_platform_foo", "EGL_EXT_platform_foo", - * and "EGL_MESA_platform_foo" in order, for some platform "foo". - */ -int -gl_renderer_setup_egl_client_extensions(struct gl_renderer *gr) -{ - const char *extensions; - const char *extension_suffix = platform_to_extension(gr->platform); - char s[64]; - - extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); - if (!extensions) { - weston_log("Retrieving EGL client extension string failed.\n"); - return 0; - } - - gl_renderer_log_extensions("EGL client extensions", - extensions); - - if (weston_check_egl_extension(extensions, "EGL_EXT_platform_base")) { - gr->get_platform_display = - (void *) eglGetProcAddress("eglGetPlatformDisplayEXT"); - gr->create_platform_window = - (void *) eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"); - gr->has_platform_base = true; - } else { - weston_log("warning: EGL_EXT_platform_base not supported.\n"); - - /* Surfaceless is unusable without platform_base extension */ - if (gr->platform == EGL_PLATFORM_SURFACELESS_MESA) - return -1; - - return 0; - } - - snprintf(s, sizeof s, "EGL_KHR_platform_%s", extension_suffix); - if (weston_check_egl_extension(extensions, s)) - return 0; - - snprintf(s, sizeof s, "EGL_EXT_platform_%s", extension_suffix); - if (weston_check_egl_extension(extensions, s)) - return 0; - - snprintf(s, sizeof s, "EGL_MESA_platform_%s", extension_suffix); - if (weston_check_egl_extension(extensions, s)) - return 0; - - /* at this point we definitely have some platform extensions but - * haven't found the supplied platform, so chances are it's - * not supported. */ - - return -1; -} - -int -gl_renderer_setup_egl_extensions(struct weston_compositor *ec) -{ - static const struct { - char *extension, *entrypoint; - } swap_damage_ext_to_entrypoint[] = { - { - .extension = "EGL_EXT_swap_buffers_with_damage", - .entrypoint = "eglSwapBuffersWithDamageEXT", - }, - { - .extension = "EGL_KHR_swap_buffers_with_damage", - .entrypoint = "eglSwapBuffersWithDamageKHR", - }, - }; - struct gl_renderer *gr = get_renderer(ec); - const char *extensions; - EGLBoolean ret; - unsigned i; - - 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"); - gr->set_damage_region = - (void *) eglGetProcAddress("eglSetDamageRegionKHR"); - - extensions = - (const char *) eglQueryString(gr->egl_display, EGL_EXTENSIONS); - if (!extensions) { - weston_log("Retrieving EGL extension string failed.\n"); - return -1; - } - - if (weston_check_egl_extension(extensions, "EGL_IMG_context_priority")) - gr->has_context_priority = true; - - if (weston_check_egl_extension(extensions, "EGL_WL_bind_wayland_display")) - gr->has_bind_display = true; - if (gr->has_bind_display) { - assert(gr->bind_display); - assert(gr->unbind_display); - assert(gr->query_buffer); - ret = gr->bind_display(gr->egl_display, ec->wl_display); - if (!ret) - gr->has_bind_display = false; - } - - if (weston_check_egl_extension(extensions, "EGL_EXT_buffer_age")) - gr->has_egl_buffer_age = true; - - if (weston_check_egl_extension(extensions, "EGL_KHR_partial_update")) { - assert(gr->set_damage_region); - gr->has_egl_partial_update = true; - } - - for (i = 0; i < ARRAY_LENGTH(swap_damage_ext_to_entrypoint); i++) { - if (weston_check_egl_extension(extensions, - swap_damage_ext_to_entrypoint[i].extension)) { - gr->swap_buffers_with_damage = - (void *) eglGetProcAddress( - swap_damage_ext_to_entrypoint[i].entrypoint); - assert(gr->swap_buffers_with_damage); - break; - } - } - - if (weston_check_egl_extension(extensions, "EGL_KHR_no_config_context") || - weston_check_egl_extension(extensions, "EGL_MESA_configless_context")) - gr->has_configless_context = true; - - if (weston_check_egl_extension(extensions, "EGL_KHR_surfaceless_context")) - gr->has_surfaceless_context = true; - - if (weston_check_egl_extension(extensions, "EGL_EXT_image_dma_buf_import")) - gr->has_dmabuf_import = true; - - if (weston_check_egl_extension(extensions, - "EGL_EXT_image_dma_buf_import_modifiers")) { - gr->query_dmabuf_formats = - (void *) eglGetProcAddress("eglQueryDmaBufFormatsEXT"); - gr->query_dmabuf_modifiers = - (void *) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); - assert(gr->query_dmabuf_formats); - assert(gr->query_dmabuf_modifiers); - gr->has_dmabuf_import_modifiers = true; - } - - if (weston_check_egl_extension(extensions, "EGL_KHR_fence_sync") && - weston_check_egl_extension(extensions, "EGL_ANDROID_native_fence_sync")) { - gr->create_sync = - (void *) eglGetProcAddress("eglCreateSyncKHR"); - gr->destroy_sync = - (void *) eglGetProcAddress("eglDestroySyncKHR"); - gr->dup_native_fence_fd = - (void *) eglGetProcAddress("eglDupNativeFenceFDANDROID"); - assert(gr->create_sync); - assert(gr->destroy_sync); - assert(gr->dup_native_fence_fd); - gr->has_native_fence_sync = true; - } else { - weston_log("warning: Disabling render GPU timeline and explicit " - "synchronization due to missing " - "EGL_ANDROID_native_fence_sync extension\n"); - } - - if (weston_check_egl_extension(extensions, "EGL_KHR_wait_sync")) { - gr->wait_sync = (void *) eglGetProcAddress("eglWaitSyncKHR"); - assert(gr->wait_sync); - gr->has_wait_sync = true; - } else { - weston_log("warning: Disabling explicit synchronization due" - "to missing EGL_KHR_wait_sync extension\n"); - } - - return 0; -} diff --git a/libweston/renderer-gl/gl-renderer-internal.h b/libweston/renderer-gl/gl-renderer-internal.h deleted file mode 100644 index 529cb2f..0000000 --- a/libweston/renderer-gl/gl-renderer-internal.h +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright © 2019 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef GL_RENDERER_INTERNAL_H -#define GL_RENDERER_INTERNAL_H - -#include -#include -#include "shared/weston-egl-ext.h" /* for PFN* stuff */ - -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; -}; - -struct gl_renderer { - struct weston_renderer base; - bool fragment_shader_debug; - bool fan_debug; - struct weston_binding *fragment_binding; - struct weston_binding *fan_binding; - - EGLenum platform; - EGLDisplay egl_display; - EGLContext egl_context; - EGLConfig egl_config; - - EGLSurface dummy_surface; - - uint32_t gl_version; - - struct wl_array vertices; - struct wl_array vtxcnt; - - PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d; - PFNEGLCREATEIMAGEKHRPROC create_image; - PFNEGLDESTROYIMAGEKHRPROC destroy_image; - PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage; - - PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display; - PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC create_platform_window; - bool has_platform_base; - - bool has_unpack_subimage; - - PFNEGLBINDWAYLANDDISPLAYWL bind_display; - PFNEGLUNBINDWAYLANDDISPLAYWL unbind_display; - PFNEGLQUERYWAYLANDBUFFERWL query_buffer; - bool has_bind_display; - - bool has_context_priority; - - bool has_egl_image_external; - - bool has_egl_buffer_age; - bool has_egl_partial_update; - PFNEGLSETDAMAGEREGIONKHRPROC set_damage_region; - - bool has_configless_context; - - bool has_surfaceless_context; - - bool has_dmabuf_import; - struct wl_list dmabuf_images; - struct wl_list dmabuf_formats; - - bool has_gl_texture_rg; - - 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 texture_shader_xyuv; - struct gl_shader invert_color_shader; - struct gl_shader solid_shader; - struct gl_shader *current_shader; - - struct wl_signal destroy_signal; - - struct wl_listener output_destroy_listener; - - bool has_dmabuf_import_modifiers; - PFNEGLQUERYDMABUFFORMATSEXTPROC query_dmabuf_formats; - PFNEGLQUERYDMABUFMODIFIERSEXTPROC query_dmabuf_modifiers; - - bool has_native_fence_sync; - PFNEGLCREATESYNCKHRPROC create_sync; - PFNEGLDESTROYSYNCKHRPROC destroy_sync; - PFNEGLDUPNATIVEFENCEFDANDROIDPROC dup_native_fence_fd; - - bool has_wait_sync; - PFNEGLWAITSYNCKHRPROC wait_sync; -}; - -static inline struct gl_renderer * -get_renderer(struct weston_compositor *ec) -{ - return (struct gl_renderer *)ec->renderer; -} - -void -gl_renderer_print_egl_error_state(void); - -void -gl_renderer_log_extensions(const char *name, const char *extensions); - -void -log_egl_config_info(EGLDisplay egldpy, EGLConfig eglconfig); - -EGLConfig -gl_renderer_get_egl_config(struct gl_renderer *gr, - EGLint egl_surface_type, - const uint32_t *drm_formats, - unsigned drm_formats_count); - -int -gl_renderer_setup_egl_display(struct gl_renderer *gr, void *native_display); - -int -gl_renderer_setup_egl_client_extensions(struct gl_renderer *gr); - -int -gl_renderer_setup_egl_extensions(struct weston_compositor *ec); - -#endif /* GL_RENDERER_INTERNAL_H */ diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c deleted file mode 100755 index 78e94a2..0000000 --- a/libweston/renderer-gl/gl-renderer.c +++ /dev/null @@ -1,3820 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * Copyright © 2015,2019 Collabora, Ltd. - * Copyright © 2016 NVIDIA Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "linux-sync-file.h" -#include "timeline.h" - -#include "gl-renderer.h" -#include "gl-renderer-internal.h" -#include "vertex-clipping.h" -#include "linux-dmabuf.h" -#include "linux-dmabuf-unstable-v1-server-protocol.h" -#include "linux-explicit-synchronization.h" -#include "pixel-formats.h" - -#include "shared/fd-util.h" -#include "shared/helpers.h" -#include "shared/platform.h" -#include "shared/timespec-util.h" -#include "shared/weston-egl-ext.h" - -#define GR_GL_VERSION(major, minor) \ - (((uint32_t)(major) << 16) | (uint32_t)(minor)) - -#define GR_GL_VERSION_INVALID \ - GR_GL_VERSION(0, 0) - -#define BUFFER_DAMAGE_COUNT 2 - -enum gl_border_status { - BORDER_STATUS_CLEAN = 0, - BORDER_TOP_DIRTY = 1 << GL_RENDERER_BORDER_TOP, - BORDER_LEFT_DIRTY = 1 << GL_RENDERER_BORDER_LEFT, - BORDER_RIGHT_DIRTY = 1 << GL_RENDERER_BORDER_RIGHT, - BORDER_BOTTOM_DIRTY = 1 << GL_RENDERER_BORDER_BOTTOM, - BORDER_ALL_DIRTY = 0xf, - BORDER_SIZE_CHANGED = 0x10 -}; - -struct gl_border_image { - GLuint tex; - int32_t width, height; - int32_t tex_width; - void *data; -}; - -struct gl_output_state { - EGLSurface egl_surface; - pixman_region32_t buffer_damage[BUFFER_DAMAGE_COUNT]; - int buffer_damage_index; - enum gl_border_status border_damage[BUFFER_DAMAGE_COUNT]; - struct gl_border_image borders[4]; - enum gl_border_status border_status; - - struct weston_matrix output_matrix; - - EGLSyncKHR begin_render_sync, end_render_sync; - - /* struct timeline_render_point::link */ - struct wl_list timeline_render_point_list; -}; - -enum buffer_type { - BUFFER_TYPE_NULL, - BUFFER_TYPE_SOLID, /* internal solid color surfaces without a buffer */ - BUFFER_TYPE_SHM, - BUFFER_TYPE_EGL -}; - -struct gl_renderer; - -struct egl_image { - struct gl_renderer *renderer; - EGLImageKHR image; - int refcount; -}; - -enum import_type { - IMPORT_TYPE_INVALID, - IMPORT_TYPE_DIRECT, - IMPORT_TYPE_GL_CONVERSION -}; - -struct dmabuf_image { - struct linux_dmabuf_buffer *dmabuf; - int num_images; - struct egl_image *images[3]; - struct wl_list link; - - enum import_type import_type; - GLenum target; - struct gl_shader *shader; -}; - -struct dmabuf_format { - uint32_t format; - struct wl_list link; - - uint64_t *modifiers; - unsigned *external_only; - int num_modifiers; -}; - -struct yuv_plane_descriptor { - int width_divisor; - int height_divisor; - uint32_t format; - int plane_index; -}; - -enum texture_type { - TEXTURE_Y_XUXV_WL, - TEXTURE_Y_UV_WL, - TEXTURE_Y_U_V_WL, - TEXTURE_XYUV_WL -}; - -struct yuv_format_descriptor { - uint32_t format; - int input_planes; - int output_planes; - enum texture_type texture_type; - struct yuv_plane_descriptor plane[4]; -}; - -struct gl_surface_state { - GLfloat color[4]; - struct gl_shader *shader; - - GLuint textures[3]; - int num_textures; - bool needs_full_upload; - pixman_region32_t texture_damage; - - /* These are only used by SHM surfaces to detect when we need - * to do a full upload to specify a new internal texture - * format */ - GLenum gl_format[3]; - GLenum gl_pixel_type; - - struct egl_image* images[3]; - GLenum target; - int num_images; - - struct weston_buffer_reference buffer_ref; - struct weston_buffer_release_reference buffer_release_ref; - enum buffer_type buffer_type; - int pitch; /* in pixels */ - int height; /* in pixels */ - bool y_inverted; - bool direct_display; - - /* Extension needed for SHM YUV texture */ - int offset[3]; /* offset per plane */ - int hsub[3]; /* horizontal subsampling per plane */ - int vsub[3]; /* vertical subsampling per plane */ - - struct weston_surface *surface; - - /* Whether this surface was used in the current output repaint. - Used only in the context of a gl_renderer_repaint_output call. */ - bool used_in_output_repaint; - - struct wl_listener surface_destroy_listener; - struct wl_listener renderer_destroy_listener; -}; - -enum timeline_render_point_type { - TIMELINE_RENDER_POINT_TYPE_BEGIN, - TIMELINE_RENDER_POINT_TYPE_END -}; - -struct timeline_render_point { - struct wl_list link; /* gl_output_state::timeline_render_point_list */ - - enum timeline_render_point_type type; - int fd; - struct weston_output *output; - struct wl_event_source *event_source; -}; - -static inline const char * -dump_format(uint32_t format, char out[4]) -{ -#if BYTE_ORDER == BIG_ENDIAN - format = __builtin_bswap32(format); -#endif - memcpy(out, &format, 4); - return out; -} - -static inline struct gl_output_state * -get_output_state(struct weston_output *output) -{ - return (struct gl_output_state *)output->renderer_state; -} - -static int -gl_renderer_create_surface(struct weston_surface *surface); - -static inline struct gl_surface_state * -get_surface_state(struct weston_surface *surface) -{ - if (!surface->renderer_state) - gl_renderer_create_surface(surface); - - return (struct gl_surface_state *)surface->renderer_state; -} - -static void -timeline_render_point_destroy(struct timeline_render_point *trp) -{ - wl_list_remove(&trp->link); - wl_event_source_remove(trp->event_source); - close(trp->fd); - free(trp); -} - -static int -timeline_render_point_handler(int fd, uint32_t mask, void *data) -{ - struct timeline_render_point *trp = data; - const char *tp_name = trp->type == TIMELINE_RENDER_POINT_TYPE_BEGIN ? - "renderer_gpu_begin" : "renderer_gpu_end"; - - if (mask & WL_EVENT_READABLE) { - struct timespec tspec = { 0 }; - - if (weston_linux_sync_file_read_timestamp(trp->fd, - &tspec) == 0) { - TL_POINT(trp->output->compositor, tp_name, TLP_GPU(&tspec), - TLP_OUTPUT(trp->output), TLP_END); - } - } - - timeline_render_point_destroy(trp); - - return 0; -} - -static EGLSyncKHR -create_render_sync(struct gl_renderer *gr) -{ - static const EGLint attribs[] = { EGL_NONE }; - - if (!gr->has_native_fence_sync) - return EGL_NO_SYNC_KHR; - - return gr->create_sync(gr->egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID, - attribs); -} - -static void -timeline_submit_render_sync(struct gl_renderer *gr, - struct weston_compositor *ec, - struct weston_output *output, - EGLSyncKHR sync, - enum timeline_render_point_type type) -{ - struct gl_output_state *go; - struct wl_event_loop *loop; - int fd; - struct timeline_render_point *trp; - -// OHOS remove timeline -// if (!weston_log_scope_is_enabled(ec->timeline) || - if (true || - !gr->has_native_fence_sync || - sync == EGL_NO_SYNC_KHR) - return; - - go = get_output_state(output); - loop = wl_display_get_event_loop(ec->wl_display); - - fd = gr->dup_native_fence_fd(gr->egl_display, sync); - if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) - return; - - trp = zalloc(sizeof *trp); - if (trp == NULL) { - close(fd); - return; - } - - trp->type = type; - trp->fd = fd; - trp->output = output; - trp->event_source = wl_event_loop_add_fd(loop, fd, - WL_EVENT_READABLE, - timeline_render_point_handler, - trp); - - wl_list_insert(&go->timeline_render_point_list, &trp->link); -} - -static struct egl_image* -egl_image_create(struct gl_renderer *gr, EGLenum target, - EGLClientBuffer buffer, const EGLint *attribs) -{ - struct egl_image *img; - - img = zalloc(sizeof *img); - img->renderer = gr; - img->refcount = 1; - img->image = gr->create_image(gr->egl_display, EGL_NO_CONTEXT, - target, buffer, attribs); - - if (img->image == EGL_NO_IMAGE_KHR) { - free(img); - return NULL; - } - - return img; -} - -static struct egl_image* -egl_image_ref(struct egl_image *image) -{ - image->refcount++; - - return image; -} - -static int -egl_image_unref(struct egl_image *image) -{ - struct gl_renderer *gr = image->renderer; - - assert(image->refcount > 0); - - image->refcount--; - if (image->refcount > 0) - return image->refcount; - - gr->destroy_image(gr->egl_display, image->image); - free(image); - - return 0; -} - -static struct dmabuf_image* -dmabuf_image_create(void) -{ - struct dmabuf_image *img; - - img = zalloc(sizeof *img); - wl_list_init(&img->link); - - return img; -} - -static void -dmabuf_image_destroy(struct dmabuf_image *image) -{ - int i; - - for (i = 0; i < image->num_images; ++i) - egl_image_unref(image->images[i]); - - if (image->dmabuf) - linux_dmabuf_buffer_set_user_data(image->dmabuf, NULL, NULL); - - wl_list_remove(&image->link); - free(image); -} - -#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_view *ev, 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_view_to_global_float(ev, 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 (!ev->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 bool -merge_down(pixman_box32_t *a, pixman_box32_t *b, pixman_box32_t *merge) -{ - if (a->x1 == b->x1 && a->x2 == b->x2 && a->y1 == b->y2) { - merge->x1 = a->x1; - merge->x2 = a->x2; - merge->y1 = b->y1; - merge->y2 = a->y2; - return true; - } - return false; -} - -static int -compress_bands(pixman_box32_t *inrects, int nrects, - pixman_box32_t **outrects) -{ - bool merged = false; - pixman_box32_t *out, merge_rect; - int i, j, nout; - - if (!nrects) { - *outrects = NULL; - return 0; - } - - /* nrects is an upper bound - we're not too worried about - * allocating a little extra - */ - out = malloc(sizeof(pixman_box32_t) * nrects); - out[0] = inrects[0]; - nout = 1; - for (i = 1; i < nrects; i++) { - for (j = 0; j < nout; j++) { - merged = merge_down(&inrects[i], &out[j], &merge_rect); - if (merged) { - out[j] = merge_rect; - break; - } - } - if (!merged) { - out[nout] = inrects[i]; - nout++; - } - } - *outrects = out; - return nout; -} - -static int -texture_region(struct weston_view *ev, pixman_region32_t *region, - pixman_region32_t *surf_region) -{ - struct gl_surface_state *gs = get_surface_state(ev->surface); - struct weston_compositor *ec = ev->surface->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; - pixman_box32_t *raw_rects; - int i, j, k, nrects, nsurf, raw_nrects; - bool used_band_compression; - raw_rects = pixman_region32_rectangles(region, &raw_nrects); - surf_rects = pixman_region32_rectangles(surf_region, &nsurf); - - if (raw_nrects < 4) { - used_band_compression = false; - nrects = raw_nrects; - rects = raw_rects; - } else { - nrects = compress_bands(raw_rects, raw_nrects, &rects); - used_band_compression = true; - } - /* 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(ev, rect, surf_rect, ex, ey); - if (n < 3) - continue; - - /* emit edge points: */ - for (k = 0; k < n; k++) { - weston_view_from_global_float(ev, ex[k], ey[k], - &sx, &sy); - /* position: */ - *(v++) = ex[k]; - *(v++) = ey[k]; - /* texcoord: */ - weston_surface_to_buffer_float(ev->surface, - 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; - } - } - - if (used_band_compression) - free(rects); - return nvtx; -} - -static void -triangle_fan_debug(struct weston_view *view, int first, int count) -{ - struct weston_compositor *compositor = view->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_view *ev, pixman_region32_t *region, - pixman_region32_t *surf_region) -{ - struct weston_compositor *ec = ev->surface->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 vertices, actually). - */ - nfans = texture_region(ev, 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(ev, 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_view *view, - struct weston_output *output) -{ - int i; - struct gl_surface_state *gs = get_surface_state(view->surface); - struct gl_output_state *go = get_output_state(output); - - glUniformMatrix4fv(shader->proj_uniform, - 1, GL_FALSE, go->output_matrix.d); - glUniform4fv(shader->color_uniform, 1, gs->color); - glUniform1f(shader->alpha_uniform, view->alpha); - - for (i = 0; i < gs->num_textures; i++) - glUniform1i(shader->tex_uniforms[i], i); -} - -static int -ensure_surface_buffer_is_ready(struct gl_renderer *gr, - struct gl_surface_state *gs) -{ - EGLint attribs[] = { - EGL_SYNC_NATIVE_FENCE_FD_ANDROID, - -1, - EGL_NONE - }; - struct weston_surface *surface = gs->surface; - struct weston_buffer *buffer = gs->buffer_ref.buffer; - EGLSyncKHR sync; - EGLint wait_ret; - EGLint destroy_ret; - - if (!buffer) - return 0; - - if (surface->acquire_fence_fd < 0) - return 0; - - /* We should only get a fence if we support EGLSyncKHR, since - * we don't advertise the explicit sync protocol otherwise. */ - assert(gr->has_native_fence_sync); - /* We should only get a fence for non-SHM buffers, since surface - * commit would have failed otherwise. */ - assert(wl_shm_buffer_get(buffer->resource) == NULL); - - attribs[1] = dup(surface->acquire_fence_fd); - if (attribs[1] == -1) { - linux_explicit_synchronization_send_server_error( - gs->surface->synchronization_resource, - "Failed to dup acquire fence"); - return -1; - } - - sync = gr->create_sync(gr->egl_display, - EGL_SYNC_NATIVE_FENCE_ANDROID, - attribs); - if (sync == EGL_NO_SYNC_KHR) { - linux_explicit_synchronization_send_server_error( - gs->surface->synchronization_resource, - "Failed to create EGLSyncKHR object"); - close(attribs[1]); - return -1; - } - - wait_ret = gr->wait_sync(gr->egl_display, sync, 0); - if (wait_ret == EGL_FALSE) { - linux_explicit_synchronization_send_server_error( - gs->surface->synchronization_resource, - "Failed to wait on EGLSyncKHR object"); - /* Continue to try to destroy the sync object. */ - } - - - destroy_ret = gr->destroy_sync(gr->egl_display, sync); - if (destroy_ret == EGL_FALSE) { - linux_explicit_synchronization_send_server_error( - gs->surface->synchronization_resource, - "Failed to destroy on EGLSyncKHR object"); - } - - return (wait_ret == EGL_TRUE && destroy_ret == EGL_TRUE) ? 0 : -1; -} - - - /* Checks if a view needs to be censored on an output - * Checks for 2 types of censor requirements - * - recording_censor: Censor protected view when a - * protected view is captured. - * - unprotected_censor: Censor regions of protected views - * when displayed on an output which has lower protection capability. - * Returns the originally stored gl_shader if content censoring is required, - * NULL otherwise. - */ -static struct gl_shader * -setup_censor_overrides(struct weston_output *output, - struct weston_view *ev) -{ - struct gl_shader *replaced_shader = NULL; - struct weston_compositor *ec = ev->surface->compositor; - struct gl_renderer *gr = get_renderer(ec); - struct gl_surface_state *gs = get_surface_state(ev->surface); - bool recording_censor = - (output->disable_planes > 0) && - (ev->surface->desired_protection > WESTON_HDCP_DISABLE); - - bool unprotected_censor = - (ev->surface->desired_protection > output->current_protection); - - if (gs->direct_display) { - gs->color[0] = 0.40; - gs->color[1] = 0.0; - gs->color[2] = 0.0; - gs->color[3] = 1.0; - gs->shader = &gr->solid_shader; - return gs->shader; - } - - /* When not in enforced mode, the client is notified of the protection */ - /* change, so content censoring is not required */ - if (ev->surface->protection_mode != - WESTON_SURFACE_PROTECTION_MODE_ENFORCED) - return NULL; - - if (recording_censor || unprotected_censor) { - replaced_shader = gs->shader; - gs->color[0] = 0.40; - gs->color[1] = 0.0; - gs->color[2] = 0.0; - gs->color[3] = 1.0; - gs->shader = &gr->solid_shader; - } - - return replaced_shader; -} - -static void -draw_view(struct weston_view *ev, struct weston_output *output, - pixman_region32_t *damage) /* in global coordinates */ -{ - struct weston_compositor *ec = ev->surface->compositor; - struct gl_renderer *gr = get_renderer(ec); - struct gl_surface_state *gs = get_surface_state(ev->surface); - /* repaint bounding region in global coordinates: */ - pixman_region32_t repaint; - /* opaque region in surface coordinates: */ - pixman_region32_t surface_opaque; - /* non-opaque region in surface coordinates: */ - pixman_region32_t surface_blend; - GLint filter; - int i; - struct gl_shader *replaced_shader = NULL; - - /* In case of a runtime switch of renderers, we may not have received - * an attach for this surface since the switch. In that case we don't - * have a valid buffer or a proper shader set up so skip rendering. */ - if (!gs->shader && !gs->direct_display) - return; - - pixman_region32_init(&repaint); - pixman_region32_intersect(&repaint, - &ev->transform.boundingbox, damage); - pixman_region32_subtract(&repaint, &repaint, &ev->clip); - - if (!pixman_region32_not_empty(&repaint)) - goto out; - - if (ensure_surface_buffer_is_ready(gr, gs) < 0) - goto out; - - replaced_shader = setup_censor_overrides(output, ev); - - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - - if (gr->fan_debug) { - use_shader(gr, &gr->solid_shader); - shader_uniforms(&gr->solid_shader, ev, output); - } - - use_shader(gr, gs->shader); - shader_uniforms(gs->shader, ev, output); - - if (ev->transform.enabled || output->zoom.active || - output->current_scale != ev->surface->buffer_viewport.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, - ev->surface->width, ev->surface->height); - if (ev->geometry.scissor_enabled) - pixman_region32_intersect(&surface_blend, &surface_blend, - &ev->geometry.scissor); - pixman_region32_subtract(&surface_blend, &surface_blend, - &ev->surface->opaque); - - /* XXX: Should we be using ev->transform.opaque here? */ - pixman_region32_init(&surface_opaque); - if (ev->geometry.scissor_enabled) - pixman_region32_intersect(&surface_opaque, - &ev->surface->opaque, - &ev->geometry.scissor); - else - pixman_region32_copy(&surface_opaque, &ev->surface->opaque); - - if (pixman_region32_not_empty(&surface_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, ev, output); - } - - if (ev->alpha < 1.0) - glEnable(GL_BLEND); - else - glDisable(GL_BLEND); - - repaint_region(ev, &repaint, &surface_opaque); - gs->used_in_output_repaint = true; - } - - if (pixman_region32_not_empty(&surface_blend)) { - use_shader(gr, gs->shader); - glEnable(GL_BLEND); - repaint_region(ev, &repaint, &surface_blend); - gs->used_in_output_repaint = true; - } - - pixman_region32_fini(&surface_blend); - pixman_region32_fini(&surface_opaque); - -out: - pixman_region32_fini(&repaint); - - if (replaced_shader) - gs->shader = replaced_shader; -} - -static void -repaint_views(struct weston_output *output, pixman_region32_t *damage) -{ - struct weston_compositor *compositor = output->compositor; - struct weston_view *view; - - wl_list_for_each_reverse(view, &compositor->view_list, link) - if (view->plane == &compositor->primary_plane) - draw_view(view, output, damage); -} - -static int -gl_renderer_create_fence_fd(struct weston_output *output); - -/* Updates the release fences of surfaces that were used in the current output - * repaint. Should only be used from gl_renderer_repaint_output, so that the - * information in gl_surface_state.used_in_output_repaint is accurate. - */ -static void -update_buffer_release_fences(struct weston_compositor *compositor, - struct weston_output *output) -{ - struct weston_view *view; - - wl_list_for_each_reverse(view, &compositor->view_list, link) { - struct gl_surface_state *gs; - struct weston_buffer_release *buffer_release; - int fence_fd; - - if (view->plane != &compositor->primary_plane) - continue; - - gs = get_surface_state(view->surface); - buffer_release = gs->buffer_release_ref.buffer_release; - - if (!gs->used_in_output_repaint || !buffer_release) - continue; - - fence_fd = gl_renderer_create_fence_fd(output); - - /* If we have a buffer_release then it means we support fences, - * and we should be able to create the release fence. If we - * can't, something has gone horribly wrong, so disconnect the - * client. - */ - if (fence_fd == -1) { - linux_explicit_synchronization_send_server_error( - buffer_release->resource, - "Failed to create release fence"); - fd_clear(&buffer_release->fence_fd); - continue; - } - - /* At the moment it is safe to just replace the fence_fd, - * discarding the previous one: - * - * 1. If the previous fence fd represents a sync fence from - * a previous repaint cycle, that fence fd is now not - * sufficient to provide the release guarantee and should - * be replaced. - * - * 2. If the fence fd represents a sync fence from another - * output in the same repaint cycle, it's fine to replace - * it since we are rendering to all outputs using the same - * EGL context, so a fence issued for a later output rendering - * is guaranteed to signal after fences for previous output - * renderings. - * - * Note that the above is only valid if the buffer_release - * fences only originate from the GL renderer, which guarantees - * a total order of operations and fences. If we introduce - * fences from other sources (e.g., plane out-fences), we will - * need to merge fences instead. - */ - fd_update(&buffer_release->fence_fd, fence_fd); - } -} - -static void -draw_output_border_texture(struct gl_output_state *go, - enum gl_renderer_border_side side, - int32_t x, int32_t y, - int32_t width, int32_t height) -{ - struct gl_border_image *img = &go->borders[side]; - static GLushort indices [] = { 0, 1, 3, 3, 1, 2 }; - - if (!img->data) { - if (img->tex) { - glDeleteTextures(1, &img->tex); - img->tex = 0; - } - - return; - } - - if (!img->tex) { - glGenTextures(1, &img->tex); - glBindTexture(GL_TEXTURE_2D, img->tex); - - 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); - } else { - glBindTexture(GL_TEXTURE_2D, img->tex); - } - - if (go->border_status & (1 << side)) { - glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); - glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0); - glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0); - glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA_EXT, - img->tex_width, img->height, 0, - GL_BGRA_EXT, GL_UNSIGNED_BYTE, img->data); - } - - GLfloat texcoord[] = { - 0.0f, 0.0f, - (GLfloat)img->width / (GLfloat)img->tex_width, 0.0f, - (GLfloat)img->width / (GLfloat)img->tex_width, 1.0f, - 0.0f, 1.0f, - }; - - GLfloat verts[] = { - x, y, - x + width, y, - x + width, y + height, - x, y + height - }; - - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, verts); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, texcoord); - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices); - - glDisableVertexAttribArray(1); - glDisableVertexAttribArray(0); -} - -static int -output_has_borders(struct weston_output *output) -{ - struct gl_output_state *go = get_output_state(output); - - return go->borders[GL_RENDERER_BORDER_TOP].data || - go->borders[GL_RENDERER_BORDER_RIGHT].data || - go->borders[GL_RENDERER_BORDER_BOTTOM].data || - go->borders[GL_RENDERER_BORDER_LEFT].data; -} - -static void -draw_output_borders(struct weston_output *output, - enum gl_border_status border_status) -{ - struct gl_output_state *go = get_output_state(output); - struct gl_renderer *gr = get_renderer(output->compositor); - struct gl_shader *shader = &gr->texture_shader_rgba; - struct gl_border_image *top, *bottom, *left, *right; - struct weston_matrix matrix; - int full_width, full_height; - - if (border_status == BORDER_STATUS_CLEAN) - return; /* Clean. Nothing to do. */ - - top = &go->borders[GL_RENDERER_BORDER_TOP]; - bottom = &go->borders[GL_RENDERER_BORDER_BOTTOM]; - left = &go->borders[GL_RENDERER_BORDER_LEFT]; - right = &go->borders[GL_RENDERER_BORDER_RIGHT]; - - full_width = output->current_mode->width + left->width + right->width; - full_height = output->current_mode->height + top->height + bottom->height; - - glDisable(GL_BLEND); - use_shader(gr, shader); - - glViewport(0, 0, full_width, full_height); - - weston_matrix_init(&matrix); - weston_matrix_translate(&matrix, -full_width/2.0, -full_height/2.0, 0); - weston_matrix_scale(&matrix, 2.0/full_width, -2.0/full_height, 1); - glUniformMatrix4fv(shader->proj_uniform, 1, GL_FALSE, matrix.d); - - glUniform1i(shader->tex_uniforms[0], 0); - glUniform1f(shader->alpha_uniform, 1); - glActiveTexture(GL_TEXTURE0); - - if (border_status & BORDER_TOP_DIRTY) - draw_output_border_texture(go, GL_RENDERER_BORDER_TOP, - 0, 0, - full_width, top->height); - if (border_status & BORDER_LEFT_DIRTY) - draw_output_border_texture(go, GL_RENDERER_BORDER_LEFT, - 0, top->height, - left->width, output->current_mode->height); - if (border_status & BORDER_RIGHT_DIRTY) - draw_output_border_texture(go, GL_RENDERER_BORDER_RIGHT, - full_width - right->width, top->height, - right->width, output->current_mode->height); - if (border_status & BORDER_BOTTOM_DIRTY) - draw_output_border_texture(go, GL_RENDERER_BORDER_BOTTOM, - 0, full_height - bottom->height, - full_width, bottom->height); -} - -static void -output_get_border_damage(struct weston_output *output, - enum gl_border_status border_status, - pixman_region32_t *damage) -{ - struct gl_output_state *go = get_output_state(output); - struct gl_border_image *top, *bottom, *left, *right; - int full_width, full_height; - - if (border_status == BORDER_STATUS_CLEAN) - return; /* Clean. Nothing to do. */ - - top = &go->borders[GL_RENDERER_BORDER_TOP]; - bottom = &go->borders[GL_RENDERER_BORDER_BOTTOM]; - left = &go->borders[GL_RENDERER_BORDER_LEFT]; - right = &go->borders[GL_RENDERER_BORDER_RIGHT]; - - full_width = output->current_mode->width + left->width + right->width; - full_height = output->current_mode->height + top->height + bottom->height; - if (border_status & BORDER_TOP_DIRTY) - pixman_region32_union_rect(damage, damage, - 0, 0, - full_width, top->height); - if (border_status & BORDER_LEFT_DIRTY) - pixman_region32_union_rect(damage, damage, - 0, top->height, - left->width, output->current_mode->height); - if (border_status & BORDER_RIGHT_DIRTY) - pixman_region32_union_rect(damage, damage, - full_width - right->width, top->height, - right->width, output->current_mode->height); - if (border_status & BORDER_BOTTOM_DIRTY) - pixman_region32_union_rect(damage, damage, - 0, full_height - bottom->height, - full_width, bottom->height); -} - -static void -output_get_damage(struct weston_output *output, - pixman_region32_t *buffer_damage, uint32_t *border_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); - *border_damage = BORDER_ALL_DIRTY; - } else { - for (i = 0; i < buffer_age - 1; i++) - *border_damage |= go->border_damage[(go->buffer_damage_index + i) % BUFFER_DAMAGE_COUNT]; - - if (*border_damage & BORDER_SIZE_CHANGED) { - /* If we've had a resize, we have to do a full - * repaint. */ - *border_damage |= BORDER_ALL_DIRTY; - 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[(go->buffer_damage_index + i) % BUFFER_DAMAGE_COUNT]); - } - } -} - -static void -output_rotate_damage(struct weston_output *output, - pixman_region32_t *output_damage, - enum gl_border_status border_status) -{ - struct gl_output_state *go = get_output_state(output); - struct gl_renderer *gr = get_renderer(output->compositor); - - if (!gr->has_egl_buffer_age) - return; - - go->buffer_damage_index += BUFFER_DAMAGE_COUNT - 1; - go->buffer_damage_index %= BUFFER_DAMAGE_COUNT; - - pixman_region32_copy(&go->buffer_damage[go->buffer_damage_index], output_damage); - go->border_damage[go->buffer_damage_index] = border_status; -} - -/** - * Given a region in Weston's (top-left-origin) global co-ordinate space, - * translate it to the co-ordinate space used by GL for our output - * rendering. This requires shifting it into output co-ordinate space: - * translating for output offset within the global co-ordinate space, - * multiplying by output scale to get buffer rather than logical size. - * - * Finally, if borders are drawn around the output, we translate the area - * to account for the border region around the outside, and add any - * damage if the borders have been redrawn. - * - * @param output The output whose co-ordinate space we are after - * @param global_region The affected region in global co-ordinate space - * @param[out] rects Y-inverted quads in {x,y,w,h} order; caller must free - * @param[out] nrects Number of quads (4x number of co-ordinates) - */ -static void -pixman_region_to_egl_y_invert(struct weston_output *output, - struct pixman_region32 *global_region, - EGLint **rects, - EGLint *nrects) -{ - struct gl_output_state *go = get_output_state(output); - pixman_region32_t transformed; - struct pixman_box32 *box; - int buffer_height; - EGLint *d; - int i; - - /* Translate from global to output co-ordinate space. */ - pixman_region32_init(&transformed); - pixman_region32_copy(&transformed, global_region); - pixman_region32_translate(&transformed, -output->x, -output->y); - weston_transformed_region(output->width, output->height, - output->transform, - output->current_scale, - &transformed, &transformed); - - /* If we have borders drawn around the output, shift our output damage - * to account for borders being drawn around the outside, adding any - * damage resulting from borders being redrawn. */ - if (output_has_borders(output)) { - pixman_region32_translate(&transformed, - go->borders[GL_RENDERER_BORDER_LEFT].width, - go->borders[GL_RENDERER_BORDER_TOP].height); - output_get_border_damage(output, go->border_status, - &transformed); - } - - /* Convert from a Pixman region into {x,y,w,h} quads, flipping in the - * Y axis to account for GL's lower-left-origin co-ordinate space. */ - box = pixman_region32_rectangles(&transformed, nrects); - *rects = malloc(*nrects * 4 * sizeof(EGLint)); - - buffer_height = go->borders[GL_RENDERER_BORDER_TOP].height + - output->current_mode->height + - go->borders[GL_RENDERER_BORDER_BOTTOM].height; - - d = *rects; - for (i = 0; i < *nrects; ++i) { - *d++ = box[i].x1; - *d++ = buffer_height - box[i].y2; - *d++ = box[i].x2 - box[i].x1; - *d++ = box[i].y2 - box[i].y1; - } - - pixman_region32_fini(&transformed); -} - -/* NOTE: We now allow falling back to ARGB gl visuals when XRGB is - * unavailable, so we're assuming the background has no transparency - * and that everything with a blend, like drop shadows, will have something - * opaque (like the background) drawn underneath it. - * - * Depending on the underlying hardware, violating that assumption could - * result in seeing through to another display plane. - */ -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; - /* areas we've damaged since we last used this buffer */ - pixman_region32_t previous_damage; - /* total area we need to repaint this time */ - pixman_region32_t total_damage; - enum gl_border_status border_status = BORDER_STATUS_CLEAN; - struct weston_view *view; - - if (use_output(output) < 0) - return; - - /* Clear the used_in_output_repaint flag, so that we can properly track - * which surfaces were used in this output repaint. */ - wl_list_for_each_reverse(view, &compositor->view_list, link) { - if (view->plane == &compositor->primary_plane) { - struct gl_surface_state *gs = - get_surface_state(view->surface); - gs->used_in_output_repaint = false; - } - } - - if (go->begin_render_sync != EGL_NO_SYNC_KHR) - gr->destroy_sync(gr->egl_display, go->begin_render_sync); - if (go->end_render_sync != EGL_NO_SYNC_KHR) - gr->destroy_sync(gr->egl_display, go->end_render_sync); - - go->begin_render_sync = create_render_sync(gr); - - /* Calculate the viewport */ - glViewport(go->borders[GL_RENDERER_BORDER_LEFT].width, - go->borders[GL_RENDERER_BORDER_BOTTOM].height, - output->current_mode->width, - output->current_mode->height); - - /* Calculate the global GL matrix */ - go->output_matrix = output->matrix; - weston_matrix_translate(&go->output_matrix, - -(output->current_mode->width / 2.0), - -(output->current_mode->height / 2.0), 0); - weston_matrix_scale(&go->output_matrix, - 2.0 / output->current_mode->width, - -2.0 / output->current_mode->height, 1); - - /* In fan debug mode, redraw everything to make sure that we clear any - * fans left over from previous draws on this buffer. - * This precludes the use of EGL_EXT_swap_buffers_with_damage and - * EGL_KHR_partial_update, since we damage the whole area. */ - if (gr->fan_debug) { - pixman_region32_t undamaged; - pixman_region32_init(&undamaged); - pixman_region32_subtract(&undamaged, &output->region, - output_damage); - gr->fan_debug = false; - repaint_views(output, &undamaged); - gr->fan_debug = true; - pixman_region32_fini(&undamaged); - } - - /* previous_damage covers regions damaged in previous paints since we - * last used this buffer */ - pixman_region32_init(&previous_damage); - pixman_region32_init(&total_damage); /* total area to redraw */ - - /* Update previous_damage using buffer_age (if available), and store - * current damaged region for future use. */ - output_get_damage(output, &previous_damage, &border_status); - output_rotate_damage(output, output_damage, go->border_status); - - /* Redraw both areas which have changed since we last used this buffer, - * as well as the areas we now want to repaint, to make sure the - * buffer is up to date. */ - pixman_region32_union(&total_damage, &previous_damage, output_damage); - border_status |= go->border_status; - - if (gr->has_egl_partial_update && !gr->fan_debug) { - int n_egl_rects; - EGLint *egl_rects; - - /* For partial_update, we need to pass the region which has - * changed since we last rendered into this specific buffer; - * this is total_damage. */ - pixman_region_to_egl_y_invert(output, &total_damage, - &egl_rects, &n_egl_rects); - gr->set_damage_region(gr->egl_display, go->egl_surface, - egl_rects, n_egl_rects); - free(egl_rects); - } - - repaint_views(output, &total_damage); - - pixman_region32_fini(&total_damage); - pixman_region32_fini(&previous_damage); - - draw_output_borders(output, border_status); - - wl_signal_emit(&output->frame_signal, output_damage); - - go->end_render_sync = create_render_sync(gr); - - if (gr->swap_buffers_with_damage && !gr->fan_debug) { - int n_egl_rects; - EGLint *egl_rects; - - /* For swap_buffers_with_damage, we need to pass the region - * which has changed since the previous SwapBuffers on this - * surface - this is output_damage. */ - pixman_region_to_egl_y_invert(output, output_damage, - &egl_rects, &n_egl_rects); - ret = gr->swap_buffers_with_damage(gr->egl_display, - go->egl_surface, - egl_rects, n_egl_rects); - free(egl_rects); - } else { - 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(); - } - - go->border_status = BORDER_STATUS_CLEAN; - - /* We have to submit the render sync objects after swap buffers, since - * the objects get assigned a valid sync file fd only after a gl flush. - */ - timeline_submit_render_sync(gr, compositor, output, - go->begin_render_sync, - TIMELINE_RENDER_POINT_TYPE_BEGIN); - timeline_submit_render_sync(gr, compositor, output, go->end_render_sync, - TIMELINE_RENDER_POINT_TYPE_END); - - update_buffer_release_fences(compositor, output); -} - -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; - struct gl_output_state *go = get_output_state(output); - - x += go->borders[GL_RENDERER_BORDER_LEFT].width; - y += go->borders[GL_RENDERER_BORDER_BOTTOM].height; - - 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 GLenum gl_format_from_internal(GLenum internal_format) -{ - switch (internal_format) { - case GL_R8_EXT: - return GL_RED_EXT; - case GL_RG8_EXT: - return GL_RG_EXT; - default: - return internal_format; - } -} - -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; - struct weston_view *view; - bool texture_used; - pixman_box32_t *rectangles; - uint8_t *data; - int i, j, n; - - 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. - */ - texture_used = false; - wl_list_for_each(view, &surface->views, surface_link) { - if (view->plane == &surface->compositor->primary_plane) { - texture_used = true; - break; - } - } - if (!texture_used) - return; - - if (!pixman_region32_not_empty(&gs->texture_damage) && - !gs->needs_full_upload) - goto done; - - data = wl_shm_buffer_get_data(buffer->shm_buffer); - - if (!gr->has_unpack_subimage) { - wl_shm_buffer_begin_access(buffer->shm_buffer); - for (j = 0; j < gs->num_textures; j++) { - glBindTexture(GL_TEXTURE_2D, gs->textures[j]); - glTexImage2D(GL_TEXTURE_2D, 0, - gs->gl_format[j], - gs->pitch / gs->hsub[j], - buffer->height / gs->vsub[j], - 0, - gl_format_from_internal(gs->gl_format[j]), - gs->gl_pixel_type, - data + gs->offset[j]); - } - wl_shm_buffer_end_access(buffer->shm_buffer); - - goto done; - } - - if (gs->needs_full_upload) { - glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0); - glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0); - wl_shm_buffer_begin_access(buffer->shm_buffer); - for (j = 0; j < gs->num_textures; j++) { - glBindTexture(GL_TEXTURE_2D, gs->textures[j]); - glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, - gs->pitch / gs->hsub[j]); - glTexImage2D(GL_TEXTURE_2D, 0, - gs->gl_format[j], - gs->pitch / gs->hsub[j], - buffer->height / gs->vsub[j], - 0, - gl_format_from_internal(gs->gl_format[j]), - gs->gl_pixel_type, - data + gs->offset[j]); - } - wl_shm_buffer_end_access(buffer->shm_buffer); - goto done; - } - - rectangles = pixman_region32_rectangles(&gs->texture_damage, &n); - wl_shm_buffer_begin_access(buffer->shm_buffer); - for (i = 0; i < n; i++) { - pixman_box32_t r; - - r = weston_surface_to_buffer_rect(surface, rectangles[i]); - - for (j = 0; j < gs->num_textures; j++) { - glBindTexture(GL_TEXTURE_2D, gs->textures[j]); - glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, - gs->pitch / gs->hsub[j]); - glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, - r.x1 / gs->hsub[j]); - glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, - r.y1 / gs->hsub[j]); - glTexSubImage2D(GL_TEXTURE_2D, 0, - r.x1 / gs->hsub[j], - r.y1 / gs->vsub[j], - (r.x2 - r.x1) / gs->hsub[j], - (r.y2 - r.y1) / gs->vsub[j], - gl_format_from_internal(gs->gl_format[j]), - gs->gl_pixel_type, - data + gs->offset[j]); - } - } - wl_shm_buffer_end_access(buffer->shm_buffer); - -done: - pixman_region32_fini(&gs->texture_damage); - pixman_region32_init(&gs->texture_damage); - gs->needs_full_upload = false; - - weston_buffer_reference(&gs->buffer_ref, NULL); - weston_buffer_release_reference(&gs->buffer_release_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); - GLenum gl_format[3] = {0, 0, 0}; - GLenum gl_pixel_type; - int pitch; - int num_planes; - - buffer->shm_buffer = shm_buffer; - buffer->width = wl_shm_buffer_get_width(shm_buffer); - buffer->height = wl_shm_buffer_get_height(shm_buffer); - - num_planes = 1; - gs->offset[0] = 0; - gs->hsub[0] = 1; - gs->vsub[0] = 1; - - 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; - gl_format[0] = GL_BGRA_EXT; - gl_pixel_type = GL_UNSIGNED_BYTE; - es->is_opaque = true; - break; - case WL_SHM_FORMAT_ARGB8888: - gs->shader = &gr->texture_shader_rgba; - pitch = wl_shm_buffer_get_stride(shm_buffer) / 4; - gl_format[0] = GL_BGRA_EXT; - gl_pixel_type = GL_UNSIGNED_BYTE; - es->is_opaque = false; - break; - case WL_SHM_FORMAT_RGB565: - gs->shader = &gr->texture_shader_rgbx; - pitch = wl_shm_buffer_get_stride(shm_buffer) / 2; - gl_format[0] = GL_RGB; - gl_pixel_type = GL_UNSIGNED_SHORT_5_6_5; - es->is_opaque = true; - break; - case WL_SHM_FORMAT_YUV420: - gs->shader = &gr->texture_shader_y_u_v; - pitch = wl_shm_buffer_get_stride(shm_buffer); - gl_pixel_type = GL_UNSIGNED_BYTE; - num_planes = 3; - gs->offset[1] = gs->offset[0] + (pitch / gs->hsub[0]) * - (buffer->height / gs->vsub[0]); - gs->hsub[1] = 2; - gs->vsub[1] = 2; - gs->offset[2] = gs->offset[1] + (pitch / gs->hsub[1]) * - (buffer->height / gs->vsub[1]); - gs->hsub[2] = 2; - gs->vsub[2] = 2; - if (gr->has_gl_texture_rg) { - gl_format[0] = GL_R8_EXT; - gl_format[1] = GL_R8_EXT; - gl_format[2] = GL_R8_EXT; - } else { - gl_format[0] = GL_LUMINANCE; - gl_format[1] = GL_LUMINANCE; - gl_format[2] = GL_LUMINANCE; - } - es->is_opaque = true; - break; - case WL_SHM_FORMAT_NV12: - pitch = wl_shm_buffer_get_stride(shm_buffer); - gl_pixel_type = GL_UNSIGNED_BYTE; - num_planes = 2; - gs->offset[1] = gs->offset[0] + (pitch / gs->hsub[0]) * - (buffer->height / gs->vsub[0]); - gs->hsub[1] = 2; - gs->vsub[1] = 2; - if (gr->has_gl_texture_rg) { - gs->shader = &gr->texture_shader_y_uv; - gl_format[0] = GL_R8_EXT; - gl_format[1] = GL_RG8_EXT; - } else { - gs->shader = &gr->texture_shader_y_xuxv; - gl_format[0] = GL_LUMINANCE; - gl_format[1] = GL_LUMINANCE_ALPHA; - } - es->is_opaque = true; - break; - case WL_SHM_FORMAT_YUYV: - gs->shader = &gr->texture_shader_y_xuxv; - pitch = wl_shm_buffer_get_stride(shm_buffer) / 2; - gl_pixel_type = GL_UNSIGNED_BYTE; - num_planes = 2; - gs->offset[1] = 0; - gs->hsub[1] = 2; - gs->vsub[1] = 1; - if (gr->has_gl_texture_rg) - gl_format[0] = GL_RG8_EXT; - else - gl_format[0] = GL_LUMINANCE_ALPHA; - gl_format[1] = GL_BGRA_EXT; - es->is_opaque = true; - break; - default: - weston_log("warning: unknown shm buffer format: %08x\n", - wl_shm_buffer_get_format(shm_buffer)); - return; - } - - /* 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 || - gl_format[0] != gs->gl_format[0] || - gl_format[1] != gs->gl_format[1] || - gl_format[2] != gs->gl_format[2] || - gl_pixel_type != gs->gl_pixel_type || - gs->buffer_type != BUFFER_TYPE_SHM) { - gs->pitch = pitch; - gs->height = buffer->height; - gs->target = GL_TEXTURE_2D; - gs->gl_format[0] = gl_format[0]; - gs->gl_format[1] = gl_format[1]; - gs->gl_format[2] = gl_format[2]; - gs->gl_pixel_type = gl_pixel_type; - gs->buffer_type = BUFFER_TYPE_SHM; - gs->needs_full_upload = true; - gs->y_inverted = true; - gs->direct_display = false; - - gs->surface = es; - - ensure_textures(gs, num_planes); - } -} - -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++) { - egl_image_unref(gs->images[i]); - gs->images[i] = NULL; - } - gs->num_images = 0; - gs->target = GL_TEXTURE_2D; - es->is_opaque = false; - switch (format) { - case EGL_TEXTURE_RGB: - es->is_opaque = true; - /* fallthrough */ - 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; - es->is_opaque = true; - break; - case EGL_TEXTURE_Y_U_V_WL: - num_planes = 3; - gs->shader = &gr->texture_shader_y_u_v; - es->is_opaque = true; - break; - case EGL_TEXTURE_Y_XUXV_WL: - num_planes = 2; - gs->shader = &gr->texture_shader_y_xuxv; - es->is_opaque = true; - 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] = egl_image_create(gr, - 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]->image); - } - - gs->pitch = buffer->width; - gs->height = buffer->height; - gs->buffer_type = BUFFER_TYPE_EGL; - gs->y_inverted = buffer->y_inverted; -} - -static void -gl_renderer_destroy_dmabuf(struct linux_dmabuf_buffer *dmabuf) -{ - struct dmabuf_image *image = linux_dmabuf_buffer_get_user_data(dmabuf); - - dmabuf_image_destroy(image); -} - -static struct egl_image * -import_simple_dmabuf(struct gl_renderer *gr, - struct dmabuf_attributes *attributes) -{ - struct egl_image *image; - EGLint attribs[50]; - int atti = 0; - bool has_modifier; - - /* This requires the Mesa commit in - * Mesa 10.3 (08264e5dad4df448e7718e782ad9077902089a07) or - * Mesa 10.2.7 (55d28925e6109a4afd61f109e845a8a51bd17652). - * Otherwise Mesa closes the fd behind our back and re-importing - * will fail. - * https://bugs.freedesktop.org/show_bug.cgi?id=76188 - */ - - attribs[atti++] = EGL_WIDTH; - attribs[atti++] = attributes->width; - attribs[atti++] = EGL_HEIGHT; - attribs[atti++] = attributes->height; - attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; - attribs[atti++] = attributes->format; - - if (attributes->modifier[0] != DRM_FORMAT_MOD_INVALID) { - if (!gr->has_dmabuf_import_modifiers) - return NULL; - has_modifier = true; - } else { - has_modifier = false; - } - - if (attributes->n_planes > 0) { - attribs[atti++] = EGL_DMA_BUF_PLANE0_FD_EXT; - attribs[atti++] = attributes->fd[0]; - attribs[atti++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT; - attribs[atti++] = attributes->offset[0]; - attribs[atti++] = EGL_DMA_BUF_PLANE0_PITCH_EXT; - attribs[atti++] = attributes->stride[0]; - if (has_modifier) { - attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT; - attribs[atti++] = attributes->modifier[0] & 0xFFFFFFFF; - attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT; - attribs[atti++] = attributes->modifier[0] >> 32; - } - } - - if (attributes->n_planes > 1) { - attribs[atti++] = EGL_DMA_BUF_PLANE1_FD_EXT; - attribs[atti++] = attributes->fd[1]; - attribs[atti++] = EGL_DMA_BUF_PLANE1_OFFSET_EXT; - attribs[atti++] = attributes->offset[1]; - attribs[atti++] = EGL_DMA_BUF_PLANE1_PITCH_EXT; - attribs[atti++] = attributes->stride[1]; - if (has_modifier) { - attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT; - attribs[atti++] = attributes->modifier[1] & 0xFFFFFFFF; - attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT; - attribs[atti++] = attributes->modifier[1] >> 32; - } - } - - if (attributes->n_planes > 2) { - attribs[atti++] = EGL_DMA_BUF_PLANE2_FD_EXT; - attribs[atti++] = attributes->fd[2]; - attribs[atti++] = EGL_DMA_BUF_PLANE2_OFFSET_EXT; - attribs[atti++] = attributes->offset[2]; - attribs[atti++] = EGL_DMA_BUF_PLANE2_PITCH_EXT; - attribs[atti++] = attributes->stride[2]; - if (has_modifier) { - attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT; - attribs[atti++] = attributes->modifier[2] & 0xFFFFFFFF; - attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT; - attribs[atti++] = attributes->modifier[2] >> 32; - } - } - - if (gr->has_dmabuf_import_modifiers) { - if (attributes->n_planes > 3) { - attribs[atti++] = EGL_DMA_BUF_PLANE3_FD_EXT; - attribs[atti++] = attributes->fd[3]; - attribs[atti++] = EGL_DMA_BUF_PLANE3_OFFSET_EXT; - attribs[atti++] = attributes->offset[3]; - attribs[atti++] = EGL_DMA_BUF_PLANE3_PITCH_EXT; - attribs[atti++] = attributes->stride[3]; - attribs[atti++] = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT; - attribs[atti++] = attributes->modifier[3] & 0xFFFFFFFF; - attribs[atti++] = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT; - attribs[atti++] = attributes->modifier[3] >> 32; - } - } - - attribs[atti++] = EGL_NONE; - - image = egl_image_create(gr, EGL_LINUX_DMA_BUF_EXT, NULL, - attribs); - - return image; -} - -/* The kernel header drm_fourcc.h defines the DRM formats below. We duplicate - * some of the definitions here so that building Weston won't require - * bleeding-edge kernel headers. - */ -#ifndef DRM_FORMAT_R8 -#define DRM_FORMAT_R8 fourcc_code('R', '8', ' ', ' ') /* [7:0] R */ -#endif - -#ifndef DRM_FORMAT_GR88 -#define DRM_FORMAT_GR88 fourcc_code('G', 'R', '8', '8') /* [15:0] G:R 8:8 little endian */ -#endif - -#ifndef DRM_FORMAT_XYUV8888 -#define DRM_FORMAT_XYUV8888 fourcc_code('X', 'Y', 'U', 'V') /* [31:0] X:Y:Cb:Cr 8:8:8:8 little endian */ -#endif - -struct yuv_format_descriptor yuv_formats[] = { - { - .format = DRM_FORMAT_YUYV, - .input_planes = 1, - .output_planes = 2, - .texture_type = TEXTURE_Y_XUXV_WL, - {{ - .width_divisor = 1, - .height_divisor = 1, - .format = DRM_FORMAT_GR88, - .plane_index = 0 - }, { - .width_divisor = 2, - .height_divisor = 1, - .format = DRM_FORMAT_ARGB8888, - .plane_index = 0 - }} - }, { - .format = DRM_FORMAT_NV12, - .input_planes = 2, - .output_planes = 2, - .texture_type = TEXTURE_Y_UV_WL, - {{ - .width_divisor = 1, - .height_divisor = 1, - .format = DRM_FORMAT_R8, - .plane_index = 0 - }, { - .width_divisor = 2, - .height_divisor = 2, - .format = DRM_FORMAT_GR88, - .plane_index = 1 - }} - }, { - .format = DRM_FORMAT_YUV420, - .input_planes = 3, - .output_planes = 3, - .texture_type = TEXTURE_Y_U_V_WL, - {{ - .width_divisor = 1, - .height_divisor = 1, - .format = DRM_FORMAT_R8, - .plane_index = 0 - }, { - .width_divisor = 2, - .height_divisor = 2, - .format = DRM_FORMAT_R8, - .plane_index = 1 - }, { - .width_divisor = 2, - .height_divisor = 2, - .format = DRM_FORMAT_R8, - .plane_index = 2 - }} - }, { - .format = DRM_FORMAT_YUV444, - .input_planes = 3, - .output_planes = 3, - .texture_type = TEXTURE_Y_U_V_WL, - {{ - .width_divisor = 1, - .height_divisor = 1, - .format = DRM_FORMAT_R8, - .plane_index = 0 - }, { - .width_divisor = 1, - .height_divisor = 1, - .format = DRM_FORMAT_R8, - .plane_index = 1 - }, { - .width_divisor = 1, - .height_divisor = 1, - .format = DRM_FORMAT_R8, - .plane_index = 2 - }} - }, { - .format = DRM_FORMAT_XYUV8888, - .input_planes = 1, - .output_planes = 1, - .texture_type = TEXTURE_XYUV_WL, - {{ - .width_divisor = 1, - .height_divisor = 1, - .format = DRM_FORMAT_XBGR8888, - .plane_index = 0 - }} - } -}; - -static struct egl_image * -import_dmabuf_single_plane(struct gl_renderer *gr, - const struct dmabuf_attributes *attributes, - struct yuv_plane_descriptor *descriptor) -{ - struct dmabuf_attributes plane; - struct egl_image *image; - char fmt[4]; - - plane.width = attributes->width / descriptor->width_divisor; - plane.height = attributes->height / descriptor->height_divisor; - plane.format = descriptor->format; - plane.n_planes = 1; - plane.fd[0] = attributes->fd[descriptor->plane_index]; - plane.offset[0] = attributes->offset[descriptor->plane_index]; - plane.stride[0] = attributes->stride[descriptor->plane_index]; - plane.modifier[0] = attributes->modifier[descriptor->plane_index]; - - image = import_simple_dmabuf(gr, &plane); - if (!image) { - weston_log("Failed to import plane %d as %.4s\n", - descriptor->plane_index, - dump_format(descriptor->format, fmt)); - return NULL; - } - - return image; -} - -static bool -import_yuv_dmabuf(struct gl_renderer *gr, - struct dmabuf_image *image) -{ - unsigned i; - int j; - int ret; - struct yuv_format_descriptor *format = NULL; - struct dmabuf_attributes *attributes = &image->dmabuf->attributes; - char fmt[4]; - - for (i = 0; i < ARRAY_LENGTH(yuv_formats); ++i) { - if (yuv_formats[i].format == attributes->format) { - format = &yuv_formats[i]; - break; - } - } - - if (!format) { - weston_log("Error during import, and no known conversion for format " - "%.4s in the renderer\n", - dump_format(attributes->format, fmt)); - return false; - } - - if (attributes->n_planes != format->input_planes) { - weston_log("%.4s dmabuf must contain %d plane%s (%d provided)\n", - dump_format(format->format, fmt), - format->input_planes, - (format->input_planes > 1) ? "s" : "", - attributes->n_planes); - return false; - } - - for (j = 0; j < format->output_planes; ++j) { - image->images[j] = import_dmabuf_single_plane(gr, attributes, - &format->plane[j]); - if (!image->images[j]) { - while (j) { - ret = egl_image_unref(image->images[--j]); - assert(ret == 0); - } - return false; - } - } - - image->num_images = format->output_planes; - - switch (format->texture_type) { - case TEXTURE_Y_XUXV_WL: - image->shader = &gr->texture_shader_y_xuxv; - break; - case TEXTURE_Y_UV_WL: - image->shader = &gr->texture_shader_y_uv; - break; - case TEXTURE_Y_U_V_WL: - image->shader = &gr->texture_shader_y_u_v; - break; - case TEXTURE_XYUV_WL: - image->shader = &gr->texture_shader_xyuv; - break; - default: - assert(false); - } - - return true; -} - -static void -gl_renderer_query_dmabuf_modifiers_full(struct gl_renderer *gr, int format, - uint64_t **modifiers, - unsigned **external_only, - int *num_modifiers); - -static struct dmabuf_format* -dmabuf_format_create(struct gl_renderer *gr, uint32_t format) -{ - struct dmabuf_format *dmabuf_format; - - dmabuf_format = calloc(1, sizeof(struct dmabuf_format)); - if (!dmabuf_format) - return NULL; - - dmabuf_format->format = format; - - gl_renderer_query_dmabuf_modifiers_full(gr, format, - &dmabuf_format->modifiers, - &dmabuf_format->external_only, - &dmabuf_format->num_modifiers); - - if (dmabuf_format->num_modifiers == 0) { - free(dmabuf_format); - return NULL; - } - - wl_list_insert(&gr->dmabuf_formats, &dmabuf_format->link); - return dmabuf_format; -} - -static void -dmabuf_format_destroy(struct dmabuf_format *format) -{ - free(format->modifiers); - free(format->external_only); - wl_list_remove(&format->link); - free(format); -} - -static GLenum -choose_texture_target(struct gl_renderer *gr, - struct dmabuf_attributes *attributes) -{ - struct dmabuf_format *tmp, *format = NULL; - - wl_list_for_each(tmp, &gr->dmabuf_formats, link) { - if (tmp->format == attributes->format) { - format = tmp; - break; - } - } - - if (!format) - format = dmabuf_format_create(gr, attributes->format); - - if (format) { - int i; - - for (i = 0; i < format->num_modifiers; ++i) { - if (format->modifiers[i] == attributes->modifier[0]) { - if(format->external_only[i]) - return GL_TEXTURE_EXTERNAL_OES; - else - return GL_TEXTURE_2D; - } - } - } - - if (attributes->n_planes > 1) - return GL_TEXTURE_EXTERNAL_OES; - - switch (attributes->format & ~DRM_FORMAT_BIG_ENDIAN) { - case DRM_FORMAT_YUYV: - case DRM_FORMAT_YVYU: - case DRM_FORMAT_UYVY: - case DRM_FORMAT_VYUY: - case DRM_FORMAT_AYUV: - case DRM_FORMAT_XYUV8888: - return GL_TEXTURE_EXTERNAL_OES; - default: - return GL_TEXTURE_2D; - } -} - -static struct dmabuf_image * -import_dmabuf(struct gl_renderer *gr, - struct linux_dmabuf_buffer *dmabuf) -{ - struct egl_image *egl_image; - struct dmabuf_image *image; - - image = dmabuf_image_create(); - image->dmabuf = dmabuf; - - egl_image = import_simple_dmabuf(gr, &dmabuf->attributes); - if (egl_image) { - image->num_images = 1; - image->images[0] = egl_image; - image->import_type = IMPORT_TYPE_DIRECT; - image->target = choose_texture_target(gr, &dmabuf->attributes); - - switch (image->target) { - case GL_TEXTURE_2D: - image->shader = &gr->texture_shader_rgba; - break; - default: - image->shader = &gr->texture_shader_egl_external; - } - } else { - if (!import_yuv_dmabuf(gr, image)) { - dmabuf_image_destroy(image); - return NULL; - } - image->import_type = IMPORT_TYPE_GL_CONVERSION; - image->target = GL_TEXTURE_2D; - } - - return image; -} - -static void -gl_renderer_query_dmabuf_formats(struct weston_compositor *wc, - int **formats, int *num_formats) -{ - struct gl_renderer *gr = get_renderer(wc); - static const int fallback_formats[] = { - DRM_FORMAT_ARGB8888, - DRM_FORMAT_XRGB8888, - DRM_FORMAT_YUYV, - DRM_FORMAT_NV12, - DRM_FORMAT_YUV420, - DRM_FORMAT_YUV444, - DRM_FORMAT_XYUV8888, - }; - bool fallback = false; - EGLint num; - - assert(gr->has_dmabuf_import); - - if (!gr->has_dmabuf_import_modifiers || - !gr->query_dmabuf_formats(gr->egl_display, 0, NULL, &num)) { - num = gr->has_gl_texture_rg ? ARRAY_LENGTH(fallback_formats) : 2; - fallback = true; - } - - *formats = calloc(num, sizeof(int)); - if (*formats == NULL) { - *num_formats = 0; - return; - } - - if (fallback) { - memcpy(*formats, fallback_formats, num * sizeof(int)); - *num_formats = num; - return; - } - - if (!gr->query_dmabuf_formats(gr->egl_display, num, *formats, &num)) { - *num_formats = 0; - free(*formats); - return; - } - - *num_formats = num; -} - -static void -gl_renderer_query_dmabuf_modifiers_full(struct gl_renderer *gr, int format, - uint64_t **modifiers, - unsigned **external_only, - int *num_modifiers) -{ - int num; - - assert(gr->has_dmabuf_import); - - if (!gr->has_dmabuf_import_modifiers || - !gr->query_dmabuf_modifiers(gr->egl_display, format, 0, NULL, - NULL, &num) || - num == 0) { - *num_modifiers = 0; - return; - } - - *modifiers = calloc(num, sizeof(uint64_t)); - if (*modifiers == NULL) { - *num_modifiers = 0; - return; - } - if (external_only) { - *external_only = calloc(num, sizeof(unsigned)); - if (*external_only == NULL) { - *num_modifiers = 0; - free(*modifiers); - return; - } - } - if (!gr->query_dmabuf_modifiers(gr->egl_display, format, - num, *modifiers, external_only ? - *external_only : NULL, &num)) { - *num_modifiers = 0; - free(*modifiers); - if (external_only) - free(*external_only); - return; - } - - *num_modifiers = num; -} - -static void -gl_renderer_query_dmabuf_modifiers(struct weston_compositor *wc, int format, - uint64_t **modifiers, - int *num_modifiers) -{ - struct gl_renderer *gr = get_renderer(wc); - - gl_renderer_query_dmabuf_modifiers_full(gr, format, modifiers, NULL, - num_modifiers); -} - -static bool -gl_renderer_import_dmabuf(struct weston_compositor *ec, - struct linux_dmabuf_buffer *dmabuf) -{ - struct gl_renderer *gr = get_renderer(ec); - struct dmabuf_image *image; - int i; - - assert(gr->has_dmabuf_import); - - for (i = 0; i < dmabuf->attributes.n_planes; i++) { - /* return if EGL doesn't support import modifiers */ - if (dmabuf->attributes.modifier[i] != DRM_FORMAT_MOD_INVALID) - if (!gr->has_dmabuf_import_modifiers) - return false; - - /* return if modifiers passed are unequal */ - if (dmabuf->attributes.modifier[i] != - dmabuf->attributes.modifier[0]) - return false; - } - - /* reject all flags we do not recognize or handle */ - if (dmabuf->attributes.flags & ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT) - return false; - - image = import_dmabuf(gr, dmabuf); - if (!image) - return false; - - wl_list_insert(&gr->dmabuf_images, &image->link); - linux_dmabuf_buffer_set_user_data(dmabuf, image, - gl_renderer_destroy_dmabuf); - - return true; -} - -static bool -import_known_dmabuf(struct gl_renderer *gr, - struct dmabuf_image *image) -{ - switch (image->import_type) { - case IMPORT_TYPE_DIRECT: - image->images[0] = import_simple_dmabuf(gr, &image->dmabuf->attributes); - if (!image->images[0]) - return false; - image->num_images = 1; - break; - - case IMPORT_TYPE_GL_CONVERSION: - if (!import_yuv_dmabuf(gr, image)) - return false; - break; - - default: - weston_log("Invalid import type for dmabuf\n"); - return false; - } - - return true; -} - -static bool -dmabuf_is_opaque(struct linux_dmabuf_buffer *dmabuf) -{ - const struct pixel_format_info *info; - - info = pixel_format_get_info(dmabuf->attributes.format & - ~DRM_FORMAT_BIG_ENDIAN); - if (!info) - return false; - - return pixel_format_is_opaque(info); -} - -static void -gl_renderer_attach_dmabuf(struct weston_surface *surface, - struct weston_buffer *buffer, - struct linux_dmabuf_buffer *dmabuf) -{ - struct gl_renderer *gr = get_renderer(surface->compositor); - struct gl_surface_state *gs = get_surface_state(surface); - struct dmabuf_image *image; - int i; - - if (!gr->has_dmabuf_import) { - linux_dmabuf_buffer_send_server_error(dmabuf, - "EGL dmabuf import not supported"); - return; - } - - buffer->width = dmabuf->attributes.width; - buffer->height = dmabuf->attributes.height; - - /* - * GL-renderer uses the OpenGL convention of texture coordinates, where - * the origin is at bottom-left. Because dmabuf buffers have the origin - * at top-left, we must invert the Y_INVERT flag to get the image right. - */ - buffer->y_inverted = - !(dmabuf->attributes.flags & ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT); - - for (i = 0; i < gs->num_images; i++) - egl_image_unref(gs->images[i]); - gs->num_images = 0; - - gs->pitch = buffer->width; - gs->height = buffer->height; - gs->buffer_type = BUFFER_TYPE_EGL; - gs->y_inverted = buffer->y_inverted; - gs->direct_display = dmabuf->direct_display; - surface->is_opaque = dmabuf_is_opaque(dmabuf); - - /* - * We try to always hold an imported EGLImage from the dmabuf - * to prevent the client from preventing re-imports. But, we also - * need to re-import every time the contents may change because - * GL driver's caching may need flushing. - * - * Here we release the cache reference which has to be final. - */ - if (dmabuf->direct_display) - return; - - image = linux_dmabuf_buffer_get_user_data(dmabuf); - - /* The dmabuf_image should have been created during the import */ - assert(image != NULL); - - for (i = 0; i < image->num_images; ++i) - egl_image_unref(image->images[i]); - - if (!import_known_dmabuf(gr, image)) { - linux_dmabuf_buffer_send_server_error(dmabuf, "EGL dmabuf import failed"); - return; - } - - gs->num_images = image->num_images; - for (i = 0; i < gs->num_images; ++i) - gs->images[i] = egl_image_ref(image->images[i]); - - gs->target = image->target; - ensure_textures(gs, gs->num_images); - for (i = 0; i < gs->num_images; ++i) { - glActiveTexture(GL_TEXTURE0 + i); - glBindTexture(gs->target, gs->textures[i]); - gr->image_target_texture_2d(gs->target, gs->images[i]->image); - } - - gs->shader = image->shader; -} - -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; - struct linux_dmabuf_buffer *dmabuf; - EGLint format; - int i; - - weston_buffer_reference(&gs->buffer_ref, buffer); - weston_buffer_release_reference(&gs->buffer_release_ref, - es->buffer_release_ref.buffer_release); - - if (!buffer) { - for (i = 0; i < gs->num_images; i++) { - egl_image_unref(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 = true; - gs->direct_display = false; - es->is_opaque = false; - return; - } - - shm_buffer = wl_shm_buffer_get(buffer->resource); - - if (shm_buffer) - gl_renderer_attach_shm(es, buffer, shm_buffer); - else if (gr->has_bind_display && - gr->query_buffer(gr->egl_display, (void *)buffer->resource, - EGL_TEXTURE_FORMAT, &format)) - gl_renderer_attach_egl(es, buffer, format); - else if ((dmabuf = linux_dmabuf_buffer_get(buffer->resource))) - gl_renderer_attach_dmabuf(es, buffer, dmabuf); - else { - weston_log("unhandled buffer type!\n"); - if (gr->has_bind_display) { - weston_log("eglQueryWaylandBufferWL failed\n"); - gl_renderer_print_egl_error_state(); - } - weston_buffer_reference(&gs->buffer_ref, NULL); - weston_buffer_release_reference(&gs->buffer_release_ref, NULL); - gs->buffer_type = BUFFER_TYPE_NULL; - gs->y_inverted = true; - es->is_opaque = false; - weston_buffer_send_server_error(buffer, - "disconnecting due to unhandled buffer type"); - } -} - -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->buffer_type = BUFFER_TYPE_SOLID; - gs->pitch = 1; - gs->height = 1; - - gs->shader = &gr->solid_shader; -} - -static void -gl_renderer_surface_get_content_size(struct weston_surface *surface, - int *width, int *height) -{ - struct gl_surface_state *gs = get_surface_state(surface); - - if (gs->buffer_type == BUFFER_TYPE_NULL) { - *width = 0; - *height = 0; - } else { - *width = gs->pitch; - *height = gs->height; - } -} - -static uint32_t -pack_color(pixman_format_code_t format, float *c) -{ - uint8_t r = round(c[0] * 255.0f); - uint8_t g = round(c[1] * 255.0f); - uint8_t b = round(c[2] * 255.0f); - uint8_t a = round(c[3] * 255.0f); - - switch (format) { - case PIXMAN_a8b8g8r8: - return (a << 24) | (b << 16) | (g << 8) | r; - default: - assert(0); - return 0; - } -} - -static int -gl_renderer_surface_copy_content(struct weston_surface *surface, - void *target, size_t size, - int src_x, int src_y, - int width, int height) -{ - static const GLfloat verts[4 * 2] = { - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, - 0.0f, 1.0f - }; - static const GLfloat projmat_normal[16] = { /* transpose */ - 2.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 2.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - -1.0f, -1.0f, 0.0f, 1.0f - }; - static const GLfloat projmat_yinvert[16] = { /* transpose */ - 2.0f, 0.0f, 0.0f, 0.0f, - 0.0f, -2.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - -1.0f, 1.0f, 0.0f, 1.0f - }; - const pixman_format_code_t format = PIXMAN_a8b8g8r8; - const size_t bytespp = 4; /* PIXMAN_a8b8g8r8 */ - const GLenum gl_format = GL_RGBA; /* PIXMAN_a8b8g8r8 little-endian */ - struct gl_renderer *gr = get_renderer(surface->compositor); - struct gl_surface_state *gs = get_surface_state(surface); - int cw, ch; - GLuint fbo; - GLuint tex; - GLenum status; - const GLfloat *proj; - int i; - - gl_renderer_surface_get_content_size(surface, &cw, &ch); - - switch (gs->buffer_type) { - case BUFFER_TYPE_NULL: - return -1; - case BUFFER_TYPE_SOLID: - *(uint32_t *)target = pack_color(format, gs->color); - return 0; - case BUFFER_TYPE_SHM: - gl_renderer_flush_damage(surface); - /* fall through */ - case BUFFER_TYPE_EGL: - break; - } - - glGenTextures(1, &tex); - glBindTexture(GL_TEXTURE_2D, tex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cw, ch, - 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - glBindTexture(GL_TEXTURE_2D, 0); - - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, tex, 0); - - status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) { - weston_log("%s: fbo error: %#x\n", __func__, status); - glDeleteFramebuffers(1, &fbo); - glDeleteTextures(1, &tex); - return -1; - } - - glViewport(0, 0, cw, ch); - glDisable(GL_BLEND); - use_shader(gr, gs->shader); - if (gs->y_inverted) - proj = projmat_normal; - else - proj = projmat_yinvert; - - glUniformMatrix4fv(gs->shader->proj_uniform, 1, GL_FALSE, proj); - glUniform1f(gs->shader->alpha_uniform, 1.0f); - - for (i = 0; i < gs->num_textures; i++) { - glUniform1i(gs->shader->tex_uniforms[i], i); - - glActiveTexture(GL_TEXTURE0 + i); - glBindTexture(gs->target, gs->textures[i]); - glTexParameteri(gs->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(gs->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - } - - /* position: */ - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, verts); - glEnableVertexAttribArray(0); - - /* texcoord: */ - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, verts); - glEnableVertexAttribArray(1); - - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - - glDisableVertexAttribArray(1); - glDisableVertexAttribArray(0); - - glPixelStorei(GL_PACK_ALIGNMENT, bytespp); - glReadPixels(src_x, src_y, width, height, gl_format, - GL_UNSIGNED_BYTE, target); - - glDeleteFramebuffers(1, &fbo); - glDeleteTextures(1, &tex); - - return 0; -} - -static void -surface_state_destroy(struct gl_surface_state *gs, struct gl_renderer *gr) -{ - int i; - - wl_list_remove(&gs->surface_destroy_listener.link); - wl_list_remove(&gs->renderer_destroy_listener.link); - - gs->surface->renderer_state = NULL; - - glDeleteTextures(gs->num_textures, gs->textures); - - for (i = 0; i < gs->num_images; i++) - egl_image_unref(gs->images[i]); - - weston_buffer_reference(&gs->buffer_ref, NULL); - weston_buffer_release_reference(&gs->buffer_release_ref, NULL); - pixman_region32_fini(&gs->texture_damage); - free(gs); -} - -static void -surface_state_handle_surface_destroy(struct wl_listener *listener, void *data) -{ - struct gl_surface_state *gs; - struct gl_renderer *gr; - - gs = container_of(listener, struct gl_surface_state, - surface_destroy_listener); - - gr = get_renderer(gs->surface->compositor); - - surface_state_destroy(gs, gr); -} - -static void -surface_state_handle_renderer_destroy(struct wl_listener *listener, void *data) -{ - struct gl_surface_state *gs; - struct gl_renderer *gr; - - gr = data; - - gs = container_of(listener, struct gl_surface_state, - renderer_destroy_listener); - - surface_state_destroy(gs, gr); -} - -static int -gl_renderer_create_surface(struct weston_surface *surface) -{ - struct gl_surface_state *gs; - struct gl_renderer *gr = get_renderer(surface->compositor); - - gs = zalloc(sizeof *gs); - if (gs == NULL) - 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 = true; - gs->direct_display = false; - - gs->surface = surface; - - pixman_region32_init(&gs->texture_damage); - surface->renderer_state = gs; - - gs->surface_destroy_listener.notify = - surface_state_handle_surface_destroy; - wl_signal_add(&surface->destroy_signal, - &gs->surface_destroy_listener); - - gs->renderer_destroy_listener.notify = - surface_state_handle_renderer_destroy; - wl_signal_add(&gr->destroy_signal, - &gs->renderer_destroy_listener); - - if (surface->buffer_ref.buffer) { - gl_renderer_attach(surface, surface->buffer_ref.buffer); - gl_renderer_flush_damage(surface); - } - - return 0; -} - -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 texture_fragment_shader_xyuv[] = - "precision mediump float;\n" - "uniform sampler2D tex;\n" - "varying vec2 v_texcoord;\n" - "uniform float alpha;\n" - "void main() {\n" - " float y = 1.16438356 * (texture2D(tex, v_texcoord).b - 0.0625);\n" - " float u = texture2D(tex, v_texcoord).g - 0.5;\n" - " float v = texture2D(tex, v_texcoord).r - 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 (shader->vertex_shader == GL_NONE) - return -1; - - 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); - if (shader->fragment_shader == GL_NONE) - return -1; - - 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; -} - -void -gl_renderer_log_extensions(const char *name, const char *extensions) -{ -// OHOS -// 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"); - weston_log("%s:", name); - weston_log("%s:", extensions); -} - -static void -log_egl_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); - gl_renderer_log_extensions("EGL extensions", str ? str : "(null)"); -} - -static void -log_gl_info(void) -{ - const char *str; - - 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); - gl_renderer_log_extensions("GL extensions", str ? str : "(null)"); -} - -static void -gl_renderer_output_set_border(struct weston_output *output, - enum gl_renderer_border_side side, - int32_t width, int32_t height, - int32_t tex_width, unsigned char *data) -{ - struct gl_output_state *go = get_output_state(output); - - if (go->borders[side].width != width || - go->borders[side].height != height) - /* In this case, we have to blow everything and do a full - * repaint. */ - go->border_status |= BORDER_SIZE_CHANGED | BORDER_ALL_DIRTY; - - if (data == NULL) { - width = 0; - height = 0; - } - - go->borders[side].width = width; - go->borders[side].height = height; - go->borders[side].tex_width = tex_width; - go->borders[side].data = data; - go->border_status |= 1 << side; -} - -static int -gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface); - -static EGLSurface -gl_renderer_create_window_surface(struct gl_renderer *gr, - EGLNativeWindowType window_for_legacy, - void *window_for_platform, - const uint32_t *drm_formats, - unsigned drm_formats_count) -{ - EGLSurface egl_surface = EGL_NO_SURFACE; - EGLConfig egl_config; - - egl_config = gl_renderer_get_egl_config(gr, EGL_WINDOW_BIT, - drm_formats, drm_formats_count); - if (egl_config == EGL_NO_CONFIG_KHR) - return EGL_NO_SURFACE; - - log_egl_config_info(gr->egl_display, egl_config); - - if (gr->create_platform_window) - egl_surface = gr->create_platform_window(gr->egl_display, - egl_config, - window_for_platform, - NULL); - else - egl_surface = eglCreateWindowSurface(gr->egl_display, - egl_config, - window_for_legacy, NULL); - - return egl_surface; -} - -static int -gl_renderer_output_create(struct weston_output *output, - EGLSurface surface) -{ - struct gl_output_state *go; - int i; - - go = zalloc(sizeof *go); - if (go == NULL) - return -1; - - go->egl_surface = surface; - - for (i = 0; i < BUFFER_DAMAGE_COUNT; i++) - pixman_region32_init(&go->buffer_damage[i]); - - wl_list_init(&go->timeline_render_point_list); - - go->begin_render_sync = EGL_NO_SYNC_KHR; - go->end_render_sync = EGL_NO_SYNC_KHR; - - output->renderer_state = go; - - return 0; -} - -static int -gl_renderer_output_window_create(struct weston_output *output, - const struct gl_renderer_output_options *options) -{ - struct weston_compositor *ec = output->compositor; - struct gl_renderer *gr = get_renderer(ec); - EGLSurface egl_surface = EGL_NO_SURFACE; - int ret = 0; - - egl_surface = gl_renderer_create_window_surface(gr, - options->window_for_legacy, - options->window_for_platform, - options->drm_formats, - options->drm_formats_count); - if (egl_surface == EGL_NO_SURFACE) { - weston_log("failed to create egl surface\n"); - return -1; - } - - ret = gl_renderer_output_create(output, egl_surface); - if (ret < 0) - weston_platform_destroy_egl_surface(gr->egl_display, egl_surface); - - return ret; -} - -static int -gl_renderer_output_pbuffer_create(struct weston_output *output, - const struct gl_renderer_pbuffer_options *options) -{ - struct gl_renderer *gr = get_renderer(output->compositor); - EGLConfig pbuffer_config; - EGLSurface egl_surface; - int ret; - EGLint pbuffer_attribs[] = { - EGL_WIDTH, options->width, - EGL_HEIGHT, options->height, - EGL_NONE - }; - - pbuffer_config = gl_renderer_get_egl_config(gr, EGL_PBUFFER_BIT, - options->drm_formats, - options->drm_formats_count); - if (pbuffer_config == EGL_NO_CONFIG_KHR) { - weston_log("failed to choose EGL config for PbufferSurface\n"); - return -1; - } - - log_egl_config_info(gr->egl_display, pbuffer_config); - - egl_surface = eglCreatePbufferSurface(gr->egl_display, pbuffer_config, - pbuffer_attribs); - if (egl_surface == EGL_NO_SURFACE) { - weston_log("failed to create egl surface\n"); - gl_renderer_print_egl_error_state(); - return -1; - } - - ret = gl_renderer_output_create(output, egl_surface); - if (ret < 0) - eglDestroySurface(gr->egl_display, egl_surface); - - return ret; -} - -static 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); - struct timeline_render_point *trp, *tmp; - int i; - - for (i = 0; i < 2; i++) - pixman_region32_fini(&go->buffer_damage[i]); - - eglMakeCurrent(gr->egl_display, - EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT); - - weston_platform_destroy_egl_surface(gr->egl_display, go->egl_surface); - - if (!wl_list_empty(&go->timeline_render_point_list)) - weston_log("warning: discarding pending timeline render" - "objects at output destruction"); - - wl_list_for_each_safe(trp, tmp, &go->timeline_render_point_list, link) - timeline_render_point_destroy(trp); - - if (go->begin_render_sync != EGL_NO_SYNC_KHR) - gr->destroy_sync(gr->egl_display, go->begin_render_sync); - if (go->end_render_sync != EGL_NO_SYNC_KHR) - gr->destroy_sync(gr->egl_display, go->end_render_sync); - - free(go); -} - -static int -gl_renderer_create_fence_fd(struct weston_output *output) -{ - struct gl_output_state *go = get_output_state(output); - struct gl_renderer *gr = get_renderer(output->compositor); - int fd; - - if (go->end_render_sync == EGL_NO_SYNC_KHR) - return -1; - - fd = gr->dup_native_fence_fd(gr->egl_display, go->end_render_sync); - if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) - return -1; - - return fd; -} - -static void -gl_renderer_destroy(struct weston_compositor *ec) -{ - struct gl_renderer *gr = get_renderer(ec); - struct dmabuf_image *image, *next; - struct dmabuf_format *format, *next_format; - - wl_signal_emit(&gr->destroy_signal, gr); - - 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); - - - wl_list_for_each_safe(image, next, &gr->dmabuf_images, link) - dmabuf_image_destroy(image); - - wl_list_for_each_safe(format, next_format, &gr->dmabuf_formats, link) - dmabuf_format_destroy(format); - - if (gr->dummy_surface != EGL_NO_SURFACE) - weston_platform_destroy_egl_surface(gr->egl_display, - gr->dummy_surface); - - eglTerminate(gr->egl_display); - eglReleaseThread(); - - wl_list_remove(&gr->output_destroy_listener.link); - - wl_array_release(&gr->vertices); - wl_array_release(&gr->vtxcnt); - - if (gr->fragment_binding) - weston_binding_destroy(gr->fragment_binding); - if (gr->fan_binding) - weston_binding_destroy(gr->fan_binding); - - free(gr); -} - -static void -output_handle_destroy(struct wl_listener *listener, void *data) -{ - struct gl_renderer *gr; - struct weston_output *output = data; - - gr = container_of(listener, struct gl_renderer, - output_destroy_listener); - - if (wl_list_empty(&output->compositor->output_list)) - eglMakeCurrent(gr->egl_display, gr->dummy_surface, - gr->dummy_surface, gr->egl_context); -} - -static int -gl_renderer_create_pbuffer_surface(struct gl_renderer *gr) { - EGLConfig pbuffer_config; - static const EGLint pbuffer_attribs[] = { - EGL_WIDTH, 10, - EGL_HEIGHT, 10, - EGL_NONE - }; - - pbuffer_config = gr->egl_config; - if (pbuffer_config == EGL_NO_CONFIG_KHR) { - pbuffer_config = - gl_renderer_get_egl_config(gr, EGL_PBUFFER_BIT, - NULL, 0); - } - if (pbuffer_config == EGL_NO_CONFIG_KHR) { - weston_log("failed to choose EGL config for PbufferSurface\n"); - return -1; - } - - gr->dummy_surface = eglCreatePbufferSurface(gr->egl_display, - pbuffer_config, - pbuffer_attribs); - - if (gr->dummy_surface == EGL_NO_SURFACE) { - weston_log("failed to create PbufferSurface\n"); - return -1; - } - - return 0; -} - -static int -gl_renderer_display_create(struct weston_compositor *ec, - const struct gl_renderer_display_options *options) -{ - struct gl_renderer *gr; - - gr = zalloc(sizeof *gr); - if (gr == NULL) - return -1; - - gr->platform = options->egl_platform; - - if (gl_renderer_setup_egl_client_extensions(gr) < 0) - goto fail; - - 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.surface_set_color = gl_renderer_surface_set_color; - gr->base.destroy = gl_renderer_destroy; - gr->base.surface_get_content_size = - gl_renderer_surface_get_content_size; - gr->base.surface_copy_content = gl_renderer_surface_copy_content; - - if (gl_renderer_setup_egl_display(gr, options->egl_native_display) < 0) - goto fail; - - log_egl_info(gr->egl_display); - - ec->renderer = &gr->base; - - if (gl_renderer_setup_egl_extensions(ec) < 0) - goto fail_with_error; - - if (!gr->has_configless_context) { - EGLint egl_surface_type = options->egl_surface_type; - - if (!gr->has_surfaceless_context) - egl_surface_type |= EGL_PBUFFER_BIT; - - gr->egl_config = - gl_renderer_get_egl_config(gr, - egl_surface_type, - options->drm_formats, - options->drm_formats_count); - if (gr->egl_config == EGL_NO_CONFIG_KHR) { - weston_log("failed to choose EGL config\n"); - goto fail_terminate; - } - } - - ec->capabilities |= WESTON_CAP_ROTATION_ANY; - ec->capabilities |= WESTON_CAP_CAPTURE_YFLIP; - ec->capabilities |= WESTON_CAP_VIEW_CLIP_MASK; - if (gr->has_native_fence_sync && gr->has_wait_sync) - ec->capabilities |= WESTON_CAP_EXPLICIT_SYNC; - - wl_list_init(&gr->dmabuf_images); - if (gr->has_dmabuf_import) { - gr->base.import_dmabuf = gl_renderer_import_dmabuf; - gr->base.query_dmabuf_formats = - gl_renderer_query_dmabuf_formats; - gr->base.query_dmabuf_modifiers = - gl_renderer_query_dmabuf_modifiers; - } - wl_list_init(&gr->dmabuf_formats); - - if (gr->has_surfaceless_context) { - weston_log("EGL_KHR_surfaceless_context available\n"); - gr->dummy_surface = EGL_NO_SURFACE; - } else { - weston_log("EGL_KHR_surfaceless_context unavailable. " - "Trying PbufferSurface\n"); - - if (gl_renderer_create_pbuffer_surface(gr) < 0) - goto fail_with_error; - } - - wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_RGB565); - wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_YUV420); - wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_NV12); - wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_YUYV); - - wl_signal_init(&gr->destroy_signal); - - if (gl_renderer_setup(ec, gr->dummy_surface) < 0) { - if (gr->dummy_surface != EGL_NO_SURFACE) - weston_platform_destroy_egl_surface(gr->egl_display, - gr->dummy_surface); - goto fail_with_error; - } - - return 0; - -fail_with_error: - gl_renderer_print_egl_error_state(); -fail_terminate: - eglTerminate(gr->egl_display); -fail: - free(gr); - ec->renderer = NULL; - return -1; -} - -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_xuxv.vertex_source = vertex_shader; - gr->texture_shader_y_xuxv.fragment_source = - texture_fragment_shader_y_xuxv; - - gr->texture_shader_xyuv.vertex_source = vertex_shader; - gr->texture_shader_xyuv.fragment_source = texture_fragment_shader_xyuv; - - gr->solid_shader.vertex_source = vertex_shader; - gr->solid_shader.fragment_source = solid_fragment_shader; - - return 0; -} - -static void -fragment_debug_binding(struct weston_keyboard *keyboard, - const struct timespec *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 = !gr->fragment_shader_debug; - - 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->texture_shader_xyuv); - 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_keyboard *keyboard, - const struct timespec *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 uint32_t -get_gl_version(void) -{ - const char *version; - int major, minor; - - version = (const char *) glGetString(GL_VERSION); - if (version && - (sscanf(version, "%d.%d", &major, &minor) == 2 || - sscanf(version, "OpenGL ES %d.%d", &major, &minor) == 2)) { - return GR_GL_VERSION(major, minor); - } - - return GR_GL_VERSION_INVALID; -} - -static int -gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) -{ - struct gl_renderer *gr = get_renderer(ec); - const char *extensions; - EGLBoolean ret; - - EGLint context_attribs[16] = { - EGL_CONTEXT_CLIENT_VERSION, 0, - }; - unsigned int nattr = 2; - - if (!eglBindAPI(EGL_OPENGL_ES_API)) { - weston_log("failed to bind EGL_OPENGL_ES_API\n"); - gl_renderer_print_egl_error_state(); - return -1; - } - - /* - * Being the compositor we require minimum output latency, - * so request a high priority context for ourselves - that should - * reschedule all of our rendering and its dependencies to be completed - * first. If the driver doesn't permit us to create a high priority - * context, it will fallback to the default priority (MEDIUM). - */ - if (gr->has_context_priority) { - context_attribs[nattr++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG; - context_attribs[nattr++] = EGL_CONTEXT_PRIORITY_HIGH_IMG; - } - - assert(nattr < ARRAY_LENGTH(context_attribs)); - context_attribs[nattr] = EGL_NONE; - - /* try to create an OpenGLES 3 context first */ - context_attribs[1] = 3; - gr->egl_context = eglCreateContext(gr->egl_display, gr->egl_config, - EGL_NO_CONTEXT, context_attribs); - if (gr->egl_context == NULL) { - /* and then fallback to OpenGLES 2 */ - context_attribs[1] = 2; - 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; - } - } - - if (gr->has_context_priority) { - EGLint value = EGL_CONTEXT_PRIORITY_MEDIUM_IMG; - - eglQueryContext(gr->egl_display, gr->egl_context, - EGL_CONTEXT_PRIORITY_LEVEL_IMG, &value); - - if (value != EGL_CONTEXT_PRIORITY_HIGH_IMG) { - weston_log("Failed to obtain a high priority context.\n"); - /* Not an error, continue on as normal */ - } - } - - 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; - } - - gr->gl_version = get_gl_version(); - if (gr->gl_version == GR_GL_VERSION_INVALID) { - weston_log("warning: failed to detect GLES version, " - "defaulting to 2.0.\n"); - gr->gl_version = GR_GL_VERSION(2, 0); - } - - log_gl_info(); - - gr->image_target_texture_2d = - (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES"); - - extensions = (const char *) glGetString(GL_EXTENSIONS); - if (!extensions) { - weston_log("Retrieving GL extension string failed.\n"); - return -1; - } - - if (!weston_check_egl_extension(extensions, "GL_EXT_texture_format_BGRA8888")) { - weston_log("GL_EXT_texture_format_BGRA8888 not available\n"); - return -1; - } - - if (weston_check_egl_extension(extensions, "GL_EXT_read_format_bgra")) - ec->read_format = PIXMAN_a8r8g8b8; - else - ec->read_format = PIXMAN_a8b8g8r8; - - if (gr->gl_version >= GR_GL_VERSION(3, 0) || - weston_check_egl_extension(extensions, "GL_EXT_unpack_subimage")) - gr->has_unpack_subimage = true; - - if (gr->gl_version >= GR_GL_VERSION(3, 0) || - weston_check_egl_extension(extensions, "GL_EXT_texture_rg")) - gr->has_gl_texture_rg = true; - - if (weston_check_egl_extension(extensions, "GL_OES_EGL_image_external")) - gr->has_egl_image_external = true; - - glActiveTexture(GL_TEXTURE0); - - if (compile_shaders(ec)) - return -1; - -// OHOS remove debugger -// gr->fragment_binding = -// weston_compositor_add_debug_binding(ec, KEY_S, -// fragment_debug_binding, -// ec); -// gr->fan_binding = -// weston_compositor_add_debug_binding(ec, KEY_F, -// fan_debug_repaint_binding, -// ec); - - gr->output_destroy_listener.notify = output_handle_destroy; - wl_signal_add(&ec->output_destroyed_signal, - &gr->output_destroy_listener); - - 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; -} - -WL_EXPORT struct gl_renderer_interface gl_renderer_interface = { - .display_create = gl_renderer_display_create, - .output_window_create = gl_renderer_output_window_create, - .output_pbuffer_create = gl_renderer_output_pbuffer_create, - .output_destroy = gl_renderer_output_destroy, - .output_set_border = gl_renderer_output_set_border, - .create_fence_fd = gl_renderer_create_fence_fd, -}; diff --git a/libweston/renderer-gl/gl-renderer.h b/libweston/renderer-gl/gl-renderer.h deleted file mode 100755 index 2bf938e..0000000 --- a/libweston/renderer-gl/gl-renderer.h +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright © 2012 John Kåre Alsaker - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include - -#include -#include "backend.h" -#include "libweston-internal.h" -#include // OHOS hilog - -// OHOS hilog -#ifndef weston_log -#define weston_log(fmt, ...) (HILOG_INFO(LOG_CORE, fmt, ##__VA_ARGS__)) -#define weston_log_continue(fmt, ...) (HILOG_DEBUG(LOG_CORE, fmt, ##__VA_ARGS__)) -#endif - -#ifdef ENABLE_EGL - -#include -#include - -#else - -typedef int EGLint; -typedef int EGLenum; -typedef void *EGLDisplay; -typedef void *EGLSurface; -typedef void *EGLConfig; -typedef intptr_t EGLNativeDisplayType; -typedef intptr_t EGLNativeWindowType; -#define EGL_DEFAULT_DISPLAY ((EGLNativeDisplayType)0) -#define EGL_PBUFFER_BIT 0x0001 -#define EGL_WINDOW_BIT 0x0004 - -#endif /* ENABLE_EGL */ - -enum gl_renderer_border_side { - GL_RENDERER_BORDER_TOP = 0, - GL_RENDERER_BORDER_LEFT = 1, - GL_RENDERER_BORDER_RIGHT = 2, - GL_RENDERER_BORDER_BOTTOM = 3, -}; - -/** - * Options passed to the \c display_create method of the GL renderer interface. - * - * \see struct gl_renderer_interface - */ -struct gl_renderer_display_options { - /** The EGL platform identifier */ - EGLenum egl_platform; - /** The native display corresponding to the given EGL platform */ - void *egl_native_display; - /** EGL_SURFACE_TYPE bits for the base EGLConfig */ - EGLint egl_surface_type; - /** Array of DRM pixel formats acceptable for the base EGLConfig */ - const uint32_t *drm_formats; - /** The \c drm_formats array length */ - unsigned drm_formats_count; -}; - -struct gl_renderer_output_options { - /** Native window handle for \c eglCreateWindowSurface */ - EGLNativeWindowType window_for_legacy; - /** Native window handle for \c eglCreatePlatformWindowSurface */ - void *window_for_platform; - /** Array of DRM pixel formats acceptable for the window */ - const uint32_t *drm_formats; - /** The \c drm_formats array length */ - unsigned drm_formats_count; -}; - -struct gl_renderer_pbuffer_options { - /** Width of the rendering surface in pixels */ - int width; - /** Height of the rendering surface in pixels */ - int height; - /** Array of DRM pixel formats acceptable for the pbuffer */ - const uint32_t *drm_formats; - /** The \c drm_formats array length */ - unsigned drm_formats_count; -}; - -struct gl_renderer_interface { - /** - * Initialize GL-renderer with the given EGL platform and native display - * - * \param ec The weston_compositor where to initialize. - * \param options The options struct describing display configuration - * \return 0 on success, -1 on failure. - * - * This function creates an EGLDisplay and initializes it. It also - * creates the GL ES context and sets it up. It attempts GL ES 3.0 - * and falls back to GL ES 2.0 if 3.0 is not supported. - * - * If \c platform is zero or EGL_EXT_platform_base is not supported, - * choosing the platform is left for the EGL implementation. Otherwise - * the given platform is used explicitly if the EGL implementation - * advertises it. Without the advertisement this function fails. - * - * If neither EGL_KHR_no_config_context or EGL_MESA_configless_context - * are supported, the arguments egl_surface_type, drm_formats, and - * drm_formats_count are used to find a so called base EGLConfig. The - * GL context is created with the base EGLConfig, and outputs will be - * required to use the same config as well. If one or both of the - * extensions are supported, these arguments are unused, and each - * output can use a different EGLConfig (pixel format). - * - * The first format in drm_formats that matches any EGLConfig - * determines which EGLConfig is chosen. On EGL GBM platform, the - * pixel format must match exactly. On other platforms, it is enough - * that each R, G, B, A channel has the same number of bits as in the - * DRM format. - */ - int (*display_create)(struct weston_compositor *ec, - const struct gl_renderer_display_options *options); - - /** - * Attach GL-renderer to the output with a native window - * - * \param output The output to create a rendering surface for. - * \param options The options struct describing output configuration - * \return 0 on success, -1 on failure. - * - * This function creates the renderer data structures needed to repaint - * the output. The repaint results will be directed to the given native - * window. - * - * If EGL_EXT_platform_base is supported then \c window_for_platform is - * used, otherwise \c window_for_legacy is used. This is because the - * handle on X11 platform is different between the two. - * - * The first format in drm_formats that matches any EGLConfig - * determines which EGLConfig is chosen. See \c display_create about - * how the matching works and the possible limitations. - * - * This function should be used only if \c display_create was called - * with \c EGL_WINDOW_BIT in \c egl_surface_type. - */ - int (*output_window_create)(struct weston_output *output, - const struct gl_renderer_output_options *options); - - /** - * Attach GL-renderer to the output with internal pixel storage - * - * \param output The output to create a rendering surface for. - * \param options The options struct describing the pbuffer - * \return 0 on success, -1 on failure. - * - * This function creates the renderer data structures needed to repaint - * the output. The repaint results will be kept internal and can only - * be accessed through e.g. screen capture. - * - * The first format in drm_formats that matches any EGLConfig - * determines which EGLConfig is chosen. See \c display_create about - * how the matching works and the possible limitations. - * - * This function should be used only if \c display_create was called - * with \c EGL_PBUFFER_BIT in \c egl_surface_type. - */ - int (*output_pbuffer_create)(struct weston_output *output, - const struct gl_renderer_pbuffer_options *options); - - void (*output_destroy)(struct weston_output *output); - - /* Sets the output border. - * - * The side specifies the side for which we are setting the border. - * The width and height are the width and height of the border. - * The tex_width patemeter specifies the width of the actual - * texture; this may be larger than width if the data is not - * tightly packed. - * - * The top and bottom textures will extend over the sides to the - * full width of the bordered window. The right and left edges, - * however, will extend only to the top and bottom of the - * compositor surface. This is demonstrated by the picture below: - * - * +-----------------------+ - * | TOP | - * +-+-------------------+-+ - * | | | | - * |L| |R| - * |E| |I| - * |F| |G| - * |T| |H| - * | | |T| - * | | | | - * +-+-------------------+-+ - * | BOTTOM | - * +-----------------------+ - */ - void (*output_set_border)(struct weston_output *output, - enum gl_renderer_border_side side, - int32_t width, int32_t height, - int32_t tex_width, unsigned char *data); - - /* Create fence sync FD to wait for GPU rendering. - * - * Return FD on success, -1 on failure or unsupported - * EGL_ANDROID_native_fence_sync extension. - */ - int (*create_fence_fd)(struct weston_output *output); -}; diff --git a/libweston/renderer-gl/meson.build b/libweston/renderer-gl/meson.build deleted file mode 100644 index 374e65b..0000000 --- a/libweston/renderer-gl/meson.build +++ /dev/null @@ -1,39 +0,0 @@ -if not get_option('renderer-gl') - subdir_done() -endif - -config_h.set('ENABLE_EGL', '1') - -srcs_renderer_gl = [ - 'egl-glue.c', - 'gl-renderer.c', - linux_dmabuf_unstable_v1_protocol_c, - linux_dmabuf_unstable_v1_server_protocol_h, -] - -deps_renderer_gl = [ - dep_libm, - dep_pixman, - dep_libweston_private, - dep_libdrm_headers, - dep_vertex_clipping -] - -foreach name : [ 'egl', 'glesv2' ] - d = dependency(name, required: false) - if not d.found() - error('gl-renderer requires @0@ which was not found. Or, you can use \'-Drenderer-gl=false\'.'.format(name)) - endif - deps_renderer_gl += d -endforeach - -plugin_gl = shared_library( - 'gl-renderer', - srcs_renderer_gl, - include_directories: common_inc, - dependencies: deps_renderer_gl, - name_prefix: '', - install: true, - install_dir: dir_module_libweston -) -env_modmap += 'gl-renderer.so=@0@;'.format(plugin_gl.full_path()) diff --git a/libweston/screenshooter.c b/libweston/screenshooter.c deleted file mode 100644 index 4ea519b..0000000 --- a/libweston/screenshooter.c +++ /dev/null @@ -1,494 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "shared/helpers.h" -#include "shared/timespec-util.h" -#include "backend.h" -#include "libweston-internal.h" - -#include "wcap/wcap-decode.h" - -struct screenshooter_frame_listener { - struct wl_listener listener; - struct weston_buffer *buffer; - struct weston_output *output; - weston_screenshooter_done_func_t done; - void *data; -}; - -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 = l->output; - struct weston_compositor *compositor = output->compositor; - int32_t stride; - uint8_t *pixels, *d, *s; - - weston_output_disable_planes_decr(output); - 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) { - l->done(l->data, WESTON_SCREENSHOOTER_NO_MEMORY); - 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); - - wl_shm_buffer_begin_access(l->buffer->shm_buffer); - - 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; - } - - wl_shm_buffer_end_access(l->buffer->shm_buffer); - - l->done(l->data, WESTON_SCREENSHOOTER_SUCCESS); - free(pixels); - free(l); -} - -WL_EXPORT int -weston_screenshooter_shoot(struct weston_output *output, - struct weston_buffer *buffer, - weston_screenshooter_done_func_t done, void *data) -{ - struct screenshooter_frame_listener *l; - - if (!wl_shm_buffer_get(buffer->resource)) { - done(data, WESTON_SCREENSHOOTER_BAD_BUFFER); - return -1; - } - - 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) { - done(data, WESTON_SCREENSHOOTER_BAD_BUFFER); - return -1; - } - - l = malloc(sizeof *l); - if (l == NULL) { - done(data, WESTON_SCREENSHOOTER_NO_MEMORY); - return -1; - } - - l->buffer = buffer; - l->output = output; - l->done = done; - l->data = data; - l->listener.notify = screenshooter_frame_notify; - wl_signal_add(&output->frame_signal, &l->listener); - weston_output_disable_planes_incr(output); - weston_output_damage(output); - - return 0; -} - -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, destroying; -}; - -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 -weston_recorder_destroy(struct weston_recorder *recorder); - -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 = recorder->output; - struct weston_compositor *compositor = output->compositor; - uint32_t msecs = timespec_to_msec(&output->frame_time); - pixman_box32_t *r; - pixman_region32_t damage, transformed_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_init(&transformed_damage); - pixman_region32_intersect(&damage, &output->region, data); - pixman_region32_translate(&damage, -output->x, -output->y); - weston_transformed_region(output->width, output->height, - output->transform, output->current_scale, - &damage, &transformed_damage); - pixman_region32_fini(&damage); - - r = pixman_region32_rectangles(&transformed_damage, &n); - if (n == 0) { - pixman_region32_fini(&transformed_damage); - return; - } - - 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); - - p = outbuf; - run = prev = 0; /* quiet gcc */ - for (j = 0; j < height; j++) { - if (do_yflip) - s = recorder->rect + width * j; - else - s = recorder->rect + width * (height - j - 1); - y_orig = r[i].y2 - j - 1; - 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(&transformed_damage); - recorder->count++; - - if (recorder->destroying) - weston_recorder_destroy(recorder); -} - -static void -weston_recorder_free(struct weston_recorder *recorder) -{ - if (recorder == NULL) - return; - - free(recorder->tmpbuf); - free(recorder->rect); - free(recorder->frame); - free(recorder); -} - -static struct weston_recorder * -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 = zalloc(sizeof *recorder); - if (recorder == NULL) { - weston_log("%s: out of memory\n", __func__); - return NULL; - } - - stride = output->current_mode->width; - size = stride * 4 * output->current_mode->height; - recorder->frame = zalloc(size); - recorder->rect = malloc(size); - recorder->output = output; - - if ((recorder->frame == NULL) || (recorder->rect == NULL)) { - weston_log("%s: out of memory\n", __func__); - goto err_recorder; - } - - if (!do_yflip) { - recorder->tmpbuf = malloc(size); - if (recorder->tmpbuf == NULL) { - weston_log("%s: out of memory\n", __func__); - goto err_recorder; - } - } - - 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"); - goto err_recorder; - } - - recorder->fd = open(filename, - O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); - - if (recorder->fd < 0) { - weston_log("problem opening output file %s: %s\n", filename, - strerror(errno)); - goto err_recorder; - } - - 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); - weston_output_disable_planes_incr(output); - weston_output_damage(output); - - return recorder; - -err_recorder: - weston_recorder_free(recorder); - return NULL; -} - -static void -weston_recorder_destroy(struct weston_recorder *recorder) -{ - wl_list_remove(&recorder->frame_listener.link); - close(recorder->fd); - weston_output_disable_planes_decr(recorder->output); - weston_recorder_free(recorder); -} - -WL_EXPORT struct weston_recorder * -weston_recorder_start(struct weston_output *output, const char *filename) -{ - struct wl_listener *listener; - - listener = wl_signal_get(&output->frame_signal, - weston_recorder_frame_notify); - if (listener) { - weston_log("a recorder on output %s is already running\n", - output->name); - return NULL; - } - - weston_log("starting recorder for output %s, file %s\n", - output->name, filename); - return weston_recorder_create(output, filename); -} - -WL_EXPORT void -weston_recorder_stop(struct weston_recorder *recorder) -{ - weston_log("stopping recorder, total file size %dM, %d frames\n", - recorder->total / (1024 * 1024), recorder->count); - - recorder->destroying = 1; - weston_output_schedule_repaint(recorder->output); -} diff --git a/libweston/spring-tool.c b/libweston/spring-tool.c deleted file mode 100644 index b032d73..0000000 --- a/libweston/spring-tool.c +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright © 2011 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include -#include - -#include "config.h" - -#include -#include "shared/timespec-util.h" - -WL_EXPORT void -weston_view_geometry_dirty(struct weston_view *view) -{ -} - -WL_EXPORT int -weston_log(const char *fmt, ...) -{ - return 0; -} - -WL_EXPORT void -weston_view_schedule_repaint(struct weston_view *view) -{ -} - -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; - struct timespec time = { 0 }; - - weston_spring_init(&spring, k, current, target); - spring.friction = friction; - spring.previous = 0.48; - spring.timestamp = (struct timespec) { 0 }; - - while (!weston_spring_done(&spring)) { - printf("\t%" PRId64 "\t%f\n", - timespec_to_msec(&time), spring.current); - weston_spring_update(&spring, &time); - timespec_add_msec(&time, &time, 16); - } - - return 0; -} diff --git a/libweston/tde-render-part.c b/libweston/tde-render-part.c deleted file mode 100644 index 1f3da8e..0000000 --- a/libweston/tde-render-part.c +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright (c) 2021 Huawei Device Co., Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "tde-render-part.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "libweston/weston-log.h" -#include "pixman-renderer-protected.h" - -struct tde_image_t { - int32_t width; - int32_t height; - uint32_t format; - int n_planes; // 默认为1,引入plane是为了保证方案的通用性 - __u64 phyaddr; // 物理地址 - int fd[MAX_DMABUF_PLANES]; - uint32_t offset[MAX_DMABUF_PLANES]; - uint32_t stride[MAX_DMABUF_PLANES]; -}; - -struct tde_output_state_t { - struct tde_image_t image; -}; - -struct tde_surface_state_t { - struct tde_image_t image; -}; - -struct tde_renderer_t { - GfxFuncs *gfx_funcs; - int use_tde; -}; - -struct drm_hisilicon_phy_addr { - uint64_t phyaddr; // return the phy addr - int fd; // dmabuf file descriptor -}; - -#define DRM_HISILICON_GEM_FD_TO_PHYADDR (0x1) -#define DRM_IOCTL_HISILICON_GEM_FD_TO_PHYADDR \ - (DRM_IOWR(DRM_COMMAND_BASE + DRM_HISILICON_GEM_FD_TO_PHYADDR, \ - struct drm_hisilicon_phy_addr)) - -static uint64_t drm_fd_phyaddr(struct weston_compositor *compositor, int fd) -{ - struct drm_backend *backend = to_drm_backend(compositor); - struct drm_hisilicon_phy_addr args = { .fd = fd }; - - int ret = ioctl(backend->drm.fd, - DRM_IOCTL_HISILICON_GEM_FD_TO_PHYADDR, &args); - return args.phyaddr; -} - -static void drm_close_handle(struct weston_compositor *compositor, int fd) -{ - struct drm_backend *backend = to_drm_backend(compositor); - uint32_t gem_handle; - int ret = drmPrimeFDToHandle(backend->drm.fd , fd, &gem_handle); - if (ret) { - weston_log("Failed to PrimeFDToHandle gem handle"); - return; - } - - struct drm_gem_close gem_close = { .handle = gem_handle }; - ret = drmIoctl(backend->drm.fd, DRM_IOCTL_GEM_CLOSE, &gem_close); - if (ret) { - weston_log("Failed to close gem handle"); - return; - } -} - -static uint64_t dst_image_phyaddr(struct weston_output *wo) -{ - struct drm_output *output = to_drm_output(wo); - struct drm_backend *backend = to_drm_backend(wo->compositor); - - int prime_fd; - int ret = drmPrimeHandleToFD(backend->drm.fd, - output->dumb[output->current_image]->handles[0], - DRM_CLOEXEC, &prime_fd); - - uint64_t phyaddr = drm_fd_phyaddr(wo->compositor, prime_fd); - close(prime_fd); - return phyaddr; -} - -static void src_surface_init(ISurface *surface, struct tde_image_t buffer) -{ - surface->width = buffer.width; - surface->height = buffer.height; - surface->phyAddr = buffer.phyaddr; - surface->stride = buffer.stride[0]; - surface->enColorFmt = PIXEL_FMT_RGBA_8888; - surface->bAlphaExt1555 = true; - surface->bAlphaMax255 = true; - surface->alpha0 = 0XFF; - surface->alpha1 = 0XFF; -} - -static void dst_surface_init(ISurface *surface, pixman_image_t *target_image, - struct weston_output *output) -{ - surface->width = pixman_image_get_width(target_image); - surface->height = pixman_image_get_height(target_image); - surface->phyAddr = dst_image_phyaddr(output); - surface->enColorFmt = PIXEL_FMT_BGRA_8888; - surface->stride = pixman_image_get_stride(target_image); - surface->bAlphaExt1555 = true; - surface->bAlphaMax255 = true; - surface->alpha0 = 0XFF; - surface->alpha1 = 0XFF; -} - -static int tde_repaint_region(struct weston_view *ev, - struct weston_output *output, - pixman_region32_t *buffer_region, - pixman_region32_t *repaint_output) -{ - struct pixman_renderer *renderer = output->compositor->renderer; - struct pixman_surface_state *surface = get_surface_state(ev->surface); - struct pixman_output_state *output_state = get_output_state(output); - - int ret = 0; - ret = renderer->tde->gfx_funcs->InitGfx(); - if (ret) { - return -1; - } - - pixman_image_t *target_image = output_state->hw_buffer; - if (output_state->shadow_image) { - target_image = output_state->shadow_image; - } - - ISurface dstSurface = {}; - dst_surface_init(&dstSurface, target_image, output); - - pixman_box32_t dstRect = *pixman_region32_extents(repaint_output); - IRect IdstRect = {dstRect.x1, dstRect.y1, - dstRect.x2 - dstRect.x1, dstRect.y2 - dstRect.y1}; - - if (ev->surface->type == 2) { - GfxOpt opt = { - .blendType = BLEND_SRCOVER, - .enableScale = true, - .enPixelAlpha = true, - }; - renderer->tde->gfx_funcs->FillRect(&dstSurface, &IdstRect, - 0x00000000, &opt); - } else { - GfxOpt opt = { - .blendType = BLEND_SRC, - .rotateType = ROTATE_90, - .enableScale = true, - .enPixelAlpha = true, - }; - pixman_box32_t srcRect = *pixman_region32_extents(buffer_region); - IRect IsrcRect = {srcRect.x1, srcRect.y1, - srcRect.x2 - srcRect.x1 ,srcRect.y2 - srcRect.y1}; - ISurface srcSurface = {}; - src_surface_init(&srcSurface, surface->tde->image); - - ret = renderer->tde->gfx_funcs->Blit(&srcSurface, &IsrcRect, &dstSurface, &IdstRect, &opt); - } - - ret = renderer->tde->gfx_funcs->DeinitGfx(); - return 0; -} - -static bool import_dmabuf(struct weston_compositor *ec, - struct linux_dmabuf_buffer *dmabuf) -{ - return true; -} - -static void query_dmabuf_formats(struct weston_compositor *wc, - int **formats, int *num_formats) -{ - static const int fallback_formats[] = { - DRM_FORMAT_ARGB8888, - DRM_FORMAT_XRGB8888, - DRM_FORMAT_YUYV, - DRM_FORMAT_NV12, - DRM_FORMAT_YUV420, - DRM_FORMAT_YUV444, - }; - int num = ARRAY_LENGTH(fallback_formats); - if (num > 0) { - *formats = calloc(num, sizeof(int)); - } - - memcpy_s(*formats, num * sizeof(int), fallback_formats, sizeof(fallback_formats)); - *num_formats = num; -} - -static void query_dmabuf_modifiers(struct weston_compositor *wc, int format, - uint64_t **modifiers, int *num_modifiers) -{ - *modifiers = NULL; - *num_modifiers = 0; -} - -int tde_renderer_alloc_hook(struct pixman_renderer *renderer) -{ - renderer->tde = zalloc(sizeof(*renderer->tde)); - if (renderer->tde == NULL) { - return -1; - } - - if (GfxInitialize(&renderer->tde->gfx_funcs) == 0) { - renderer->tde->use_tde = 1; - } - - renderer->base.import_dmabuf = import_dmabuf; - renderer->base.query_dmabuf_formats = query_dmabuf_formats; - renderer->base.query_dmabuf_modifiers = query_dmabuf_modifiers; - return 0; -} - -int tde_renderer_free_hook(struct pixman_renderer *renderer) -{ - GfxUninitialize(&renderer->tde->gfx_funcs); - free(renderer->tde); - return 0; -} - -int tde_output_state_alloc_hook(struct pixman_output_state *state) -{ - state->tde = zalloc(sizeof(*state->tde)); - return 0; -} - -int tde_output_state_free_hook(struct pixman_output_state *state) -{ - free(state->tde); - return 0; -} - -int tde_surface_state_alloc_hook(struct pixman_surface_state *state) -{ - state->tde = zalloc(sizeof(*state->tde)); - return 0; -} - -int tde_surface_state_free_hook(struct pixman_surface_state *state) -{ - free(state->tde); - return 0; -} - -static void buffer_state_handle_buffer_destroy(struct wl_listener *listener, - void *data) -{ - struct pixman_surface_state *ps = container_of(listener, - struct pixman_surface_state, buffer_destroy_listener); - if (ps->image) { - tde_unref_image_hook(ps->image); - - pixman_image_unref(ps->image); - ps->image = NULL; - } - - ps->buffer_destroy_listener.notify = NULL; -} - -int tde_render_attach_hook(struct weston_surface *es, struct weston_buffer *buffer) -{ - if (!buffer) { - return -1; - } - - struct linux_dmabuf_buffer *dmabuf = linux_dmabuf_buffer_get(buffer->resource); - if (!dmabuf) { - return -1; - } - - struct pixman_surface_state *ps = get_surface_state(es); - weston_buffer_reference(&ps->buffer_ref, buffer); - weston_buffer_release_reference(&ps->buffer_release_ref, - es->buffer_release_ref.buffer_release); - - if (ps->buffer_destroy_listener.notify) { - wl_list_remove(&ps->buffer_destroy_listener.link); - ps->buffer_destroy_listener.notify = NULL; - } - - if (ps->image) { - tde_unref_image_hook(ps->image); - - pixman_image_unref(ps->image); - ps->image = NULL; - } - - pixman_format_code_t pixman_format = PIXMAN_a8r8g8b8; - buffer->legacy_buffer = NULL; - buffer->width = dmabuf->attributes.width; - buffer->height = dmabuf->attributes.height; - int stride = dmabuf->attributes.stride[0]; - int fd = dmabuf->attributes.fd[0]; - - ps->tde->image.width = dmabuf->attributes.width; - ps->tde->image.height = dmabuf->attributes.height; - ps->tde->image.stride[0] = dmabuf->attributes.stride[0]; - ps->tde->image.format = dmabuf->attributes.format; - ps->tde->image.fd[0] = fd; - - uint32_t* ptr = (uint32_t*)mmap(NULL, stride * buffer->height, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - - ps->tde->image.phyaddr = drm_fd_phyaddr(es->compositor, fd); - drm_close_handle(es->compositor, fd); - if (ps->tde->image.phyaddr == 0) { - if (ptr) { - munmap(ptr, stride * buffer->height); - } - return 0; - } - - ps->image = pixman_image_create_bits(pixman_format, buffer->width, buffer->height, ptr, stride); - - ps->buffer_destroy_listener.notify = buffer_state_handle_buffer_destroy; - wl_signal_add(&buffer->destroy_signal, &ps->buffer_destroy_listener); - return 0; -} - -int tde_repaint_region_hook(struct weston_view *ev, struct weston_output *output, - pixman_region32_t *buffer_region, pixman_region32_t *repaint_output) -{ - struct pixman_renderer *renderer = output->compositor->renderer; - if (!renderer->tde->use_tde) { - return -1; - } - - return tde_repaint_region(ev, output, buffer_region, repaint_output); -} - -int tde_unref_image_hook(pixman_image_t *image) -{ - if (image == NULL) { - return 0; - } - - int height = pixman_image_get_height(image); - int stride = pixman_image_get_stride(image); - void *ptr = pixman_image_get_data(image); - if (ptr) { - munmap(ptr, height * stride); - } - - return 0; -} diff --git a/libweston/tde-render-part.h b/libweston/tde-render-part.h deleted file mode 100644 index 26b0262..0000000 --- a/libweston/tde-render-part.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2021 Huawei Device Co., Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "pixman-renderer-protected.h" - -#ifndef LIBWESTON_TDE_RENDER_PART_H -#define LIBWESTON_TDE_RENDER_PART_H - -// hook return 0: success, other: failure -int tde_renderer_alloc_hook(struct pixman_renderer *renderer); -int tde_renderer_free_hook(struct pixman_renderer *renderer); - -int tde_output_state_alloc_hook(struct pixman_output_state *state); -int tde_output_state_free_hook(struct pixman_output_state *state); - -int tde_surface_state_alloc_hook(struct pixman_surface_state *state); -int tde_surface_state_free_hook(struct pixman_surface_state *state); - -int tde_render_attach_hook(struct weston_surface *es, struct weston_buffer *buffer); - -int tde_repaint_region_hook(struct weston_view *ev, struct weston_output *output, - pixman_region32_t *buffer_region, - pixman_region32_t *repaint_output); - -int tde_unref_image_hook(pixman_image_t *image); - -#endif // LIBWESTON_TDE_RENDER_PART_H diff --git a/libweston/timeline.c b/libweston/timeline.c deleted file mode 100644 index d5738f4..0000000 --- a/libweston/timeline.c +++ /dev/null @@ -1,461 +0,0 @@ -/* - * Copyright © 2014 Pekka Paalanen - * Copyright © 2014, 2019 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include - -#include -#include -#include "timeline.h" -#include "weston-log-internal.h" - -/** - * Timeline itself is not a subscriber but a scope (a producer of data), and it - * re-routes the data it produces to all the subscriptions (and implicitly - * to the subscribers) using a subscription iteration to go through all of them. - * - * Public API: - * * weston_timeline_refresh_subscription_objects() - allows outside parts of - * libweston notify/signal timeline code about the fact that underlying object - * has suffered some modifications and needs to re-emit the object ID. - * * weston_log_timeline_point() - which will disseminate data to all - * subscriptions - * - * Do note that only weston_timeline_refresh_subscription_objects() - * is exported in libweston. - * - * Destruction of the objects assigned to each underlying objects happens in - * two places: one in the logging framework callback of the log scope - * ('destroy_subscription'), and secondly, when the object itself gets - * destroyed. - * - * timeline_emit_context - For each subscription this object will be created to - * store a buffer when the object itself will be written and a subscription, - * which will be used to force the object ID if there is a need to do so (the - * underlying object has been refreshed, or better said has suffered some - * modification). Data written to a subscription will be flushed before the - * data written to the FILE *. - * - * @param cur a FILE * - * @param subscription a pointer to an already created subscription - * - * @ingroup internal-log - * @sa weston_timeline_point - */ -struct timeline_emit_context { - FILE *cur; - struct weston_log_subscription *subscription; -}; - -/** Create a timeline subscription and hang it off the subscription - * - * Called when the subscription is created. - * - * @ingroup internal-log - */ -void -weston_timeline_create_subscription(struct weston_log_subscription *sub, - void *user_data) -{ - struct weston_timeline_subscription *tl_sub = zalloc(sizeof(*tl_sub)); - if (!tl_sub) - return; - - wl_list_init(&tl_sub->objects); - - /* attach this timeline_subscription to it */ - weston_log_subscription_set_data(sub, tl_sub); -} - -static void -weston_timeline_destroy_subscription_object(struct weston_timeline_subscription_object *sub_obj) -{ - /* remove the notify listener */ - wl_list_remove(&sub_obj->destroy_listener.link); - sub_obj->destroy_listener.notify = NULL; - - wl_list_remove(&sub_obj->subscription_link); - free(sub_obj); -} - -/** Destroy the timeline subscription and all timeline subscription objects - * associated with it. - * - * Called when (before) the subscription is destroyed. - * - * @ingroup internal-log - */ -void -weston_timeline_destroy_subscription(struct weston_log_subscription *sub, - void *user_data) -{ - struct weston_timeline_subscription *tl_sub = - weston_log_subscription_get_data(sub); - struct weston_timeline_subscription_object *sub_obj, *tmp_sub_obj; - - if (!tl_sub) - return; - - wl_list_for_each_safe(sub_obj, tmp_sub_obj, - &tl_sub->objects, subscription_link) - weston_timeline_destroy_subscription_object(sub_obj); - - free(tl_sub); -} - -static bool -weston_timeline_check_object_refresh(struct weston_timeline_subscription_object *obj) -{ - if (obj->force_refresh == true) { - obj->force_refresh = false; - return true; - } - return false; -} - -static struct weston_timeline_subscription_object * -weston_timeline_subscription_search(struct weston_timeline_subscription *tl_sub, - void *object) -{ - struct weston_timeline_subscription_object *sub_obj; - - wl_list_for_each(sub_obj, &tl_sub->objects, subscription_link) - if (sub_obj->object == object) - return sub_obj; - - return NULL; -} - -static struct weston_timeline_subscription_object * -weston_timeline_subscription_object_create(void *object, - struct weston_timeline_subscription *tm_sub) -{ - struct weston_timeline_subscription_object *sub_obj; - - sub_obj = zalloc(sizeof(*sub_obj)); - sub_obj->id = ++tm_sub->next_id; - sub_obj->object = object; - - /* when the object is created so that it has the chance to display the - * object ID, we set the refresh status; it will only be re-freshed by - * the backend (or part parts) when the underlying objects has suffered - * modifications */ - sub_obj->force_refresh = true; - - wl_list_insert(&tm_sub->objects, &sub_obj->subscription_link); - - return sub_obj; -} - -static void -weston_timeline_destroy_subscription_object_notify(struct wl_listener *listener, void *data) -{ - struct weston_timeline_subscription_object *sub_obj; - - sub_obj = wl_container_of(listener, sub_obj, destroy_listener); - weston_timeline_destroy_subscription_object(sub_obj); -} - -static struct weston_timeline_subscription_object * -weston_timeline_subscription_output_ensure(struct weston_timeline_subscription *tl_sub, - struct weston_output *output) -{ - struct weston_timeline_subscription_object *sub_obj; - - sub_obj = weston_timeline_subscription_search(tl_sub, output); - if (!sub_obj) { - sub_obj = weston_timeline_subscription_object_create(output, tl_sub); - - sub_obj->destroy_listener.notify = - weston_timeline_destroy_subscription_object_notify; - wl_signal_add(&output->destroy_signal, - &sub_obj->destroy_listener); - } - return sub_obj; -} - -static struct weston_timeline_subscription_object * -weston_timeline_subscription_surface_ensure(struct weston_timeline_subscription *tl_sub, - struct weston_surface *surface) -{ - struct weston_timeline_subscription_object *sub_obj; - - sub_obj = weston_timeline_subscription_search(tl_sub, surface); - if (!sub_obj) { - sub_obj = weston_timeline_subscription_object_create(surface, tl_sub); - - sub_obj->destroy_listener.notify = - weston_timeline_destroy_subscription_object_notify; - wl_signal_add(&surface->destroy_signal, - &sub_obj->destroy_listener); - } - - return sub_obj; -} - -static void -fprint_quoted_string(struct weston_log_subscription *sub, const char *str) -{ - if (!str) { - weston_log_subscription_printf(sub, "null"); - return; - } - - weston_log_subscription_printf(sub, "\"%s\"", str); -} - -static void -emit_weston_output_print_id(struct weston_log_subscription *sub, - struct weston_timeline_subscription_object *sub_obj, - const char *name) -{ - if (!weston_timeline_check_object_refresh(sub_obj)) - return; - - weston_log_subscription_printf(sub, "{ \"id\":%u, " - "\"type\":\"weston_output\", \"name\":", sub_obj->id); - fprint_quoted_string(sub, name); - weston_log_subscription_printf(sub, " }\n"); -} - -static int -emit_weston_output(struct timeline_emit_context *ctx, void *obj) -{ - struct weston_log_subscription *sub = ctx->subscription; - struct weston_output *output = obj; - struct weston_timeline_subscription_object *sub_obj; - struct weston_timeline_subscription *tl_sub; - - tl_sub = weston_log_subscription_get_data(sub); - sub_obj = weston_timeline_subscription_output_ensure(tl_sub, output); - emit_weston_output_print_id(sub, sub_obj, output->name); - - assert(sub_obj->id != 0); - fprintf(ctx->cur, "\"wo\":%u", sub_obj->id); - - return 1; -} - - -static void -check_weston_surface_description(struct weston_log_subscription *sub, - struct weston_surface *s, - struct weston_timeline_subscription *tm_sub, - struct weston_timeline_subscription_object *sub_obj) -{ - struct weston_surface *mains; - char d[512]; - char mainstr[32]; - - if (!weston_timeline_check_object_refresh(sub_obj)) - return; - - mains = weston_surface_get_main_surface(s); - if (mains != s) { - struct weston_timeline_subscription_object *new_sub_obj; - - new_sub_obj = weston_timeline_subscription_surface_ensure(tm_sub, mains); - check_weston_surface_description(sub, mains, tm_sub, new_sub_obj); - if (snprintf(mainstr, sizeof(mainstr), ", \"main_surface\":%u", - new_sub_obj->id) < 0) - mainstr[0] = '\0'; - } else { - mainstr[0] = '\0'; - } - - if (!s->get_label || s->get_label(s, d, sizeof(d)) < 0) - d[0] = '\0'; - - weston_log_subscription_printf(sub, "{ \"id\":%u, " - "\"type\":\"weston_surface\", \"desc\":", - sub_obj->id); - fprint_quoted_string(sub, d[0] ? d : NULL); - weston_log_subscription_printf(sub, "%s }\n", mainstr); -} - -static int -emit_weston_surface(struct timeline_emit_context *ctx, void *obj) -{ - struct weston_log_subscription *sub = ctx->subscription; - struct weston_surface *surface = obj; - struct weston_timeline_subscription_object *sub_obj; - struct weston_timeline_subscription *tl_sub; - - tl_sub = weston_log_subscription_get_data(sub); - sub_obj = weston_timeline_subscription_surface_ensure(tl_sub, surface); - check_weston_surface_description(sub, surface, tl_sub, sub_obj); - - assert(sub_obj->id != 0); - fprintf(ctx->cur, "\"ws\":%u", sub_obj->id); - - return 1; -} - -static int -emit_vblank_timestamp(struct timeline_emit_context *ctx, void *obj) -{ - struct timespec *ts = obj; - - fprintf(ctx->cur, "\"vblank_monotonic\":[%" PRId64 ", %ld]", - (int64_t)ts->tv_sec, ts->tv_nsec); - - return 1; -} - -static int -emit_gpu_timestamp(struct timeline_emit_context *ctx, void *obj) -{ - struct timespec *ts = obj; - - fprintf(ctx->cur, "\"gpu\":[%" PRId64 ", %ld]", - (int64_t)ts->tv_sec, ts->tv_nsec); - - return 1; -} - -static struct weston_timeline_subscription_object * -weston_timeline_get_subscription_object(struct weston_log_subscription *sub, - void *object) -{ - struct weston_timeline_subscription *tl_sub; - - tl_sub = weston_log_subscription_get_data(sub); - if (!tl_sub) - return NULL; - - return weston_timeline_subscription_search(tl_sub, object); -} - -/** Sets (on) the timeline subscription object refresh status. - * - * This function 'notifies' timeline to print the object ID. The timeline code - * will reset it back, so there's no need for users to do anything about it. - * - * Can be used from outside libweston. - * - * @param wc a weston_compositor instance - * @param object the underyling object - * - * @ingroup log - */ -WL_EXPORT void -weston_timeline_refresh_subscription_objects(struct weston_compositor *wc, - void *object) -{ - struct weston_log_subscription *sub = NULL; - - while ((sub = weston_log_subscription_iterate(wc->timeline, sub))) { - struct weston_timeline_subscription_object *sub_obj; - - sub_obj = weston_timeline_get_subscription_object(sub, object); - if (sub_obj) - sub_obj->force_refresh = true; - } -} - -typedef int (*type_func)(struct timeline_emit_context *ctx, void *obj); - -static const type_func type_dispatch[] = { - [TLT_OUTPUT] = emit_weston_output, - [TLT_SURFACE] = emit_weston_surface, - [TLT_VBLANK] = emit_vblank_timestamp, - [TLT_GPU] = emit_gpu_timestamp, -}; - -/** Disseminates the message to all subscriptions of the scope \c - * timeline_scope - * - * The TL_POINT() is a wrapper over this function, but it uses the weston_compositor - * instance to pass the timeline scope. - * - * @param timeline_scope the timeline scope - * @param name the name of the timeline point. Interpretable by the tool reading - * the output (wesgr). - * - * @ingroup log - */ -WL_EXPORT void -weston_timeline_point(struct weston_log_scope *timeline_scope, - const char *name, ...) -{ - struct timespec ts; - enum timeline_type otype; - void *obj; - char buf[512]; - struct weston_log_subscription *sub = NULL; - - if (!weston_log_scope_is_enabled(timeline_scope)) - return; - - clock_gettime(CLOCK_MONOTONIC, &ts); - - while ((sub = weston_log_subscription_iterate(timeline_scope, sub))) { - va_list argp; - struct timeline_emit_context ctx = {}; - - memset(buf, 0, sizeof(buf)); - ctx.cur = fmemopen(buf, sizeof(buf), "w"); - ctx.subscription = sub; - - if (!ctx.cur) { - weston_log("Timeline error in fmemopen, closing.\n"); - return; - } - - fprintf(ctx.cur, "{ \"T\":[%" PRId64 ", %ld], \"N\":\"%s\"", - (int64_t)ts.tv_sec, ts.tv_nsec, name); - - va_start(argp, name); - while (1) { - otype = va_arg(argp, enum timeline_type); - if (otype == TLT_END) - break; - - obj = va_arg(argp, void *); - if (type_dispatch[otype]) { - fprintf(ctx.cur, ", "); - type_dispatch[otype](&ctx, obj); - } - } - va_end(argp); - - fprintf(ctx.cur, " }\n"); - fflush(ctx.cur); - if (ferror(ctx.cur)) { - weston_log("Timeline error in constructing entry, closing.\n"); - } else { - weston_log_subscription_printf(ctx.subscription, "%s", buf); - } - - fclose(ctx.cur); - - } -} diff --git a/libweston/timeline.h b/libweston/timeline.h deleted file mode 100644 index aaed743..0000000 --- a/libweston/timeline.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright © 2014 Pekka Paalanen - * Copyright © 2014, 2019 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_TIMELINE_H -#define WESTON_TIMELINE_H - -#include -#include - -#include -#include - -enum timeline_type { - TLT_END = 0, - TLT_OUTPUT, - TLT_SURFACE, - TLT_VBLANK, - TLT_GPU, -}; - -/** Timeline subscription created for each subscription - * - * Created automatically by weston_log_scope::new_subscription and - * destroyed by weston_log_scope::destroy_subscription. - * - * @ingroup internal-log - */ -struct weston_timeline_subscription { - unsigned int next_id; - struct wl_list objects; /**< weston_timeline_subscription_object::subscription_link */ -}; - -/** - * Created when object is first seen for a particular timeline subscription - * Destroyed when the subscription got destroyed or object was destroyed - * - * @ingroup internal-log - */ -struct weston_timeline_subscription_object { - void *object; /**< points to the object */ - unsigned int id; - bool force_refresh; - struct wl_list subscription_link; /**< weston_timeline_subscription::objects */ - struct wl_listener destroy_listener; -}; - -#define TYPEVERIFY(type, arg) ({ \ - typeof(arg) tmp___ = (arg); \ - (void)((type)0 == tmp___); \ - tmp___; }) - -/** - * Should be used as the last argument when using TL_POINT macro - * - * @ingroup log - */ -#define TLP_END TLT_END, NULL - -#define TLP_OUTPUT(o) TLT_OUTPUT, TYPEVERIFY(struct weston_output *, (o)) -#define TLP_SURFACE(s) TLT_SURFACE, TYPEVERIFY(struct weston_surface *, (s)) -#define TLP_VBLANK(t) TLT_VBLANK, TYPEVERIFY(const struct timespec *, (t)) -#define TLP_GPU(t) TLT_GPU, TYPEVERIFY(const struct timespec *, (t)) - -/** This macro is used to add timeline points. - * - * Use TLP_END when done for the vargs. - * - * @param ec weston_compositor instance - * - * @ingroup log - */ -#define TL_POINT(ec, ...) do { \ - weston_timeline_point(ec->timeline, __VA_ARGS__); \ -} while (0) - -void -weston_timeline_point(struct weston_log_scope *timeline_scope, - const char *name, ...); - -#endif /* WESTON_TIMELINE_H */ diff --git a/libweston/touch-calibration.c b/libweston/touch-calibration.c deleted file mode 100644 index 9dd99bb..0000000 --- a/libweston/touch-calibration.c +++ /dev/null @@ -1,716 +0,0 @@ -/* - * Copyright 2017-2018 Collabora, Ltd. - * Copyright 2017-2018 General Electric Company - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include "shared/helpers.h" -#include "shared/string-helpers.h" -#include -#include "shared/timespec-util.h" -#include -#include "libweston-internal.h" -#include "backend.h" - -#include "weston-touch-calibration-server-protocol.h" - -struct weston_touch_calibrator { - struct wl_resource *resource; - - struct weston_compositor *compositor; - - struct weston_surface *surface; - struct wl_listener surface_destroy_listener; - struct wl_listener surface_commit_listener; - - struct weston_touch_device *device; - struct wl_listener device_destroy_listener; - - struct weston_output *output; - struct wl_listener output_destroy_listener; - - struct weston_view *view; - - /** The calibration procedure has been cancelled. */ - bool calibration_cancelled; - - /** The current touch sequence has been cancelled. */ - bool touch_cancelled; -}; - -static struct weston_touch_calibrator * -calibrator_from_device(struct weston_touch_device *device) -{ - return device->aggregate->seat->compositor->touch_calibrator; -} - -static uint32_t -wire_uint_from_double(double c) -{ - assert(c >= 0.0); - assert(c <= 1.0); - - return round(c * 0xffffffff); -} - -static bool -normalized_is_valid(const struct weston_point2d_device_normalized *p) -{ - return p->x >= 0.0 && p->x <= 1.0 && - p->y >= 0.0 && p->y <= 1.0; -} - -WL_EXPORT void -notify_touch_calibrator(struct weston_touch_device *device, - const struct timespec *time, int32_t slot, - const struct weston_point2d_device_normalized *norm, - int touch_type) -{ - struct weston_touch_calibrator *calibrator; - struct wl_resource *res; - uint32_t msecs; - uint32_t x = 0; - uint32_t y = 0; - - calibrator = calibrator_from_device(device); - if (!calibrator) - return; - - res = calibrator->resource; - - /* Ignore any touch events coming from another device */ - if (device != calibrator->device) { - if (touch_type == WL_TOUCH_DOWN) - weston_touch_calibrator_send_invalid_touch(res); - return; - } - - /* Ignore all events if we have sent 'cancel' event until all - * touches (on the seat) are up. - */ - if (calibrator->touch_cancelled) { - if (calibrator->device->aggregate->num_tp == 0) { - assert(touch_type == WL_TOUCH_UP); - calibrator->touch_cancelled = false; - } - return; - } - - msecs = timespec_to_msec(time); - if (touch_type != WL_TOUCH_UP) { - if (normalized_is_valid(norm)) { - x = wire_uint_from_double(norm->x); - y = wire_uint_from_double(norm->y); - } else { - /* Coordinates are out of bounds */ - if (touch_type == WL_TOUCH_MOTION) { - weston_touch_calibrator_send_cancel(res); - calibrator->touch_cancelled = true; - } - weston_touch_calibrator_send_invalid_touch(res); - return; - } - } - - switch (touch_type) { - case WL_TOUCH_UP: - weston_touch_calibrator_send_up(res, msecs, slot); - break; - case WL_TOUCH_DOWN: - weston_touch_calibrator_send_down(res, msecs, slot, x, y); - break; - case WL_TOUCH_MOTION: - weston_touch_calibrator_send_motion(res, msecs, slot, x, y); - break; - default: - return; - } -} - -WL_EXPORT void -notify_touch_calibrator_frame(struct weston_touch_device *device) -{ - struct weston_touch_calibrator *calibrator; - - calibrator = calibrator_from_device(device); - if (!calibrator) - return; - - weston_touch_calibrator_send_frame(calibrator->resource); -} - -WL_EXPORT void -notify_touch_calibrator_cancel(struct weston_touch_device *device) -{ - struct weston_touch_calibrator *calibrator; - - calibrator = calibrator_from_device(device); - if (!calibrator) - return; - - weston_touch_calibrator_send_cancel(calibrator->resource); -} - -static void -map_calibrator(struct weston_touch_calibrator *calibrator) -{ - struct weston_compositor *c = calibrator->compositor; - struct weston_touch_device *device = calibrator->device; - static const struct weston_touch_device_matrix identity = { - .m = { 1, 0, 0, 0, 1, 0} - }; - - assert(!calibrator->view); - assert(calibrator->output); - assert(calibrator->surface); - assert(calibrator->surface->resource); - - calibrator->view = weston_view_create(calibrator->surface); - if (!calibrator->view) { - wl_resource_post_no_memory(calibrator->surface->resource); - return; - } - - weston_layer_entry_insert(&c->calibrator_layer.view_list, - &calibrator->view->layer_link); - - weston_view_set_position(calibrator->view, - calibrator->output->x, - calibrator->output->y); - calibrator->view->output = calibrator->surface->output; - calibrator->view->is_mapped = true; - - calibrator->surface->output = calibrator->output; - calibrator->surface->is_mapped = true; - - weston_output_schedule_repaint(calibrator->output); - - device->ops->get_calibration(device, &device->saved_calibration); - device->ops->set_calibration(device, &identity); -} - -static void -unmap_calibrator(struct weston_touch_calibrator *calibrator) -{ - struct weston_touch_device *device = calibrator->device; - - wl_list_remove(&calibrator->surface_commit_listener.link); - wl_list_init(&calibrator->surface_commit_listener.link); - - if (!calibrator->view) - return; - - weston_view_destroy(calibrator->view); - calibrator->view = NULL; - weston_surface_unmap(calibrator->surface); - - /* Reload saved calibration */ - if (device) - device->ops->set_calibration(device, - &device->saved_calibration); -} - -void -touch_calibrator_mode_changed(struct weston_compositor *compositor) -{ - struct weston_touch_calibrator *calibrator; - - calibrator = compositor->touch_calibrator; - if (!calibrator) - return; - - if (calibrator->calibration_cancelled) - return; - - if (compositor->touch_mode == WESTON_TOUCH_MODE_CALIB) - map_calibrator(calibrator); -} - -static void -touch_calibrator_surface_committed(struct wl_listener *listener, void *data) -{ - struct weston_touch_calibrator *calibrator = - container_of(listener, struct weston_touch_calibrator, - surface_commit_listener); - struct weston_surface *surface = calibrator->surface; - - wl_list_remove(&calibrator->surface_commit_listener.link); - wl_list_init(&calibrator->surface_commit_listener.link); - - if (surface->width != calibrator->output->width || - surface->height != calibrator->output->height) { - wl_resource_post_error(calibrator->resource, - WESTON_TOUCH_CALIBRATOR_ERROR_BAD_SIZE, - "calibrator surface size does not match"); - return; - } - - weston_compositor_set_touch_mode_calib(calibrator->compositor); - /* results in call to touch_calibrator_mode_changed() */ -} - -static void -touch_calibrator_convert(struct wl_client *client, - struct wl_resource *resource, - int32_t x, - int32_t y, - uint32_t coordinate_id) -{ - struct weston_touch_calibrator *calibrator; - struct wl_resource *coordinate_resource; - struct weston_output *output; - struct weston_surface *surface; - uint32_t version; - struct weston_vector p = { { 0.0, 0.0, 0.0, 1.0 } }; - struct weston_point2d_device_normalized norm; - - version = wl_resource_get_version(resource); - calibrator = wl_resource_get_user_data(resource); - surface = calibrator->surface; - output = calibrator->output; - - coordinate_resource = - wl_resource_create(client, &weston_touch_coordinate_interface, - version, coordinate_id); - if (!coordinate_resource) { - wl_client_post_no_memory(client); - return; - } - - if (calibrator->calibration_cancelled) { - weston_touch_coordinate_send_result(coordinate_resource, 0, 0); - wl_resource_destroy(coordinate_resource); - return; - } - - if (!surface || !surface->is_mapped) { - wl_resource_post_error(resource, - WESTON_TOUCH_CALIBRATOR_ERROR_NOT_MAPPED, - "calibrator surface is not mapped"); - return; - } - assert(calibrator->view); - assert(output); - - if (x < 0 || y < 0 || x >= surface->width || y >= surface->height) { - wl_resource_post_error(resource, - WESTON_TOUCH_CALIBRATOR_ERROR_BAD_COORDINATES, - "convert(%d, %d) input is out of bounds", - x, y); - return; - } - - /* Convert from surface-local coordinates into global, from global - * into output-raw, do perspective division and normalize. - */ - weston_view_to_global_float(calibrator->view, x, y, &p.f[0], &p.f[1]); - weston_matrix_transform(&output->matrix, &p); - norm.x = p.f[0] / (p.f[3] * output->current_mode->width); - norm.y = p.f[1] / (p.f[3] * output->current_mode->height); - - if (!normalized_is_valid(&norm)) { - wl_resource_post_error(resource, - WESTON_TOUCH_CALIBRATOR_ERROR_BAD_COORDINATES, - "convert(%d, %d) output is out of bounds", - x, y); - return; - } - - weston_touch_coordinate_send_result(coordinate_resource, - wire_uint_from_double(norm.x), - wire_uint_from_double(norm.y)); - wl_resource_destroy(coordinate_resource); -} - -static void -destroy_touch_calibrator(struct wl_resource *resource) -{ - struct weston_touch_calibrator *calibrator; - - calibrator = wl_resource_get_user_data(resource); - - calibrator->compositor->touch_calibrator = NULL; - - weston_compositor_set_touch_mode_normal(calibrator->compositor); - - if (calibrator->surface) { - unmap_calibrator(calibrator); - wl_list_remove(&calibrator->surface_destroy_listener.link); - wl_list_remove(&calibrator->surface_commit_listener.link); - } - - if (calibrator->device) - wl_list_remove(&calibrator->device_destroy_listener.link); - - if (calibrator->output) - wl_list_remove(&calibrator->output_destroy_listener.link); - - free(calibrator); -} - -static void -touch_calibrator_destroy(struct wl_client *client, - struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static const struct weston_touch_calibrator_interface -touch_calibrator_implementation = { - touch_calibrator_destroy, - touch_calibrator_convert -}; - -static void -touch_calibrator_cancel_calibration(struct weston_touch_calibrator *calibrator) -{ - weston_touch_calibrator_send_cancel_calibration(calibrator->resource); - calibrator->calibration_cancelled = true; - - if (calibrator->surface) - unmap_calibrator(calibrator); -} - -static void -touch_calibrator_output_destroyed(struct wl_listener *listener, void *data) -{ - struct weston_touch_calibrator *calibrator = - container_of(listener, struct weston_touch_calibrator, - output_destroy_listener); - - assert(calibrator->output == data); - calibrator->output = NULL; - - touch_calibrator_cancel_calibration(calibrator); -} - -static void -touch_calibrator_device_destroyed(struct wl_listener *listener, void *data) -{ - struct weston_touch_calibrator *calibrator = - container_of(listener, struct weston_touch_calibrator, - device_destroy_listener); - - assert(calibrator->device == data); - calibrator->device = NULL; - - touch_calibrator_cancel_calibration(calibrator); -} - -static void -touch_calibrator_surface_destroyed(struct wl_listener *listener, void *data) -{ - struct weston_touch_calibrator *calibrator = - container_of(listener, struct weston_touch_calibrator, - surface_destroy_listener); - - assert(calibrator->surface->resource == data); - - unmap_calibrator(calibrator); - calibrator->surface = NULL; -} - -static void -touch_calibration_destroy(struct wl_client *client, - struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static struct weston_touch_device * -weston_compositor_find_touch_device_by_syspath( - struct weston_compositor *compositor, - const char *syspath) -{ - struct weston_seat *seat; - struct weston_touch *touch; - struct weston_touch_device *device; - - if (!syspath) - return NULL; - - wl_list_for_each(seat, &compositor->seat_list, link) { - touch = weston_seat_get_touch(seat); - if (!touch) - continue; - - wl_list_for_each(device, &touch->device_list, link) { - if (strcmp(device->syspath, syspath) == 0) - return device; - } - } - - return NULL; -} - -static void -touch_calibration_create_calibrator( - struct wl_client *client, - struct wl_resource *touch_calibration_resource, - struct wl_resource *surface_resource, - const char *syspath, - uint32_t calibrator_id) -{ - struct weston_compositor *compositor; - struct weston_touch_calibrator *calibrator; - struct weston_touch_device *device; - struct weston_output *output = NULL; - struct weston_surface *surface; - uint32_t version; - int ret; - - version = wl_resource_get_version(touch_calibration_resource); - compositor = wl_resource_get_user_data(touch_calibration_resource); - - if (compositor->touch_calibrator != NULL) { - wl_resource_post_error(touch_calibration_resource, - WESTON_TOUCH_CALIBRATION_ERROR_ALREADY_EXISTS, - "a calibrator has already been created"); - return; - } - - calibrator = zalloc(sizeof *calibrator); - if (!calibrator) { - wl_client_post_no_memory(client); - return; - } - - calibrator->compositor = compositor; - calibrator->resource = wl_resource_create(client, - &weston_touch_calibrator_interface, - version, calibrator_id); - if (!calibrator->resource) { - wl_client_post_no_memory(client); - goto err_dealloc; - } - - surface = wl_resource_get_user_data(surface_resource); - assert(surface); - ret = weston_surface_set_role(surface, "weston_touch_calibrator", - touch_calibration_resource, - WESTON_TOUCH_CALIBRATION_ERROR_INVALID_SURFACE); - if (ret < 0) - goto err_destroy_resource; - - calibrator->surface_destroy_listener.notify = - touch_calibrator_surface_destroyed; - wl_resource_add_destroy_listener(surface->resource, - &calibrator->surface_destroy_listener); - calibrator->surface = surface; - - calibrator->surface_commit_listener.notify = - touch_calibrator_surface_committed; - wl_signal_add(&surface->commit_signal, - &calibrator->surface_commit_listener); - - device = weston_compositor_find_touch_device_by_syspath(compositor, - syspath); - if (device) { - output = device->ops->get_output(device); - if (weston_touch_device_can_calibrate(device) && output) - calibrator->device = device; - } - - if (!calibrator->device) { - wl_resource_post_error(touch_calibration_resource, - WESTON_TOUCH_CALIBRATION_ERROR_INVALID_DEVICE, - "the given touch device '%s' is not valid", - syspath ?: ""); - goto err_unlink_surface; - } - - calibrator->device_destroy_listener.notify = - touch_calibrator_device_destroyed; - wl_signal_add(&calibrator->device->destroy_signal, - &calibrator->device_destroy_listener); - - wl_resource_set_implementation(calibrator->resource, - &touch_calibrator_implementation, - calibrator, destroy_touch_calibrator); - - assert(output); - calibrator->output_destroy_listener.notify = - touch_calibrator_output_destroyed; - wl_signal_add(&output->destroy_signal, - &calibrator->output_destroy_listener); - calibrator->output = output; - - weston_touch_calibrator_send_configure(calibrator->resource, - output->width, - output->height); - - compositor->touch_calibrator = calibrator; - - return; - -err_unlink_surface: - wl_list_remove(&calibrator->surface_commit_listener.link); - wl_list_remove(&calibrator->surface_destroy_listener.link); - -err_destroy_resource: - wl_resource_destroy(calibrator->resource); - -err_dealloc: - free(calibrator); -} - -static void -touch_calibration_save(struct wl_client *client, - struct wl_resource *touch_calibration_resource, - const char *device_name, - struct wl_array *matrix_data) -{ - struct weston_touch_device *device; - struct weston_compositor *compositor; - struct weston_touch_device_matrix calibration; - struct weston_touch_calibrator *calibrator; - int i = 0; - float *c; - - compositor = wl_resource_get_user_data(touch_calibration_resource); - - device = weston_compositor_find_touch_device_by_syspath(compositor, - device_name); - if (!device || !weston_touch_device_can_calibrate(device)) { - wl_resource_post_error(touch_calibration_resource, - WESTON_TOUCH_CALIBRATION_ERROR_INVALID_DEVICE, - "the given device is not valid"); - return; - } - - wl_array_for_each(c, matrix_data) { - calibration.m[i++] = *c; - } - - /* If calibration can't be saved, don't set it as current */ - if (compositor->touch_calibration_save && - compositor->touch_calibration_save(compositor, device, - &calibration) < 0) - return; - - /* If calibrator is still mapped, the compositor will use - * saved_calibration when going back to normal touch handling. - * Continuing calibrating after save request is undefined. */ - calibrator = compositor->touch_calibrator; - if (calibrator && - calibrator->surface && - weston_surface_is_mapped(calibrator->surface)) - device->saved_calibration = calibration; - else - device->ops->set_calibration(device, &calibration); -} - -static const struct weston_touch_calibration_interface -touch_calibration_implementation = { - touch_calibration_destroy, - touch_calibration_create_calibrator, - touch_calibration_save -}; - -static void -bind_touch_calibration(struct wl_client *client, - void *data, uint32_t version, uint32_t id) -{ - struct weston_compositor *compositor = data; - struct wl_resource *resource; - struct weston_touch_device *device; - struct weston_seat *seat; - struct weston_touch *touch; - const char *name; - - resource = wl_resource_create(client, - &weston_touch_calibration_interface, - version, id); - if (resource == NULL) { - wl_client_post_no_memory(client); - return; - } - - wl_resource_set_implementation(resource, - &touch_calibration_implementation, - compositor, NULL); - - wl_list_for_each(seat, &compositor->seat_list, link) { - touch = weston_seat_get_touch(seat); - if (!touch) - continue; - - wl_list_for_each(device, &touch->device_list, link) { - if (!weston_touch_device_can_calibrate(device)) - continue; - - name = device->ops->get_calibration_head_name(device); - if (!name) - continue; - - weston_touch_calibration_send_touch_device(resource, - device->syspath, name); - } - } -} - -/** Advertise touch_calibration support - * - * \param compositor The compositor to init for. - * \param save The callback function for saving a new calibration, or NULL. - * \return Zero on success, -1 on failure or if already enabled. - * - * Calling this initializes the weston_touch_calibration protocol support, - * so that the interface will be advertised to clients. It is recommended - * to use some mechanism, e.g. wl_display_set_global_filter(), to restrict - * access to the interface. - * - * There is no way to disable this once enabled. - * - * If the save callback is NULL, a new calibration provided by a client will - * always be accepted. If the save callback is not NULL, it must return - * success for the new calibration to be accepted. - */ -WL_EXPORT int -weston_compositor_enable_touch_calibrator(struct weston_compositor *compositor, - weston_touch_calibration_save_func save) -{ - if (compositor->touch_calibration) - return -1; - - compositor->touch_calibration = wl_global_create(compositor->wl_display, - &weston_touch_calibration_interface, 1, - compositor, bind_touch_calibration); - if (!compositor->touch_calibration) - return -1; - - compositor->touch_calibration_save = save; - weston_layer_init(&compositor->calibrator_layer, compositor); - - /* needs to be stacked above everything except lock screen and cursor, - * otherwise the position value is arbitrary */ - weston_layer_set_position(&compositor->calibrator_layer, - WESTON_LAYER_POSITION_TOP_UI + 120); - - return 0; -} diff --git a/libweston/version.h b/libweston/version.h deleted file mode 100644 index 0176f69..0000000 --- a/libweston/version.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright © 2013 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_VERSION_H -#define WESTON_VERSION_H - -#define WESTON_VERSION_MAJOR 5 -#define WESTON_VERSION_MINOR 0 -#define WESTON_VERSION_MICRO 0 -#define WESTON_VERSION "5.0.0" - -/* This macro may not do what you expect. Weston doesn't guarantee - * a stable API between 1.X and 1.Y, and thus this macro will return - * FALSE on any WESTON_VERSION_AT_LEAST(1,X,0) if the actual version - * is 1.Y.0 and X != Y). In particular, it fails if X < Y, that is, - * 1.3.0 is considered to not be "at least" 1.4.0. - * - * If you want to test for the version number being 1.3.0 or above or - * maybe in a range (eg 1.2.0 to 1.4.0), just use the WESTON_VERSION_* - * defines above directly. - */ - -#define WESTON_VERSION_AT_LEAST(major, minor, micro) \ - (WESTON_VERSION_MAJOR == (major) && \ - WESTON_VERSION_MINOR == (minor) && \ - WESTON_VERSION_MICRO >= (micro)) - -#endif diff --git a/libweston/vertex-clipping.c b/libweston/vertex-clipping.c deleted file mode 100644 index a71e733..0000000 --- a/libweston/vertex-clipping.c +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#include -#include -#include - -#include "vertex-clipping.h" - -float -float_difference(float a, float b) -{ - /* http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/ */ - static const float max_diff = 4.0f * FLT_MIN; - static const float max_rel_diff = 4.0e-5; - float diff = a - b; - float 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 float -clip_intersect_y(float p1x, float p1y, float p2x, float p2y, - float x_arg) -{ - float a; - float 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 float -clip_intersect_x(float p1x, float p1y, float p2x, float p2y, - float y_arg) -{ - float a; - float 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, float x, float y) -{ - *ctx->vertices.x++ = x; - *ctx->vertices.y++ = y; -} - -static enum path_transition -path_transition_left_edge(struct clip_context *ctx, float x, float 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, float x, float 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, float x, float 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, float x, float 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, - float x, float y, float clip_x) -{ - float 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, - float x, float y, float clip_y) -{ - float 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, - float *dst_x, float *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, - float *dst_x, float *dst_y) -{ - enum path_transition trans; - int i; - - if (src->n < 2) - return 0; - - 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, - float *dst_x, float *dst_y) -{ - enum path_transition trans; - int i; - - if (src->n < 2) - return 0; - - 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, - float *dst_x, float *dst_y) -{ - enum path_transition trans; - int i; - - if (src->n < 2) - return 0; - - 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, - float *dst_x, float *dst_y) -{ - enum path_transition trans; - int i; - - if (src->n < 2) - return 0; - - 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, - float *ex, - float *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, - float *ex, - float *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/libweston/vertex-clipping.h b/libweston/vertex-clipping.h deleted file mode 100644 index 0c69902..0000000 --- a/libweston/vertex-clipping.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef _WESTON_VERTEX_CLIPPING_H -#define _WESTON_VERTEX_CLIPPING_H - -struct polygon8 { - float x[8]; - float y[8]; - int n; -}; - -struct clip_context { - struct { - float x; - float y; - } prev; - - struct { - float x1, y1; - float x2, y2; - } clip; - - struct { - float *x; - float *y; - } vertices; -}; - -float -float_difference(float a, float b); - -int -clip_simple(struct clip_context *ctx, - struct polygon8 *surf, - float *ex, - float *ey); - -int -clip_transformed(struct clip_context *ctx, - struct polygon8 *surf, - float *ex, - float *ey);\ - -#endif diff --git a/libweston/weston-direct-display.c b/libweston/weston-direct-display.c deleted file mode 100644 index bf25b5a..0000000 --- a/libweston/weston-direct-display.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright © 2019 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include - -#include -#include "linux-dmabuf.h" -#include "weston-direct-display-server-protocol.h" -#include "libweston-internal.h" - -static void -direct_display_enable(struct wl_client *client, - struct wl_resource *resource, - struct wl_resource *dmabuf_res) -{ - struct linux_dmabuf_buffer *dmabuf; - - dmabuf = wl_resource_get_user_data(dmabuf_res); - assert(dmabuf); - dmabuf->direct_display = true; -} - -static void -direct_display_destroy(struct wl_client *client, - struct wl_resource *global_resource) -{ - wl_resource_destroy(global_resource); -} - -static const struct weston_direct_display_v1_interface - weston_direct_display_interface_v1 = { - direct_display_enable, - direct_display_destroy, -}; - -static void -bind_direct_display(struct wl_client *client, void *data, - uint32_t version, uint32_t id) -{ - struct wl_resource *resource; - struct weston_compositor *ec = data; - - resource = wl_resource_create(client, - &weston_direct_display_v1_interface, - version, id); - if (!resource) { - wl_client_post_no_memory(client); - return; - } - - wl_resource_set_implementation(resource, - &weston_direct_display_interface_v1, - ec, NULL); -} - -WL_EXPORT int -weston_direct_display_setup(struct weston_compositor *ec) -{ - if (!wl_global_create(ec->wl_display, - &weston_direct_display_v1_interface, 1, - ec, bind_direct_display)) - return -1; - - return 0; -} diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c deleted file mode 100644 index 521cb2c..0000000 --- a/libweston/weston-launch.c +++ /dev/null @@ -1,916 +0,0 @@ -/* - * Copyright © 2012 Benjamin Franzke - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "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 - -#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 - -#ifdef BUILD_DRM_COMPOSITOR - -#include - -#else - -static inline int -drmDropMaster(int drm_fd) -{ - return 0; -} - -static inline int -drmSetMaster(int drm_fd) -{ - return 0; -} - -#endif - -/* major()/minor() */ -#ifdef MAJOR_IN_MKDEV -# include -#endif -#ifdef MAJOR_IN_SYSMACROS -# include -#endif - -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(int *ngroups) -{ - int n; - gid_t *groups; - - n = getgroups(0, NULL); - - if (n < 0) { - fprintf(stderr, "Unable to retrieve groups: %s\n", - strerror(errno)); - return NULL; - } - - groups = malloc(n * sizeof(gid_t)); - if (!groups) - return NULL; - - if (getgroups(n, groups) < 0) { - fprintf(stderr, "Unable to retrieve groups: %s\n", - strerror(errno)); - free(groups); - return NULL; - } - - *ngroups = n; - return groups; -} - -static bool -weston_launch_allowed(struct weston_launch *wl) -{ - struct group *gr; - gid_t *groups; - int ngroups; -#ifdef HAVE_SYSTEMD_LOGIN - char *session, *seat; - int err; -#endif - - if (getuid() == 0) - return true; - - gr = getgrnam("weston-launch"); - if (gr) { - groups = read_groups(&ngroups); - if (groups && ngroups > 0) { - while (ngroups--) { - if (groups[ngroups] == gr->gr_gid) { - free(groups); - return true; - } - } - 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 true; - } - free(session); - } -#endif - - return false; -} - -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) { - fprintf(stderr, "weston: socketpair failed: %s\n", - strerror(errno)); - return -1; - } - - if (fcntl(wl->sock[0], F_SETFD, FD_CLOEXEC) < 0) { - fprintf(stderr, "weston: fcntl failed: %s\n", - strerror(errno)); - return -1; - } - - 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 -open_tty_by_number(int ttynr) -{ - int ret; - char filename[16]; - - ret = snprintf(filename, sizeof filename, "/dev/tty%d", ttynr); - if (ret < 0) - return -1; - - return open(filename, O_RDWR | O_NOCTTY); -} - -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: %s\n", - message->path, strerror(errno)); - 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 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_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; - case WESTON_LAUNCHER_DEACTIVATE_DONE: - close_input_fds(wl); - drmDropMaster(wl->drm_fd); - ioctl(wl->tty, VT_RELDISP, 1); - break; - } - - return ret; -} - -static void -quit(struct weston_launch *wl, int status) -{ - struct vt_mode mode = { 0 }; - int err; - int oldtty; - - 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); - } - - /* - * Get a fresh handle to the tty as the previous one is in - * hang-up state since weston (the controlling process for - * the tty) exit at this point. Reopen before closing the - * file descriptor to avoid a potential race condition. - * - * A similar fix exists in logind, see: - * https://github.com/systemd/systemd/pull/990 - */ - oldtty = wl->tty; - wl->tty = open_tty_by_number(wl->ttynr); - close(oldtty); - - if (ioctl(wl->tty, KDSKBMUTE, 0) && - ioctl(wl->tty, KDSKBMODE, wl->kb_mode)) - fprintf(stderr, "failed to restore keyboard mode: %s\n", - strerror(errno)); - - if (ioctl(wl->tty, KDSETMODE, KD_TEXT)) - fprintf(stderr, "failed to set KD_TEXT mode on tty: %s\n", - strerror(errno)); - - /* 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"); - - if (wl->tty != STDIN_FILENO) - close(wl->tty); - - exit(status); -} - -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) { - fprintf(stderr, "weston: reading signalfd failed: %s\n", - strerror(errno)); - 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) - break; - - if (wl->verbose) - fprintf(stderr, "weston-launch: sending %s to pid %d\n", - strsignal(sig.ssi_signo), wl->child); - - kill(wl->child, sig.ssi_signo); - break; - case SIGUSR1: - send_reply(wl, WESTON_LAUNCHER_DEACTIVATE); - 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); - - if (tty0 < 0) { - fprintf(stderr, "weston: could not open tty0: %s\n", - strerror(errno)); - return -1; - } - - if (ioctl(tty0, VT_OPENQRY, &wl->ttynr) < 0 || wl->ttynr == -1) - { - fprintf(stderr, "weston: failed to find non-opened console: %s\n", - strerror(errno)); - return -1; - } - - wl->tty = open_tty_by_number(wl->ttynr); - close(tty0); - } - - if (wl->tty < 0) { - fprintf(stderr, "weston: failed to open tty: %s\n", - strerror(errno)); - return -1; - } - - if (fstat(wl->tty, &buf) == -1 || - major(buf.st_rdev) != TTY_MAJOR || minor(buf.st_rdev) == 0) { - fprintf(stderr, "weston: weston-launch must be run from a virtual terminal\n"); - return -1; - } - - if (!wl->new_user || tty) { - if (fstat(wl->tty, &buf) < 0) { - fprintf(stderr, "weston: stat %s failed: %s\n", tty, - strerror(errno)); - return -1; - } - - if (major(buf.st_rdev) != TTY_MAJOR) { - fprintf(stderr, - "weston: invalid tty device: %s\n", tty); - return -1; - } - - wl->ttynr = minor(buf.st_rdev); - } - - if (ioctl(wl->tty, VT_ACTIVATE, wl->ttynr) < 0) { - fprintf(stderr, - "weston: failed to activate VT: %s\n", - strerror(errno)); - return -1; - } - - if (ioctl(wl->tty, VT_WAITACTIVE, wl->ttynr) < 0) { - fprintf(stderr, - "weston: failed to wait for VT to be active: %s\n", - strerror(errno)); - return -1; - } - - if (ioctl(wl->tty, KDGKBMODE, &wl->kb_mode)) { - fprintf(stderr, - "weston: failed to get current keyboard mode: %s\n", - strerror(errno)); - return -1; - } - - if (ioctl(wl->tty, KDSKBMUTE, 1) && - ioctl(wl->tty, KDSKBMODE, K_OFF)) { - fprintf(stderr, - "weston: failed to set K_OFF keyboard mode: %s\n", - strerror(errno)); - return -1; - } - - if (ioctl(wl->tty, KDSETMODE, KD_GRAPHICS)) { - fprintf(stderr, - "weston: failed to set KD_GRAPHICS mode on tty: %s\n", - strerror(errno)); - return -1; - } - - mode.mode = VT_PROCESS; - mode.relsig = SIGUSR1; - mode.acqsig = SIGUSR2; - if (ioctl(wl->tty, VT_SETMODE, &mode) < 0) { - fprintf(stderr, - "weston: failed to take control of vt handling %s\n", - strerror(errno)); - return -1; - } - - return 0; -} - -static int -setup_session(struct weston_launch *wl, char **child_argv) -{ - char **env; - char *term; - int i; - - if (wl->tty != STDIN_FILENO) { - if (setsid() < 0) { - fprintf(stderr, "weston: setsid failed %s\n", - strerror(errno)); - exit(EXIT_FAILURE); - } - if (ioctl(wl->tty, TIOCSCTTY, 0) < 0) { - fprintf(stderr, "TIOCSCTTY failed - tty is in use %s\n", - strerror(errno)); - exit(EXIT_FAILURE); - } - } - - 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) - fprintf(stderr, "putenv %s failed\n", env[i]); - } - free(env); - } - - /* - * We open a new session, so it makes sense - * to run a new login shell - */ - child_argv[0] = "/bin/sh"; - child_argv[1] = "-l"; - child_argv[2] = "-c"; - child_argv[3] = "exec " BINDIR "/weston \"$@\""; - child_argv[4] = "weston"; - return 5; -} - -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) { - fprintf(stderr, "weston: dropping privileges failed %s\n", - strerror(errno)); - exit(EXIT_FAILURE); - } -} - -static void -launch_compositor(struct weston_launch *wl, int argc, char *argv[]) -{ - char *child_argv[MAX_ARGV_SIZE]; - sigset_t mask; - int o, i; - - if (wl->verbose) - printf("weston-launch: spawned weston with pid: %d\n", getpid()); - if (wl->new_user) { - o = setup_session(wl, child_argv); - } else { - child_argv[0] = BINDIR "/weston"; - o = 1; - } - for (i = 0; i < argc; ++i) - child_argv[o + i] = argv[i]; - child_argv[o + i] = NULL; - - 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); - - - execv(child_argv[0], child_argv); - fprintf(stderr, "weston: exec failed: %s\n", strerror(errno)); - exit(EXIT_FAILURE); -} - -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" - " e.g. -u joe, requires root.\n"); - fprintf(stderr, " -t, --tty Start session on alternative tty,\n" - " e.g. -t /dev/tty4, requires -u option.\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) { - fprintf(stderr, "weston: Permission denied. -u allowed for root only\n"); - exit(EXIT_FAILURE); - } - break; - case 't': - tty = optarg; - break; - case 'v': - wl.verbose = 1; - break; - case 'h': - help("weston-launch"); - exit(EXIT_FAILURE); - default: - exit(EXIT_FAILURE); - } - } - - if ((argc - optind) > (MAX_ARGV_SIZE - 6)) { - fprintf(stderr, - "weston: Too many arguments to pass to weston: %s\n", - strerror(E2BIG)); - exit(EXIT_FAILURE); - } - - if (tty && !wl.new_user) { - fprintf(stderr, "weston: -t/--tty option requires -u/--user option as well\n"); - exit(EXIT_FAILURE); - } - - if (wl.new_user) - wl.pw = getpwnam(wl.new_user); - else - wl.pw = getpwuid(getuid()); - if (wl.pw == NULL) { - fprintf(stderr, "weston: failed to get username: %s\n", - strerror(errno)); - exit(EXIT_FAILURE); - } - - if (!weston_launch_allowed(&wl)) { - fprintf(stderr, "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.\n"); - exit(EXIT_FAILURE); - } - - 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) { - fprintf(stderr, "weston: fork failed %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } - - if (wl.child == 0) - launch_compositor(&wl, argc - optind, argv + optind); - - close(wl.sock[1]); - - 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) { - fprintf(stderr, "poll failed: %s\n", strerror(errno)); - return -1; - } - if (fds[0].revents & POLLIN) - handle_socket_msg(&wl); - if (fds[1].revents) - handle_signal(&wl); - } - - return 0; -} diff --git a/libweston/weston-launch.h b/libweston/weston-launch.h deleted file mode 100644 index 575d2cf..0000000 --- a/libweston/weston-launch.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright © 2012 Benjamin Franzke - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _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, - WESTON_LAUNCHER_DEACTIVATE_DONE, -}; - -struct weston_launcher_message { - int opcode; -}; - -struct weston_launcher_open { - struct weston_launcher_message header; - int flags; - char path[0]; -}; - -#endif diff --git a/libweston/weston-log-file.c b/libweston/weston-log-file.c deleted file mode 100644 index 25e36b4..0000000 --- a/libweston/weston-log-file.c +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright © 2019 Collabora Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include "shared/helpers.h" -#include - -#include "weston-log-internal.h" - -#include - -/** File type of stream - */ -struct weston_debug_log_file { - struct weston_log_subscriber base; - FILE *file; -}; - -static struct weston_debug_log_file * -to_weston_debug_log_file(struct weston_log_subscriber *sub) -{ - return container_of(sub, struct weston_debug_log_file, base); -} - -static void -weston_log_file_write(struct weston_log_subscriber *sub, - const char *data, size_t len) -{ - struct weston_debug_log_file *stream = to_weston_debug_log_file(sub); - fwrite(data, len, 1, stream->file); -} - -static void -weston_log_subscriber_destroy_log(struct weston_log_subscriber *subscriber) -{ - struct weston_debug_log_file *file = to_weston_debug_log_file(subscriber); - - weston_log_subscriber_release(subscriber); - free(file); -} - -/** Creates a file type of subscriber - * - * Should be destroyed using weston_log_subscriber_destroy() - * - * @param dump_to if specified, used for writing data to - * @returns a weston_log_subscriber object or NULL in case of failure - * - * @sa weston_log_subscriber_destroy - * - */ -WL_EXPORT struct weston_log_subscriber * -weston_log_subscriber_create_log(FILE *dump_to) -{ - struct weston_debug_log_file *file = zalloc(sizeof(*file)); - - if (!file) - return NULL; - - if (dump_to) - file->file = dump_to; - else - file->file = stderr; - - - file->base.write = weston_log_file_write; - file->base.destroy = weston_log_subscriber_destroy_log; - file->base.destroy_subscription = NULL; - file->base.complete = NULL; - - wl_list_init(&file->base.subscription_list); - - return &file->base; -} diff --git a/libweston/weston-log-flight-rec.c b/libweston/weston-log-flight-rec.c deleted file mode 100644 index 7364c81..0000000 --- a/libweston/weston-log-flight-rec.c +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright © 2019 Collabora Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include "shared/helpers.h" -#include - -#include "weston-log-internal.h" - -#include -#include -#include -#include -#include -#include - -struct weston_ring_buffer { - uint32_t append_pos; /**< where in the buffer we are */ - uint32_t size; /**< max length of the ring buffer */ - char *buf; /**< the buffer itself */ - FILE *file; /**< where to write in case we need to dump the buf */ - bool overlap; /**< in case buff overlaps, hint from where to print buf contents */ -}; - -/** allows easy access to the ring buffer in case of a core dump - */ -WL_EXPORT struct weston_ring_buffer *weston_primary_flight_recorder_ring_buffer = NULL; - -/** A black box type of stream, used to aggregate data continuously, and - * when needed, to dump its contents for inspection. - */ -struct weston_debug_log_flight_recorder { - struct weston_log_subscriber base; - struct weston_ring_buffer rb; -}; - -static void -weston_ring_buffer_init(struct weston_ring_buffer *rb, size_t size, char *buf) -{ - rb->append_pos = 0; - rb->size = size - 1; - rb->buf = buf; - rb->overlap = false; - rb->file = stderr; -} - -static struct weston_debug_log_flight_recorder * -to_flight_recorder(struct weston_log_subscriber *sub) -{ - return container_of(sub, struct weston_debug_log_flight_recorder, base); -} - -static void -weston_log_flight_recorder_adjust_end(struct weston_ring_buffer *rb, - size_t bytes_to_advance) -{ - if (rb->append_pos == rb->size - bytes_to_advance) - rb->append_pos = 0; - else - rb->append_pos += bytes_to_advance; -} - -static void -weston_log_flight_recorder_write_chunks(struct weston_ring_buffer *rb, - const char *data, size_t len) -{ - /* no of chunks that matches our buffer size */ - size_t nr_chunks = len / rb->size; - - /* bytes left that do not fill entire buffer */ - size_t bytes_left_last_chunk = len % rb->size; - const char *c_data = data; - - /* might overlap multiple times, memcpy is redundant, - * that's why we don't even modify append_pos */ - while (nr_chunks-- > 0) { - memcpy(&rb->buf[rb->append_pos], c_data, rb->size); - c_data += rb->size; - } - - if (bytes_left_last_chunk) - memcpy(&rb->buf[rb->append_pos], c_data, bytes_left_last_chunk); - - /* adjust append_pos */ - weston_log_flight_recorder_adjust_end(rb, bytes_left_last_chunk); -} - -static void -weston_log_flight_recorder_write_chunks_overlap(struct weston_ring_buffer *rb, - const char *data, size_t len) -{ - size_t transfer_remains = - (rb->append_pos + len) - rb->size; - size_t transfer_to_end = len - transfer_remains; - const char *c_data = data; - - /* transfer what remains until the end of the buffer */ - memcpy(&rb->buf[rb->append_pos], c_data, transfer_to_end); - c_data += transfer_to_end; - - /* reset append_pos as we filled up the buffer */ - rb->append_pos = 0; - - /* transfer what remains */ - weston_log_flight_recorder_write_chunks(rb, c_data, transfer_remains); - rb->overlap = true; -} - -static void -weston_log_flight_recorder_write_data(struct weston_ring_buffer *rb, - const char *data, size_t len) -{ - /* - * If append_pos is at the beginning of the buffer, we determine if we - * should do it in chunks, and if there are any bytes left we transfer - * those as well. - * - * If the append_pos is somewhere inside the buffer we determine how - * many bytes we need to transfer between we reach the end and overlap, - * then we proceed as in the first step. - */ - if (rb->append_pos == 0) - weston_log_flight_recorder_write_chunks(rb, data, len); - else - weston_log_flight_recorder_write_chunks_overlap(rb, data, len); -} - -static void -weston_log_flight_recorder_write(struct weston_log_subscriber *sub, - const char *data, size_t len) -{ - struct weston_debug_log_flight_recorder *flight_rec = - to_flight_recorder(sub); - struct weston_ring_buffer *rb = &flight_rec->rb; - - /* in case the data is bigger than the size of the buf */ - if (rb->size < len) { - weston_log_flight_recorder_write_data(rb, data, len); - } else { - /* if we can transfer it without wrapping it do it at once */ - if (rb->append_pos <= rb->size - len) { - memcpy(&rb->buf[rb->append_pos], data, len); - - /* - * adjust append_pos, take care of the situation were - * to fill up the entire buf - */ - weston_log_flight_recorder_adjust_end(rb, len); - } else { - weston_log_flight_recorder_write_data(rb, data, len); - } - } - -} - -static void -weston_log_flight_recorder_map_memory(struct weston_debug_log_flight_recorder *flight_rec) -{ - size_t i = 0; - - for (i = 0; i < flight_rec->rb.size; i++) - flight_rec->rb.buf[i] = 0xff; -} - -static void -weston_log_subscriber_display_flight_rec_data(struct weston_ring_buffer *rb, - FILE *file) -{ - FILE *file_d = stderr; - if (file) - file_d = file; - - if (!rb->overlap) { - if (rb->append_pos) - fwrite(rb->buf, sizeof(char), rb->append_pos, file_d); - else - fwrite(rb->buf, sizeof(char), rb->size, file_d); - } else { - /* from append_pos to size */ - fwrite(&rb->buf[rb->append_pos], sizeof(char), - rb->size - rb->append_pos, file_d); - /* from 0 to append_pos */ - fwrite(rb->buf, sizeof(char), rb->append_pos, file_d); - } -} - -WL_EXPORT void -weston_log_subscriber_display_flight_rec(struct weston_log_subscriber *sub) -{ - struct weston_debug_log_flight_recorder *flight_rec = - to_flight_recorder(sub); - struct weston_ring_buffer *rb = &flight_rec->rb; - - weston_log_subscriber_display_flight_rec_data(rb, rb->file); -} - -static void -weston_log_subscriber_destroy_flight_rec(struct weston_log_subscriber *sub) -{ - struct weston_debug_log_flight_recorder *flight_rec = to_flight_recorder(sub); - - /* Resets weston_primary_flight_recorder_ring_buffer to NULL if it - * is the destroyed subscriber */ - if (weston_primary_flight_recorder_ring_buffer == &flight_rec->rb) - weston_primary_flight_recorder_ring_buffer = NULL; - - weston_log_subscriber_release(sub); - free(flight_rec->rb.buf); - free(flight_rec); -} - -/** Create a flight recorder type of subscriber - * - * Allocates both the flight recorder and the underlying ring buffer. Use - * weston_log_subscriber_destroy() to clean-up. - * - * @param size specify the maximum size (in bytes) of the backing storage - * for the flight recorder - * @returns a weston_log_subscriber object or NULL in case of failure - */ -WL_EXPORT struct weston_log_subscriber * -weston_log_subscriber_create_flight_rec(size_t size) -{ - struct weston_debug_log_flight_recorder *flight_rec; - char *weston_rb; - - assert("Can't create more than one flight recorder." && - !weston_primary_flight_recorder_ring_buffer); - - flight_rec = zalloc(sizeof(*flight_rec)); - if (!flight_rec) - return NULL; - - flight_rec->base.write = weston_log_flight_recorder_write; - flight_rec->base.destroy = weston_log_subscriber_destroy_flight_rec; - flight_rec->base.destroy_subscription = NULL; - flight_rec->base.complete = NULL; - wl_list_init(&flight_rec->base.subscription_list); - - weston_rb = zalloc(sizeof(char) * size); - if (!weston_rb) { - free(flight_rec); - return NULL; - } - - weston_ring_buffer_init(&flight_rec->rb, size, weston_rb); - weston_primary_flight_recorder_ring_buffer = &flight_rec->rb; - - /* write some data to the rb such that the memory gets mapped */ - weston_log_flight_recorder_map_memory(flight_rec); - - return &flight_rec->base; -} - -/** Retrieve flight recorder ring buffer contents, could be useful when - * implementing an assert()-like wrapper. - * - * @param file a FILE type already opened. Can also pass stderr/stdout under gdb - * if the program is loaded into memory. - * - * Uses the global exposed weston_primary_flight_recorder_ring_buffer. - * - */ -WL_EXPORT void -weston_log_flight_recorder_display_buffer(FILE *file) -{ - if (!weston_primary_flight_recorder_ring_buffer) - return; - - weston_log_subscriber_display_flight_rec_data(weston_primary_flight_recorder_ring_buffer, - file); -} diff --git a/libweston/weston-log-internal.h b/libweston/weston-log-internal.h deleted file mode 100644 index f1bfa6a..0000000 --- a/libweston/weston-log-internal.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright © 2019 Collabora Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef WESTON_LOG_INTERNAL_H -#define WESTON_LOG_INTERNAL_H - -#include "wayland-util.h" - -struct weston_log_subscription; - -/** Subscriber allows each type of stream to customize or to provide its own - * methods to manipulate the underlying storage. It follows also an - * object-oriented approach, contains the ops callbacks and a list of - * subcriptions of type weston_log_subscription. Each subscription created will - * be both added to this subscription list and that of the weston_log_scope. - * - * A kind of stream can inherit the subscriber class and provide its own callbacks: - * @code - * struct weston_log_data_stream { - * struct weston_log_subscriber base; - * struct weston_data_stream opaque; - * }; - * @endcode - * - * Passing the base class will require container retrieval type of methods - * to be allowed to reach the opaque type (i.e., container_of()). - * - * @ingroup internal-log - * - */ -struct weston_log_subscriber { - /** write the data pointed by @param data */ - void (*write)(struct weston_log_subscriber *sub, const char *data, size_t len); - /** For destroying the subscriber */ - void (*destroy)(struct weston_log_subscriber *sub); - /** For the type of streams that required additional destroy operation - * for destroying the stream */ - void (*destroy_subscription)(struct weston_log_subscriber *sub); - /** For the type of streams that can inform the 'consumer' part that - * write operation has been terminated/finished and should close the - * stream. - */ - void (*complete)(struct weston_log_subscriber *sub); - struct wl_list subscription_list; /**< weston_log_subscription::owner_link */ -}; - -void -weston_log_subscription_create(struct weston_log_subscriber *owner, - struct weston_log_scope *scope); - -void -weston_log_subscription_destroy(struct weston_log_subscription *sub); - -void -weston_log_subscription_add(struct weston_log_scope *scope, - struct weston_log_subscription *sub); -void -weston_log_subscription_remove(struct weston_log_subscription *sub); - -void -weston_log_subscriber_release(struct weston_log_subscriber *subscriber); - -void -weston_log_bind_weston_debug(struct wl_client *client, - void *data, uint32_t version, uint32_t id); - -struct weston_log_scope * -weston_log_get_scope(struct weston_log_context *log_ctx, const char *name); - -void -weston_log_run_cb_new_subscription(struct weston_log_subscription *sub); - -void -weston_debug_protocol_advertise_scopes(struct weston_log_context *log_ctx, - struct wl_resource *res); - -int -weston_vlog(const char *fmt, va_list ap); -int -weston_vlog_continue(const char *fmt, va_list ap); - -void * -weston_log_subscription_get_data(struct weston_log_subscription *sub); - -void -weston_log_subscription_set_data(struct weston_log_subscription *sub, void *data); - -void -weston_timeline_create_subscription(struct weston_log_subscription *sub, - void *user_data); - -void -weston_timeline_destroy_subscription(struct weston_log_subscription *sub, - void *user_data); - -#endif /* WESTON_LOG_INTERNAL_H */ diff --git a/libweston/weston-log-wayland.c b/libweston/weston-log-wayland.c deleted file mode 100644 index 0add772..0000000 --- a/libweston/weston-log-wayland.c +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright © 2019 Collabora Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include "shared/helpers.h" -#include - -#include "weston-log-internal.h" -#include "weston-debug-server-protocol.h" - -#include -#include -#include -#include -#include -#include - -/** A debug stream created by a client - * - * A client provides a file descriptor for the server to write debug messages - * into. A weston_log_debug_wayland is associated to one weston_log_scope via the - * scope name, and the scope provides the messages. There can be several - * streams for the same scope, all streams getting the same messages. - * - * The following is specific to weston-debug protocol. - * Subscription/unsubscription takes place in the stream_create(), respectively - * in stream_destroy(). - */ -struct weston_log_debug_wayland { - struct weston_log_subscriber base; - int fd; /**< client provided fd */ - struct wl_resource *resource; /**< weston_debug_stream_v1 object */ -}; - -static struct weston_log_debug_wayland * -to_weston_log_debug_wayland(struct weston_log_subscriber *sub) -{ - return container_of(sub, struct weston_log_debug_wayland, base); -} - -static void -stream_close_unlink(struct weston_log_debug_wayland *stream) -{ - if (stream->fd != -1) - close(stream->fd); - stream->fd = -1; -} - -static void WL_PRINTF(2, 3) -stream_close_on_failure(struct weston_log_debug_wayland *stream, - const char *fmt, ...) -{ - char *msg; - va_list ap; - int ret; - - stream_close_unlink(stream); - - va_start(ap, fmt); - ret = vasprintf(&msg, fmt, ap); - va_end(ap); - - if (ret > 0) { - weston_debug_stream_v1_send_failure(stream->resource, msg); - free(msg); - } else { - weston_debug_stream_v1_send_failure(stream->resource, - "MEMFAIL"); - } -} - -/** Write data into a specific debug stream - * - * \param sub The subscriber's stream to write into; must not be NULL. - * \param[in] data Pointer to the data to write. - * \param len Number of bytes to write. - * - * Writes the given data (binary verbatim) into the debug stream. - * If \c len is zero or negative, the write is silently dropped. - * - * Writing is continued until all data has been written or - * a write fails. If the write fails due to a signal, it is re-tried. - * Otherwise on failure, the stream is closed and - * \c weston_debug_stream_v1.failure event is sent to the client. - * - * \memberof weston_log_debug_wayland - */ -static void -weston_log_debug_wayland_write(struct weston_log_subscriber *sub, - const char *data, size_t len) -{ - ssize_t len_ = len; - ssize_t ret; - int e; - struct weston_log_debug_wayland *stream = to_weston_log_debug_wayland(sub); - - if (stream->fd == -1) - return; - - while (len_ > 0) { - ret = write(stream->fd, data, len_); - e = errno; - if (ret < 0) { - if (e == EINTR) - continue; - - stream_close_on_failure(stream, - "Error writing %zd bytes: %s (%d)", - len_, strerror(e), e); - break; - } - - len_ -= ret; - data += ret; - } -} - -/** Close the debug stream and send success event - * - * \param sub Subscriber's stream to close. - * - * Closes the debug stream and sends \c weston_debug_stream_v1.complete - * event to the client. This tells the client the debug information dump - * is complete. - * - * \memberof weston_log_debug_wayland - */ -static void -weston_log_debug_wayland_complete(struct weston_log_subscriber *sub) -{ - struct weston_log_debug_wayland *stream = to_weston_log_debug_wayland(sub); - - stream_close_unlink(stream); - weston_debug_stream_v1_send_complete(stream->resource); -} - -static void -weston_log_debug_wayland_to_destroy(struct weston_log_subscriber *sub) -{ - struct weston_log_debug_wayland *stream = to_weston_log_debug_wayland(sub); - - if (stream->fd != -1) - stream_close_on_failure(stream, "debug name removed"); -} - -static struct weston_log_debug_wayland * -stream_create(struct weston_log_context *log_ctx, const char *name, - int32_t streamfd, struct wl_resource *stream_resource) -{ - struct weston_log_debug_wayland *stream; - struct weston_log_scope *scope; - - stream = zalloc(sizeof *stream); - if (!stream) - return NULL; - - stream->fd = streamfd; - stream->resource = stream_resource; - - stream->base.write = weston_log_debug_wayland_write; - stream->base.destroy = NULL; - stream->base.destroy_subscription = weston_log_debug_wayland_to_destroy; - stream->base.complete = weston_log_debug_wayland_complete; - wl_list_init(&stream->base.subscription_list); - - scope = weston_log_get_scope(log_ctx, name); - if (scope) { - weston_log_subscription_create(&stream->base, scope); - } else { - stream_close_on_failure(stream, - "Debug stream name '%s' is unknown.", - name); - } - - return stream; -} - -static void -stream_destroy(struct wl_resource *stream_resource) -{ - struct weston_log_debug_wayland *stream; - stream = wl_resource_get_user_data(stream_resource); - - stream_close_unlink(stream); - weston_log_subscriber_release(&stream->base); - free(stream); -} - -static void -weston_debug_stream_destroy(struct wl_client *client, - struct wl_resource *stream_resource) -{ - wl_resource_destroy(stream_resource); -} - -static const struct weston_debug_stream_v1_interface - weston_debug_stream_impl = { - weston_debug_stream_destroy -}; - -static void -weston_debug_destroy(struct wl_client *client, - struct wl_resource *global_resource) -{ - wl_resource_destroy(global_resource); -} - -static void -weston_debug_subscribe(struct wl_client *client, - struct wl_resource *global_resource, - const char *name, - int32_t streamfd, - uint32_t new_stream_id) -{ - struct weston_log_context *log_ctx; - struct wl_resource *stream_resource; - uint32_t version; - struct weston_log_debug_wayland *stream; - - log_ctx = wl_resource_get_user_data(global_resource); - version = wl_resource_get_version(global_resource); - - stream_resource = wl_resource_create(client, - &weston_debug_stream_v1_interface, - version, new_stream_id); - if (!stream_resource) - goto fail; - - stream = stream_create(log_ctx, name, streamfd, stream_resource); - if (!stream) - goto fail; - - wl_resource_set_implementation(stream_resource, - &weston_debug_stream_impl, - stream, stream_destroy); - return; - -fail: - close(streamfd); - wl_client_post_no_memory(client); -} - -static const struct weston_debug_v1_interface weston_debug_impl = { - weston_debug_destroy, - weston_debug_subscribe -}; - -void -weston_log_bind_weston_debug(struct wl_client *client, - void *data, uint32_t version, uint32_t id) -{ - struct weston_log_context *log_ctx = data; - struct wl_resource *resource; - - resource = wl_resource_create(client, - &weston_debug_v1_interface, - version, id); - if (!resource) { - wl_client_post_no_memory(client); - return; - } - wl_resource_set_implementation(resource, &weston_debug_impl, - log_ctx, NULL); - - weston_debug_protocol_advertise_scopes(log_ctx, resource); -} diff --git a/libweston/weston-log.c b/libweston/weston-log.c deleted file mode 100644 index b6f46c3..0000000 --- a/libweston/weston-log.c +++ /dev/null @@ -1,1011 +0,0 @@ -/* - * Copyright © 2017 Pekka Paalanen - * Copyright © 2018 Zodiac Inflight Innovations - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include "shared/helpers.h" -#include - -#include "weston-log-internal.h" -#include "weston-debug-server-protocol.h" - -#include -#include -#include -#include -#include -#include - -/** - * @defgroup log Public Logging/Debugging API - * @defgroup internal-log Private/Internal Logging/Debugging API - * @defgroup debug-protocol weston-debug protocol specific - */ - -/** Main weston-log context - * - * One per weston_compositor. Stores list of scopes created and a list pending - * subscriptions. - * - * A pending subscription is a subscription to a scope which hasn't been - * created. When the scope is finally created the pending subscription will be - * removed from the pending subscription list, but not before was added in the - * scope's subscription list and that of the subscriber list. - * - * Pending subscriptions only make sense for other types of streams, other than - * those created by weston-debug protocol. In the case of the weston-debug - * protocol, the subscription processes is done automatically whenever a client - * connects and subscribes to a scope which was previously advertised by the - * compositor. - * - * @ingroup internal-log - */ -struct weston_log_context { - struct wl_global *global; - struct wl_listener compositor_destroy_listener; - struct wl_list scope_list; /**< weston_log_scope::compositor_link */ - struct wl_list pending_subscription_list; /**< weston_log_subscription::source_link */ -}; - -/** weston-log message scope - * - * This is used for scoping logging/debugging messages. Clients can subscribe - * to only the scopes they are interested in. A scope is identified by its name - * (also referred to as debug stream name). - * - * @ingroup log - */ -struct weston_log_scope { - char *name; - char *desc; - weston_log_scope_cb new_subscription; - weston_log_scope_cb destroy_subscription; - void *user_data; - struct wl_list compositor_link; - struct wl_list subscription_list; /**< weston_log_subscription::source_link */ -}; - -/** Ties a subscriber to a scope - * - * A subscription is created each time we'd want to subscribe to a scope. From - * the stream type we can retrieve the subscriber and from the subscriber we - * reach each of the streams callbacks. See also weston_log_subscriber object. - * - * When a subscription has been created we store it in the scope's subscription - * list and in the subscriber's subscription list. The subscription might be a - * pending subscription until the scope for which there's was a subscribe has - * been created. The scope creation will take of looking through the pending - * subscription list. - * - * A subscription can reached from a subscriber subscription list by using the - * streams base class. - * - * @ingroup internal-log - */ -struct weston_log_subscription { - struct weston_log_subscriber *owner; - struct wl_list owner_link; /**< weston_log_subscriber::subscription_list */ - - char *scope_name; - struct weston_log_scope *source; - struct wl_list source_link; /**< weston_log_scope::subscription_list or - weston_log_context::pending_subscription_list */ - - void *data; -}; - -static struct weston_log_subscription * -find_pending_subscription(struct weston_log_context *log_ctx, - const char *scope_name) -{ - struct weston_log_subscription *sub; - - wl_list_for_each(sub, &log_ctx->pending_subscription_list, source_link) - if (!strcmp(sub->scope_name, scope_name)) - return sub; - - return NULL; -} - -/** Create a pending subscription and add it the list of pending subscriptions - * - * @param owner a subscriber represented by weston_log_subscriber object - * @param scope_name the name of the scope (which we don't have in the list of scopes) - * @param log_ctx the logging context used to add the pending subscription - * - * @memberof weston_log_subscription - */ -static void -weston_log_subscription_create_pending(struct weston_log_subscriber *owner, - const char *scope_name, - struct weston_log_context *log_ctx) -{ - assert(owner); - assert(scope_name); - struct weston_log_subscription *sub = zalloc(sizeof(*sub)); - - if (!sub) - return; - - sub->scope_name = strdup(scope_name); - sub->owner = owner; - - wl_list_insert(&log_ctx->pending_subscription_list, &sub->source_link); -} - -/** Destroys the pending subscription created previously with - * weston_log_subscription_create_pending() - * - * @param sub the weston_log_subscription object to remove from the list - * of subscriptions and to destroy the subscription - * - * @memberof weston_log_subscription - */ -static void -weston_log_subscription_destroy_pending(struct weston_log_subscription *sub) -{ - assert(sub); - /* pending subsriptions do not have a source */ - wl_list_remove(&sub->source_link); - free(sub->scope_name); - free(sub); -} - -/** Write to the stream's subscription - * - * @memberof weston_log_subscription - */ -static void -weston_log_subscription_write(struct weston_log_subscription *sub, - const char *data, size_t len) -{ - if (sub->owner && sub->owner->write) - sub->owner->write(sub->owner, data, len); -} - -/** Write a formatted string to the stream's subscription - * - * @memberof weston_log_subscription - */ -static void -weston_log_subscription_vprintf(struct weston_log_subscription *sub, - const char *fmt, va_list ap) -{ - static const char oom[] = "Out of memory"; - char *str; - int len; - - if (!weston_log_scope_is_enabled(sub->source)) - return; - - len = vasprintf(&str, fmt, ap); - if (len >= 0) { - weston_log_subscription_write(sub, str, len); - free(str); - } else { - weston_log_subscription_write(sub, oom, sizeof oom - 1); - } -} - -void -weston_log_subscription_set_data(struct weston_log_subscription *sub, void *data) -{ - /* don't allow data to already be set */ - assert(!sub->data); - sub->data = data; -} - -void * -weston_log_subscription_get_data(struct weston_log_subscription *sub) -{ - return sub->data; -} - -/** Creates a new subscription using the subscriber by \c owner. - * - * The subscription created is added to the \c owner subscription list. - * Destroying the subscription using weston_log_subscription_destroy() will - * remove the link from the subscription list and free storage alloc'ed. - * - * Note that this adds the subscription to the scope's subscription list - * hence the \c scope required argument. - * - * @param owner the subscriber owner, must be created before creating a - * subscription - * @param scope the scope in order to add the subscription to the scope's - * subscription list - * @returns a weston_log_subscription object in case of success, or NULL - * otherwise - * - * @sa weston_log_subscription_destroy, weston_log_subscription_remove, - * weston_log_subscription_add - * @memberof weston_log_subscription - */ -void -weston_log_subscription_create(struct weston_log_subscriber *owner, - struct weston_log_scope *scope) -{ - struct weston_log_subscription *sub; - assert(owner); - assert(scope); - assert(scope->name); - - sub = zalloc(sizeof(*sub)); - if (!sub) - return; - - sub->owner = owner; - sub->scope_name = strdup(scope->name); - - wl_list_insert(&sub->owner->subscription_list, &sub->owner_link); - - weston_log_subscription_add(scope, sub); - weston_log_run_cb_new_subscription(sub); -} - -/** Destroys the subscription - * - * Removes the subscription from the scopes subscription list and from - * subscriber's subscription list. It destroys the subscription afterwads. - * - * @memberof weston_log_subscription - */ -void -weston_log_subscription_destroy(struct weston_log_subscription *sub) -{ - assert(sub); - - if (sub->owner->destroy_subscription) - sub->owner->destroy_subscription(sub->owner); - - if (sub->source->destroy_subscription) - sub->source->destroy_subscription(sub, sub->source->user_data); - - if (sub->owner) - wl_list_remove(&sub->owner_link); - - weston_log_subscription_remove(sub); - free(sub->scope_name); - free(sub); -} - -/** Adds the subscription \c sub to the subscription list of the - * scope. - * - * This should used when the scope has been created, and the subscription \c - * sub has be created before calling this function. - * - * @param scope the scope - * @param sub the subscription, it must be created before, see - * weston_log_subscription_create() - * - * @memberof weston_log_subscription - */ -void -weston_log_subscription_add(struct weston_log_scope *scope, - struct weston_log_subscription *sub) -{ - assert(scope); - assert(sub); - /* don't allow subscriptions to have a source already! */ - assert(!sub->source); - - sub->source = scope; - wl_list_insert(&scope->subscription_list, &sub->source_link); -} - -/** Removes the subscription from the scope's subscription list - * - * @memberof weston_log_subscription - */ -void -weston_log_subscription_remove(struct weston_log_subscription *sub) -{ - assert(sub); - if (sub->source) - wl_list_remove(&sub->source_link); - sub->source = NULL; -} - -/** Look-up the scope from the scope list stored in the log context, by - * matching against the \c name. - * - * @param log_ctx - * @param name the scope name, see weston_log_ctx_add_log_scope() and - * weston_compositor_add_log_scope() - * @returns NULL if none found, or a pointer to a weston_log_scope - * - * @ingroup internal-log - */ -struct weston_log_scope * -weston_log_get_scope(struct weston_log_context *log_ctx, const char *name) -{ - struct weston_log_scope *scope; - wl_list_for_each(scope, &log_ctx->scope_list, compositor_link) - if (strcmp(name, scope->name) == 0) - return scope; - return NULL; -} - -/** Wrapper to invoke the weston_log_scope_cb. Allows to call the cb - * new_subscription of a log scope. - * - * @ingroup internal-log - */ -void -weston_log_run_cb_new_subscription(struct weston_log_subscription *sub) -{ - if (sub->source->new_subscription) - sub->source->new_subscription(sub, sub->source->user_data); -} - -/** Advertise the log scope name and the log scope description - * - * This is only used by the weston-debug protocol! - * - * @ingroup internal-log - */ -void -weston_debug_protocol_advertise_scopes(struct weston_log_context *log_ctx, - struct wl_resource *res) -{ - struct weston_log_scope *scope; - wl_list_for_each(scope, &log_ctx->scope_list, compositor_link) - weston_debug_v1_send_available(res, scope->name, scope->desc); -} - -/** Disable debug-protocol - * - * @param log_ctx The log context where the debug-protocol is linked - * - * @ingroup internal-log - */ -static void -weston_log_ctx_disable_debug_protocol(struct weston_log_context *log_ctx) -{ - if (!log_ctx->global) - return; - - wl_global_destroy(log_ctx->global); - log_ctx->global = NULL; -} - -/** Creates weston_log_context structure - * - * \return NULL in case of failure, or a weston_log_context object in case of - * success - * - * weston_log_context is a singleton for each weston_compositor. - * @ingroup log - * - */ -WL_EXPORT struct weston_log_context * -weston_log_ctx_create(void) -{ - struct weston_log_context *log_ctx; - - log_ctx = zalloc(sizeof *log_ctx); - if (!log_ctx) - return NULL; - - wl_list_init(&log_ctx->scope_list); - wl_list_init(&log_ctx->pending_subscription_list); - wl_list_init(&log_ctx->compositor_destroy_listener.link); - - return log_ctx; -} - -/** Destroy weston_log_context structure - * - * \param log_ctx The log context to destroy. - * - * @ingroup log - * - */ -WL_EXPORT void -weston_log_ctx_destroy(struct weston_log_context *log_ctx) -{ - struct weston_log_scope *scope; - struct weston_log_subscription *pending_sub, *pending_sub_tmp; - - /* We can't destroy the log context if there's still a compositor - * that depends on it. This is an user error */ - assert(wl_list_empty(&log_ctx->compositor_destroy_listener.link)); - - weston_log_ctx_disable_debug_protocol(log_ctx); - - wl_list_for_each(scope, &log_ctx->scope_list, compositor_link) - fprintf(stderr, "Internal warning: debug scope '%s' has not been destroyed.\n", - scope->name); - - /* Remove head to not crash if scope removed later. */ - wl_list_remove(&log_ctx->scope_list); - - /* Remove any pending subscription(s) which nobody subscribed to */ - wl_list_for_each_safe(pending_sub, pending_sub_tmp, - &log_ctx->pending_subscription_list, source_link) { - weston_log_subscription_destroy_pending(pending_sub); - } - - /* pending_subscription_list should be empty at this point */ - - free(log_ctx); -} - -static void -compositor_destroy_listener(struct wl_listener *listener, void *data) -{ - struct weston_log_context *log_ctx = - wl_container_of(listener, log_ctx, compositor_destroy_listener); - - /* We have to keep this list initalized as weston_log_ctx_destroy() has - * to check if there's any compositor destroy listener registered */ - wl_list_remove(&log_ctx->compositor_destroy_listener.link); - wl_list_init(&log_ctx->compositor_destroy_listener.link); - - weston_log_ctx_disable_debug_protocol(log_ctx); -} - -/** Enable weston-debug protocol extension - * - * \param compositor The libweston compositor where to enable. - * - * This enables the weston_debug_v1 Wayland protocol extension which any client - * can use to get debug messages from the compositor. - * - * WARNING: This feature should not be used in production. If a client - * provides a file descriptor that blocks writes, it will block the whole - * compositor indefinitely. - * - * There is no control on which client is allowed to subscribe to debug - * messages. Any and all clients are allowed. - * - * The debug extension is disabled by default, and once enabled, cannot be - * disabled again. - * - * @ingroup debug-protocol - */ -WL_EXPORT void -weston_compositor_enable_debug_protocol(struct weston_compositor *compositor) -{ - struct weston_log_context *log_ctx = compositor->weston_log_ctx; - assert(log_ctx); - if (log_ctx->global) - return; - - log_ctx->global = wl_global_create(compositor->wl_display, - &weston_debug_v1_interface, 1, - log_ctx, weston_log_bind_weston_debug); - if (!log_ctx->global) - return; - - log_ctx->compositor_destroy_listener.notify = compositor_destroy_listener; - wl_signal_add(&compositor->destroy_signal, &log_ctx->compositor_destroy_listener); - - fprintf(stderr, "WARNING: debug protocol has been enabled. " - "This is a potential denial-of-service attack vector and " - "information leak.\n"); -} - -/** Determine if the debug protocol has been enabled - * - * \param wc The libweston compositor to verify if debug protocol has been - * enabled - * - * @ingroup debug-protocol - */ -WL_EXPORT bool -weston_compositor_is_debug_protocol_enabled(struct weston_compositor *wc) -{ - return wc->weston_log_ctx->global != NULL; -} - -/** Register a new stream name, creating a log scope. - * - * @param log_ctx The weston_log_context where to add. - * @param name The debug stream/scope name; must not be NULL. - * @param description The log scope description for humans; must not be NULL. - * @param new_subscription Optional callback when a client subscribes to this - * scope. - * @param destroy_subscription Optional callback when a client destroys the - * subscription. - * @param user_data Optional user data pointer for the callback. - * @returns A valid pointer on success, NULL on failure. - * - * This function is used to create a log scope. All debug message printing - * happens for a scope, which allows clients to subscribe to the kind of debug - * messages they want by \c name. For the weston-debug protocol, - * subscription for the scope will happen automatically but for other types of - * streams, weston_log_subscribe() should be called as to create a subscription - * and tie it to the scope created by this function. - * - * \p name must be unique in the weston_compositor instance. \p name - * and \p description must both be provided. In case of the weston-debug - * protocol, the description is printed when a client asks for a list of - * supported log scopes. - * - * \p new_subscription, if not NULL, is called when a client subscribes to the log - * scope creating a debug stream. This is for log scopes that need to print - * messages as a response to a client appearing, e.g. printing a list of - * windows on demand or a static preamble. The argument \p user_data is - * passed in to the callback and is otherwise unused. - * - * For one-shot debug streams, \c new_subscription should finally call - * weston_log_subscription_complete() to close the stream and tell the client - * the printing is complete. Otherwise the client expects more data to be - * written. The complete callback in weston_log_subscriber should be installed - * to trigger it and it is set-up automatically for the weston-debug protocol. - * - * As subscription can take place before creating the scope, any pending - * subscriptions to scope added by weston_log_subscribe(), will be checked - * against the scope being created and if found will be added to the scope's - * subscription list. - * - * The log scope must be destroyed using weston_log_scope_destroy() - * before destroying the weston_compositor. - * - * @memberof weston_log_scope - * @sa weston_log_scope_cb, weston_log_subscribe - */ -WL_EXPORT struct weston_log_scope * -weston_log_ctx_add_log_scope(struct weston_log_context *log_ctx, - const char *name, - const char *description, - weston_log_scope_cb new_subscription, - weston_log_scope_cb destroy_subscription, - void *user_data) -{ - struct weston_log_scope *scope; - struct weston_log_subscription *pending_sub = NULL; - - if (!name || !description) { - fprintf(stderr, "Error: cannot add a debug scope without name or description.\n"); - return NULL; - } - - if (!log_ctx) { - fprintf(stderr, "Error: cannot add debug scope '%s', infra not initialized.\n", - name); - return NULL; - } - - if (weston_log_get_scope(log_ctx, name)){ - fprintf(stderr, "Error: debug scope named '%s' is already registered.\n", - name); - return NULL; - } - - scope = zalloc(sizeof *scope); - if (!scope) { - fprintf(stderr, "Error adding debug scope '%s': out of memory.\n", - name); - return NULL; - } - - scope->name = strdup(name); - scope->desc = strdup(description); - scope->new_subscription = new_subscription; - scope->destroy_subscription = destroy_subscription; - scope->user_data = user_data; - wl_list_init(&scope->subscription_list); - - if (!scope->name || !scope->desc) { - fprintf(stderr, "Error adding debug scope '%s': out of memory.\n", - name); - free(scope->name); - free(scope->desc); - free(scope); - return NULL; - } - - wl_list_insert(log_ctx->scope_list.prev, &scope->compositor_link); - - /* check if there are any pending subscriptions to this scope */ - while ((pending_sub = find_pending_subscription(log_ctx, scope->name)) != NULL) { - weston_log_subscription_create(pending_sub->owner, scope); - - /* remove it from pending */ - weston_log_subscription_destroy_pending(pending_sub); - } - - - return scope; -} - -/** Register a new stream name, creating a log scope. - * - * @param compositor The compositor that contains the log context where the log - * scope will be linked. - * @param name The debug stream/scope name; must not be NULL. - * @param description The log scope description for humans; must not be NULL. - * @param new_subscription Optional callback when a client subscribes to this - * scope. - * @param destroy_subscription Optional callback when a client destroys the - * subscription. - * @param user_data Optional user data pointer for the callback. - * @returns A valid pointer on success, NULL on failure. - * - * This function works like weston_log_ctx_add_log_scope(), but the log scope - * created is linked to the log context of \c compositor. - * - * @memberof weston_compositor - * @sa weston_log_ctx_add_log_scope - */ -WL_EXPORT struct weston_log_scope * -weston_compositor_add_log_scope(struct weston_compositor *compositor, - const char *name, - const char *description, - weston_log_scope_cb new_subscription, - weston_log_scope_cb destroy_subscription, - void *user_data) -{ - struct weston_log_scope *scope; - scope = weston_log_ctx_add_log_scope(compositor->weston_log_ctx, - name, description, - new_subscription, - destroy_subscription, - user_data); - return scope; -} - -/** Destroy a log scope - * - * @param scope The log scope to destroy; may be NULL. - * - * Destroys the log scope, calling each stream's destroy callback if one was - * installed/created. - * - * @memberof weston_log_scope - */ -WL_EXPORT void -weston_log_scope_destroy(struct weston_log_scope *scope) -{ - struct weston_log_subscription *sub, *sub_tmp; - - if (!scope) - return; - - wl_list_for_each_safe(sub, sub_tmp, &scope->subscription_list, source_link) - weston_log_subscription_destroy(sub); - - wl_list_remove(&scope->compositor_link); - free(scope->name); - free(scope->desc); - free(scope); -} - -/** Are there any active subscriptions to the scope? - * - * \param scope The log scope to check; may be NULL. - * \return True if any streams are open for this scope, false otherwise. - * - * As printing some debugging messages may be relatively expensive, one - * can use this function to determine if there is a need to gather the - * debugging information at all. If this function returns false, all - * printing for this scope is dropped, so gathering the information is - * pointless. - * - * The return value of this function should not be stored, as new clients - * may subscribe to the debug scope later. - * - * If the given scope is NULL, this function will always return false, - * making it safe to use in teardown or destroy code, provided the - * scope is initialized to NULL before creation and set to NULL after - * destruction. - * - * \memberof weston_log_scope - */ -WL_EXPORT bool -weston_log_scope_is_enabled(struct weston_log_scope *scope) -{ - if (!scope) - return false; - - return !wl_list_empty(&scope->subscription_list); -} - -/** Close the stream's complete callback if one was installed/created. - * - * @ingroup log - */ -WL_EXPORT void -weston_log_subscription_complete(struct weston_log_subscription *sub) -{ - if (sub->owner && sub->owner->complete) - sub->owner->complete(sub->owner); -} - -/** Close the log scope. - * - * @param scope The log scope to complete; may be NULL. - * - * Complete the log scope, calling each stream's complete callback if one was - * installed/created. This can be useful to signal the reading end that the - * data has been transmited and should no longer expect that written over the - * stream. Particularly useful for the weston-debug protocol. - * - * @memberof weston_log_scope - * @sa weston_log_ctx_add_log_scope, weston_compositor_add_log_scope, - * weston_log_scope_destroy - */ -WL_EXPORT void -weston_log_scope_complete(struct weston_log_scope *scope) -{ - struct weston_log_subscription *sub; - - if (!scope) - return; - - wl_list_for_each(sub, &scope->subscription_list, source_link) - weston_log_subscription_complete(sub); -} - -/** Write log data for a scope - * - * \param scope The debug scope to write for; may be NULL, in which case - * nothing will be written. - * \param[in] data Pointer to the data to write. - * \param len Number of bytes to write. - * - * Writes the given data to all subscribed clients' streams. - * - * \memberof weston_log_scope - */ -WL_EXPORT void -weston_log_scope_write(struct weston_log_scope *scope, - const char *data, size_t len) -{ - struct weston_log_subscription *sub; - - if (!scope) - return; - - wl_list_for_each(sub, &scope->subscription_list, source_link) - weston_log_subscription_write(sub, data, len); -} - -/** Write a formatted string for a scope (varargs) - * - * \param scope The log scope to write for; may be NULL, in which case - * nothing will be written. - * \param fmt Printf-style format string. - * \param ap Formatting arguments. - * - * Writes to formatted string to all subscribed clients' streams. - * - * The behavioral details for each stream are the same as for - * weston_debug_stream_write(). - * - * \memberof weston_log_scope - */ -WL_EXPORT int -weston_log_scope_vprintf(struct weston_log_scope *scope, - const char *fmt, va_list ap) -{ - static const char oom[] = "Out of memory"; - char *str; - int len = 0; - - if (!weston_log_scope_is_enabled(scope)) - return len; - - len = vasprintf(&str, fmt, ap); - if (len >= 0) { - weston_log_scope_write(scope, str, len); - free(str); - } else { - weston_log_scope_write(scope, oom, sizeof oom - 1); - } - - return len; -} - -/** Write a formatted string for a scope - * - * \param scope The log scope to write for; may be NULL, in which case - * nothing will be written. - * \param fmt Printf-style format string and arguments. - * - * Writes to formatted string to all subscribed clients' streams. - * - * The behavioral details for each stream are the same as for - * weston_debug_stream_write(). - * - * \memberof weston_log_scope - */ -WL_EXPORT int -weston_log_scope_printf(struct weston_log_scope *scope, - const char *fmt, ...) -{ - va_list ap; - int len; - - va_start(ap, fmt); - len = weston_log_scope_vprintf(scope, fmt, ap); - va_end(ap); - - return len; -} - -/** Write a formatted string for a subscription - * - * \param sub The subscription to write for; may be NULL, in which case - * nothing will be written. - * \param fmt Printf-style format string and arguments. - * - * Writes to formatted string to the stream that created the subscription. - * - * @ingroup log - */ -WL_EXPORT void -weston_log_subscription_printf(struct weston_log_subscription *sub, - const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - weston_log_subscription_vprintf(sub, fmt, ap); - va_end(ap); -} - -/** Write debug scope name and current time into string - * - * \param[in] scope debug scope; may be NULL - * \param[out] buf Buffer to store the string. - * \param len Available size in the buffer in bytes. - * \return \c buf - * - * Reads the current local wall-clock time and formats it into a string. - * and append the debug scope name to it, if a scope is available. - * The string is NUL-terminated, even if truncated. - * - * @memberof weston_log_scope - */ -WL_EXPORT char * -weston_log_scope_timestamp(struct weston_log_scope *scope, - char *buf, size_t len) -{ - struct timeval tv; - struct tm *bdt; - char string[128]; - size_t ret = 0; - - gettimeofday(&tv, NULL); - - bdt = localtime(&tv.tv_sec); - if (bdt) - ret = strftime(string, sizeof string, - "%Y-%m-%d %H:%M:%S", bdt); - - if (ret > 0) { - snprintf(buf, len, "[%s.%03ld][%s]", string, - tv.tv_usec / 1000, - (scope) ? scope->name : "no scope"); - } else { - snprintf(buf, len, "[?][%s]", - (scope) ? scope->name : "no scope"); - } - - return buf; -} - -void -weston_log_subscriber_release(struct weston_log_subscriber *subscriber) -{ - struct weston_log_subscription *sub, *sub_tmp; - - wl_list_for_each_safe(sub, sub_tmp, &subscriber->subscription_list, owner_link) - weston_log_subscription_destroy(sub); -} - -/** Destroy a file type or a flight-rec type subscriber. - * - * They are created, respectively, with weston_log_subscriber_create_log() - * and weston_log_subscriber_create_flight_rec() - * - * @param subscriber the weston_log_subscriber object to destroy - * - * @ingroup log - */ -WL_EXPORT void -weston_log_subscriber_destroy(struct weston_log_subscriber *subscriber) -{ - subscriber->destroy(subscriber); -} - -/** Subscribe to a scope - * - * Creates a subscription which is used to subscribe the \p subscriber - * to the scope \c scope_name. - * - * If \c scope_name has already been created (using - * weston_log_ctx_add_log_scope or weston_compositor_add_log_scope) the - * subscription will take place immediately, otherwise we store the - * subscription into a pending list. See also weston_log_ctx_add_log_scope() - * and weston_compositor_add_log_scope() - * - * @param log_ctx the log context, used for accessing pending list - * @param subscriber the subscriber, which has to be created before - * @param scope_name the scope name. In case the scope is not created - * we temporarily store the subscription in the pending list. - * - * @ingroup log - */ -WL_EXPORT void -weston_log_subscribe(struct weston_log_context *log_ctx, - struct weston_log_subscriber *subscriber, - const char *scope_name) -{ - assert(log_ctx); - assert(subscriber); - assert(scope_name); - - struct weston_log_scope *scope; - - scope = weston_log_get_scope(log_ctx, scope_name); - if (scope) - weston_log_subscription_create(subscriber, scope); - else - /* - * if we don't have already as scope for it, add it to pending - * subscription list - */ - weston_log_subscription_create_pending(subscriber, scope_name, log_ctx); -} - -/** Iterate over all subscriptions in a scope - * - * @param scope the scope for which you want to iterate - * @param sub_iter the iterator, use NULL to start from the 'head' - * @returns the next subscription from the log scope - * - * This is (quite) useful when 'log_scope' and 'log_subscription' are opaque. Do note - * that \c sub_iter needs to be NULL-initialized before calling this function. - * - */ -WL_EXPORT struct weston_log_subscription * -weston_log_subscription_iterate(struct weston_log_scope *scope, - struct weston_log_subscription *sub_iter) -{ - struct wl_list *list = &scope->subscription_list; - struct wl_list *node; - - /* go to the next item in the list or if not set starts with the head */ - if (sub_iter) - node = sub_iter->source_link.next; - else - node = list->next; - - assert(node); - assert(!sub_iter || node != &sub_iter->source_link); - - /* if we're at the end */ - if (node == list) - return NULL; - - return container_of(node, struct weston_log_subscription, source_link); -} diff --git a/libweston/zoom.c b/libweston/zoom.c deleted file mode 100644 index 064d6a8..0000000 --- a/libweston/zoom.c +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright © 2012 Scott Moreau - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include - -#include -#include "backend.h" -#include "libweston-internal.h" -#include "text-cursor-position-server-protocol.h" -#include "shared/helpers.h" - -static void -weston_zoom_frame_z(struct weston_animation *animation, - struct weston_output *output, - const struct timespec *time) -{ - if (animation->frame_counter <= 1) - output->zoom.spring_z.timestamp = *time; - - weston_spring_update(&output->zoom.spring_z, time); - - 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 = false; - output->zoom.seat = NULL; - weston_output_disable_planes_decr(output); - wl_list_remove(&output->zoom.motion_listener.link); - } - 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 void -zoom_area_center_from_point(struct weston_output *output, - double *x, double *y) -{ - float level = output->zoom.spring_z.current; - - *x = (*x - output->x) * level + output->width / 2.; - *y = (*y - output->y) * level + output->height / 2.; -} - -static void -weston_output_update_zoom_transform(struct weston_output *output) -{ - double x = output->zoom.current.x; /* global pointer coords */ - double y = output->zoom.current.y; - float level; - - level = output->zoom.spring_z.current; - - if (!output->zoom.active || level > output->zoom.max_level || - level == 0.0f) - return; - - zoom_area_center_from_point(output, &x, &y); - - output->zoom.trans_x = x - output->width / 2; - output->zoom.trans_y = y - output->height / 2; - - if (output->zoom.trans_x < 0) - output->zoom.trans_x = 0; - if (output->zoom.trans_y < 0) - output->zoom.trans_y = 0; - if (output->zoom.trans_x > level * output->width) - output->zoom.trans_x = level * output->width; - if (output->zoom.trans_y > level * output->height) - output->zoom.trans_y = level * output->height; -} - -static void -weston_zoom_transition(struct weston_output *output) -{ - 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) -{ - struct weston_seat *seat = output->zoom.seat; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - if (!pointer) - return; - - assert(output->zoom.active); - - output->zoom.current.x = wl_fixed_to_double(pointer->x); - output->zoom.current.y = wl_fixed_to_double(pointer->y); - - weston_zoom_transition(output); - weston_output_update_zoom_transform(output); -} - -static void -motion(struct wl_listener *listener, void *data) -{ - struct weston_output_zoom *zoom = - container_of(listener, struct weston_output_zoom, motion_listener); - struct weston_output *output = - container_of(zoom, struct weston_output, zoom); - - weston_output_update_zoom(output); -} - -WL_EXPORT void -weston_output_activate_zoom(struct weston_output *output, - struct weston_seat *seat) -{ - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - if (!pointer || output->zoom.active) - return; - - output->zoom.active = true; - output->zoom.seat = seat; - weston_output_disable_planes_incr(output); - wl_signal_add(&pointer->motion_signal, - &output->zoom.motion_listener); -} - -WL_EXPORT void -weston_output_init_zoom(struct weston_output *output) -{ - output->zoom.active = false; - output->zoom.seat = NULL; - 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; - 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); - output->zoom.motion_listener.notify = motion; -} diff --git a/man/meson.build b/man/meson.build deleted file mode 100644 index a2b8edc..0000000 --- a/man/meson.build +++ /dev/null @@ -1,55 +0,0 @@ -man_conf = configuration_data() -man_conf.set('weston_native_backend', opt_backend_native) -man_conf.set('weston_modules_dir', dir_module_weston) -man_conf.set('libweston_modules_dir', dir_module_libweston) -man_conf.set('weston_shell_client', get_option('desktop-shell-client-default')) -man_conf.set('weston_libexecdir', dir_libexec) -man_conf.set('weston_bindir', dir_bin) -man_conf.set('xserver_path', get_option('xwayland-path')) -man_conf.set('version', version_weston) - -configure_file( - input: 'weston.man', - output: 'weston.1', - install_dir: join_paths(dir_man, 'man1'), - configuration: man_conf -) - -configure_file( - input: 'weston-bindings.man', - output: 'weston-bindings.7', - install_dir: join_paths(dir_man, 'man7'), - configuration: man_conf -) - -configure_file( - input: 'weston-debug.man', - output: 'weston-debug.1', - install_dir: join_paths(dir_man, 'man1'), - configuration: man_conf -) - -configure_file( - input: 'weston.ini.man', - output: 'weston.ini.5', - install_dir: join_paths(dir_man, 'man5'), - configuration: man_conf -) - -if get_option('backend-drm') - configure_file( - input: 'weston-drm.man', - output: 'weston-drm.7', - install_dir: join_paths(dir_man, 'man7'), - configuration: man_conf - ) -endif - -if get_option('backend-rdp') - configure_file( - input: 'weston-rdp.man', - output: 'weston-rdp.7', - install_dir: join_paths(dir_man, 'man7'), - configuration: man_conf - ) -endif diff --git a/man/weston-bindings.man b/man/weston-bindings.man deleted file mode 100644 index e88a9e8..0000000 --- a/man/weston-bindings.man +++ /dev/null @@ -1,136 +0,0 @@ -.\" shorthand for double quote that works everywhere. -.ds q \N'34' -.TH weston-bindings 7 "2019-03-27" "Weston @version@" -.SH NAME -weston-bindings \- a list of keyboard bindings for -.B Weston -\- the reference Wayland -compositor -.SH INTRODUCTION -The Weston desktop shell has a number of keyboard shortcuts. They are listed here. -.SH DESCRIPTION -Almost all keyboard shortcuts for -.B Weston -include a specified modifier -.I mod -which is determined in the file -.BR weston.ini (5). -.\" Begin list -.P -.RE -.B mod + Shift + F -.RS 4 -Make active window fullscreen -.P -.RE -.B mod + K -.RS 4 -Kill active window -.P -.RE -.B mod + Shift + M -.RS 4 -Maximize active window -.P -.RE -.B mod + PageUp, mod + PageDown -.RS 4 -Zoom desktop in (or out) -.P -.RE -.B mod + Tab -.RS 4 -Switch active window -.P -.RE -.B mod + Up, mod + Down -.RS 4 -Increment/decrement active workspace number, if there are multiple -.P -.RE -.B mod + Shift + Up, mod + Shift + Down -.RS 4 -Move active window to the succeeding/preceding workspace, if possible -.P -.RE -.B mod + F1/F2/F3/F4/F5/F6 -.RS 4 -Jump to the numbered workspace, if it exists -.P -.RE -.B Ctrl + Alt + Backspace -.RS 4 -If supported, terminate Weston. (Note this combination often is used to hard restart Xorg.) -.P -.RE -.B Ctrl + Alt + F -.RS 4 -Toggle if Weston is fullscreen; only works when nested under a Wayland compositor -.P -.RE -.B Ctrl + Alt + S -.RS 4 -Share output screen, if possible -.P -.RE -.B Ctrl + Alt + F1/F2/F3/F4/F5/F6/F7/F8 -.RS 4 -Switch virtual terminal, if possible -.P -.RE -.B Super + S -.RS 4 -Make a screenshot of the desktop -.P -.RE -.B Super + R -.RS 4 -Start or stop recording video of the desktop - -.SS "TOUCH / MOUSE BINDINGS" - -There are also a number of bindings involving a mouse: -.P -.RE -.B \fI\fI, \fI\fB, \fI\fB -.RS 4 -Activate clicked window -.P -.RE -.B Super + Alt + \fI\fB -.RS 4 -Change the opacity of a window -.P -.RE -.B mod + \fI\fB -.RS 4 -Zoom/magnify the visible desktop -.P -.RE -.B mod + \fI\fB -.RS 4 -Click and drag to move a window -.P -.RE -.B mod + Shift + \fI\fB, mod + \fI\fB, mod + \fI\fB -.RS 4 -Click and drag to resize a window -.P -.RE -.B mod + \fI\fB -.RS 4 -Rotate the window (if supported) - -.SS DEBUG BINDINGS -The combination \fBmod + Shift + Space\fR begins a debug binding. Debug -bindings are completed by pressing an additional key. For example, pressing -F may toggle texture mesh wireframes with the GL renderer. -(In fact, most debug effects can be disabled again by repeating the command.) -Debug bindings are often tied to specific backends. - -.SH "SEE ALSO" -.BR weston (1), -.BR weston-launch (1), -.BR weston-drm (7), -.BR weston.ini (5), -.BR xkeyboard-config (7) diff --git a/man/weston-debug.man b/man/weston-debug.man deleted file mode 100644 index 907783e..0000000 --- a/man/weston-debug.man +++ /dev/null @@ -1,51 +0,0 @@ -.TH WESTON-DEBUG 1 "2018-09-11" "Weston @version@" -.SH NAME -weston-debug \- a tool for getting debug messages from compositor. -.SH SYNOPSIS -.B weston-debug [options] [names] -. -.\" *************************************************************** -.SH DESCRIPTION - -.B weston-debug -is a debugging tool which uses weston_debug_v1 interface to get the -debug messages from the compositor. The debug messages are categorized into different -debug streams by the compositor (example: logs, proto, list, etc.,) and the compositor -requires a file descriptor to stream the messages. - -This tool accepts a file name or a file descriptor (not both) and any desired debug stream -names from the user as command line arguments and subscribes the desired streams from the -compositor by using the weston_debug_v1 interface. After the subscription, the -compositor will start to write the debug messages to the shared file descriptor. - -If no file name or file descriptor argument is given, the tool will use the stdout file -descriptor. - -. -.\" *************************************************************** -.SH OPTIONS -. -.B weston-debug -accepts the following command line options. -.TP -. B \-h, \-\-help -Print the help text and exit with success. -.TP -. B \-l, \-\-list -List the available debug streams supported by the compositor. May be used -together with --all or a list of debug stream names. -.TP -. B \-a, \-\-all -Bind all debug streams offered by the compositor. Mututally exclusive with -explicitly specifying stream names. -.TP -. B \-o FILE, \-\-output FILE -Direct output to file named FILE. Use - for stdout. -Stdout is the default. Mutually exclusive with -f. -.TP -. B \-f FD, \-\-outfd FD -Direct output to the file descriptor FD. -Stdout (1) is the default. Mutually exclusive with -o. -.TP -.B [names] -A list of debug streams to bind to. Mutually exclusive with --all. diff --git a/man/weston-drm.man b/man/weston-drm.man deleted file mode 100644 index 01a336e..0000000 --- a/man/weston-drm.man +++ /dev/null @@ -1,240 +0,0 @@ -.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 is 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 . - -The DRM backend also supports virtual outputs that are transmitted over -an RTP session as a series of JPEG images (RTP payload type 26) to a remote -client. Virtual outputs are configured in the -.BR remote-output -section of -.BR weston.ini. -. -.\" *************************************************************** -.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: -.TP -\fBmode\fR=\fIwidth\fBx\fIheight\fR -.TP -\fBmode\fR=\fIwidth\fBx\fIheight\fB@\fIrefresh_rate\fR -Specify a mode with a given refresh-rate measured in Hz. -.TP -\fBmode\fR=\fIwidth\fBx\fIheight\fB@\fIrefresh_rate ratio\fR -Here \fIratio\fR is Picture Aspect-Ratio which can have values as 4:3, 16:9, -64:27, and 256:135. This resolution-format helps to select a CEA mode, if such a -video mode is present in the mode-list of the output. - -CEA defines the timing of a video mode, which is considered as a standard for -HDMI spcification and compliance testing. It defines each and every parameter of -a video mode, like hactive, vactive, vfront, vback etc., including aspect-ratio -information. For CEA modes, the drm layer, stores this aspect-ratio information -in user-mode (drmModeModeInfo) flag bits 19-22. For the non-CEA modes a value of -0 is stored in the aspect-ratio flag bits. - -Each CEA-mode is identified by a unique, Video Identification Code (VIC). -For example, VIC=4 is 1280x720@60 aspect-ratio 16:9. This mode will be -different than a non-CEA mode 1280x720@60 0:0. When the video mode -1280x720@60 0:0 is applied, since its timing doesn't exactly match with the CEA -information for VIC=4, it would be treated as a non-CEA mode. Also, while setting -the HDMI-AVI-Inforframe, VIC parameter will be given as '0'. If video mode -1280x720@60 16:9 is applied, its CEA timimgs matches with that of video mode with -VIC=4, so the VIC parameter in HDMI-AVI-Infoframe will be set to 4. - -Many a times, an output may have both CEA and non-CEA modes, which are similar -in all resepct, differing only in the aspect-ratio. A user can select a CEA mode -by giving the aspect-ratio, along with the other arguments for the mode. -By omitting the aspect-ratio, user can specify the non-CEA modes. -This helps when certification testing is done, in tests like 7-27, the -HDMI-analyzer applies a particular CEA mode, and expects the applied mode to be -with exactly same timings, including the aspect-ratio and VIC field. - -The resolution can also be 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 ", " rotate-90 ", " rotate-180 ", " rotate-270 ", " -.BR flipped ", " flipped-rotate-90 ", " flipped-rotate-180 ", and " -.BR flipped-rotate-270 . -.TP -\fBpixman-shadow\fR=\fIboolean\fR -If using the Pixman-renderer, use shadow framebuffers. Defaults to -.BR true . -.TP -\fBsame-as\fR=\fIname\fR -Make this output (connector) a clone of another. The argument -.IR name " is the " -.BR name " value of another output section. The -referred to output section must exist. When this key is present in an -output section, all other keys have no effect on the configuration. - -NOTE: cms-colord plugin does not work correctly with this option. The plugin -chooses an arbitrary monitor to load the color profile for, but the -profile is applied equally to all cloned monitors regardless of their -properties. -.TP -\fBforce-on\fR=\fItrue\fR -Force the output to be enabled even if the connector is disconnected. -Defaults to false. Note that -.BR mode=off " will override " force-on=true . -When a connector is disconnected, there is no EDID information to provide -a list of video modes. Therefore a forced output should also have a -detailed mode line specified. - -.SS Section remote-output -.TP -\fBname\fR=\fIname\fR -Specify unique name for the output. -.TP -\fBmode\fR=\fImode\fR -Specify the video mode for the output. The argument -.I mode -is a resolution setting, such as: -.TP -\fBmode\fR=\fIwidthxheight\fR -.TP -\fBmode\fR=\fIwidthxheight@refresh_rate -If refresh_rate is not specified it will default to a 60Hz. -.TP -\fBhost\fR=\fIhost\fR -Specify the host name or IP Address that the remote output will be -transmitted to. -.TP -\fBport\fR=\fIport\fR -Specify the port number to transmit the remote output to. Usable port range -is 1-65533. -.TP -\fBgst-pipeline\fR=\fIpipeline\fR -Specify the gstreamer pipeline. It is necessary that source is appsrc, -its name is "src", and sink name is "sink" in -.I pipeline\fR. -Ignore port and host configuration if the gst-pipeline is specified. - -. -.\" *************************************************************** -.SH OPTIONS -. -When the DRM backend is loaded, -.B weston -will understand the following additional command line options. -.TP -.B \-\-current\-mode -By default, use the current video mode of all outputs, instead of -switching to the monitor preferred mode. -.TP -\fB\-\-drm\-device\fR=\fIcardN\fR -Use the DRM device -.I cardN -instead of the default heuristics based on seat assignments and boot VGA -status. For example, use -.BR card0 . -.TP -\fB\-\-seat\fR=\fIseatid\fR -Use graphics and input devices designated for seat -.I seatid -instead of the seat defined in the environment variable -.BR XDG_SEAT ". If neither is specified, seat0 will be assumed." -.TP -\fB\-\-tty\fR=\fIx\fR -Launch Weston on tty -.I x -instead of using the current tty. -.TP -.B \-\-continue\-without\-input -Allow Weston to start without input devices. Used for testing purposes. -. -.\" *************************************************************** -.SH ENVIRONMENT -. -.TP -.B WESTON_LIBINPUT_LOG_PRIORITY -The minimum libinput verbosity level to be printed to Weston's log. -Valid values are -.BR debug ", " info ", and " error ". Default is " info . -.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 . -.TP -.B XDG_SEAT -The seat Weston will start on, unless overridden on the command line. -. -.\" *************************************************************** -.SH "SEE ALSO" -.BR weston (1) -.\".BR weston-launch (1), -.\".BR weston.ini (5) diff --git a/man/weston-rdp.man b/man/weston-rdp.man deleted file mode 100644 index f6cdd1d..0000000 --- a/man/weston-rdp.man +++ /dev/null @@ -1,92 +0,0 @@ -.TH WESTON-RDP 7 "2017-12-14" "Weston @version@" -.SH NAME -weston-rdp \- the RDP backend for Weston -.SH SYNOPSIS -.B weston --backend=rdp-backend.so -. -.\" *************************************************************** -.SH DESCRIPTION -The RDP backend allows to run a -.B weston -environment without the need of specific graphic hardware, or input devices. Users can interact with -.B weston -only by connecting using the RDP protocol. - -The RDP backend uses FreeRDP to implement the RDP part, it acts as a RDP server -listening for incoming connections. It supports different codecs for encoding the -graphical content. Depending on what is supported by the RDP client, the backend will -encode images using remoteFx codec, NS codec or will fallback to raw bitmapUpdate. - -On the security part, the backend supports RDP security or TLS, keys and certificates -must be provided to the backend depending on which kind of security is requested. The RDP -backend will announce security options based on which files have been given. - -The RDP backend is multi-seat aware, so if two clients connect on the backend, -they will get their own seat. - -.\" *************************************************************** -.SH OPTIONS -. -When the RDP backend is loaded, -.B weston -will understand the following additional command line options. -.TP -.B \-\-address\fR=\fIaddress\fR -The IP address on which the RDP backend will listen for RDP connections. By -default it listens on 0.0.0.0. -.TP -\fB\-\-port\fR=\fIport\fR -The TCP port to listen on for connections, it defaults to 3389. -.TP -\fB\-\-no-clients-resize -By default when a client connects on the RDP backend, it will instruct weston to -resize to the dimensions of the client's announced resolution. When this option is -set, weston will force the client to resize to its own resolution. -.TP -\fB\-\-rdp4\-key\fR=\fIfile\fR -The file containing the RSA key for doing RDP security. As RDP security is known -to be insecure, this option should be avoided in production. -.TP -\fB\-\-rdp\-tls\-key\fR=\fIfile\fR -The file containing the key for doing TLS security. To have TLS security you also need -to ship a file containing a certificate. -.TP -\fB\-\-rdp\-tls\-cert\fR=\fIfile\fR -The file containing the certificate for doing TLS security. To have TLS security you also need -to ship a key file. - - -.\" *************************************************************** -.SH Generating cryptographic material for the RDP backend -. -To generate a key file to use for RDP security, you need the -.BR winpr-makecert -utility shipped with FreeRDP: - -.nf -$ winpr-makecert -rdp -silent -n rdp-security -.fi - -This will create a rdp-security.key file. - - -You can generate a key and certificate file to use with TLS security using a typical -.B openssl -invocations: - -.nf -$ openssl genrsa -out tls.key 2048 -Generating RSA private key, 2048 bit long modulus -[...] -$ openssl req -new -key tls.key -out tls.csr -[...] -$ openssl x509 -req -days 365 -signkey tls.key -in tls.csr -out tls.crt -[...] -.fi - -You will get the tls.key and tls.crt files to use with the RDP backend. -. -.\" *************************************************************** -.SH "SEE ALSO" -.BR weston (1) -.\".BR weston.ini (5) diff --git a/man/weston.ini.man b/man/weston.ini.man deleted file mode 100644 index e1364c6..0000000 --- a/man/weston.ini.man +++ /dev/null @@ -1,663 +0,0 @@ -.\" shorthand for double quote that works everywhere. -.ds q \N'34' -.TH weston.ini 5 "2019-03-26" "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)" -.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 and options" -.BR "libinput " "Input device configuration" -.BR "shell " "Desktop customization" -.BR "launcher " "Add launcher to the panel" -.BR "output " "Output configuration" -.BR "input-method " "Onscreen keyboard input" -.BR "keyboard " "Keyboard layouts" -.BR "terminal " "Terminal application options" -.BR "xwayland " "XWayland options" -.BR "screen-share " "Screen sharing 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 and general options. -.TP 7 -.BI "shell=" desktop-shell.so -specifies a shell to load (string). This can be used to load your own -implemented shell or one with Weston as default. Available shells -in the -.IR "@weston_modules_dir@" -directory are: -.PP -.RS 10 -.nf -.BR desktop-shell.so -.fi -.RE -.TP 7 -.BI "xwayland=" true -ask Weston to load the XWayland module (boolean). -.RE -.TP 7 -.BI "modules=" cms-colord.so,screen-share.so -specifies the modules to load (string). Available modules in the -.IR "@weston_modules_dir@" -directory are: -.PP -.RS 10 -.nf -.BR cms-colord.so -.BR screen-share.so -.fi -.RE -.TP 7 -.BI "backend=" headless-backend.so -overrides defaults backend. Available backend modules in the -.IR "@libweston_modules_dir@" -directory are: -.PP -.RS 10 -.nf -.BR drm-backend.so -.BR fbdev-backend.so -.BR headless-backend.so -.BR rdp-backend.so -.BR wayland-backend.so -.BR x11-backend.so -.fi -.RE -.TP 7 -.BI "repaint-window=" N -Set the approximate length of the repaint window in milliseconds. The repaint -window is used to control and reduce the output latency for clients. If the -window is longer than the output refresh period, the repaint will be done -immediately when the previous repaint finishes, not processing client requests -in between. If the repaint window is too short, the compositor may miss the -target vertical blank, increasing output latency. The default value is 7 -milliseconds. The allowed range is from -10 to 1000 milliseconds. Using a -negative value will force the compositor to always miss the target vblank. -.TP 7 -.BI "gbm-format="format -sets the GBM format used for the framebuffer for the GBM backend. Can be -.B xrgb8888, -.B xrgb2101010, -.B rgb565. -By default, xrgb8888 is used. -.RS -.PP -.RE -.TP 7 -.BI "idle-time="seconds -sets Weston's idle timeout in seconds. This idle timeout is the time -after which Weston will enter an "inactive" mode and screen will fade to -black. A value of 0 disables the timeout. - -.IR Important -: This option may also be set via Weston's '-i' command -line option and will take precedence over the current .ini option. This -means that if both weston.ini and command line define this idle-timeout -time, the one specified in the command-line will be used. On the other -hand, if none of these sets the value, default idle timeout will be -set to 300 seconds. -.RS -.PP -.RE -.TP 7 -.BI "require-input=" true -require an input device for launch -.TP 7 -.BI "pageflip-timeout="milliseconds -sets Weston's pageflip timeout in milliseconds. This sets a timer to exit -gracefully with a log message and an exit code of 1 in case the DRM driver is -non-responsive. Setting it to 0 disables this feature. -.TP 7 -.BI "wait-for-debugger=" true -Raises SIGSTOP before initializing the compositor. This allows the user to -attach with a debugger and continue execution by sending SIGCONT. This is -useful for debugging a crash on start-up when it would be inconvenient to -launch weston directly from a debugger. Boolean, defaults to -.BR false . -There is also a command line option to do the same. -.TP 7 -.BI "remoting="remoting-plugin.so -specifies a plugin for remote output to load (string). This can be used to load -your own implemented remoting plugin or one with Weston as default. Available -remoting plugins in the -.IR "__libweston_modules_dir__" -directory are: -.PP -.RS 10 -.nf -.BR remoting-plugin.so -.fi -.RE -.TP 7 -.BI "use-pixman=" true -Enables pixman-based rendering for all outputs on backends that support it. -Boolean, defaults to -.BR false . -There is also a command line option to do the same. - -.SH "LIBINPUT SECTION" -The -.B libinput -section is used to configure input devices when using the libinput input device -backend. The defaults are determined by libinput and vary according to what is -most sensible for any given device. -.PP -Available configuration are: -.TP 7 -.BI "enable-tap=" false -Enables tap to click on touchpad devices. -.TP 7 -.BI "tap-and-drag=" false -For touchpad devices with \fBenable-tap\fR enabled. If the user taps, then -taps a second time, this time holding, the virtual mouse button stays down for -as long as the user keeps their finger on the touchpad, allowing the user to -click and drag with taps alone. -.TP 7 -.BI "tap-and-drag-lock=" false -For touchpad devices with \fBenable-tap\fR and \fBtap-and-drag\fR enabled. -In the middle of a tap-and-drag, if the user releases the touchpad for less -than a certain number of milliseconds, then touches it again, the virtual mouse -button will remain pressed and the drag can continue. -.TP 7 -.BI "disable-while-typing=" true -For devices that may be accidentally triggered while typing on the keyboard, -causing a disruption of the typing. Disables them while the keyboard is in -use. -.TP 7 -.BI "middle-button-emulation=" false -For pointer devices with left and right buttons, but no middle button. When -enabled, a middle button event is emitted when the left and right buttons are -pressed simultaneously. -.TP 7 -.BI "left-handed=" false -Configures the device for use by left-handed people. Exactly what this option -does depends on the device. For pointers with left and right buttons, the -buttons are swapped. On tablets, the tablet is logically turned upside down, -because it will be physically turned upside down. -.TP 7 -.BI "rotation=" n -Changes the direction of the logical north, rotating it \fIn\fR degrees -clockwise away from the default orientation, where \fIn\fR is a whole -number between 0 and 359 inclusive. Needed for trackballs, mainly. Allows the -user to orient the trackball sideways, for example. -.TP 7 -.BI "accel-profile=" "{flat,adaptive}" -Set the pointer acceleration profile. The pointer's screen speed is -proportional to the physical speed with a certain constant of proportionality. -Call that constant alpha. \fIflat\fR keeps alpha fixed. See \fBaccel-speed\fR. -\fIadaptive\fR causes alpha to increase with physical speed, giving the user -more control when the speed is slow, and more reach when the speed is high. -\fIadaptive\fR is the default. -.TP 7 -.BI "accel-speed=" v -If \fBaccel-profile\fR is set to \fIflat\fR, it simply sets the value of alpha. -If \fBaccel-profile\fR is set to \fIadaptive\fR, the effect is more -complicated, but generally speaking, it will change the pointer's speed. -\fIv\fR is normalised and must lie in the range [-1, 1]. The exact mapping -between \fIv\fR and alpha is hardware-dependent, but higher values cause higher -cursor speeds. -.TP 7 -.BI "natural-scroll=" false -Enables natural scrolling, mimicking the behaviour of touchscreen scrolling. -That is, if the wheel, finger, or fingers are moved down, the surface is -scrolled up instead of down, as if the finger, or fingers were in contact with -the surface being scrolled. -.TP 7 -.BI "scroll-method=" {two-finger,edge,button,none} -Sets the scroll method. \fItwo-finger\fR scrolls with two fingers on a -touchpad. \fIedge\fR scrolls with one finger on the right edge of a touchpad. -\fIbutton\fR scrolls when the pointer is moved while a certain button is -pressed. See \fBscroll-button\fR. \fInone\fR disables scrolling altogether. -.TP 7 -.BI "scroll-button=" {BTN_LEFT,BTN_RIGHT,BTN_MIDDLE,...} -For devices with \fBscroll-method\fR set to \fIbutton\fR. Specifies the -button that will trigger scrolling. See /usr/include/linux/input-event-codes.h -for the complete list of possible values. -.TP 7 -.BI "touchscreen_calibrator=" true -Advertise the touchscreen calibrator interface to all clients. This is a -potential denial-of-service attack vector, so it should only be enabled on -trusted userspace. Boolean, defaults to -.BR false . - -The interface is required for running touchscreen calibrator applications. It -provides the application raw touch events, bypassing the normal touch handling. -It also allows the application to upload a new calibration into the compositor. - -Even though this option is listed in the libinput section, it does affect all -Weston configurations regardless of the used backend. If the backend does not -use libinput, the interface can still be advertised, but it will not list any -devices. -.TP 7 -.BI "calibration_helper=" /bin/echo -An optional calibration helper program to permanently save a new touchscreen -calibration. String, defaults to unset. - -The given program will be executed with seven arguments when a calibrator -application requests the server to take a new calibration matrix into use. -The program is executed synchronously and will therefore block Weston for its -duration. If the program exit status is non-zero, Weston will not apply the -new calibration. If the helper is unset or the program exit status is zero, -Weston will use the new calibration immediately. - -The program is invoked as: -.PP -.RS 10 -.I calibration_helper syspath m1 m2 m3 m4 m5 m6 -.RE -.RS -.PP -.RI "where " syspath -is the udev sys path for the device and -.IR m1 " through " m6 -are the calibration matrix elements in libinput's -.BR LIBINPUT_CALIBRATION_MATRIX " udev property format." -The sys path is an absolute path and starts with the sys mount point. -.RE - -.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 "client=" file -sets the path for the shell client to run. If not specified -.I @weston_shell_client@ -is launched (string). -.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 centered ", " scale ", " scale-crop " or " tile " (default)." -Centered shows the image once centered. If the image is smaller than the -output, the rest of the surface will be in background color. If the image -size does fit the output it will be cropped left and right, or top and bottom. -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 "clock-format=" format -sets the panel clock format (string). Can be -.BR "none" "," -.BR "minutes" "," -.BR "seconds" "." -By default, minutes format is used. -.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 "panel-position=" top -sets the position of the panel (string). Can be -.B top, -.B bottom, -.B left, -.B right, -.B none. -.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 "close-animation=" fade -sets the effect used when closing windows (string). Can be -.B fade, -.B none. -By default, the fade 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 "focus-animation=" dim-layer -sets the effect used with the focused and unfocused windows. Can be -.B dim-layer, -.B none. -By default, no animation is used. -.TP 7 -.BI "allow-zap=" true -whether the shell should quit when the Ctrl-Alt-Backspace key combination is -pressed -.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. See -.BR weston-bindings (7). -Possible values: none, 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 -.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. All -Wayland output names start with the letters WL. 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" -.BR "WL1 " "Wayland backend, Wayland 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, along with an option of a modeline string. - -See -.B "weston-drm(7)" -for examples of modes-formats supported by DRM backend. -.RE -.TP 7 -.BI "transform=" normal -How you have rotated your monitor from its normal orientation (string). -The transform key can be one of the following 8 strings: -.PP -.RS 10 -.nf -.BR "normal " "Normal output." -.BR "rotate-90 " "90 degrees clockwise." -.BR "rotate-180 " "Upside down." -.BR "rotate-270 " "90 degrees counter clockwise." -.BR "flipped " "Horizontally flipped" -.BR "flipped-rotate-90 " "Flipped and 90 degrees clockwise" -.BR "flipped-rotate-180 " "Flipped and upside down" -.BR "flipped-rotate-270 " "Flipped and 90 degrees counter clockwise" -.fi -.RE -.TP 7 -.BI "scale=" factor -The scaling multiplier applied to the entire output, in support of high -resolution ("HiDPI" or "retina") displays, that roughly corresponds to the -pixel ratio of the display's physical resolution to the logical resolution. -Applications that do not support high resolution displays typically appear tiny -and unreadable. Weston will scale the output of such applications by this -multiplier, to make them readable. Applications that do support their own output -scaling can draw their content in high resolution, in which case they avoid -compositor scaling. Weston will not scale the output of such applications, and -they are not affected by this multiplier. -.RE -.RS -.PP -An integer, 1 by default, typically configured as 2 or higher when needed, -denoting the scaling multiplier for the output. -.RE -.TP 7 -.BI "seat=" name -The logical seat name 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 -.TP 7 -.BI "allow_hdcp=" true -Allows HDCP support for this output. If set to true, HDCP can be tried for the -content-protection, provided by the backends, on this output. By -default, HDCP support is always allowed for an output. The -content-protection can actually be realized, only if the hardware -(source and sink) support HDCP, and the backend has the implementation -of content-protection protocol. Currently, HDCP is supported by drm-backend. -.RE -.TP 7 -.BI "app-ids=" app-id[,app_id]* -A comma separated list of the IDs of applications to place on this output. -These IDs should match the application IDs as set with the xdg_shell.set_app_id -request. Currently, this option is supported by kiosk-shell. -.RE -.SH "INPUT-METHOD SECTION" -.TP 7 -.BI "path=" "@weston_libexecdir@/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 -.TP 7 -.BI "repeat-rate=" "40" -sets the rate of repeating keys in characters per second (unsigned integer) -.RE -.RE -.TP 7 -.BI "repeat-delay=" "400" -sets the delay in milliseconds since key down until repeating starts (unsigned -integer) -.RE -.RE -.TP 7 -.BI "numlock-on=" "false" -sets the default state of the numlock on weston startup for the backends which -support it. -.RE -.RE -.TP 7 -.BI "vt-switching=" "true" -Whether to allow the use of Ctrl+Alt+Fn key combinations to switch away from -the compositor's virtual console. -.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 recommended -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=" "@xserver_path@" -sets the path to the xserver to run (string). -.RE -.RE -.SH "SCREEN-SHARE SECTION" -.TP 7 -.BI "command=" "@weston_bindir@/weston --backend=rdp-backend.so \ ---shell=fullscreen-shell.so --no-clients-resize" -sets the command to start a fullscreen-shell server for screen sharing (string). -.RE -.RE -.SH "SEE ALSO" -.BR weston (1), -.BR weston-bindings (7), -.BR weston-drm (7), -.BR xkeyboard-config (7) diff --git a/man/weston.man b/man/weston.man deleted file mode 100644 index 5d20c03..0000000 --- a/man/weston.man +++ /dev/null @@ -1,377 +0,0 @@ -.TH WESTON 1 "2019-03-23" "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. If your system supports -the logind D-Bus API and the support has been built into weston as well, -it is possible to start weston with just -.BR weston . - -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. -.TP -.I rdp-backend.so -The RDP backend runs in memory without the need of graphical hardware. Access -to the desktop is done by using the RDP protocol. Each connecting -client has its own seat making it a cheap way to test multi-seat support. See -.BR weston-rdp (7), -if installed. -. -.\" *************************************************************** -.SH SHELLS -Each of these shells have its own public protocol interface for clients. -This means that a client must be specifically written for a shell protocol, -otherwise it will not work. -.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 -Fullscreen shell -Fullscreen shell is intended for a client that needs to take over -whole outputs, often all outputs. This is primarily intended for -running another compositor on Weston. The other compositor does not -need to handle any platform-specifics like DRM/KMS or evdev/libinput. -The shell consists only of the shell plugin -.IR fullscreen-shell.so . -.TP -IVI-shell -In-vehicle infotainment shell is a special purpose shell that exposes -a GENIVI Layer Manager compatible API to controller modules, and a very -simple shell protocol towards clients. IVI-shell starts with loading -.IR ivi-shell.so , -and then a controller module which may launch helper clients. -. -.\" *************************************************************** -.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 the XWayland 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 -\fB\-\^c\fR\fIconfig.ini\fR, \fB\-\-config\fR=\fIconfig.ini\fR -Load -.IR config.ini " instead of " weston.ini . -The argument can also be an absolute path starting with a -.IR / . -If the path is not absolute, it will be searched in the normal config -paths, see -.BR weston.ini (5). -If also -.B --no-config -is given, no configuration file will be read. -.TP -.BR \-\-debug -Enable debug protocol extension -.I weston_debug_v1 -which any client can use to receive debugging messages from the compositor. - -.B WARNING: -This is risky for two reasons. First, a client may cause a denial-of-service -blocking the compositor by providing an unsuitable file descriptor, and -second, the debug messages may expose sensitive information. -Additionally this will expose weston-screenshooter interface allowing the user -to take screenshots of the outputs using weston-screenshooter application, -which can lead to silently leaking the output contents. This option should -not be used in production. -.TP -\fB\-\^l\fIscope1,scope2\fR, \fB\-\-logger-scopes\fR=\fIscope1,scope2\fR -Specify to which log scopes should subscribe to. When no scopes are supplied, -the log "log" scope will be subscribed by default. Useful to control which -streams to write data into the logger and can be helpful in diagnosing early -start-up code. -.TP -\fB\-\^f\fIscope1,scope2\fR, \fB\-\-flight-rec-scopes\fR=\fIscope1,scope2\fR -Specify to which scopes should subscribe to. Useful to control which streams to -write data into the flight recorder. Flight recorder has limited space, once -the flight recorder is full new data will overwrite the old data. Without any -scopes specified, it subscribes to 'log' and 'drm-backend' scopes. -.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, 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\-\-xwayland\fR -Ask Weston to load the XWayland module. -.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 -.BR \-\-no-config -Do not read -.I weston.ini -for the compositor. Avoids e.g. loading compositor modules via the -configuration file, which is useful for unit tests. -.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. -.TP -\fB\-\-wait-for-debugger\fR -Raises SIGSTOP before initializing the compositor. This allows the user to -attach with a debugger and continue execution by sending SIGCONT. This is -useful for debugging a crash on start-up when it would be inconvenient to -launch weston directly from a debugger. There is also a -.IR weston.ini " option to do the same." -. -.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 -.B \-\-fullscreen -Create a single fullscreen output -.TP -\fB\-\-output\-count\fR=\fIN\fR -Create -.I N -Wayland windows to emulate the same number of outputs. -.TP -\fB\-\-width\fR=\fIW\fR, \fB\-\-height\fR=\fIH\fR -Make all outputs have a size of -.IR W x H " pixels." -.TP -.B \-\-scale\fR=\fIN\fR -Give all outputs a scale factor of -.I N. -.TP -.B \-\-use\-pixman -Use the pixman renderer. By default, weston will try to use EGL and -GLES2 for rendering and will fall back to the pixman-based renderer for -software compositing if EGL cannot be used. Passing this option will force -weston to use the pixman renderer. -. -.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 \-\-scale\fR=\fIN\fR -Give all outputs a scale factor of -.I N. -.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. -. -.SS RDP backend options: -See -.BR weston-rdp (7). -. -. -.\" *************************************************************** -.SH FILES -. -If the environment variable is set, the configuration file is read -from the respective path. -.PP -.BI $XDG_CONFIG_HOME /weston.ini -.br -.BI $HOME /.config/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 WESTON_CONFIG_FILE -Weston sets this variable to the absolute path of the configuration file -it loads, or to the empty string if no file is used. Programs that use -.I weston.ini -will read the file specified by this variable instead, or do not read any -file if it is empty. Unset variable causes falling back to the default -name -.IR weston.ini . -.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 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 -- --xwayland -.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-bindings (7), -.BR weston-debug (1), -.BR weston-drm (7), -.BR weston-rdp (7), -.BR weston.ini (5) diff --git a/meson.build b/meson.build deleted file mode 100644 index e7d053b..0000000 --- a/meson.build +++ /dev/null @@ -1,176 +0,0 @@ -project('weston', - 'c', - version: '9.0.0', - default_options: [ - 'warning_level=3', - 'c_std=gnu99', - 'b_lundef=true', - ], - meson_version: '>= 0.47', - license: 'MIT/Expat', -) - -libweston_major = 9 - -# libweston_revision is manufactured to follow the autotools build's -# library file naming, thanks to libtool -version_weston = meson.project_version() -version_weston_arr = version_weston.split('.') -if libweston_major > version_weston_arr[0].to_int() - if libweston_major > version_weston_arr[0].to_int() + 1 - error('Bad versions in meson.build: libweston_major is too high') - endif - libweston_revision = 0 -elif libweston_major == version_weston_arr[0].to_int() - libweston_revision = version_weston_arr[2].to_int() -else - error('Bad versions in meson.build: libweston_major is too low') -endif - -dir_prefix = get_option('prefix') -dir_bin = join_paths(dir_prefix, get_option('bindir')) -dir_data = join_paths(dir_prefix, get_option('datadir')) -dir_include_libweston = 'libweston-@0@'.format(libweston_major) -dir_include_libweston_install = join_paths(dir_include_libweston, 'libweston') -dir_lib = join_paths(dir_prefix, get_option('libdir')) -dir_libexec = join_paths(dir_prefix, get_option('libexecdir')) -dir_module_weston = join_paths(dir_lib, 'weston') -dir_module_libweston = join_paths(dir_lib, 'libweston-@0@'.format(libweston_major)) -dir_data_pc = join_paths(dir_data, 'pkgconfig') -dir_lib_pc = join_paths(dir_lib, 'pkgconfig') -dir_man = join_paths(dir_prefix, get_option('mandir')) -dir_protocol_libweston = join_paths('libweston-@0@'.format(libweston_major), 'protocols') - -public_inc = include_directories('include') -common_inc = [ include_directories('.'), public_inc ] - -pkgconfig = import('pkgconfig') - -git_version_h = vcs_tag( - input: 'libweston/git-version.h.meson', - output: 'git-version.h', - fallback: version_weston -) - -config_h = configuration_data() - -cc = meson.get_compiler('c') - -global_args = [] -global_args_maybe = [ - '-Wmissing-prototypes', - '-Wno-unused-parameter', - '-Wno-shift-negative-value', # required due to Pixman - '-Wno-missing-field-initializers', - '-Wno-pedantic', - '-fvisibility=hidden', -] -foreach a : global_args_maybe - if cc.has_argument(a) - global_args += a - endif -endforeach -add_global_arguments(global_args, language: 'c') - -if cc.has_header_symbol('sys/sysmacros.h', 'major') - config_h.set('MAJOR_IN_SYSMACROS', 1) -elif cc.has_header_symbol('sys/mkdev.h', 'major') - config_h.set('MAJOR_IN_MKDEV', 1) -endif - -optional_libc_funcs = [ - 'mkostemp', 'strchrnul', 'initgroups', 'posix_fallocate', 'memfd_create' -] -foreach func : optional_libc_funcs - if cc.has_function(func) - config_h.set('HAVE_' + func.to_upper(), 1) - endif -endforeach - -optional_system_headers = [ - 'linux/sync_file.h' -] -foreach hdr : optional_system_headers - if cc.has_header(hdr) - config_h.set('HAVE_' + hdr.underscorify().to_upper(), 1) - endif -endforeach - -env_modmap = '' - -config_h.set('_GNU_SOURCE', '1') -config_h.set('_ALL_SOURCE', '1') -config_h.set('EGL_NO_X11', '1') -config_h.set('MESA_EGL_NO_X11_HEADERS', '1') - -config_h.set_quoted('PACKAGE_STRING', 'weston @0@'.format(version_weston)) -config_h.set_quoted('PACKAGE_VERSION', version_weston) -config_h.set_quoted('VERSION', version_weston) -config_h.set_quoted('PACKAGE_URL', 'https://wayland.freedesktop.org') -config_h.set_quoted('PACKAGE_BUGREPORT', 'https://gitlab.freedesktop.org/wayland/weston/issues/') - -config_h.set_quoted('BINDIR', dir_bin) -config_h.set_quoted('DATADIR', dir_data) -config_h.set_quoted('LIBEXECDIR', dir_libexec) -config_h.set_quoted('MODULEDIR', dir_module_weston) -config_h.set_quoted('LIBWESTON_MODULEDIR', dir_module_libweston) - -config_h.set10('TEST_GL_RENDERER', get_option('test-gl-renderer')) - -backend_default = get_option('backend-default') -if backend_default == 'auto' - foreach b : [ 'headless', 'fbdev', 'x11', 'wayland', 'drm' ] - if get_option('backend-' + b) - backend_default = b - endif - endforeach -endif -opt_backend_native = backend_default + '-backend.so' -config_h.set_quoted('WESTON_NATIVE_BACKEND', opt_backend_native) -message('The default backend is ' + backend_default) -if not get_option('backend-' + backend_default) - error('Backend @0@ was chosen as native but is not being built.'.format(backend_default)) -endif - -dep_xkbcommon = dependency('xkbcommon', version: '>= 0.3.0') -if dep_xkbcommon.version().version_compare('>= 0.5.0') - config_h.set('HAVE_XKBCOMMON_COMPOSE', '1') -endif - -dep_wayland_server = dependency('wayland-server', version: '>= 1.17.0') -dep_wayland_client = dependency('wayland-client', version: '>= 1.17.0') -dep_pixman = dependency('pixman-1', version: '>= 0.25.2') -dep_libinput = dependency('libinput', version: '>= 0.8.0') -dep_libevdev = dependency('libevdev') -dep_libm = cc.find_library('m') -dep_libdl = cc.find_library('dl') -dep_libdrm = dependency('libdrm', version: '>= 2.4.86') -dep_libdrm_headers = dep_libdrm.partial_dependency(compile_args: true) -dep_threads = dependency('threads') - -subdir('include') -subdir('protocol') -subdir('shared') -subdir('libweston') -subdir('libweston-desktop') -subdir('xwayland') -subdir('compositor') -subdir('desktop-shell') -subdir('fullscreen-shell') -subdir('ivi-shell') -subdir('kiosk-shell') -subdir('remoting') -subdir('pipewire') -subdir('clients') -subdir('wcap') -subdir('tests') -subdir('data') -subdir('man') - -configure_file(output: 'config.h', configuration: config_h) - -if get_option('doc') - subdir('doc/sphinx') -else - message('Documentation will not be built. Use -Ddoc to build it.') -endif diff --git a/meson_options.txt b/meson_options.txt deleted file mode 100644 index 239bd2d..0000000 --- a/meson_options.txt +++ /dev/null @@ -1,224 +0,0 @@ -# This option is not implemented: -# --with-cairo=[image|gl|glesv2] Which Cairo renderer to use for the clients -# It is hardcoded to cairo-image for now. - -option( - 'backend-drm', - type: 'boolean', - value: true, - description: 'Weston backend: DRM/KMS' -) -option( - 'backend-drm-screencast-vaapi', - type: 'boolean', - value: true, - description: 'DRM/KMS backend support for VA-API screencasting' -) -option( - 'backend-headless', - type: 'boolean', - value: true, - description: 'Weston backend: headless (testing)' -) -option( - 'backend-rdp', - type: 'boolean', - value: true, - description: 'Weston backend: RDP remote screensharing' -) -option( - 'screenshare', - type: 'boolean', - value: true, - description: 'Compositor: RDP screen-sharing support' -) -option( - 'backend-wayland', - type: 'boolean', - value: true, - description: 'Weston backend: Wayland (nested)' -) -option( - 'backend-x11', - type: 'boolean', - value: true, - description: 'Weston backend: X11 (nested)' -) -option( - 'backend-fbdev', - type: 'boolean', - value: true, - description: 'Weston backend: fbdev' -) -option( - 'backend-default', - type: 'combo', - choices: [ 'auto', 'drm', 'wayland', 'x11', 'fbdev', 'headless' ], - value: 'drm', - description: 'Default backend when no parent display server detected' -) - -option( - 'renderer-gl', - type: 'boolean', - value: true, - description: 'Weston renderer: EGL / OpenGL ES 2.x' -) - -option( - 'weston-launch', - type: 'boolean', - value: true, - description: 'Weston launcher for systems without logind' -) - -option( - 'xwayland', - type: 'boolean', - value: true, - description: 'Xwayland: support for X11 clients inside Weston' -) -option( - 'xwayland-path', - type: 'string', - value: '/usr/bin/Xwayland', - description: 'Xwayland: path to installed Xwayland binary' -) - -option( - 'systemd', - type: 'boolean', - value: true, - description: 'systemd service plugin: state notify, watchdog, socket activation' -) - -option( - 'remoting', - type: 'boolean', - value: true, - description: 'Virtual remote output with GStreamer on DRM backend' -) - -option( - 'pipewire', - type: 'boolean', - value: true, - description: 'Virtual remote output with Pipewire on DRM backend' -) - -option( - 'shell-desktop', - type: 'boolean', - value: true, - description: 'Weston shell UI: traditional desktop' -) -option( - 'shell-fullscreen', - type: 'boolean', - value: true, - description: 'Weston shell UI: fullscreen/kiosk' -) -option( - 'shell-ivi', - type: 'boolean', - value: true, - description: 'Weston shell UI: IVI (automotive)' -) -option( - 'shell-kiosk', - type: 'boolean', - value: true, - description: 'Weston shell UI: kiosk (desktop apps)' -) - -option( - 'desktop-shell-client-default', - type: 'string', - value: 'weston-desktop-shell', - description: 'Weston desktop shell: default helper client selection' -) - -option( - 'color-management-lcms', - type: 'boolean', - value: true, - description: 'Compositor color management: lcms' -) -option( - 'color-management-colord', - type: 'boolean', - value: true, - description: 'Compositor color management: colord (requires lcms)' -) - -option( - 'launcher-logind', - type: 'boolean', - value: true, - description: 'Compositor: support systemd-logind D-Bus protocol' -) - -option( - 'image-jpeg', - type: 'boolean', - value: true, - description: 'JPEG loading support' -) -option( - 'image-webp', - type: 'boolean', - value: true, - description: 'WebP loading support' -) - -option( - 'tools', - type: 'array', - choices: [ 'calibrator', 'debug', 'info', 'terminal', 'touch-calibrator' ], - description: 'List of accessory clients to build and install' -) -option( - 'demo-clients', - type: 'boolean', - value: true, - description: 'Sample clients: toytoolkit demo programs' -) -option( - 'simple-clients', - type: 'array', - choices: [ 'all', 'damage', 'im', 'egl', 'shm', 'touch', 'dmabuf-v4l', 'dmabuf-egl' ], - value: [ 'all' ], - description: 'Sample clients: simple test programs' -) - -option( - 'resize-pool', - type: 'boolean', - value: true, - description: 'Sample clients: optimize window resize performance' -) -option( - 'wcap-decode', - type: 'boolean', - value: true, - description: 'Tools: screen recording decoder tool' -) - -option( - 'test-junit-xml', - type: 'boolean', - value: true, - description: 'Tests: output JUnit XML results' -) -option( - 'test-gl-renderer', - type: 'boolean', - value: true, - description: 'Tests: allow running with GL-renderer' -) -option( - 'doc', - type: 'boolean', - value: false, - description: 'Generate documentation' -) diff --git a/pipewire/meson.build b/pipewire/meson.build deleted file mode 100644 index 3d3374b..0000000 --- a/pipewire/meson.build +++ /dev/null @@ -1,31 +0,0 @@ -if get_option('pipewire') - user_hint = 'If you rather not build this, set "pipewire=false".' - - if not get_option('backend-drm') - error('Attempting to build the pipewire plugin without the required DRM backend. ' + user_hint) - endif - - depnames = [ - 'libpipewire-0.2', 'libspa-0.1' - ] - deps_pipewire = [ dep_libweston_private ] - foreach depname : depnames - dep = dependency(depname, required: false) - if not dep.found() - error('Pipewire plugin requires @0@ which was not found. '.format(depname) + user_hint) - endif - deps_pipewire += dep - endforeach - - plugin_pipewire = shared_library( - 'pipewire-plugin', - 'pipewire-plugin.c', - include_directories: common_inc, - dependencies: deps_pipewire, - name_prefix: '', - install: true, - install_dir: dir_module_libweston - ) - env_modmap += 'pipewire-plugin.so=@0@;'.format(plugin_pipewire.full_path()) - install_headers('pipewire-plugin.h', subdir: dir_include_libweston_install) -endif diff --git a/pipewire/pipewire-plugin.c b/pipewire/pipewire-plugin.c deleted file mode 100644 index 6f89257..0000000 --- a/pipewire/pipewire-plugin.c +++ /dev/null @@ -1,855 +0,0 @@ -/* - * Copyright © 2019 Pengutronix, Michael Olbrich - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "pipewire-plugin.h" -#include "backend.h" -#include "libweston-internal.h" -#include "shared/timespec-util.h" -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include - -#define PROP_RANGE(min, max) 2, (min), (max) - -struct type { - struct spa_type_media_type media_type; - struct spa_type_media_subtype media_subtype; - struct spa_type_format_video format_video; - struct spa_type_video_format video_format; -}; - -struct weston_pipewire { - struct weston_compositor *compositor; - struct wl_list output_list; - struct wl_listener destroy_listener; - const struct weston_drm_virtual_output_api *virtual_output_api; - - struct weston_log_scope *debug; - - struct pw_loop *loop; - struct wl_event_source *loop_source; - - struct pw_core *core; - struct pw_type *t; - struct type type; - - struct pw_remote *remote; - struct spa_hook remote_listener; -}; - -struct pipewire_output { - struct weston_output *output; - void (*saved_destroy)(struct weston_output *output); - int (*saved_enable)(struct weston_output *output); - int (*saved_disable)(struct weston_output *output); - int (*saved_start_repaint_loop)(struct weston_output *output); - - struct weston_head *head; - - struct weston_pipewire *pipewire; - - uint32_t seq; - struct pw_stream *stream; - struct spa_hook stream_listener; - - struct spa_video_info_raw video_format; - - struct wl_event_source *finish_frame_timer; - struct wl_list link; - bool submitted_frame; - enum dpms_enum dpms; -}; - -struct pipewire_frame_data { - struct pipewire_output *output; - int fd; - int stride; - struct drm_fb *drm_buffer; - int fence_sync_fd; - struct wl_event_source *fence_sync_event_source; -}; - -static inline void init_type(struct type *type, struct spa_type_map *map) -{ - spa_type_media_type_map(map, &type->media_type); - spa_type_media_subtype_map(map, &type->media_subtype); - spa_type_format_video_map(map, &type->format_video); - spa_type_video_format_map(map, &type->video_format); -} - -static void -pipewire_debug_impl(struct weston_pipewire *pipewire, - struct pipewire_output *output, - const char *fmt, va_list ap) -{ - FILE *fp; - char *logstr; - size_t logsize; - char timestr[128]; - - if (!weston_log_scope_is_enabled(pipewire->debug)) - return; - - fp = open_memstream(&logstr, &logsize); - if (!fp) - return; - - weston_log_scope_timestamp(pipewire->debug, timestr, sizeof timestr); - fprintf(fp, "%s", timestr); - - if (output) - fprintf(fp, "[%s]", output->output->name); - - fprintf(fp, " "); - vfprintf(fp, fmt, ap); - fprintf(fp, "\n"); - - if (fclose(fp) == 0) - weston_log_scope_write(pipewire->debug, logstr, logsize); - - free(logstr); -} - -static void -pipewire_debug(struct weston_pipewire *pipewire, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - pipewire_debug_impl(pipewire, NULL, fmt, ap); - va_end(ap); -} - -static void -pipewire_output_debug(struct pipewire_output *output, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - pipewire_debug_impl(output->pipewire, output, fmt, ap); - va_end(ap); -} - -static struct weston_pipewire * -weston_pipewire_get(struct weston_compositor *compositor); - -static struct pipewire_output * -lookup_pipewire_output(struct weston_output *base_output) -{ - struct weston_compositor *c = base_output->compositor; - struct weston_pipewire *pipewire = weston_pipewire_get(c); - struct pipewire_output *output; - - wl_list_for_each(output, &pipewire->output_list, link) { - if (output->output == base_output) - return output; - } - return NULL; -} - -static void -pipewire_output_handle_frame(struct pipewire_output *output, int fd, - int stride, struct drm_fb *drm_buffer) -{ - const struct weston_drm_virtual_output_api *api = - output->pipewire->virtual_output_api; - size_t size = output->output->height * stride; - struct pw_type *t = output->pipewire->t; - struct pw_buffer *buffer; - struct spa_buffer *spa_buffer; - struct spa_meta_header *h; - void *ptr; - - if (pw_stream_get_state(output->stream, NULL) != - PW_STREAM_STATE_STREAMING) - goto out; - - buffer = pw_stream_dequeue_buffer(output->stream); - if (!buffer) { - weston_log("Failed to dequeue a pipewire buffer\n"); - goto out; - } - - spa_buffer = buffer->buffer; - - if ((h = spa_buffer_find_meta(spa_buffer, t->meta.Header))) { - h->pts = -1; - h->flags = 0; - h->seq = output->seq++; - h->dts_offset = 0; - } - - ptr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); - memcpy(spa_buffer->datas[0].data, ptr, size); - munmap(ptr, size); - - spa_buffer->datas[0].chunk->offset = 0; - spa_buffer->datas[0].chunk->stride = stride; - spa_buffer->datas[0].chunk->size = spa_buffer->datas[0].maxsize; - - pipewire_output_debug(output, "push frame"); - pw_stream_queue_buffer(output->stream, buffer); - -out: - close(fd); - output->submitted_frame = true; - api->buffer_released(drm_buffer); -} - -static int -pipewire_output_fence_sync_handler(int fd, uint32_t mask, void *data) -{ - struct pipewire_frame_data *frame_data = data; - struct pipewire_output *output = frame_data->output; - - pipewire_output_handle_frame(output, frame_data->fd, frame_data->stride, - frame_data->drm_buffer); - - wl_event_source_remove(frame_data->fence_sync_event_source); - close(frame_data->fence_sync_fd); - free(frame_data); - - return 0; -} - -static int -pipewire_output_submit_frame(struct weston_output *base_output, int fd, - int stride, struct drm_fb *drm_buffer) -{ - struct pipewire_output *output = lookup_pipewire_output(base_output); - struct weston_pipewire *pipewire = output->pipewire; - const struct weston_drm_virtual_output_api *api = - pipewire->virtual_output_api; - struct wl_event_loop *loop; - struct pipewire_frame_data *frame_data; - int fence_sync_fd; - - pipewire_output_debug(output, "submit frame: fd = %d drm_fb = %p", - fd, drm_buffer); - - fence_sync_fd = api->get_fence_sync_fd(output->output); - if (fence_sync_fd == -1) { - pipewire_output_handle_frame(output, fd, stride, drm_buffer); - return 0; - } - - frame_data = zalloc(sizeof *frame_data); - if (!frame_data) { - close(fence_sync_fd); - pipewire_output_handle_frame(output, fd, stride, drm_buffer); - return 0; - } - - loop = wl_display_get_event_loop(pipewire->compositor->wl_display); - - frame_data->output = output; - frame_data->fd = fd; - frame_data->stride = stride; - frame_data->drm_buffer = drm_buffer; - frame_data->fence_sync_fd = fence_sync_fd; - frame_data->fence_sync_event_source = - wl_event_loop_add_fd(loop, frame_data->fence_sync_fd, - WL_EVENT_READABLE, - pipewire_output_fence_sync_handler, - frame_data); - - return 0; -} - -static void -pipewire_output_timer_update(struct pipewire_output *output) -{ - int64_t msec; - int32_t refresh; - - if (pw_stream_get_state(output->stream, NULL) == - PW_STREAM_STATE_STREAMING) - refresh = output->output->current_mode->refresh; - else - refresh = 1000; - - msec = millihz_to_nsec(refresh) / 1000000; - wl_event_source_timer_update(output->finish_frame_timer, msec); -} - -static int -pipewire_output_finish_frame_handler(void *data) -{ - struct pipewire_output *output = data; - const struct weston_drm_virtual_output_api *api - = output->pipewire->virtual_output_api; - struct timespec now; - - if (output->submitted_frame) { - struct weston_compositor *c = output->pipewire->compositor; - output->submitted_frame = false; - weston_compositor_read_presentation_clock(c, &now); - api->finish_frame(output->output, &now, 0); - } - - if (output->dpms == WESTON_DPMS_ON) - pipewire_output_timer_update(output); - else - wl_event_source_timer_update(output->finish_frame_timer, 0); - - return 0; -} - -static void -pipewire_output_destroy(struct weston_output *base_output) -{ - struct pipewire_output *output = lookup_pipewire_output(base_output); - struct weston_mode *mode, *next; - - wl_list_for_each_safe(mode, next, &base_output->mode_list, link) { - wl_list_remove(&mode->link); - free(mode); - } - - output->saved_destroy(base_output); - - pw_stream_destroy(output->stream); - - wl_list_remove(&output->link); - weston_head_release(output->head); - free(output->head); - free(output); -} - -static int -pipewire_output_start_repaint_loop(struct weston_output *base_output) -{ - struct pipewire_output *output = lookup_pipewire_output(base_output); - - pipewire_output_debug(output, "start repaint loop"); - output->saved_start_repaint_loop(base_output); - - pipewire_output_timer_update(output); - - return 0; -} - -static void -pipewire_set_dpms(struct weston_output *base_output, enum dpms_enum level) -{ - struct pipewire_output *output = lookup_pipewire_output(base_output); - - if (output->dpms == level) - return; - - output->dpms = level; - pipewire_output_finish_frame_handler(output); -} - -static int -pipewire_output_connect(struct pipewire_output *output) -{ - struct weston_pipewire *pipewire = output->pipewire; - struct type *type = &pipewire->type; - uint8_t buffer[1024]; - struct spa_pod_builder builder = - SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - const struct spa_pod *params[1]; - struct pw_type *t = pipewire->t; - int frame_rate = output->output->current_mode->refresh / 1000; - int width = output->output->width; - int height = output->output->height; - int ret; - - params[0] = spa_pod_builder_object(&builder, - t->param.idEnumFormat, t->spa_format, - "I", type->media_type.video, - "I", type->media_subtype.raw, - ":", type->format_video.format, - "I", type->video_format.BGRx, - ":", type->format_video.size, - "R", &SPA_RECTANGLE(width, height), - ":", type->format_video.framerate, - "F", &SPA_FRACTION(0, 1), - ":", type->format_video.max_framerate, - "Fru", &SPA_FRACTION(frame_rate, 1), - PROP_RANGE(&SPA_FRACTION(1, 1), - &SPA_FRACTION(frame_rate, 1))); - - ret = pw_stream_connect(output->stream, PW_DIRECTION_OUTPUT, NULL, - (PW_STREAM_FLAG_DRIVER | - PW_STREAM_FLAG_MAP_BUFFERS), - params, 1); - if (ret != 0) { - weston_log("Failed to connect pipewire stream: %s", - spa_strerror(ret)); - return -1; - } - - return 0; -} - -static int -pipewire_output_enable(struct weston_output *base_output) -{ - struct pipewire_output *output = lookup_pipewire_output(base_output); - struct weston_compositor *c = base_output->compositor; - const struct weston_drm_virtual_output_api *api - = output->pipewire->virtual_output_api; - struct wl_event_loop *loop; - int ret; - - api->set_submit_frame_cb(base_output, pipewire_output_submit_frame); - - ret = pipewire_output_connect(output); - if (ret < 0) - return ret; - - ret = output->saved_enable(base_output); - if (ret < 0) - return ret; - - output->saved_start_repaint_loop = base_output->start_repaint_loop; - base_output->start_repaint_loop = pipewire_output_start_repaint_loop; - base_output->set_dpms = pipewire_set_dpms; - - loop = wl_display_get_event_loop(c->wl_display); - output->finish_frame_timer = - wl_event_loop_add_timer(loop, - pipewire_output_finish_frame_handler, - output); - output->dpms = WESTON_DPMS_ON; - - return 0; -} - -static int -pipewire_output_disable(struct weston_output *base_output) -{ - struct pipewire_output *output = lookup_pipewire_output(base_output); - - wl_event_source_remove(output->finish_frame_timer); - - pw_stream_disconnect(output->stream); - - return output->saved_disable(base_output); -} - -static void -pipewire_output_stream_state_changed(void *data, enum pw_stream_state old, - enum pw_stream_state state, - const char *error_message) -{ - struct pipewire_output *output = data; - - pipewire_output_debug(output, "state changed %s -> %s", - pw_stream_state_as_string(old), - pw_stream_state_as_string(state)); - - switch (state) { - case PW_STREAM_STATE_STREAMING: - weston_output_schedule_repaint(output->output); - break; - default: - break; - } -} - -static void -pipewire_output_stream_format_changed(void *data, const struct spa_pod *format) -{ - struct pipewire_output *output = data; - struct weston_pipewire *pipewire = output->pipewire; - uint8_t buffer[1024]; - struct spa_pod_builder builder = - SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - const struct spa_pod *params[2]; - struct pw_type *t = pipewire->t; - int32_t width, height, stride, size; - const int bpp = 4; - - if (!format) { - pipewire_output_debug(output, "format = None"); - pw_stream_finish_format(output->stream, 0, NULL, 0); - return; - } - - spa_format_video_raw_parse(format, &output->video_format, - &pipewire->type.format_video); - - width = output->video_format.size.width; - height = output->video_format.size.height; - stride = SPA_ROUND_UP_N(width * bpp, 4); - size = height * stride; - - pipewire_output_debug(output, "format = %dx%d", width, height); - - params[0] = spa_pod_builder_object(&builder, - t->param.idBuffers, t->param_buffers.Buffers, - ":", t->param_buffers.size, - "i", size, - ":", t->param_buffers.stride, - "i", stride, - ":", t->param_buffers.buffers, - "iru", 4, PROP_RANGE(2, 8), - ":", t->param_buffers.align, - "i", 16); - - params[1] = spa_pod_builder_object(&builder, - t->param.idMeta, t->param_meta.Meta, - ":", t->param_meta.type, "I", t->meta.Header, - ":", t->param_meta.size, "i", sizeof(struct spa_meta_header)); - - pw_stream_finish_format(output->stream, 0, params, 2); -} - -static const struct pw_stream_events stream_events = { - PW_VERSION_STREAM_EVENTS, - .state_changed = pipewire_output_stream_state_changed, - .format_changed = pipewire_output_stream_format_changed, -}; - -static struct weston_output * -pipewire_output_create(struct weston_compositor *c, char *name) -{ - struct weston_pipewire *pipewire = weston_pipewire_get(c); - struct pipewire_output *output; - struct weston_head *head; - const struct weston_drm_virtual_output_api *api; - const char *make = "Weston"; - const char *model = "Virtual Display"; - const char *serial_number = "unknown"; - const char *connector_name = "pipewire"; - - if (!name || !strlen(name)) - return NULL; - - api = pipewire->virtual_output_api; - - output = zalloc(sizeof *output); - if (!output) - return NULL; - - head = zalloc(sizeof *head); - if (!head) - goto err; - - output->stream = pw_stream_new(pipewire->remote, name, NULL); - if (!output->stream) { - weston_log("Cannot initialize pipewire stream\n"); - goto err; - } - - pw_stream_add_listener(output->stream, &output->stream_listener, - &stream_events, output); - - output->output = api->create_output(c, name); - if (!output->output) { - weston_log("Cannot create virtual output\n"); - goto err; - } - - output->saved_destroy = output->output->destroy; - output->output->destroy = pipewire_output_destroy; - output->saved_enable = output->output->enable; - output->output->enable = pipewire_output_enable; - output->saved_disable = output->output->disable; - output->output->disable = pipewire_output_disable; - output->pipewire = pipewire; - wl_list_insert(pipewire->output_list.prev, &output->link); - - weston_head_init(head, connector_name); - weston_head_set_subpixel(head, WL_OUTPUT_SUBPIXEL_NONE); - weston_head_set_monitor_strings(head, make, model, serial_number); - head->compositor = c; - output->head = head; - - weston_output_attach_head(output->output, head); - - pipewire_output_debug(output, "created"); - - return output->output; -err: - if (output->stream) - pw_stream_destroy(output->stream); - if (head) - free(head); - free(output); - return NULL; -} - -static bool -pipewire_output_is_pipewire(struct weston_output *output) -{ - return lookup_pipewire_output(output) != NULL; -} - -static int -pipewire_output_set_mode(struct weston_output *base_output, const char *modeline) -{ - struct pipewire_output *output = lookup_pipewire_output(base_output); - const struct weston_drm_virtual_output_api *api = - output->pipewire->virtual_output_api; - struct weston_mode *mode; - int n, width, height, refresh = 0; - - if (output == NULL) { - weston_log("Output is not pipewire.\n"); - return -1; - } - - if (!modeline) - return -1; - - n = sscanf(modeline, "%dx%d@%d", &width, &height, &refresh); - if (n != 2 && n != 3) - return -1; - - if (pw_stream_get_state(output->stream, NULL) != - PW_STREAM_STATE_UNCONNECTED) { - return -1; - } - - mode = zalloc(sizeof *mode); - if (!mode) - return -1; - - pipewire_output_debug(output, "mode = %dx%d@%d", width, height, refresh); - - mode->flags = WL_OUTPUT_MODE_CURRENT; - mode->width = width; - mode->height = height; - mode->refresh = (refresh ? refresh : 60) * 1000LL; - - wl_list_insert(base_output->mode_list.prev, &mode->link); - - base_output->current_mode = mode; - - api->set_gbm_format(base_output, "XRGB8888"); - - return 0; -} - -static void -pipewire_output_set_seat(struct weston_output *output, const char *seat) -{ -} - -static void -weston_pipewire_destroy(struct wl_listener *l, void *data) -{ - struct weston_pipewire *pipewire = - wl_container_of(l, pipewire, destroy_listener); - - weston_log_scope_destroy(pipewire->debug); - pipewire->debug = NULL; - - wl_event_source_remove(pipewire->loop_source); - pw_loop_leave(pipewire->loop); - pw_loop_destroy(pipewire->loop); -} - -static struct weston_pipewire * -weston_pipewire_get(struct weston_compositor *compositor) -{ - struct wl_listener *listener; - struct weston_pipewire *pipewire; - - listener = wl_signal_get(&compositor->destroy_signal, - weston_pipewire_destroy); - if (!listener) - return NULL; - - pipewire = wl_container_of(listener, pipewire, destroy_listener); - return pipewire; -} - -static int -weston_pipewire_loop_handler(int fd, uint32_t mask, void *data) -{ - struct weston_pipewire *pipewire = data; - int ret; - - ret = pw_loop_iterate(pipewire->loop, 0); - if (ret < 0) - weston_log("pipewire_loop_iterate failed: %s", - spa_strerror(ret)); - - return 0; -} - -static void -weston_pipewire_state_changed(void *data, enum pw_remote_state old, - enum pw_remote_state state, const char *error) -{ - struct weston_pipewire *pipewire = data; - - pipewire_debug(pipewire, "[remote] state changed %s -> %s", - pw_remote_state_as_string(old), - pw_remote_state_as_string(state)); - - switch (state) { - case PW_REMOTE_STATE_ERROR: - weston_log("pipewire remote error: %s\n", error); - break; - case PW_REMOTE_STATE_CONNECTED: - weston_log("connected to pipewire daemon\n"); - break; - default: - break; - } -} - - -static const struct pw_remote_events remote_events = { - PW_VERSION_REMOTE_EVENTS, - .state_changed = weston_pipewire_state_changed, -}; - -static int -weston_pipewire_init(struct weston_pipewire *pipewire) -{ - struct wl_event_loop *loop; - - pw_init(NULL, NULL); - - pipewire->loop = pw_loop_new(NULL); - if (!pipewire->loop) - return -1; - - pw_loop_enter(pipewire->loop); - - pipewire->core = pw_core_new(pipewire->loop, NULL); - pipewire->t = pw_core_get_type(pipewire->core); - init_type(&pipewire->type, pipewire->t->map); - - pipewire->remote = pw_remote_new(pipewire->core, NULL, 0); - pw_remote_add_listener(pipewire->remote, - &pipewire->remote_listener, - &remote_events, pipewire); - - pw_remote_connect(pipewire->remote); - - while (true) { - enum pw_remote_state state; - const char *error = NULL; - int ret; - - state = pw_remote_get_state(pipewire->remote, &error); - if (state == PW_REMOTE_STATE_CONNECTED) - break; - - if (state == PW_REMOTE_STATE_ERROR) { - weston_log("pipewire error: %s\n", error); - goto err; - } - - ret = pw_loop_iterate(pipewire->loop, -1); - if (ret < 0) { - weston_log("pipewire_loop_iterate failed: %s", - spa_strerror(ret)); - goto err; - } - } - - loop = wl_display_get_event_loop(pipewire->compositor->wl_display); - pipewire->loop_source = - wl_event_loop_add_fd(loop, pw_loop_get_fd(pipewire->loop), - WL_EVENT_READABLE, - weston_pipewire_loop_handler, - pipewire); - - return 0; -err: - if (pipewire->remote) - pw_remote_destroy(pipewire->remote); - pw_loop_leave(pipewire->loop); - pw_loop_destroy(pipewire->loop); - return -1; -} - -static const struct weston_pipewire_api pipewire_api = { - pipewire_output_create, - pipewire_output_is_pipewire, - pipewire_output_set_mode, - pipewire_output_set_seat, -}; - -WL_EXPORT int -weston_module_init(struct weston_compositor *compositor) -{ - int ret; - struct weston_pipewire *pipewire; - const struct weston_drm_virtual_output_api *api = - weston_drm_virtual_output_get_api(compositor); - - if (!api) - return -1; - - pipewire = zalloc(sizeof *pipewire); - if (!pipewire) - return -1; - - if (!weston_compositor_add_destroy_listener_once(compositor, - &pipewire->destroy_listener, - weston_pipewire_destroy)) { - free(pipewire); - return 0; - } - - pipewire->virtual_output_api = api; - pipewire->compositor = compositor; - wl_list_init(&pipewire->output_list); - - ret = weston_plugin_api_register(compositor, WESTON_PIPEWIRE_API_NAME, - &pipewire_api, sizeof(pipewire_api)); - - if (ret < 0) { - weston_log("Failed to register pipewire API.\n"); - goto failed; - } - - ret = weston_pipewire_init(pipewire); - if (ret < 0) { - weston_log("Failed to initialize pipewire.\n"); - goto failed; - } - - pipewire->debug = - weston_compositor_add_log_scope(compositor, "pipewire", - "Debug messages from pipewire plugin\n", - NULL, NULL, NULL); - - return 0; - -failed: - wl_list_remove(&pipewire->destroy_listener.link); - free(pipewire); - return -1; -} diff --git a/pipewire/pipewire-plugin.h b/pipewire/pipewire-plugin.h deleted file mode 100644 index 88f728a..0000000 --- a/pipewire/pipewire-plugin.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright © 2019 Pengutronix, Michael Olbrich - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef PIPEWIRE_PLUGIN_H -#define PIPEWIRE_PLUGIN_H - -#include -#include - -#define WESTON_PIPEWIRE_API_NAME "weston_pipewire_api_v1" - -struct weston_pipewire_api { - /** Create pipewire outputs - * - * Returns 0 on success, -1 on failure. - */ - struct weston_output *(*create_output)(struct weston_compositor *c, - char *name); - - /** Check if output is pipewire */ - bool (*is_pipewire_output)(struct weston_output *output); - - /** Set mode */ - int (*set_mode)(struct weston_output *output, const char *modeline); - - /** Set seat */ - void (*set_seat)(struct weston_output *output, const char *seat); -}; - -static inline const struct weston_pipewire_api * -weston_pipewire_get_api(struct weston_compositor *compositor) -{ - const void *api; - api = weston_plugin_api_get(compositor, WESTON_PIPEWIRE_API_NAME, - sizeof(struct weston_pipewire_api)); - - return (const struct weston_pipewire_api *)api; -} - -#endif /* PIPEWIRE_PLUGIN_H */ diff --git a/protocol/drm-auth.xml b/protocol/drm-auth.xml deleted file mode 100755 index f302105..0000000 --- a/protocol/drm-auth.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - Copyright (c) 2021 Huawei Device Co., Ltd. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/protocol/ivi-application.xml b/protocol/ivi-application.xml deleted file mode 100644 index f51b7f4..0000000 --- a/protocol/ivi-application.xml +++ /dev/null @@ -1,100 +0,0 @@ - - - - - Copyright (C) 2013 DENSO CORPORATION - Copyright (c) 2013 BMW Car IT GmbH - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice (including the next - paragraph) shall be included in all copies or substantial portions of the - Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - - - - - - - - This removes the link from ivi_id to wl_surface and destroys ivi_surface. - The ID, ivi_id, is free and can be used for surface_create again. - - - - - - The configure event asks the client to resize its surface. - - The size is a hint, in the sense that the client is free to - ignore it if it doesn't resize, pick a smaller size (to - satisfy aspect ratio or resize in steps of NxM pixels). - - The client is free to dismiss all but the last configure - event it received. - - The width and height arguments specify the size of the window - in surface-local coordinates. - - - - - - - - - This interface is exposed as a global singleton. - This interface is implemented by servers that provide IVI-style user interfaces. - It allows clients to associate an ivi_surface with wl_surface. - - - - - - - - - - This request gives the wl_surface the role of an IVI Surface. Creating more than - one ivi_surface for a wl_surface is not allowed. Note, that this still allows the - following example: - - 1. create a wl_surface - 2. create ivi_surface for the wl_surface - 3. destroy the ivi_surface - 4. create ivi_surface for the wl_surface (with the same or another ivi_id as before) - - surface_create will create an interface:ivi_surface with numeric ID; ivi_id in - ivi compositor. These ivi_ids are defined as unique in the system to identify - it inside of ivi compositor. The ivi compositor implements business logic how to - set properties of the surface with ivi_id according to the status of the system. - E.g. a unique ID for Car Navigation application is used for implementing special - logic of the application about where it shall be located. - The server regards the following cases as protocol errors and disconnects the client. - - wl_surface already has another role. - - ivi_id is already assigned to another wl_surface. - - If client destroys ivi_surface or wl_surface which is assigne to the ivi_surface, - ivi_id which is assigned to the ivi_surface is free for reuse. - - - - - - - - - diff --git a/protocol/ivi-hmi-controller.xml b/protocol/ivi-hmi-controller.xml deleted file mode 100644 index 2015a38..0000000 --- a/protocol/ivi-hmi-controller.xml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - Copyright (C) 2013 DENSO CORPORATION - Copyright (c) 2013 BMW Car IT GmbH - - 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. - - - - - - - - - - - - Reference protocol to control a surface by server. - To control a surface by server, it gives seat to the server - to e.g. control Home screen. Home screen has several workspaces - to group launchers of wayland application. These workspaces - are drawn on a horizontally long surface to be controlled - by motion of input device. E.g. A motion from right to left - happens, the viewport of surface is controlled in the ivi-shell - by using ivi-layout. client can recognizes the end of controlling - by event "workspace_end_control". - - - - - - - - - - - - - - - hmi-controller loaded to ivi-shall implements 4 types of layout - as a reference; tiling, side by side, full_screen, and random. - - - - - - - - - - - - home screen is a reference implementation of launcher to launch - wayland applications. The home screen has several workspaces to - group wayland applications. By defining the following keys in - weston.ini, user can add launcher icon to launch a wayland application - to a workspace. - [ivi-launcher] - workspace-id=0 - : id of workspace to add a launcher - icon-id=4001 - : ivi id of ivi_surface to draw an icon - icon=/home/user/review/build-ivi-shell/data/icon_ivi_flower.png - : path to icon image - path=/home/user/review/build-ivi-shell/weston-dnd - : path to wayland application - - - - - - - - - - - - diff --git a/protocol/meson.build b/protocol/meson.build deleted file mode 100644 index 25cea5a..0000000 --- a/protocol/meson.build +++ /dev/null @@ -1,77 +0,0 @@ -dep_scanner = dependency('wayland-scanner', native: true) -prog_scanner = find_program(dep_scanner.get_pkgconfig_variable('wayland_scanner')) - -dep_wp = dependency('wayland-protocols', version: '>= 1.18') -dir_wp_base = dep_wp.get_pkgconfig_variable('pkgdatadir') - -install_data( - [ - 'weston-debug.xml', - 'weston-direct-display.xml', - ], - install_dir: join_paths(dir_data, dir_protocol_libweston) -) - -generated_protocols = [ - [ 'input-method', 'v1' ], - [ 'input-timestamps', 'v1' ], - [ 'ivi-application', 'internal' ], - [ 'ivi-hmi-controller', 'internal' ], - [ 'fullscreen-shell', 'v1' ], - [ 'linux-dmabuf', 'v1' ], - [ 'linux-explicit-synchronization', 'v1' ], - [ 'presentation-time', 'stable' ], - [ 'pointer-constraints', 'v1' ], - [ 'relative-pointer', 'v1' ], - [ 'tablet', 'v2' ], - [ 'text-cursor-position', 'internal' ], - [ 'text-input', 'v1' ], - [ 'viewporter', 'stable' ], - [ 'weston-debug', 'internal' ], - [ 'weston-desktop-shell', 'internal' ], - [ 'weston-screenshooter', 'internal' ], - [ 'weston-content-protection', 'internal' ], - [ 'weston-test', 'internal' ], - [ 'weston-touch-calibration', 'internal' ], - [ 'weston-direct-display', 'internal' ], - [ 'xdg-output', 'v1' ], - [ 'xdg-shell', 'v6' ], - [ 'xdg-shell', 'stable' ], -] - -foreach proto: generated_protocols - proto_name = proto[0] - if proto[1] == 'internal' - base_file = proto_name - xml_path = '@0@.xml'.format(proto_name) - elif proto[1] == 'stable' - base_file = proto_name - xml_path = '@0@/stable/@1@/@1@.xml'.format(dir_wp_base, base_file) - else - base_file = '@0@-unstable-@1@'.format(proto_name, proto[1]) - xml_path = '@0@/unstable/@1@/@2@.xml'.format(dir_wp_base, proto_name, base_file) - endif - - foreach output_type: [ 'client-header', 'server-header', 'private-code' ] - if output_type == 'client-header' - output_file = '@0@-client-protocol.h'.format(base_file) - elif output_type == 'server-header' - output_file = '@0@-server-protocol.h'.format(base_file) - else - output_file = '@0@-protocol.c'.format(base_file) - if dep_scanner.version().version_compare('< 1.14.91') - output_type = 'code' - endif - endif - - var_name = output_file.underscorify() - target = custom_target( - '@0@ @1@'.format(base_file, output_type), - command: [ prog_scanner, output_type, '@INPUT@', '@OUTPUT@' ], - input: xml_path, - output: output_file, - ) - - set_variable(var_name, target) - endforeach -endforeach diff --git a/protocol/text-cursor-position.xml b/protocol/text-cursor-position.xml deleted file mode 100644 index 0fbc54e..0000000 --- a/protocol/text-cursor-position.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/protocol/weston-content-protection.xml b/protocol/weston-content-protection.xml deleted file mode 100644 index 6fdbb95..0000000 --- a/protocol/weston-content-protection.xml +++ /dev/null @@ -1,251 +0,0 @@ - - - - - Copyright 2016 The Chromium Authors. - Copyright 2018-2019 Collabora, Ltd. - Copyright © 2018-2019 Intel Corporation. - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice (including the next - paragraph) shall be included in all copies or substantial portions of the - Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - - - - This protocol specifies a set of interfaces used to provide - content-protection for e.g. HDCP, and protect surface contents on the - secured outputs and prevent from appearing in screenshots or from being - visible on non-secure outputs. - - A secure-output is defined as an output that is secured by some - content-protection mechanism e.g. HDCP, and meets at least the minimum - required content-protection level requested by a client. - - The term content-protection is defined in terms of HDCP type 0 and - HDCP type 1, but this may be extended in future. - - This protocol is not intended for implementing Digital Rights Management on - general (e.g. Desktop) systems, and would only be useful for closed systems. - As the server is the one responsible for implementing - the content-protection, the client can only trust the content-protection as - much they can trust the server. - - In order to protect the content and prevent surface contents from appearing - in screenshots or from being visible on non-secure outputs, a client must - first bind the global interface "weston_content_protection" which, if a - compositor supports secure output, is exposed by the registry. - Using the bound global object, the client uses the "get_protection" request - to instantiate an interface extension for a wl_surface object. - This extended interface will then allow surfaces to request for - content-protection, and also to censor the visibility of the surface on - non-secure outputs. Client applications should not wait for the protection - to change, as it might never change in case the content-protection cannot be - achieved. Alternatively, clients can use a timeout and start showing the - content in lower quality. - - Censored visibility is defined as the compositor censoring the protected - content on non-secure outputs. Censoring may include artificially reducing - image quality or replacing the protected content completely with - placeholder graphics. - - Censored visibility is controlled by protection mode, set by the client. - In "relax" mode, the compositor may show protected content on non-secure - outputs. It will be up to the client to adapt to secure and non-secure - presentation. In "enforce" mode, the compositor will censor the parts of - protected content that would otherwise show on non-secure outputs. - - - - - The global interface weston_content_protection is used for exposing the - content protection capabilities to a client. It provides a way for clients - to request their wl_surface contents to not be displayed on an output - below their required level of content-protection. - Using this interface clients can request for a weston_protected_surface - which is an extension to the wl_surface to provide content-protection, and - set the censored-visibility on the non-secured-outputs. - - - - - Informs the server that the client will not be using this - protocol object anymore. This does not affect any other objects, - protected_surface objects included. - - - - - - - - - - Instantiate an interface extension for the given wl_surface to - provide surface protection. If the given wl_surface already has - a weston_protected_surface associated, the surface_exists protocol - error is raised. - - - - - - - - - An additional interface to a wl_surface object, which allows a client to - request the minimum level of content-protection, request to change the - visibility of their contents, and receive notifications about changes in - content-protection. - - A protected surface has a 'status' associated with it, that indicates - what type of protection it is currently providing, specified by - content-type. Updates to this status are sent to the client - via the 'status' event. Before the first status event is sent, the client - should assume that the status is 'unprotected'. - - A client can request a content protection level to be the minimum for an - output to be considered secure, using the 'set_type' request. - It is responsibility of the client to monitor the actual - content-protection level achieved via the 'status' event, and make - decisions as to what content to show based on this. - - The server should make its best effort to achieve the desired - content-protection level on all of the outputs the client's contents are - being displayed on. Any changes to the content protection status should be - reported to the client, even if they are below the requested - content-protection level. If the client's contents are being displayed on - multiple outputs, the lowest content protection level achieved should be - reported. - - A client can also request that its content only be displayed on outputs - that are considered secure. The 'enforce/relax' requests can achieve this. - In enforce mode, the content is censored for non-secure outputs. - The implementation of censored-visibility is compositor-defined. - In relax mode there are no such limitation. On an attempt to show the - client on unsecured output, compositor would keep on showing the content - and send the 'status' event to the client. Client can take a call to - downgrade the content. - - If the wl_surface associated with the protected_surface is destroyed, - the protected_surface becomes inert. - - - - - - - - - Description of a particular type of content protection. - - A server may not necessarily support all of these types. - - Note that there is no ordering between enum members unless specified. - Over time, different types of content protection may be added, which - may be considered less secure than what is already here. - - - - - - - - - If the protected_surface is destroyed, the wl_surface desired protection - level returns to unprotected, as if set_type request was sent with type - as 'unprotected'. - - - - - - Informs the server about the type of content. The level of - content-protection depends upon the content-type set by the client - through this request. Initially, this is set to 'unprotected'. - - If the requested value is not a valid content_type enum value, the - 'invalid_type' protocol error is raised. It is not an error to request - a valid protection type the compositor does not implement or cannot - achieve. - - The requested content protection is double-buffered, see - wl_surface.commit. - - - - - - - Censor the visibility of the wl_surface contents on non-secure outputs. - See weston_protected_surface for the description. - - The force constrain mode is double-buffered, see wl_surface.commit - - - - - - Do not enforce censored-visibility of the wl_surface contents on - non-secure-outputs. See weston_protected_surface for the description. - - The relax mode is selected by default, if no explicit request is made - for enforcing the censored-visibility. - - The relax mode is double-buffered, see wl_surface.commit - - - - - - This event is sent to the client to inform about the actual protection - level for its surface in the relax mode. - - The 'type' argument indicates what that current level of content - protection that the server has currently established. - - The 'status' event is first sent, when a weston_protected_surface is - created. - - Until this event is sent for the first time, the client should assume - that its contents are not secure, and the type is 'unprotected'. - - Possible reasons the content protection status can change is due to - change in censored-visibility mode from enforced to relaxed, a new - connector being added, movement of window to another output, or, - the client attaching a buffer too large for what the server may secure. - However, it is not limited to these reasons. - - A client may want to listen to this event and lower the resolution of - their content until it can successfully be shown securely. - - In case of "enforce" mode, the client will not get any status event. - If the mode is then changed to "relax", the client will receive the - status event. - - - - - - diff --git a/protocol/weston-debug.xml b/protocol/weston-debug.xml deleted file mode 100644 index ac62661..0000000 --- a/protocol/weston-debug.xml +++ /dev/null @@ -1,139 +0,0 @@ - - - - - Copyright © 2017 Pekka Paalanen pq@iki.fi - Copyright © 2018 Zodiac Inflight Innovations - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice (including the next - paragraph) shall be included in all copies or substantial portions of the - Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - - - - - This is a generic debugging interface for Weston internals, the global - object advertized through wl_registry. - - WARNING: This interface by design allows a denial-of-service attack. It - should not be offered in production, or proper authorization mechanisms - must be enforced. - - The idea is for a client to provide a file descriptor that the server - uses for printing debug information. The server uses the file - descriptor in blocking writes mode, which exposes the denial-of-service - risk. The blocking mode is necessary to ensure all debug messages can - be easily printed in place. It also ensures message ordering if a - client subscribes to more than one debug stream. - - The available debugging features depend on the server. - - A debug stream can be one-shot where the server prints the requested - information and then closes it, or continuous where server keeps on - printing until the client stops it. Or anything in between. - - - - - Destroys the factory object, but does not affect any other objects. - - - - - - Advertises an available debug scope which the client may be able to - bind to. No information is provided by the server about the content - contained within the debug streams provided by the scope, once a - client has subscribed. - - - - - - - - - Subscribe to a named debug stream. The server will start printing - to the given file descriptor. - - If the named debug stream is a one-shot dump, the server will send - weston_debug_stream_v1.complete event once all requested data has - been printed. Otherwise, the server will continue streaming debug - prints until the subscription object is destroyed. - - If the debug stream name is unknown to the server, the server will - immediately respond with weston_debug_stream_v1.failure event. - - - - - - - - - - - Represents one subscribed debug stream, created with - weston_debug_v1.subscribe. When the object is created, it is associated - with a given file descriptor. The server will continue writing to the - file descriptor until the object is destroyed or the server sends an - event through the object. - - - - - Destroys the object, which causes the server to stop writing into - and closes the associated file descriptor if it was not closed - already. - - Use a wl_display.sync if the clients needs to guarantee the file - descriptor is closed before continuing. - - - - - - The server has successfully finished writing to and has closed the - associated file descriptor. - - This event is delivered only for one-shot debug streams where the - server dumps some data and stop. This is never delivered for - continuous debbug streams because they by definition never complete. - - - - - - The server has stopped writing to and has closed the - associated file descriptor. The data already written to the file - descriptor is correct, but it may be truncated. - - This event may be delivered at any time and for any kind of debug - stream. It may be due to a failure in or shutdown of the server. - The message argument may provide a hint of the reason. - - - - - - diff --git a/protocol/weston-desktop-shell.xml b/protocol/weston-desktop-shell.xml deleted file mode 100644 index 91c5eb4..0000000 --- a/protocol/weston-desktop-shell.xml +++ /dev/null @@ -1,135 +0,0 @@ - - - - - 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 client 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 client 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. - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 which side of the screen the panel is - located. This is so that new windows do not overlap the panel - and maximized windows maximize properly. - - - - - - - - - - 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/weston-direct-display.xml b/protocol/weston-direct-display.xml deleted file mode 100644 index 274aadd..0000000 --- a/protocol/weston-direct-display.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - Copyright © 2019 Collabora Ltd. - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice (including the next - paragraph) shall be included in all copies or substantial portions of the - Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - - - - - Weston extension to instruct the compositor to avoid any import - of the dmabuf created by 'linux-dmabuf' protocol other than the display - controller. - - Compositors are already going to use direct scan-out as much as possible but - there's no assurance that while doing so, they won't first import the dmabuf - in to the GPU. This extension assures the client that the compositor will - never attempt to import in to the GPU and pass it directly to the display - controller. - - Clients can make use of this extension to pass the dmabuf buffer to the - display controller, potentially increasing the performance and lowering the - bandwidth usage. - - Lastly, clients can make use of this extension in tandem with content-protection - one thus avoiding any GPU interaction and providing a secure-content path. - Also, in some cases, the memory where dmabuf are allocated are in specially - crafted memory zone which would be seen as an illegal memory access when the - GPU will attempt to read it. - - WARNING: This interface by design might break screenshoting functionality - as compositing might be involved while doing that. Also, do note, that in - case the dmabufer provided can't be imported by KMS, the client connection - will be terminated. - - WARNING: This extension requires 'linux-dmabuf' protocol and - 'zwp_linux_buffer_params_v1' be already created by 'zwp_linux_buffer_v1'. - - - - - This request tells the compositor not to import the dmabuf to the GPU - in order to bypass it entirely, such that the buffer will be directly - scanned-out by the display controller. If HW is not capable/or there - aren't any available resources to directly scan-out the buffer, a - placeholder should be installed in-place by the compositor. The - compositor may perform checks on the dmabuf and refuse to create a - wl_buffer if the dmabuf seems unusable for being used directly. - - Assumes that 'zwp_linux_buffer_params_v1' was already created - by 'zwp_linux_dmabuf_v1_create_params'. - - - - - - - Destroys the factory object, but does not affect any other objects. - - - - - diff --git a/protocol/weston-screenshooter.xml b/protocol/weston-screenshooter.xml deleted file mode 100644 index 8c4486c..0000000 --- a/protocol/weston-screenshooter.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/protocol/weston-test.xml b/protocol/weston-test.xml deleted file mode 100644 index 00b7185..0000000 --- a/protocol/weston-test.xml +++ /dev/null @@ -1,144 +0,0 @@ - - - - - Copyright © 2012 Intel Corporation - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice (including the next - paragraph) shall be included in all copies or substantial portions of the - Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - - - - - Internal testing facilities for the weston compositor. - - It can't be stressed enough that these should never ever be used - outside of running weston's tests. The weston-test.so module should - never be installed. - - These requests may allow clients to do very bad things. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Records an image of what is currently displayed on a given - display output, returning the image as an event. - - - - - - - The capture_screenshot_done signal is sent when a screenshot has been copied into the - provided buffer. - - - - - - - - - - - - - - - - This is a global singleton interface for Weston internal tests. - - This interface allows a test client to trigger compositor-side - test procedures. This is useful for cases, where the actual tests - are in compositor plugins, but they also require the presence of - a particular client. - - This interface is implemented by the compositor plugins containing - the testing code. - - A test client starts a test with the "run" request. It must not send - another "run" request until receiving the "finished" event. If the - compositor-side test succeeds, the "finished" event is sent. If the - compositor-side test fails, the compositor should send the protocol - error "test_failed", but it may also exit with an error (e.g. SEGV). - - Unknown test name will raise "unknown_test" protocol error. - - - - - - - - - - - - - - - - diff --git a/protocol/weston-touch-calibration.xml b/protocol/weston-touch-calibration.xml deleted file mode 100644 index 7e898c0..0000000 --- a/protocol/weston-touch-calibration.xml +++ /dev/null @@ -1,342 +0,0 @@ - - - - - Copyright 2017-2018 Collabora, Ltd. - Copyright 2017-2018 General Electric Company - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice (including the next - paragraph) shall be included in all copies or substantial portions of the - Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - - - - - This is the global interface for calibrating a touchscreen input - coordinate transformation. It is recommended to make this interface - privileged. - - This interface can be used by a client to show a calibration pattern and - receive uncalibrated touch coordinates, facilitating the computation of - a calibration transformation that will align actual touch positions - on screen with their expected coordinates. - - Immediately after being bound by a client, the compositor sends the - touch_device events. - - The client chooses a touch device from the touch_device events, creates a - wl_surface and then a weston_touch_calibrator for the wl_surface and the - chosen touch device. The client waits for the compositor to send a - configure event before it starts drawing the first calibration pattern. - After receiving the configure event, the client will iterate drawing a - pattern, getting touch input via weston_touch_calibrator, and converting - pixel coordinates to expected touch coordinates with - weston_touch_calibrator.convert until it has enough correspondences to - compute the calibration transformation or the compositor cancels the - calibration. - - Once the client has successfully computed a new calibration, it can use - weston_touch_calibration.save request to load the new calibration into - the compositor. The compositor may take this new calibration into use and - may write it into persistent storage. - - - - - - - - - - - - Destroy the binding to the global interface, does not affect any - objects already created through this interface. - - - - - - This gives the calibrator role to the surface and ties it with the - given touch input device. - - If the surface already has a role, then invalid_surface error is raised. - - If the device string is not one advertised with touch_device event's - device argument, then invalid_device error is raised. - - If a weston_touch_calibrator protocol object exists in the compositor - already, then already_exists error is raised. This limitation is - compositor-wide and not specific to any particular client. - - - - - - - - - This request asks the compositor to save the calibration data for the - given touch input device. The compositor may ignore this request. - - If the device string is not one advertised with touch_device event's - device argument, then invalid_device error is raised. - - The array must contain exactly six 'float' (the 32-bit floating - point format used by the C language on the system) numbers. For a 3x3 - calibration matrix in the form - @code - ( a b c ) - ( d e f ) - ( 0 0 1 ) - @endcode - the array must contain the values { a, b, c, d, e, f }. For the - definition of the coordinate spaces, see - libinput_device_config_calibration_set_matrix(). - - - - - - - - When a client binds to weston_touch_calibration, one touch_device event - is sent for each touchscreen that is available to be calibrated. This - is the only time the event is sent. Touch devices added in the - compositor will not generate events for existing - weston_touch_calibration objects. - - An event carries the touch device identification and the associated - output or head (display connector) name. - - On platforms using udev, the device identification is the udev sys - path. It is an absolute path and starts with the sys mount point. - - - - - - - - - On creation, this object is tied to a specific touch device. The - compositor sends a configure event which the client must obey with the - associated wl_surface. - - Once the client has committed content to the surface, the compositor can - grab the touch input device, prevent it from emitting normal touch - events, show the surface on the correct output, and relay input events - from the touch device via this protocol object. - - Touch events from other touch devices than the one tied to this object - must generate wrong_touch events on at least touch-down and must not - generate normal or calibration touch events. - - At any time, the compositor can choose to cancel the calibration - procedure by sending the cancel_calibration event. This should also be - used if the touch device disappears or anything else prevents the - calibration from continuing on the compositor side. - - If the wl_surface is destroyed, the compositor must cancel the - calibration. - - The touch event coordinates and conversion results are delivered in - calibration units. The calibration units cover the device coordinate - range exactly. Calibration units are in the closed interval [0.0, 1.0] - mapped into 32-bit unsigned integers. An integer can be converted into a - real value by dividing by 2^32-1. A calibration matrix must be computed - from the [0.0, 1.0] real values, but the matrix elements do not need to - fall into that range. - - - - - - - - - - - - This unmaps the surface if it was mapped. The input device grab - is dropped, if it was present. The surface loses its role as a - calibrator. - - - - - - This request asks the compositor to convert the surface-local - coordinates into the expected touch input coordinates appropriate for - the associated touch device. The intention is that a client uses this - request to convert marker positions that the user is supposed to touch - during calibration. - - If the compositor has cancelled the calibration, the conversion result - shall be zeroes and no errors will be raised. - - The coordinates given as arguments to this request are relative to - the associated wl_surface. - - If a client asks for conversion before it has committed valid - content to the wl_surface, the not_mapped error is raised. - - If the coordinates x, y are outside of the wl_surface content, the - bad_coordinates error is raised. - - - - - - - - - This event tells the client what size to make the surface. The client - must obey the size exactly on the next commit with a wl_buffer. - - This event shall be sent once as a response to creating a - weston_touch_calibrator object. - - - - - - - - This is sent when the compositor wants to cancel the calibration and - drop the touch device grab. The compositor unmaps the surface, if it - was mapped. - - The weston_touch_calibrator object will not send any more events. The - client should destroy it. - - - - - - For whatever reason, a touch event resulting from a user action cannot - be used for calibration. The client should show feedback to the user - that the touch was rejected. - - Possible causes for this event include the user touching a wrong - touchscreen when there are multiple ones present. This is particularly - useful when the touchscreens are cloned and there is no other way to - identify which screen the user should be touching. - - Another cause could be a touch device that sends coordinates beyond its - declared range. If motion takes a touch point outside the range, the - compositor should also send 'cancel' event to undo the touch-down. - - - - - - - A new touch point has appeared on the surface. This touch point is - assigned a unique ID. Future events from this touch point reference - this ID. The ID ceases to be valid after a touch up event and may be - reused in the future. - - For the coordinate units, see weston_touch_calibrator. - - - - - - - - - - The touch point has disappeared. No further events will be sent for - this touch point and the touch point's ID is released and may be - reused in a future touch down event. - - - - - - - - A touch point has changed coordinates. - - For the coordinate units, see weston_touch_calibrator. - - - - - - - - - - Indicates the end of a set of events that logically belong together. - A client is expected to accumulate the data in all events within the - frame before proceeding. - - A wl_touch.frame terminates at least one event but otherwise no - guarantee is provided about the set of events within a frame. A client - must assume that any state not updated in a frame is unchanged from the - previously known state. - - - - - - Sent if the compositor decides the touch stream is a global - gesture. No further events are sent to the clients from that - particular gesture. Touch cancellation applies to all touch points - currently active on this client's surface. The client is - responsible for finalizing the touch points, future touch points on - this surface may reuse the touch point ID. - - - - - - - - - - - - - - This event returns the conversion result from surface coordinates to - the expected touch device coordinates. - - For details, see weston_touch_calibrator.convert. For the coordinate - units, see weston_touch_calibrator. - - This event destroys the weston_touch_coordinate object. - - - - - - diff --git a/remoting/README b/remoting/README deleted file mode 100644 index a5e8ea5..0000000 --- a/remoting/README +++ /dev/null @@ -1,28 +0,0 @@ - Remoting plugin for Weston - - -The Remoting plugin creates a streaming image of a virtual output and transmits -it to a remote host. It is currently only supported on the drm-backend. Virtual -outputs are created and configured by adding a remote-output section to -weston.ini. See man weston-drm(7) for configuration details. This plugin is -loaded automatically if any remote-output sections are present. - -This plugin sends motion jpeg images to a client via RTP using gstreamer, and -so requires gstreamer-1.0. This plugin starts sending images immediately when -weston is run, and keeps sending them until weston shuts down. The image stream -can be received by any appropriately configured RTP client, but a sample -gstreamer RTP client script can be found at doc/scripts/remoting-client-receive.bash. - -Script usage: - remoting-client-receive.bash - - -How to compile ---------------- -Set --enable-remoting=true when configuring weston. The remoting-plugin.so -module is created and installed in the libweston path. - - -How to configure weston.ini ----------------------------- -See man weston-drm(7). diff --git a/remoting/meson.build b/remoting/meson.build deleted file mode 100644 index ac9fa51..0000000 --- a/remoting/meson.build +++ /dev/null @@ -1,33 +0,0 @@ -if get_option('remoting') - user_hint = 'If you rather not build this, set \'-Dremoting=false\'.' - - if not get_option('backend-drm') or not get_option('renderer-gl') - error('Attempting to build the remoting plugin without the required DRM backend and GL renderer. ' + user_hint) - endif - - depnames = [ - 'gstreamer-1.0', 'gstreamer-allocators-1.0', - 'gstreamer-app-1.0', 'gstreamer-video-1.0', - 'gobject-2.0', 'glib-2.0' - ] - deps_remoting = [ dep_libweston_private, dep_libdrm_headers ] - foreach depname : depnames - dep = dependency(depname, required: false) - if not dep.found() - error('Remoting plugin requires @0@ which was not found. '.format(depname) + user_hint) - endif - deps_remoting += dep - endforeach - - plugin_remoting = shared_library( - 'remoting-plugin', - 'remoting-plugin.c', - include_directories: common_inc, - dependencies: deps_remoting, - name_prefix: '', - install: true, - install_dir: dir_module_libweston - ) - env_modmap += 'remoting-plugin.so=@0@;'.format(plugin_remoting.full_path()) - install_headers('remoting-plugin.h', subdir: dir_include_libweston_install) -endif diff --git a/remoting/remoting-plugin.c b/remoting/remoting-plugin.c deleted file mode 100644 index 85b6bcf..0000000 --- a/remoting/remoting-plugin.c +++ /dev/null @@ -1,967 +0,0 @@ -/* - * Copyright © 2018 Renesas Electronics Corp. - * - * Based on vaapi-recorder by: - * Copyright (c) 2012 Intel Corporation. All Rights Reserved. - * Copyright © 2013 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * Authors: IGEL Co., Ltd. - */ - -#include "config.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "remoting-plugin.h" -#include -#include "shared/helpers.h" -#include "shared/timespec-util.h" -#include "backend.h" -#include "libweston-internal.h" - -#define MAX_RETRY_COUNT 3 - -struct weston_remoting { - struct weston_compositor *compositor; - struct wl_list output_list; - struct wl_listener destroy_listener; - const struct weston_drm_virtual_output_api *virtual_output_api; - - GstAllocator *allocator; -}; - -struct remoted_gstpipe { - int readfd; - int writefd; - struct wl_event_source *source; -}; - -/* supported gbm format list */ -struct remoted_output_support_gbm_format { - /* GBM_FORMAT_* tokens are strictly aliased with DRM_FORMAT_*, so we - * use the latter to avoid a dependency on GBM */ - uint32_t gbm_format; - const char *gst_format_string; - GstVideoFormat gst_video_format; -}; - -static const struct remoted_output_support_gbm_format supported_formats[] = { - { - .gbm_format = DRM_FORMAT_XRGB8888, - .gst_format_string = "BGRx", - .gst_video_format = GST_VIDEO_FORMAT_BGRx, - }, { - .gbm_format = DRM_FORMAT_RGB565, - .gst_format_string = "RGB16", - .gst_video_format = GST_VIDEO_FORMAT_RGB16, - }, { - .gbm_format = DRM_FORMAT_XRGB2101010, - .gst_format_string = "r210", - .gst_video_format = GST_VIDEO_FORMAT_r210, - } -}; - -struct remoted_output { - struct weston_output *output; - void (*saved_destroy)(struct weston_output *output); - int (*saved_enable)(struct weston_output *output); - int (*saved_disable)(struct weston_output *output); - int (*saved_start_repaint_loop)(struct weston_output *output); - - char *host; - int port; - char *gst_pipeline; - const struct remoted_output_support_gbm_format *format; - - struct weston_head *head; - - struct weston_remoting *remoting; - struct wl_event_source *finish_frame_timer; - struct wl_list link; - bool submitted_frame; - int fence_sync_fd; - struct wl_event_source *fence_sync_event_source; - - GstElement *pipeline; - GstAppSrc *appsrc; - GstBus *bus; - struct remoted_gstpipe gstpipe; - GstClockTime start_time; - int retry_count; - enum dpms_enum dpms; -}; - -struct mem_free_cb_data { - struct remoted_output *output; - struct drm_fb *output_buffer; -}; - -struct gst_frame_buffer_data { - struct remoted_output *output; - GstBuffer *buffer; -}; - -/* message type for pipe */ -#define GSTPIPE_MSG_BUS_SYNC 1 -#define GSTPIPE_MSG_BUFFER_RELEASE 2 - -struct gstpipe_msg_data { - int type; - void *data; -}; - -static int -remoting_gst_init(struct weston_remoting *remoting) -{ - GError *err = NULL; - - if (!gst_init_check(NULL, NULL, &err)) { - weston_log("GStreamer initialization error: %s\n", - err->message); - g_error_free(err); - return -1; - } - - remoting->allocator = gst_dmabuf_allocator_new(); - - return 0; -} - -static void -remoting_gst_deinit(struct weston_remoting *remoting) -{ - gst_object_unref(remoting->allocator); -} - -static GstBusSyncReply -remoting_gst_bus_sync_handler(GstBus *bus, GstMessage *message, - gpointer user_data) -{ - struct remoted_gstpipe *pipe = user_data; - struct gstpipe_msg_data msg = { - .type = GSTPIPE_MSG_BUS_SYNC, - .data = NULL - }; - ssize_t ret; - - ret = write(pipe->writefd, &msg, sizeof(msg)); - if (ret != sizeof(msg)) - weston_log("ERROR: failed to write, ret=%zd, errno=%d\n", - ret, errno); - - return GST_BUS_PASS; -} - -static int -remoting_gst_pipeline_init(struct remoted_output *output) -{ - GstCaps *caps; - GError *err = NULL; - GstStateChangeReturn ret; - struct weston_mode *mode = output->output->current_mode; - - if (!output->gst_pipeline) { - char pipeline_str[1024]; - /* TODO: use encodebin instead of jpegenc */ - snprintf(pipeline_str, sizeof(pipeline_str), - "rtpbin name=rtpbin " - "appsrc name=src ! videoconvert ! " - "video/x-raw,format=I420 ! jpegenc ! rtpjpegpay ! " - "rtpbin.send_rtp_sink_0 " - "rtpbin.send_rtp_src_0 ! " - "udpsink name=sink host=%s port=%d " - "rtpbin.send_rtcp_src_0 ! " - "udpsink host=%s port=%d sync=false async=false " - "udpsrc port=%d ! rtpbin.recv_rtcp_sink_0", - output->host, output->port, output->host, - output->port + 1, output->port + 2); - output->gst_pipeline = strdup(pipeline_str); - } - weston_log("GST pipeline: %s\n", output->gst_pipeline); - - output->pipeline = gst_parse_launch(output->gst_pipeline, &err); - if (!output->pipeline) { - weston_log("Could not create gstreamer pipeline. Error: %s\n", - err->message); - g_error_free(err); - return -1; - } - - output->appsrc = (GstAppSrc*) - gst_bin_get_by_name(GST_BIN(output->pipeline), "src"); - if (!output->appsrc) { - weston_log("Could not get appsrc from gstreamer pipeline\n"); - goto err; - } - - /* check sink */ - if (!gst_bin_get_by_name(GST_BIN(output->pipeline), "sink")) { - weston_log("Could not get sink from gstreamer pipeline\n"); - goto err; - } - - caps = gst_caps_new_simple("video/x-raw", - "format", G_TYPE_STRING, - output->format->gst_format_string, - "width", G_TYPE_INT, mode->width, - "height", G_TYPE_INT, mode->height, - "framerate", GST_TYPE_FRACTION, - mode->refresh, 1000, - NULL); - if (!caps) { - weston_log("Could not create gstreamer caps.\n"); - goto err; - } - g_object_set(G_OBJECT(output->appsrc), - "caps", caps, - "stream-type", 0, - "format", GST_FORMAT_TIME, - "is-live", TRUE, - NULL); - gst_caps_unref(caps); - - output->bus = gst_pipeline_get_bus(GST_PIPELINE(output->pipeline)); - if (!output->bus) { - weston_log("Could not get bus from gstreamer pipeline\n"); - goto err; - } - gst_bus_set_sync_handler(output->bus, remoting_gst_bus_sync_handler, - &output->gstpipe, NULL); - - output->start_time = 0; - ret = gst_element_set_state(output->pipeline, GST_STATE_PLAYING); - if (ret == GST_STATE_CHANGE_FAILURE) { - weston_log("Couldn't set GST_STATE_PLAYING to pipeline\n"); - goto err; - } - - return 0; - -err: - gst_object_unref(GST_OBJECT(output->pipeline)); - output->pipeline = NULL; - return -1; -} - -static void -remoting_gst_pipeline_deinit(struct remoted_output *output) -{ - if (!output->pipeline) - return; - - gst_element_set_state(output->pipeline, GST_STATE_NULL); - if (output->bus) - gst_object_unref(GST_OBJECT(output->bus)); - gst_object_unref(GST_OBJECT(output->pipeline)); - output->pipeline = NULL; -} - -static int -remoting_output_disable(struct weston_output *output); - -static void -remoting_gst_restart(void *data) -{ - struct remoted_output *output = data; - - if (remoting_gst_pipeline_init(output) < 0) { - weston_log("gst: Could not restart pipeline!!\n"); - remoting_output_disable(output->output); - } -} - -static void -remoting_gst_schedule_restart(struct remoted_output *output) -{ - struct wl_event_loop *loop; - struct weston_compositor *c = output->remoting->compositor; - - loop = wl_display_get_event_loop(c->wl_display); - wl_event_loop_add_idle(loop, remoting_gst_restart, output); -} - -static void -remoting_gst_bus_message_handler(struct remoted_output *output) -{ - GstMessage *message; - GError *error; - gchar *debug; - - /* get message from bus queue */ - message = gst_bus_pop(output->bus); - if (!message) - return; - - switch (GST_MESSAGE_TYPE(message)) { - case GST_MESSAGE_STATE_CHANGED: { - GstState new_state; - gst_message_parse_state_changed(message, NULL, &new_state, - NULL); - if (!strcmp(GST_OBJECT_NAME(message->src), "sink") && - new_state == GST_STATE_PLAYING) - output->retry_count = 0; - break; - } - case GST_MESSAGE_WARNING: - gst_message_parse_warning(message, &error, &debug); - weston_log("gst: Warning: %s: %s\n", - GST_OBJECT_NAME(message->src), error->message); - break; - case GST_MESSAGE_ERROR: - gst_message_parse_error(message, &error, &debug); - weston_log("gst: Error: %s: %s\n", - GST_OBJECT_NAME(message->src), error->message); - if (output->retry_count < MAX_RETRY_COUNT) { - output->retry_count++; - remoting_gst_pipeline_deinit(output); - remoting_gst_schedule_restart(output); - } else { - remoting_output_disable(output->output); - } - break; - default: - break; - } -} - -static void -remoting_output_buffer_release(struct remoted_output *output, void *buffer) -{ - const struct weston_drm_virtual_output_api *api - = output->remoting->virtual_output_api; - - api->buffer_released(buffer); -} - -static int -remoting_gstpipe_handler(int fd, uint32_t mask, void *data) -{ - ssize_t ret; - struct gstpipe_msg_data msg; - struct remoted_output *output = data; - - /* receive message */ - ret = read(fd, &msg, sizeof(msg)); - if (ret != sizeof(msg)) { - weston_log("ERROR: failed to read, ret=%zd, errno=%d\n", - ret, errno); - remoting_output_disable(output->output); - return 0; - } - - switch (msg.type) { - case GSTPIPE_MSG_BUS_SYNC: - remoting_gst_bus_message_handler(output); - break; - case GSTPIPE_MSG_BUFFER_RELEASE: - remoting_output_buffer_release(output, msg.data); - break; - default: - weston_log("Received unknown message! msg=%d\n", msg.type); - } - return 1; -} - -static int -remoting_gstpipe_init(struct weston_compositor *c, - struct remoted_output *output) -{ - struct wl_event_loop *loop; - int fd[2]; - - if (pipe2(fd, O_CLOEXEC) == -1) - return -1; - - output->gstpipe.readfd = fd[0]; - output->gstpipe.writefd = fd[1]; - loop = wl_display_get_event_loop(c->wl_display); - output->gstpipe.source = - wl_event_loop_add_fd(loop, output->gstpipe.readfd, - WL_EVENT_READABLE, - remoting_gstpipe_handler, output); - if (!output->gstpipe.source) { - close(fd[0]); - close(fd[1]); - return -1; - } - - return 0; -} - -static void -remoting_gstpipe_release(struct remoted_gstpipe *pipe) -{ - wl_event_source_remove(pipe->source); - close(pipe->readfd); - close(pipe->writefd); -} - -static void -remoting_output_destroy(struct weston_output *output); - -static void -weston_remoting_destroy(struct wl_listener *l, void *data) -{ - struct weston_remoting *remoting = - container_of(l, struct weston_remoting, destroy_listener); - struct remoted_output *output, *next; - - wl_list_for_each_safe(output, next, &remoting->output_list, link) - remoting_output_destroy(output->output); - - /* Finalize gstreamer */ - remoting_gst_deinit(remoting); - - wl_list_remove(&remoting->destroy_listener.link); - free(remoting); -} - -static struct weston_remoting * -weston_remoting_get(struct weston_compositor *compositor) -{ - struct wl_listener *listener; - struct weston_remoting *remoting; - - listener = wl_signal_get(&compositor->destroy_signal, - weston_remoting_destroy); - if (!listener) - return NULL; - - remoting = wl_container_of(listener, remoting, destroy_listener); - return remoting; -} - -static int -remoting_output_finish_frame_handler(void *data) -{ - struct remoted_output *output = data; - const struct weston_drm_virtual_output_api *api - = output->remoting->virtual_output_api; - struct timespec now; - int64_t msec; - - if (output->submitted_frame) { - struct weston_compositor *c = output->remoting->compositor; - output->submitted_frame = false; - weston_compositor_read_presentation_clock(c, &now); - api->finish_frame(output->output, &now, 0); - } - - if (output->dpms == WESTON_DPMS_ON) { - msec = millihz_to_nsec(output->output->current_mode->refresh) / 1000000; - wl_event_source_timer_update(output->finish_frame_timer, msec); - } else { - wl_event_source_timer_update(output->finish_frame_timer, 0); - } - return 0; -} - -static void -remoting_gst_mem_free_cb(struct mem_free_cb_data *cb_data, GstMiniObject *obj) -{ - struct remoted_output *output = cb_data->output; - struct remoted_gstpipe *pipe = &output->gstpipe; - struct gstpipe_msg_data msg = { - .type = GSTPIPE_MSG_BUFFER_RELEASE, - .data = cb_data->output_buffer - }; - ssize_t ret; - - ret = write(pipe->writefd, &msg, sizeof(msg)); - if (ret != sizeof(msg)) - weston_log("ERROR: failed to write, ret=%zd, errno=%d\n", ret, - errno); - free(cb_data); -} - -static struct remoted_output * -lookup_remoted_output(struct weston_output *output) -{ - struct weston_compositor *c = output->compositor; - struct weston_remoting *remoting = weston_remoting_get(c); - struct remoted_output *remoted_output; - - wl_list_for_each(remoted_output, &remoting->output_list, link) { - if (remoted_output->output == output) - return remoted_output; - } - - weston_log("%s: %s: could not find output\n", __FILE__, __func__); - return NULL; -} - -static void -remoting_output_gst_push_buffer(struct remoted_output *output, - GstBuffer *buffer) -{ - struct timespec current_frame_ts; - GstClockTime ts, current_frame_time; - - weston_compositor_read_presentation_clock(output->remoting->compositor, - ¤t_frame_ts); - current_frame_time = GST_TIMESPEC_TO_TIME(current_frame_ts); - if (output->start_time == 0) - output->start_time = current_frame_time; - ts = current_frame_time - output->start_time; - - if (GST_CLOCK_TIME_IS_VALID(ts)) - GST_BUFFER_PTS(buffer) = ts; - else - GST_BUFFER_PTS(buffer) = GST_CLOCK_TIME_NONE; - GST_BUFFER_DURATION(buffer) = GST_CLOCK_TIME_NONE; - - gst_app_src_push_buffer(output->appsrc, buffer); - output->submitted_frame = true; -} - -static int -remoting_output_fence_sync_handler(int fd, uint32_t mask, void *data) -{ - struct gst_frame_buffer_data *frame_data = data; - struct remoted_output *output = frame_data->output; - - remoting_output_gst_push_buffer(output, frame_data->buffer); - - wl_event_source_remove(output->fence_sync_event_source); - close(output->fence_sync_fd); - free(frame_data); - - return 0; -} - -static int -remoting_output_frame(struct weston_output *output_base, int fd, int stride, - struct drm_fb *output_buffer) -{ - struct remoted_output *output = lookup_remoted_output(output_base); - struct weston_remoting *remoting = output->remoting; - struct weston_mode *mode; - const struct weston_drm_virtual_output_api *api - = output->remoting->virtual_output_api; - struct wl_event_loop *loop; - GstBuffer *buf; - GstMemory *mem; - gsize offset = 0; - struct mem_free_cb_data *cb_data; - struct gst_frame_buffer_data *frame_data; - - if (!output) - return -1; - - cb_data = zalloc(sizeof *cb_data); - if (!cb_data) - return -1; - - mode = output->output->current_mode; - buf = gst_buffer_new(); - mem = gst_dmabuf_allocator_alloc(remoting->allocator, fd, - stride * mode->height); - gst_buffer_append_memory(buf, mem); - gst_buffer_add_video_meta_full(buf, - GST_VIDEO_FRAME_FLAG_NONE, - output->format->gst_video_format, - mode->width, - mode->height, - 1, - &offset, - &stride); - - cb_data->output = output; - cb_data->output_buffer = output_buffer; - gst_mini_object_weak_ref(GST_MINI_OBJECT(mem), - (GstMiniObjectNotify)remoting_gst_mem_free_cb, - cb_data); - - output->fence_sync_fd = api->get_fence_sync_fd(output->output); - /* Push buffer to gstreamer immediately on get_fence_sync_fd failure */ - if (output->fence_sync_fd == -1) { - remoting_output_gst_push_buffer(output, buf); - return 0; - } - - frame_data = zalloc(sizeof *frame_data); - if (!frame_data) { - close(output->fence_sync_fd); - remoting_output_gst_push_buffer(output, buf); - return 0; - } - - frame_data->output = output; - frame_data->buffer = buf; - loop = wl_display_get_event_loop(remoting->compositor->wl_display); - output->fence_sync_event_source = - wl_event_loop_add_fd(loop, output->fence_sync_fd, - WL_EVENT_READABLE, - remoting_output_fence_sync_handler, - frame_data); - - return 0; -} - -static void -remoting_output_destroy(struct weston_output *output) -{ - struct remoted_output *remoted_output = lookup_remoted_output(output); - struct weston_mode *mode, *next; - - wl_list_for_each_safe(mode, next, &output->mode_list, link) { - wl_list_remove(&mode->link); - free(mode); - } - - remoted_output->saved_destroy(output); - - remoting_gst_pipeline_deinit(remoted_output); - remoting_gstpipe_release(&remoted_output->gstpipe); - - if (remoted_output->host) - free(remoted_output->host); - if (remoted_output->gst_pipeline) - free(remoted_output->gst_pipeline); - - wl_list_remove(&remoted_output->link); - weston_head_release(remoted_output->head); - free(remoted_output->head); - free(remoted_output); -} - -static int -remoting_output_start_repaint_loop(struct weston_output *output) -{ - struct remoted_output *remoted_output = lookup_remoted_output(output); - int64_t msec; - - remoted_output->saved_start_repaint_loop(output); - - msec = millihz_to_nsec(remoted_output->output->current_mode->refresh) - / 1000000; - wl_event_source_timer_update(remoted_output->finish_frame_timer, msec); - - return 0; -} - -static void -remoting_output_set_dpms(struct weston_output *base_output, enum dpms_enum level) -{ - struct remoted_output *output = lookup_remoted_output(base_output); - - if (output->dpms == level) - return; - - output->dpms = level; - remoting_output_finish_frame_handler(output); -} - -static int -remoting_output_enable(struct weston_output *output) -{ - struct remoted_output *remoted_output = lookup_remoted_output(output); - struct weston_compositor *c = output->compositor; - const struct weston_drm_virtual_output_api *api - = remoted_output->remoting->virtual_output_api; - struct wl_event_loop *loop; - int ret; - - api->set_submit_frame_cb(output, remoting_output_frame); - - ret = remoted_output->saved_enable(output); - if (ret < 0) - return ret; - - remoted_output->saved_start_repaint_loop = output->start_repaint_loop; - output->start_repaint_loop = remoting_output_start_repaint_loop; - output->set_dpms = remoting_output_set_dpms; - - ret = remoting_gst_pipeline_init(remoted_output); - if (ret < 0) { - remoted_output->saved_disable(output); - return ret; - } - - loop = wl_display_get_event_loop(c->wl_display); - remoted_output->finish_frame_timer = - wl_event_loop_add_timer(loop, - remoting_output_finish_frame_handler, - remoted_output); - - remoted_output->dpms = WESTON_DPMS_ON; - return 0; -} - -static int -remoting_output_disable(struct weston_output *output) -{ - struct remoted_output *remoted_output = lookup_remoted_output(output); - - wl_event_source_remove(remoted_output->finish_frame_timer); - remoting_gst_pipeline_deinit(remoted_output); - - return remoted_output->saved_disable(output); -} - -static struct weston_output * -remoting_output_create(struct weston_compositor *c, char *name) -{ - struct weston_remoting *remoting = weston_remoting_get(c); - struct remoted_output *output; - struct weston_head *head; - const struct weston_drm_virtual_output_api *api; - const char *make = "Renesas"; - const char *model = "Virtual Display"; - const char *serial_number = "unknown"; - const char *connector_name = "remoting"; - - if (!name || !strlen(name)) - return NULL; - - api = remoting->virtual_output_api; - - output = zalloc(sizeof *output); - if (!output) - return NULL; - - head = zalloc(sizeof *head); - if (!head) - goto err; - - if (remoting_gstpipe_init(c, output) < 0) { - weston_log("Can not create pipe for gstreamer\n"); - goto err; - } - - output->output = api->create_output(c, name); - if (!output->output) { - weston_log("Can not create virtual output\n"); - goto err; - } - - output->saved_destroy = output->output->destroy; - output->output->destroy = remoting_output_destroy; - output->saved_enable = output->output->enable; - output->output->enable = remoting_output_enable; - output->saved_disable = output->output->disable; - output->output->disable = remoting_output_disable; - output->remoting = remoting; - wl_list_insert(remoting->output_list.prev, &output->link); - - weston_head_init(head, connector_name); - weston_head_set_subpixel(head, WL_OUTPUT_SUBPIXEL_NONE); - weston_head_set_monitor_strings(head, make, model, serial_number); - head->compositor = c; - - weston_output_attach_head(output->output, head); - output->head = head; - - /* set XRGB8888 format */ - output->format = &supported_formats[0]; - - return output->output; - -err: - if (output->gstpipe.source) - remoting_gstpipe_release(&output->gstpipe); - if (head) - free(head); - free(output); - return NULL; -} - -static bool -remoting_output_is_remoted(struct weston_output *output) -{ - struct remoted_output *remoted_output = lookup_remoted_output(output); - - if (remoted_output) - return true; - - return false; -} - -static int -remoting_output_set_mode(struct weston_output *output, const char *modeline) -{ - struct weston_mode *mode; - int n, width, height, refresh = 0; - - if (!remoting_output_is_remoted(output)) { - weston_log("Output is not remoted.\n"); - return -1; - } - - if (!modeline) - return -1; - - n = sscanf(modeline, "%dx%d@%d", &width, &height, &refresh); - if (n != 2 && n != 3) - return -1; - - mode = zalloc(sizeof *mode); - if (!mode) - return -1; - - mode->flags = WL_OUTPUT_MODE_CURRENT; - mode->width = width; - mode->height = height; - mode->refresh = (refresh ? refresh : 60) * 1000LL; - - wl_list_insert(output->mode_list.prev, &mode->link); - - output->current_mode = mode; - - return 0; -} - -static void -remoting_output_set_gbm_format(struct weston_output *output, - const char *gbm_format) -{ - struct remoted_output *remoted_output = lookup_remoted_output(output); - const struct weston_drm_virtual_output_api *api; - uint32_t format, i; - - if (!remoted_output) - return; - - api = remoted_output->remoting->virtual_output_api; - format = api->set_gbm_format(output, gbm_format); - - for (i = 0; i < ARRAY_LENGTH(supported_formats); i++) { - if (format == supported_formats[i].gbm_format) { - remoted_output->format = &supported_formats[i]; - return; - } - } -} - -static void -remoting_output_set_seat(struct weston_output *output, const char *seat) -{ - /* for now, nothing todo */ -} - -static void -remoting_output_set_host(struct weston_output *output, char *host) -{ - struct remoted_output *remoted_output = lookup_remoted_output(output); - - if (!remoted_output) - return; - - if (remoted_output->host) - free(remoted_output->host); - remoted_output->host = strdup(host); -} - -static void -remoting_output_set_port(struct weston_output *output, int port) -{ - struct remoted_output *remoted_output = lookup_remoted_output(output); - - if (remoted_output) - remoted_output->port = port; -} - -static void -remoting_output_set_gst_pipeline(struct weston_output *output, - char *gst_pipeline) -{ - struct remoted_output *remoted_output = lookup_remoted_output(output); - - if (!remoted_output) - return; - - if (remoted_output->gst_pipeline) - free(remoted_output->gst_pipeline); - remoted_output->gst_pipeline = strdup(gst_pipeline); -} - -static const struct weston_remoting_api remoting_api = { - remoting_output_create, - remoting_output_is_remoted, - remoting_output_set_mode, - remoting_output_set_gbm_format, - remoting_output_set_seat, - remoting_output_set_host, - remoting_output_set_port, - remoting_output_set_gst_pipeline, -}; - -WL_EXPORT int -weston_module_init(struct weston_compositor *compositor) -{ - int ret; - struct weston_remoting *remoting; - const struct weston_drm_virtual_output_api *api = - weston_drm_virtual_output_get_api(compositor); - - if (!api) - return -1; - - remoting = zalloc(sizeof *remoting); - if (!remoting) - return -1; - - if (!weston_compositor_add_destroy_listener_once(compositor, - &remoting->destroy_listener, - weston_remoting_destroy)) { - free(remoting); - return 0; - } - - remoting->virtual_output_api = api; - remoting->compositor = compositor; - wl_list_init(&remoting->output_list); - - ret = weston_plugin_api_register(compositor, WESTON_REMOTING_API_NAME, - &remoting_api, sizeof(remoting_api)); - - if (ret < 0) { - weston_log("Failed to register remoting API.\n"); - goto failed; - } - - /* Initialize gstreamer */ - ret = remoting_gst_init(remoting); - if (ret < 0) { - weston_log("Failed to initialize gstreamer.\n"); - goto failed; - } - - return 0; - -failed: - wl_list_remove(&remoting->destroy_listener.link); - free(remoting); - return -1; -} diff --git a/remoting/remoting-plugin.h b/remoting/remoting-plugin.h deleted file mode 100644 index fdd429c..0000000 --- a/remoting/remoting-plugin.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright © 2018 Renesas Electronics Corp. - * - * 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: IGEL Co., Ltd. - */ - -#ifndef REMOTING_PLUGIN_H -#define REMOTING_PLUGIN_H - -#include -#include - -#define WESTON_REMOTING_API_NAME "weston_remoting_api_v1" - -struct weston_remoting_api { - /** Create remoted outputs - * - * Returns 0 on success, -1 on failure. - */ - struct weston_output *(*create_output)(struct weston_compositor *c, - char *name); - - /** Check if output is remoted */ - bool (*is_remoted_output)(struct weston_output *output); - - /** Set mode */ - int (*set_mode)(struct weston_output *output, const char *modeline); - - /** Set gbm format */ - void (*set_gbm_format)(struct weston_output *output, - const char *gbm_format); - - /** Set seat */ - void (*set_seat)(struct weston_output *output, const char *seat); - - /** Set the destination Host(IP Address) */ - void (*set_host)(struct weston_output *output, char *ip); - - /** Set the port number */ - void (*set_port)(struct weston_output *output, int port); - - /** Set the pipeline for gstreamer */ - void (*set_gst_pipeline)(struct weston_output *output, - char *gst_pipeline); -}; - -static inline const struct weston_remoting_api * -weston_remoting_get_api(struct weston_compositor *compositor) -{ - const void *api; - api = weston_plugin_api_get(compositor, WESTON_REMOTING_API_NAME, - sizeof(struct weston_remoting_api)); - - return (const struct weston_remoting_api *)api; -} - -#endif /* REMOTING_PLUGIN_H */ diff --git a/shared/cairo-util.c b/shared/cairo-util.c deleted file mode 100644 index f558e1d..0000000 --- a/shared/cairo-util.c +++ /dev/null @@ -1,635 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include "cairo-util.h" - -#include "shared/helpers.h" -#include "image-loader.h" -#include - -#ifdef HAVE_PANGO -#include -#endif - -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 -render_shadow(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, shadow_height, shadow_width; - - cairo_set_source_rgba(cr, 0, 0, 0, 0.45); - 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++) { - /* when fy is set, then we are working with lower corners, - * when fx is set, then we are working with right corners - * - * 00 ------- 01 - * | | - * | | - * 10 ------- 11 - */ - 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); - - shadow_width = margin; - shadow_height = fy ? margin : top_margin; - - /* if the shadows together are greater than the surface, we need - * to fix it - set the shadow size to the half of - * the size of surface. Also handle the case when the size is - * not divisible by 2. In that case we need one part of the - * shadow to be one pixel greater. !fy or !fx, respectively, - * will do the work. - */ - if (height < 2 * shadow_height) - shadow_height = (height + !fy) / 2; - - if (width < 2 * shadow_width) - shadow_width = (width + !fx) / 2; - - cairo_reset_clip(cr); - cairo_rectangle(cr, - x + fx * (width - shadow_width), - y + fy * (height - shadow_height), - shadow_width, shadow_height); - cairo_clip (cr); - cairo_mask(cr, pattern); - } - - - shadow_width = width - 2 * margin; - shadow_height = top_margin; - if (height < 2 * shadow_height) - shadow_height = height / 2; - - if (shadow_width > 0 && shadow_height) { - /* 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, shadow_width, shadow_height); - - cairo_reset_clip(cr); - cairo_rectangle(cr, - x + margin, y, - shadow_width, shadow_height); - 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, - shadow_width, margin); - cairo_clip (cr); - cairo_mask(cr, pattern); - } - - shadow_width = margin; - if (width < 2 * shadow_width) - shadow_width = width / 2; - - shadow_height = height - margin - top_margin; - - /* if height is smaller than sum of margins, - * then the shadow is already done by the corners */ - if (shadow_height > 0 && shadow_width) { - /* 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 + top_margin, - shadow_width, shadow_height); - 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 - shadow_width, y + top_margin, - shadow_width, shadow_height); - 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); -} - -void -theme_set_background_source(struct theme *t, cairo_t *cr, uint32_t flags) -{ - cairo_pattern_t *pattern; - - if (flags & THEME_FRAME_ACTIVE) { - 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); - } else { - cairo_set_source_rgba(cr, 0.75, 0.75, 0.75, 1); - } -} - -struct theme * -theme_create(void) -{ - struct theme *t; - cairo_t *cr; - - 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); - - theme_set_background_source(t, cr, THEME_FRAME_ACTIVE); - 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); - theme_set_background_source(t, cr, 0); - 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); -} - -#ifdef HAVE_PANGO -static PangoLayout * -create_layout(cairo_t *cr, const char *title) -{ - PangoLayout *layout; - PangoFontDescription *desc; - - layout = pango_cairo_create_layout(cr); - if (title) { - pango_layout_set_text(layout, title, -1); - desc = pango_font_description_from_string("Sans Bold 10"); - pango_layout_set_font_description(layout, desc); - pango_font_description_free(desc); - } - pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); - pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT); - pango_layout_set_auto_dir (layout, FALSE); - pango_layout_set_single_paragraph_mode (layout, TRUE); - pango_layout_set_width (layout, -1); - - return layout; -} -#endif - -#ifdef HAVE_PANGO -#define SHOW_TEXT(cr) \ - pango_cairo_show_layout(cr, title_layout) -#else -#define SHOW_TEXT(cr) \ - cairo_show_text(cr, title) -#endif - -void -theme_render_frame(struct theme *t, - cairo_t *cr, int width, int height, - const char *title, cairo_rectangle_int_t *title_rect, - struct wl_list *buttons, uint32_t flags) -{ - cairo_surface_t *source; - int x, y, margin, top_margin; - int text_width, text_height; - - 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 { - render_shadow(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; - - if (title || !wl_list_empty(buttons)) - top_margin = t->titlebar_height; - else - top_margin = t->width; - - tile_source(cr, source, - margin, margin, - width - margin * 2, height - margin * 2, - t->width, top_margin); - - if (title || !wl_list_empty(buttons)) { - - cairo_rectangle (cr, title_rect->x, title_rect->y, - title_rect->width, title_rect->height); - cairo_clip(cr); - cairo_set_operator(cr, CAIRO_OPERATOR_OVER); - -#ifdef HAVE_PANGO - PangoLayout *title_layout; - PangoRectangle logical; - - title_layout = create_layout(cr, title); - - pango_layout_get_pixel_extents (title_layout, NULL, &logical); - text_width = MIN(title_rect->width, logical.width); - text_height = logical.height; - if (text_width < logical.width) - pango_layout_set_width (title_layout, text_width * PANGO_SCALE); - -#else - cairo_text_extents_t extents; - cairo_font_extents_t font_extents; - - 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); - text_width = extents.width; - text_height = font_extents.descent - font_extents.ascent; -#endif - - x = (width - text_width) / 2; - y = margin + (t->titlebar_height - text_height) / 2; - if (x < title_rect->x) - x = title_rect->x; - else if (x + text_width > (title_rect->x + title_rect->width)) - x = (title_rect->x + title_rect->width) - text_width; - - if (flags & THEME_FRAME_ACTIVE) { - cairo_move_to(cr, x + 1, y + 1); - cairo_set_source_rgb(cr, 1, 1, 1); - SHOW_TEXT(cr); - cairo_move_to(cr, x, y); - cairo_set_source_rgb(cr, 0, 0, 0); - SHOW_TEXT(cr); - } else { - cairo_move_to(cr, x, y); - cairo_set_source_rgb(cr, 0.4, 0.4, 0.4); - SHOW_TEXT(cr); - } - } -} - -enum theme_location -theme_get_location(struct theme *t, int x, int y, - int width, int height, int flags) -{ - int vlocation, hlocation, location; - int margin, top_margin, grip_size; - - if (flags & THEME_FRAME_MAXIMIZED) { - margin = 0; - grip_size = 0; - } else { - margin = t->margin; - grip_size = 8; - } - - if (flags & THEME_FRAME_NO_TITLE) - top_margin = t->width; - else - top_margin = t->titlebar_height; - - if (x < margin) - hlocation = THEME_LOCATION_EXTERIOR; - else if (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 (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 + top_margin) - 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 deleted file mode 100644 index 6fd11f6..0000000 --- a/shared/cairo-util.h +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _CAIRO_UTIL_H -#define _CAIRO_UTIL_H - -#include -#include - -#include -#include - -void -surface_flush_device(cairo_surface_t *surface); - -void -render_shadow(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 = 2, - THEME_FRAME_NO_TITLE = 4 -}; - -void -theme_set_background_source(struct theme *t, cairo_t *cr, uint32_t flags); -void -theme_render_frame(struct theme *t, - cairo_t *cr, int width, int height, - const char *title, cairo_rectangle_int_t *title_rect, - struct wl_list *buttons, 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); - -struct frame; - -enum frame_status { - FRAME_STATUS_NONE = 0, - FRAME_STATUS_REPAINT = 0x1, - FRAME_STATUS_MINIMIZE = 0x2, - FRAME_STATUS_MAXIMIZE = 0x4, - FRAME_STATUS_CLOSE = 0x8, - FRAME_STATUS_MENU = 0x10, - FRAME_STATUS_RESIZE = 0x20, - FRAME_STATUS_MOVE = 0x40, - FRAME_STATUS_ALL = 0x7f -}; - -enum frame_flag { - FRAME_FLAG_ACTIVE = 0x1, - FRAME_FLAG_MAXIMIZED = 0x2 -}; - -enum { - FRAME_BUTTON_NONE = 0, - FRAME_BUTTON_CLOSE = 0x1, - FRAME_BUTTON_MAXIMIZE = 0x2, - FRAME_BUTTON_MINIMIZE = 0x4, - FRAME_BUTTON_ALL = 0x7 -}; - -struct frame * -frame_create(struct theme *t, int32_t width, int32_t height, uint32_t buttons, - const char *title, cairo_surface_t *icon); - -void -frame_destroy(struct frame *frame); - -/* May set FRAME_STATUS_REPAINT */ -int -frame_set_title(struct frame *frame, const char *title); - -/* May set FRAME_STATUS_REPAINT */ -void -frame_set_icon(struct frame *frame, cairo_surface_t *icon); - -/* May set FRAME_STATUS_REPAINT */ -void -frame_set_flag(struct frame *frame, enum frame_flag flag); - -/* May set FRAME_STATUS_REPAINT */ -void -frame_unset_flag(struct frame *frame, enum frame_flag flag); - -/* May set FRAME_STATUS_REPAINT */ -void -frame_resize(struct frame *frame, int32_t width, int32_t height); - -/* May set FRAME_STATUS_REPAINT */ -void -frame_resize_inside(struct frame *frame, int32_t width, int32_t height); - -int32_t -frame_width(struct frame *frame); - -int32_t -frame_height(struct frame *frame); - -void -frame_interior(struct frame *frame, int32_t *x, int32_t *y, - int32_t *width, int32_t *height); -void -frame_input_rect(struct frame *frame, int32_t *x, int32_t *y, - int32_t *width, int32_t *height); -void -frame_opaque_rect(struct frame *frame, int32_t *x, int32_t *y, - int32_t *width, int32_t *height); - -int -frame_get_shadow_margin(struct frame *frame); - -uint32_t -frame_status(struct frame *frame); - -void -frame_status_clear(struct frame *frame, enum frame_status status); - -/* May set FRAME_STATUS_REPAINT */ -enum theme_location -frame_pointer_enter(struct frame *frame, void *pointer, int x, int y); - -/* May set FRAME_STATUS_REPAINT */ -enum theme_location -frame_pointer_motion(struct frame *frame, void *pointer, int x, int y); - -/* May set FRAME_STATUS_REPAINT */ -void -frame_pointer_leave(struct frame *frame, void *pointer); - -/* Call to indicate that a button has been pressed/released. The return - * value for a button release will be the same as for the corresponding - * press. This allows you to more easily track grabs. If you want the - * actual location, simply keep the location from the last - * frame_pointer_motion call. - * - * May set: - * FRAME_STATUS_MINIMIZE - * FRAME_STATUS_MAXIMIZE - * FRAME_STATUS_CLOSE - * FRAME_STATUS_MENU - * FRAME_STATUS_RESIZE - * FRAME_STATUS_MOVE - */ -enum theme_location -frame_pointer_button(struct frame *frame, void *pointer, - uint32_t button, enum wl_pointer_button_state state); - -enum theme_location -frame_touch_down(struct frame *frame, void *data, int32_t id, int x, int y); - -void -frame_touch_up(struct frame *frame, void *data, int32_t id); - -enum theme_location -frame_double_click(struct frame *frame, void *pointer, - uint32_t button, enum wl_pointer_button_state state); - -void -frame_double_touch_down(struct frame *frame, void *data, int32_t id, - int x, int y); - -void -frame_double_touch_up(struct frame *frame, void *data, int32_t id); - -void -frame_repaint(struct frame *frame, cairo_t *cr); - -#endif diff --git a/shared/config-parser.c b/shared/config-parser.c deleted file mode 100644 index 64e4827..0000000 --- a/shared/config-parser.c +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Copyright © 2011 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include "helpers.h" -#include "string-helpers.h" - -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, - * then to directories listed in XDG_CONFIG_DIRS. */ - - /* $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++; - } - - return -1; -} - -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; - - entry = config_section_get_entry(section, key); - if (entry == NULL) { - *value = default_value; - errno = ENOENT; - return -1; - } - - if (!safe_strtoint(entry->value, value)) { - *value = default_value; - 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) -{ - long int ret; - struct weston_config_entry *entry; - char *end; - - entry = config_section_get_entry(section, key); - if (entry == NULL) { - *value = default_value; - errno = ENOENT; - return -1; - } - - errno = 0; - ret = strtol(entry->value, &end, 0); - if (errno != 0 || end == entry->value || *end != '\0') { - *value = default_value; - errno = EINVAL; - return -1; - } - - /* check range */ - if (ret < 0 || ret > INT_MAX) { - *value = default_value; - errno = ERANGE; - return -1; - } - - *value = ret; - - return 0; -} - -WL_EXPORT -int -weston_config_section_get_color(struct weston_config_section *section, - const char *key, - uint32_t *color, uint32_t default_color) -{ - struct weston_config_entry *entry; - int len; - char *end; - - entry = config_section_get_entry(section, key); - if (entry == NULL) { - *color = default_color; - errno = ENOENT; - return -1; - } - - len = strlen(entry->value); - if (len == 1 && entry->value[0] == '0') { - *color = 0; - return 0; - } else if (len != 8 && len != 10) { - *color = default_color; - errno = EINVAL; - return -1; - } - - errno = 0; - *color = strtoul(entry->value, &end, 16); - if (errno != 0 || end == entry->value || *end != '\0') { - *color = default_color; - 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, - bool *value, bool 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 = false; - else if (strcmp(entry->value, "true") == 0) - *value = true; - else { - *value = default_value; - errno = EINVAL; - return -1; - } - - return 0; -} - -WL_EXPORT -const char * -weston_config_get_name_from_env(void) -{ - const char *name; - - name = getenv(WESTON_CONFIG_FILE_ENV_VAR); - if (name) - return name; - - return "weston.ini"; -} - -static struct weston_config_section * -config_add_section(struct weston_config *config, const char *name) -{ - struct weston_config_section *section; - - section = malloc(sizeof *section); - if (section == NULL) - return NULL; - - section->name = strdup(name); - if (section->name == NULL) { - free(section); - return NULL; - } - - 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); - if (entry == NULL) - return NULL; - - entry->key = strdup(key); - if (entry->key == NULL) { - free(entry); - return NULL; - } - - entry->value = strdup(value); - if (entry->value == NULL) { - free(entry->key); - free(entry); - return NULL; - } - - wl_list_insert(section->entry_list.prev, &entry->link); - - return entry; -} - -WL_EXPORT -struct weston_config * -weston_config_parse(const char *name) -{ - FILE *fp; - char line[512], *p; - struct stat filestat; - 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; - } - - if (fstat(fd, &filestat) < 0 || - !S_ISREG(filestat.st_mode)) { - close(fd); - 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; -} - -WL_EXPORT -const char * -weston_config_get_full_path(struct weston_config *config) -{ - return config == NULL ? NULL : config->path; -} - -WL_EXPORT -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; -} - -WL_EXPORT -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/fd-util.h b/shared/fd-util.h deleted file mode 100644 index da4ef6f..0000000 --- a/shared/fd-util.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright © 2018 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef FD_UTIL_H -#define FD_UTIL_H - -#include - -static inline void -fd_update(int *fd, int new_fd) -{ - if (*fd == new_fd) - return; - if (*fd >= 0) - close(*fd); - *fd = new_fd; -} - -static inline void -fd_move(int *dest, int *src) -{ - if (dest == src) - return; - fd_update(dest, *src); - *src = -1; -} - -static inline void -fd_clear(int *fd) -{ - fd_update(fd, -1); -} - -#endif /* FD_UTIL_H */ diff --git a/shared/file-util.c b/shared/file-util.c deleted file mode 100644 index 151ef2a..0000000 --- a/shared/file-util.c +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright © 2015 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "file-util.h" - -static int -current_time_str(char *str, size_t len, const char *fmt) -{ - time_t t; - struct tm *t_local; - int ret; - - t = time(NULL); - t_local = localtime(&t); - if (!t_local) { - errno = ETIME; - return -1; - } - - ret = strftime(str, len, fmt, t_local); - if (ret == 0) { - errno = ETIME; - return -1; - } - - return ret; -} - -static int -create_file_excl(const char *fname) -{ - return open(fname, O_RDWR | O_CLOEXEC | O_CREAT | O_EXCL, 00666); -} - -/** Create a unique file with date and time in the name - * - * \param path File path - * \param prefix File name prefix. - * \param suffix File name suffix. - * \param[out] name_out Buffer for the resulting file name. - * \param name_len Number of bytes usable in name_out. - * \return stdio FILE pointer, or NULL on failure. - * - * Create and open a new file with the name concatenated from - * path/prefix, date and time, and suffix. If a file with this name - * already exists, an counter number is added to the end of the - * date and time sub-string. The counter is increased until a free file - * name is found. - * - * Once creating the file succeeds, the name of the file is in name_out. - * On failure, the contents of name_out are undefined and errno is set. - */ -FILE * -file_create_dated(const char *path, const char *prefix, const char *suffix, - char *name_out, size_t name_len) -{ - char timestr[128]; - int ret; - int fd; - int cnt = 0; - int with_path; - - with_path = path && path[0]; - - if (current_time_str(timestr, sizeof(timestr), "%F_%H-%M-%S") < 0) - return NULL; - - ret = snprintf(name_out, name_len, "%s%s%s%s%s", - with_path ? path : "", with_path ? "/" : "", - prefix, timestr, suffix); - if (ret < 0 || (size_t)ret >= name_len) { - errno = ENOBUFS; - return NULL; - } - - fd = create_file_excl(name_out); - - while (fd == -1 && errno == EEXIST) { - cnt++; - - ret = snprintf(name_out, name_len, "%s%s%s%s-%d%s", - with_path ? path : "", with_path ? "/" : "", - prefix, timestr, cnt, suffix); - if (ret < 0 || (size_t)ret >= name_len) { - errno = ENOBUFS; - return NULL; - } - - fd = create_file_excl(name_out); - } - - if (fd == -1) - return NULL; - - return fdopen(fd, "w"); -} - -char * -file_name_with_datadir(const char *filename) -{ - const char *base = getenv("WESTON_DATA_DIR"); - char *out; - int len; - - if (base) - len = asprintf(&out, "%s/%s", base, filename); - else - len = asprintf(&out, "%s/weston/%s", DATADIR, filename); - - if (len == -1) - return NULL; - - return out; -} diff --git a/shared/file-util.h b/shared/file-util.h deleted file mode 100644 index b20bbda..0000000 --- a/shared/file-util.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright © 2015 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_FILE_UTIL_H -#define WESTON_FILE_UTIL_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -FILE * -file_create_dated(const char *path, const char *prefix, const char *suffix, - char *name_out, size_t name_len); - -char * -file_name_with_datadir(const char *); - -#ifdef __cplusplus -} -#endif - -#endif /* WESTON_FILE_UTIL_H */ diff --git a/shared/frame.c b/shared/frame.c deleted file mode 100644 index e8a5cad..0000000 --- a/shared/frame.c +++ /dev/null @@ -1,1021 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * Copyright © 2012-2013 Collabora, Ltd. - * Copyright © 2013 Jason Ekstrand - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include - -#include "cairo-util.h" -#include "shared/file-util.h" - -enum frame_button_flags { - FRAME_BUTTON_ALIGN_RIGHT = 0x1, - FRAME_BUTTON_DECORATED = 0x2, - FRAME_BUTTON_CLICK_DOWN = 0x4, -}; - -struct frame_button { - struct frame *frame; - struct wl_list link; /* buttons_list */ - - cairo_surface_t *icon; - enum frame_button_flags flags; - int hover_count; - int press_count; - - struct { - int x, y; - int width, height; - } allocation; - - enum frame_status status_effect; -}; - -struct frame_pointer_button { - struct wl_list link; - uint32_t button; - enum theme_location press_location; - struct frame_button *frame_button; -}; - -struct frame_pointer { - struct wl_list link; - void *data; - - int x, y; - - struct frame_button *hover_button; - struct wl_list down_buttons; -}; - -struct frame_touch { - struct wl_list link; - void *data; - - int x, y; - - struct frame_button *button; -}; - -struct frame { - int32_t width, height; - char *title; - uint32_t flags; - struct theme *theme; - - struct { - int32_t x, y; - int32_t width, height; - } interior; - int shadow_margin; - int opaque_margin; - int geometry_dirty; - - cairo_rectangle_int_t title_rect; - - uint32_t status; - - struct wl_list buttons; - struct wl_list pointers; - struct wl_list touches; -}; - -static struct frame_button * -frame_button_create_from_surface(struct frame *frame, cairo_surface_t *icon, - enum frame_status status_effect, - enum frame_button_flags flags) -{ - struct frame_button *button; - - button = calloc(1, sizeof *button); - if (!button) - return NULL; - - button->icon = icon; - button->frame = frame; - button->flags = flags; - button->status_effect = status_effect; - - wl_list_insert(frame->buttons.prev, &button->link); - - return button; -} - -static struct frame_button * -frame_button_create(struct frame *frame, const char *icon_name, - enum frame_status status_effect, - enum frame_button_flags flags) -{ - struct frame_button *button; - cairo_surface_t *icon; - - icon = cairo_image_surface_create_from_png(icon_name); - if (cairo_surface_status(icon) != CAIRO_STATUS_SUCCESS) - goto error; - - button = frame_button_create_from_surface(frame, icon, status_effect, - flags); - if (!button) - goto error; - - return button; - -error: - cairo_surface_destroy(icon); - return NULL; -} - -static void -frame_button_destroy(struct frame_button *button) -{ - cairo_surface_destroy(button->icon); - free(button); -} - -static void -frame_button_enter(struct frame_button *button) -{ - if (!button->hover_count) - button->frame->status |= FRAME_STATUS_REPAINT; - button->hover_count++; -} - -static void -frame_button_leave(struct frame_button *button, struct frame_pointer *pointer) -{ - button->hover_count--; - if (!button->hover_count) - button->frame->status |= FRAME_STATUS_REPAINT; -} - -static void -frame_button_press(struct frame_button *button) -{ - if (!button->press_count) - button->frame->status |= FRAME_STATUS_REPAINT; - button->press_count++; - - if (button->flags & FRAME_BUTTON_CLICK_DOWN) - button->frame->status |= button->status_effect; -} - -static void -frame_button_release(struct frame_button *button) -{ - button->press_count--; - if (button->press_count) - return; - - button->frame->status |= FRAME_STATUS_REPAINT; - - if (!(button->flags & FRAME_BUTTON_CLICK_DOWN)) - button->frame->status |= button->status_effect; -} - -static void -frame_button_cancel(struct frame_button *button) -{ - button->press_count--; - if (!button->press_count) - button->frame->status |= FRAME_STATUS_REPAINT; -} - -static void -frame_button_repaint(struct frame_button *button, cairo_t *cr) -{ - int x, y; - - if (!button->allocation.width) - return; - if (!button->allocation.height) - return; - - x = button->allocation.x; - y = button->allocation.y; - - cairo_save(cr); - - if (button->flags & FRAME_BUTTON_DECORATED) { - 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); - - if (button->press_count) { - cairo_set_source_rgb(cr, 0.7, 0.7, 0.7); - } else if (button->hover_count) { - cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); - } else { - cairo_set_source_rgb(cr, 0.88, 0.88, 0.88); - } - - cairo_fill (cr); - - x += 4; - } - - cairo_set_source_surface(cr, button->icon, x, y); - cairo_paint(cr); - - cairo_restore(cr); -} - -static struct frame_pointer * -frame_pointer_get(struct frame *frame, void *data) -{ - struct frame_pointer *pointer; - - wl_list_for_each(pointer, &frame->pointers, link) - if (pointer->data == data) - return pointer; - - pointer = calloc(1, sizeof *pointer); - if (!pointer) - return NULL; - - pointer->data = data; - wl_list_init(&pointer->down_buttons); - wl_list_insert(&frame->pointers, &pointer->link); - - return pointer; -} - -static void -frame_pointer_destroy(struct frame_pointer *pointer) -{ - wl_list_remove(&pointer->link); - free(pointer); -} - -static struct frame_touch * -frame_touch_get(struct frame *frame, void *data) -{ - struct frame_touch *touch; - - wl_list_for_each(touch, &frame->touches, link) - if (touch->data == data) - return touch; - - touch = calloc(1, sizeof *touch); - if (!touch) - return NULL; - - touch->data = data; - wl_list_insert(&frame->touches, &touch->link); - - return touch; -} - -static void -frame_touch_destroy(struct frame_touch *touch) -{ - wl_list_remove(&touch->link); - free(touch); -} - -void -frame_destroy(struct frame *frame) -{ - struct frame_button *button, *next; - struct frame_touch *touch, *next_touch; - struct frame_pointer *pointer, *next_pointer; - - wl_list_for_each_safe(button, next, &frame->buttons, link) - frame_button_destroy(button); - - wl_list_for_each_safe(touch, next_touch, &frame->touches, link) - frame_touch_destroy(touch); - - wl_list_for_each_safe(pointer, next_pointer, &frame->pointers, link) - frame_pointer_destroy(pointer); - - free(frame->title); - free(frame); -} - -struct frame * -frame_create(struct theme *t, int32_t width, int32_t height, uint32_t buttons, - const char *title, cairo_surface_t *icon) -{ - struct frame *frame; - struct frame_button *button; - - frame = calloc(1, sizeof *frame); - if (!frame) - return NULL; - - frame->width = width; - frame->height = height; - frame->flags = 0; - frame->theme = t; - frame->status = FRAME_STATUS_REPAINT; - frame->geometry_dirty = 1; - - wl_list_init(&frame->buttons); - wl_list_init(&frame->pointers); - wl_list_init(&frame->touches); - - if (title) { - frame->title = strdup(title); - if (!frame->title) - goto free_frame; - } - - if (title) { - if (icon) { - button = frame_button_create_from_surface(frame, - icon, - FRAME_STATUS_MENU, - FRAME_BUTTON_CLICK_DOWN); - } else { - char *name = file_name_with_datadir("icon_window.png"); - - if (!name) - goto free_frame; - - button = frame_button_create(frame, - name, - FRAME_STATUS_MENU, - FRAME_BUTTON_CLICK_DOWN); - free(name); - } - if (!button) - goto free_frame; - } - - if (buttons & FRAME_BUTTON_CLOSE) { - char *name = file_name_with_datadir("sign_close.png"); - - if (!name) - goto free_frame; - - button = frame_button_create(frame, - name, - FRAME_STATUS_CLOSE, - FRAME_BUTTON_ALIGN_RIGHT | - FRAME_BUTTON_DECORATED); - free(name); - if (!button) - goto free_frame; - } - - if (buttons & FRAME_BUTTON_MAXIMIZE) { - char *name = file_name_with_datadir("sign_maximize.png"); - - if (!name) - goto free_frame; - - button = frame_button_create(frame, - name, - FRAME_STATUS_MAXIMIZE, - FRAME_BUTTON_ALIGN_RIGHT | - FRAME_BUTTON_DECORATED); - free(name); - if (!button) - goto free_frame; - } - - if (buttons & FRAME_BUTTON_MINIMIZE) { - char *name = file_name_with_datadir("sign_minimize.png"); - - if (!name) - goto free_frame; - - button = frame_button_create(frame, - name, - FRAME_STATUS_MINIMIZE, - FRAME_BUTTON_ALIGN_RIGHT | - FRAME_BUTTON_DECORATED); - free(name); - if (!button) - goto free_frame; - } - - return frame; - -free_frame: - frame_destroy(frame); - return NULL; -} - -int -frame_set_title(struct frame *frame, const char *title) -{ - char *dup = NULL; - - if (title) { - dup = strdup(title); - if (!dup) - return -1; - } - - free(frame->title); - frame->title = dup; - - frame->geometry_dirty = 1; - frame->status |= FRAME_STATUS_REPAINT; - - return 0; -} - -void -frame_set_icon(struct frame *frame, cairo_surface_t *icon) -{ - struct frame_button *button; - wl_list_for_each(button, &frame->buttons, link) { - if (button->status_effect != FRAME_STATUS_MENU) - continue; - if (button->icon) - cairo_surface_destroy(button->icon); - button->icon = icon; - frame->status |= FRAME_STATUS_REPAINT; - } -} - -void -frame_set_flag(struct frame *frame, enum frame_flag flag) -{ - if (flag & FRAME_FLAG_MAXIMIZED && !(frame->flags & FRAME_FLAG_MAXIMIZED)) - frame->geometry_dirty = 1; - - frame->flags |= flag; - frame->status |= FRAME_STATUS_REPAINT; -} - -void -frame_unset_flag(struct frame *frame, enum frame_flag flag) -{ - if (flag & FRAME_FLAG_MAXIMIZED && frame->flags & FRAME_FLAG_MAXIMIZED) - frame->geometry_dirty = 1; - - frame->flags &= ~flag; - frame->status |= FRAME_STATUS_REPAINT; -} - -void -frame_resize(struct frame *frame, int32_t width, int32_t height) -{ - frame->width = width; - frame->height = height; - - frame->geometry_dirty = 1; - frame->status |= FRAME_STATUS_REPAINT; -} - -void -frame_resize_inside(struct frame *frame, int32_t width, int32_t height) -{ - struct theme *t = frame->theme; - int decoration_width, decoration_height, titlebar_height; - - if (frame->title || !wl_list_empty(&frame->buttons)) - titlebar_height = t->titlebar_height; - else - titlebar_height = t->width; - - if (frame->flags & FRAME_FLAG_MAXIMIZED) { - decoration_width = t->width * 2; - decoration_height = t->width + titlebar_height; - } else { - decoration_width = (t->width + t->margin) * 2; - decoration_height = t->width + - titlebar_height + t->margin * 2; - } - - frame_resize(frame, width + decoration_width, - height + decoration_height); -} - -int32_t -frame_width(struct frame *frame) -{ - return frame->width; -} - -int32_t -frame_height(struct frame *frame) -{ - return frame->height; -} - -static void -frame_refresh_geometry(struct frame *frame) -{ - struct frame_button *button; - struct theme *t = frame->theme; - int x_l, x_r, y, w, h, titlebar_height; - int32_t decoration_width, decoration_height; - - if (!frame->geometry_dirty) - return; - - if (frame->title || !wl_list_empty(&frame->buttons)) - titlebar_height = t->titlebar_height; - else - titlebar_height = t->width; - - if (frame->flags & FRAME_FLAG_MAXIMIZED) { - decoration_width = t->width * 2; - decoration_height = t->width + titlebar_height; - - frame->interior.x = t->width; - frame->interior.y = titlebar_height; - frame->interior.width = frame->width - decoration_width; - frame->interior.height = frame->height - decoration_height; - - frame->opaque_margin = 0; - frame->shadow_margin = 0; - } else { - decoration_width = (t->width + t->margin) * 2; - decoration_height = t->width + titlebar_height + t->margin * 2; - - frame->interior.x = t->width + t->margin; - frame->interior.y = titlebar_height + t->margin; - frame->interior.width = frame->width - decoration_width; - frame->interior.height = frame->height - decoration_height; - - frame->opaque_margin = t->margin + t->frame_radius; - frame->shadow_margin = t->margin; - } - - x_r = frame->width - t->width - frame->shadow_margin; - x_l = t->width + frame->shadow_margin; - y = t->width + frame->shadow_margin; - wl_list_for_each(button, &frame->buttons, link) { - const int button_padding = 4; - w = cairo_image_surface_get_width(button->icon); - h = cairo_image_surface_get_height(button->icon); - - if (button->flags & FRAME_BUTTON_DECORATED) - w += 10; - - if (button->flags & FRAME_BUTTON_ALIGN_RIGHT) { - x_r -= w; - - button->allocation.x = x_r; - button->allocation.y = y; - button->allocation.width = w + 1; - button->allocation.height = h + 1; - - x_r -= button_padding; - } else { - button->allocation.x = x_l; - button->allocation.y = y; - button->allocation.width = w + 1; - button->allocation.height = h + 1; - - x_l += w; - x_l += button_padding; - } - } - - frame->title_rect.x = x_l; - frame->title_rect.y = y; - frame->title_rect.width = x_r - x_l; - frame->title_rect.height = titlebar_height; - - frame->geometry_dirty = 0; -} - -void -frame_interior(struct frame *frame, int32_t *x, int32_t *y, - int32_t *width, int32_t *height) -{ - frame_refresh_geometry(frame); - - if (x) - *x = frame->interior.x; - if (y) - *y = frame->interior.y; - if (width) - *width = frame->interior.width; - if (height) - *height = frame->interior.height; -} - -void -frame_input_rect(struct frame *frame, int32_t *x, int32_t *y, - int32_t *width, int32_t *height) -{ - frame_refresh_geometry(frame); - - if (x) - *x = frame->shadow_margin; - if (y) - *y = frame->shadow_margin; - if (width) - *width = frame->width - frame->shadow_margin * 2; - if (height) - *height = frame->height - frame->shadow_margin * 2; -} - -void -frame_opaque_rect(struct frame *frame, int32_t *x, int32_t *y, - int32_t *width, int32_t *height) -{ - frame_refresh_geometry(frame); - - if (x) - *x = frame->opaque_margin; - if (y) - *y = frame->opaque_margin; - if (width) - *width = frame->width - frame->opaque_margin * 2; - if (height) - *height = frame->height - frame->opaque_margin * 2; -} - -int -frame_get_shadow_margin(struct frame *frame) -{ - frame_refresh_geometry(frame); - - return frame->shadow_margin; -} - -uint32_t -frame_status(struct frame *frame) -{ - return frame->status; -} - -void -frame_status_clear(struct frame *frame, enum frame_status status) -{ - frame->status &= ~status; -} - -static struct frame_button * -frame_find_button(struct frame *frame, int x, int y) -{ - struct frame_button *button; - int rel_x, rel_y; - - wl_list_for_each(button, &frame->buttons, link) { - rel_x = x - button->allocation.x; - rel_y = y - button->allocation.y; - - if (0 <= rel_x && rel_x < button->allocation.width && - 0 <= rel_y && rel_y < button->allocation.height) - return button; - } - - return NULL; -} - -enum theme_location -frame_pointer_enter(struct frame *frame, void *data, int x, int y) -{ - return frame_pointer_motion(frame, data, x, y); -} - -enum theme_location -frame_pointer_motion(struct frame *frame, void *data, int x, int y) -{ - struct frame_pointer *pointer = frame_pointer_get(frame, data); - struct frame_button *button = frame_find_button(frame, x, y); - enum theme_location location; - - location = theme_get_location(frame->theme, x, y, - frame->width, frame->height, - frame->flags & FRAME_FLAG_MAXIMIZED ? - THEME_FRAME_MAXIMIZED : 0); - if (!pointer) - return location; - - pointer->x = x; - pointer->y = y; - - if (pointer->hover_button == button) - return location; - - if (pointer->hover_button) - frame_button_leave(pointer->hover_button, pointer); - - pointer->hover_button = button; - - if (pointer->hover_button) - frame_button_enter(pointer->hover_button); - - return location; -} - -static void -frame_pointer_button_destroy(struct frame_pointer_button *button) -{ - wl_list_remove(&button->link); - free(button); -} - -static void -frame_pointer_button_press(struct frame *frame, struct frame_pointer *pointer, - struct frame_pointer_button *button) -{ - if (button->button == BTN_RIGHT) { - if (button->press_location == THEME_LOCATION_TITLEBAR) - frame->status |= FRAME_STATUS_MENU; - - frame_pointer_button_destroy(button); - - } else if (button->button == BTN_LEFT) { - if (pointer->hover_button) { - frame_button_press(pointer->hover_button); - } else { - switch (button->press_location) { - case THEME_LOCATION_TITLEBAR: - frame->status |= FRAME_STATUS_MOVE; - - frame_pointer_button_destroy(button); - 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: - frame->status |= FRAME_STATUS_RESIZE; - - frame_pointer_button_destroy(button); - break; - default: - break; - } - } - } -} - -static void -frame_pointer_button_release(struct frame *frame, struct frame_pointer *pointer, - struct frame_pointer_button *button) -{ - if (button->button == BTN_LEFT && button->frame_button) { - if (button->frame_button == pointer->hover_button) - frame_button_release(button->frame_button); - else - frame_button_cancel(button->frame_button); - } -} - -static void -frame_pointer_button_cancel(struct frame *frame, struct frame_pointer *pointer, - struct frame_pointer_button *button) -{ - if (button->frame_button) - frame_button_cancel(button->frame_button); -} - -void -frame_pointer_leave(struct frame *frame, void *data) -{ - struct frame_pointer *pointer = frame_pointer_get(frame, data); - struct frame_pointer_button *button, *next; - if (!pointer) - return; - - if (pointer->hover_button) - frame_button_leave(pointer->hover_button, pointer); - - wl_list_for_each_safe(button, next, &pointer->down_buttons, link) { - frame_pointer_button_cancel(frame, pointer, button); - frame_pointer_button_destroy(button); - } - - frame_pointer_destroy(pointer); -} - -enum theme_location -frame_pointer_button(struct frame *frame, void *data, - uint32_t btn, enum wl_pointer_button_state state) -{ - struct frame_pointer *pointer = frame_pointer_get(frame, data); - struct frame_pointer_button *button; - enum theme_location location = THEME_LOCATION_EXTERIOR; - - if (!pointer) - return location; - - location = theme_get_location(frame->theme, pointer->x, pointer->y, - frame->width, frame->height, - frame->flags & FRAME_FLAG_MAXIMIZED ? - THEME_FRAME_MAXIMIZED : 0); - - if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - button = malloc(sizeof *button); - if (!button) - return location; - - button->button = btn; - button->press_location = location; - button->frame_button = pointer->hover_button; - wl_list_insert(&pointer->down_buttons, &button->link); - - frame_pointer_button_press(frame, pointer, button); - } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { - button = NULL; - wl_list_for_each(button, &pointer->down_buttons, link) - if (button->button == btn) - break; - /* Make sure we didn't hit the end */ - if (&button->link == &pointer->down_buttons) - return location; - - location = button->press_location; - frame_pointer_button_release(frame, pointer, button); - frame_pointer_button_destroy(button); - } - - return location; -} - -enum theme_location -frame_touch_down(struct frame *frame, void *data, int32_t id, int x, int y) -{ - struct frame_touch *touch = frame_touch_get(frame, data); - struct frame_button *button = frame_find_button(frame, x, y); - enum theme_location location; - - location = theme_get_location(frame->theme, x, y, - frame->width, frame->height, - frame->flags & FRAME_FLAG_MAXIMIZED ? - THEME_FRAME_MAXIMIZED : 0); - - if (id > 0) - return location; - - if (touch && button) { - touch->button = button; - frame_button_press(touch->button); - return location; - } - - switch (location) { - case THEME_LOCATION_TITLEBAR: - frame->status |= FRAME_STATUS_MOVE; - 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: - frame->status |= FRAME_STATUS_RESIZE; - break; - default: - break; - } - return location; -} - -void -frame_touch_up(struct frame *frame, void *data, int32_t id) -{ - struct frame_touch *touch = frame_touch_get(frame, data); - - if (id > 0) - return; - - if (touch && touch->button) { - frame_button_release(touch->button); - frame_touch_destroy(touch); - } -} - -enum theme_location -frame_double_click(struct frame *frame, void *data, - uint32_t btn, enum wl_pointer_button_state state) -{ - struct frame_pointer *pointer = frame_pointer_get(frame, data); - struct frame_button *button; - enum theme_location location = THEME_LOCATION_EXTERIOR; - - location = theme_get_location(frame->theme, pointer->x, pointer->y, - frame->width, frame->height, - frame->flags & FRAME_FLAG_MAXIMIZED ? - THEME_FRAME_MAXIMIZED : 0); - - button = frame_find_button(frame, pointer->x, pointer->y); - - if (location != THEME_LOCATION_TITLEBAR || btn != BTN_LEFT) - return location; - - if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - if (button) - frame_button_press(button); - else - frame->status |= FRAME_STATUS_MAXIMIZE; - } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { - if (button) - frame_button_release(button); - } - - return location; -} - -void -frame_double_touch_down(struct frame *frame, void *data, int32_t id, - int x, int y) -{ - struct frame_touch *touch = frame_touch_get(frame, data); - struct frame_button *button = frame_find_button(frame, x, y); - enum theme_location location; - - if (touch && button) { - touch->button = button; - frame_button_press(touch->button); - return; - } - - location = theme_get_location(frame->theme, x, y, - frame->width, frame->height, - frame->flags & FRAME_FLAG_MAXIMIZED ? - THEME_FRAME_MAXIMIZED : 0); - - switch (location) { - case THEME_LOCATION_TITLEBAR: - frame->status |= FRAME_STATUS_MAXIMIZE; - 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: - frame->status |= FRAME_STATUS_RESIZE; - break; - default: - break; - } -} - -void -frame_double_touch_up(struct frame *frame, void *data, int32_t id) -{ - struct frame_touch *touch = frame_touch_get(frame, data); - - if (touch && touch->button) { - frame_button_release(touch->button); - frame_touch_destroy(touch); - } -} - -void -frame_repaint(struct frame *frame, cairo_t *cr) -{ - struct frame_button *button; - uint32_t flags = 0; - - frame_refresh_geometry(frame); - - if (frame->flags & FRAME_FLAG_MAXIMIZED) - flags |= THEME_FRAME_MAXIMIZED; - - if (frame->flags & FRAME_FLAG_ACTIVE) - flags |= THEME_FRAME_ACTIVE; - - cairo_save(cr); - theme_render_frame(frame->theme, cr, frame->width, frame->height, - frame->title, &frame->title_rect, - &frame->buttons, flags); - cairo_restore(cr); - - wl_list_for_each(button, &frame->buttons, link) - frame_button_repaint(button, cr); - - frame_status_clear(frame, FRAME_STATUS_REPAINT); -} diff --git a/shared/helpers.h b/shared/helpers.h deleted file mode 100644 index adb6889..0000000 --- a/shared/helpers.h +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_HELPERS_H -#define WESTON_HELPERS_H - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @file - * Simple misc helper macros. - */ - -/** - * Compile-time computation of number of items in a hardcoded array. - * - * @param a the array being measured. - * @return the number of items hardcoded into the array. - */ -#ifndef ARRAY_LENGTH -#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) -#endif - -/** - * Returns the smaller of two values. - * - * @param x the first item to compare. - * @param y the second item to compare. - * @return the value that evaluates to lesser than the other. - */ -#ifndef MIN -#define MIN(x,y) (((x) < (y)) ? (x) : (y)) -#endif - -/** - * Returns the bigger of two values. - * - * @param x the first item to compare. - * @param y the second item to compare. - * @return the value that evaluates to more than the other. - */ -#ifndef MAX -#define MAX(x,y) (((x) > (y)) ? (x) : (y)) -#endif - -/** - * Returns a pointer to the containing struct of a given member item. - * - * To demonstrate, the following example retrieves a pointer to - * `example_container` given only its `destroy_listener` member: - * - * @code - * struct example_container { - * struct wl_listener destroy_listener; - * // other members... - * }; - * - * void example_container_destroy(struct wl_listener *listener, void *data) - * { - * struct example_container *ctr; - * - * ctr = wl_container_of(listener, ctr, destroy_listener); - * // destroy ctr... - * } - * @endcode - * - * @param ptr A valid pointer to the contained item. - * - * @param type A pointer to the type of content that the list item - * stores. Type does not need be a valid pointer; a null or - * an uninitialised pointer will suffice. - * - * @param member The named location of ptr within the sample type. - * - * @return The container for the specified pointer. - */ -#ifndef container_of -#define container_of(ptr, type, member) ({ \ - const __typeof__( ((type *)0)->member ) *__mptr = (ptr); \ - (type *)( (char *)__mptr - offsetof(type,member) );}) -#endif - -/** - * Build-time static assertion support - * - * A build-time equivalent to assert(), will generate a compilation error - * if the supplied condition does not evaluate true. - * - * The following example demonstrates use of static_assert to ensure that - * arrays which are supposed to mirror each other have a consistent - * size. - * - * This is only a fallback definition; support must be provided by the - * compiler itself. - * - * @code - * int small[4]; - * long expanded[4]; - * - * static_assert(ARRAY_LENGTH(small) == ARRAY_LENGTH(expanded), - * "size mismatch between small and expanded arrays"); - * for (i = 0; i < ARRAY_LENGTH(small); i++) - * expanded[i] = small[4]; - * @endcode - * - * @param cond Expression to check for truth - * @param msg Message to print on failure - */ -#ifndef static_assert -# ifdef _Static_assert -# define static_assert(cond, msg) _Static_assert(cond, msg) -# else -# define static_assert(cond, msg) -# endif -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* WESTON_HELPERS_H */ diff --git a/shared/image-loader.c b/shared/image-loader.c deleted file mode 100644 index 2385c7a..0000000 --- a/shared/image-loader.c +++ /dev/null @@ -1,440 +0,0 @@ -/* - * Copyright © 2008-2012 Kristian Høgsberg - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "shared/helpers.h" -#include "image-loader.h" - -#ifdef HAVE_JPEG -#include -#endif - -#ifdef HAVE_WEBP -#include -#endif - -static int -stride_for_width(int width) -{ - return width * 4; -} - -static void -pixman_image_destroy_func(pixman_image_t *image, void *data) -{ - free(data); -} - -#ifdef HAVE_JPEG - -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 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; -} - -#else - -static pixman_image_t * -load_jpeg(FILE *fp) -{ - fprintf(stderr, "JPEG support disabled at compile-time\n"); - return NULL; -} - -#endif - -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) { - uint32_t alpha = p[3]; - uint32_t w; - - if (alpha == 0) { - w = 0; - } else { - uint32_t red = p[0]; - uint32_t green = p[1]; - uint32_t 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 *volatile data = NULL; - png_byte **volatile 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); -} - -#else - -static pixman_image_t * -load_webp(FILE *fp) -{ - fprintf(stderr, "WebP support disabled at compile-time\n"); - return NULL; -} - -#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 }, - { { 'R', 'I', 'F', 'F' }, 4, load_webp } -}; - -pixman_image_t * -load_image(const char *filename) -{ - pixman_image_t *image = NULL; - unsigned char header[4]; - FILE *fp; - unsigned int i; - - if (!filename || !*filename) - return NULL; - - fp = fopen(filename, "rb"); - if (!fp) { - fprintf(stderr, "%s: %s\n", filename, strerror(errno)); - return NULL; - } - - if (fread(header, sizeof header, 1, fp) != 1) { - fclose(fp); - fprintf(stderr, "%s: unable to read file header\n", filename); - 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, "%s: unrecognized file header " - "0x%02x 0x%02x 0x%02x 0x%02x\n", - filename, header[0], header[1], header[2], header[3]); - } else if (!image) { - /* load probably printed something, but just in case */ - fprintf(stderr, "%s: error reading image\n", filename); - } - - return image; -} diff --git a/shared/image-loader.h b/shared/image-loader.h deleted file mode 100644 index 7def60b..0000000 --- a/shared/image-loader.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright © 2013 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#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 deleted file mode 100644 index 4e8d6b4..0000000 --- a/shared/matrix.c +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright © 2011 Intel Corporation - * Copyright © 2012 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include - -#ifdef UNIT_TEST -#define WL_EXPORT -#else -#include -#endif - -#include - - -/* - * 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/meson.build b/shared/meson.build deleted file mode 100644 index 8073dcd..0000000 --- a/shared/meson.build +++ /dev/null @@ -1,84 +0,0 @@ -srcs_libshared = [ - 'config-parser.c', - 'option-parser.c', - 'file-util.c', - 'os-compatibility.c', - 'xalloc.c', -] -deps_libshared = dep_wayland_client - -lib_libshared = static_library( - 'shared', - srcs_libshared, - include_directories: common_inc, - dependencies: deps_libshared, - pic: true, - install: false -) -dep_libshared = declare_dependency( - link_with: lib_libshared, - include_directories: public_inc, - dependencies: deps_libshared -) - -srcs_cairo_shared = [ - 'image-loader.c', - 'cairo-util.c', - 'frame.c', -] - -deps_cairo_shared = [ - dep_libshared, - dependency('cairo'), - dependency('libpng'), - dep_pixman, - dep_libm, -] - -dep_pango = dependency('pango', required: false) -dep_pangocairo = dependency('pangocairo', required: false) -dep_glib = dependency('glib-2.0', version: '>= 2.36', required: false) - -if dep_pango.found() and dep_pangocairo.found() and dep_glib.found() - deps_cairo_shared += [ dep_pango, dep_pangocairo, dep_glib ] - config_h.set('HAVE_PANGO', '1') -endif - -if get_option('image-jpeg') - dep_libjpeg = dependency('libjpeg', required: false) - if not dep_libjpeg.found() - dep_libjpeg = cc.find_library('jpeg', required: false) - endif - if not dep_libjpeg.found() - error('JPEG image loading requires libjpeg or jpeg, neither was found. Or, you can use \'-Dimage-jpeg=false\'.') - endif - deps_cairo_shared += dep_libjpeg - config_h.set('HAVE_JPEG', '1') -endif - -if get_option('image-webp') - dep_webp = dependency('libwebp', required: false) - if not dep_webp.found() - error('WEBP image loading requires libwebp which was not found. Or, you can use \'-Dimage-webp=false\'.') - endif - deps_cairo_shared += dep_webp - config_h.set('HAVE_WEBP', '1') -endif - -lib_cairo_shared = static_library( - 'cairo-shared', - srcs_cairo_shared, - include_directories: common_inc, - dependencies: deps_cairo_shared, - install: false -) -dep_lib_cairo_shared = declare_dependency( - link_with: lib_cairo_shared, - dependencies: deps_cairo_shared -) - -dep_matrix_c = declare_dependency( - sources: 'matrix.c', - include_directories: public_inc, - dependencies: dep_libm -) diff --git a/shared/option-parser.c b/shared/option-parser.c deleted file mode 100644 index 55be1b5..0000000 --- a/shared/option-parser.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright © 2012 Kristian Høgsberg - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include "string-helpers.h" - -static bool -handle_option(const struct weston_option *option, char *value) -{ - char* p; - - switch (option->type) { - case WESTON_OPTION_INTEGER: - if (!safe_strtoint(value, option->data)) - return false; - return true; - case WESTON_OPTION_UNSIGNED_INTEGER: - errno = 0; - * (uint32_t *) option->data = strtoul(value, &p, 10); - if (errno != 0 || p == value || *p != '\0') - return false; - return true; - case WESTON_OPTION_STRING: - * (char **) option->data = strdup(value); - return true; - default: - assert(0); - return false; - } -} - -static bool -long_option(const struct weston_option *options, int count, char *arg) -{ - int k, len; - - for (k = 0; k < count; k++) { - if (!options[k].name) - continue; - - len = strlen(options[k].name); - if (strncmp(options[k].name, arg + 2, len) != 0) - continue; - - if (options[k].type == WESTON_OPTION_BOOLEAN) { - if (!arg[len + 2]) { - * (bool *) options[k].data = true; - - return true; - } - } else if (arg[len+2] == '=') { - return handle_option(options + k, arg + len + 3); - } - } - - return false; -} - -static bool -long_option_with_arg(const struct weston_option *options, int count, char *arg, - char *param) -{ - int k, len; - - for (k = 0; k < count; k++) { - if (!options[k].name) - continue; - - len = strlen(options[k].name); - if (strncmp(options[k].name, arg + 2, len) != 0) - continue; - - /* Since long_option() should handle all booleans, we should - * never reach this - */ - assert(options[k].type != WESTON_OPTION_BOOLEAN); - - return handle_option(options + k, param); - } - - return false; -} - -static bool -short_option(const struct weston_option *options, int count, char *arg) -{ - int k; - - if (!arg[1]) - return false; - - for (k = 0; k < count; k++) { - if (options[k].short_name != arg[1]) - continue; - - if (options[k].type == WESTON_OPTION_BOOLEAN) { - if (!arg[2]) { - * (bool *) options[k].data = true; - - return true; - } - } else if (arg[2]) { - return handle_option(options + k, arg + 2); - } else { - return false; - } - } - - return false; -} - -static bool -short_option_with_arg(const struct weston_option *options, int count, char *arg, char *param) -{ - int k; - - if (!arg[1]) - return false; - - for (k = 0; k < count; k++) { - if (options[k].short_name != arg[1]) - continue; - - if (options[k].type == WESTON_OPTION_BOOLEAN) - continue; - - return handle_option(options + k, param); - } - - return false; -} - -int -parse_options(const struct weston_option *options, - int count, int *argc, char *argv[]) -{ - int i, j; - - for (i = 1, j = 1; i < *argc; i++) { - if (argv[i][0] == '-') { - if (argv[i][1] == '-') { - /* Long option, e.g. --foo or --foo=bar */ - if (long_option(options, count, argv[i])) - continue; - - /* ...also handle --foo bar */ - if (i + 1 < *argc && - long_option_with_arg(options, count, - argv[i], argv[i+1])) { - i++; - continue; - } - } else { - /* Short option, e.g -f or -f42 */ - if (short_option(options, count, argv[i])) - continue; - - /* ...also handle -f 42 */ - if (i+1 < *argc && - short_option_with_arg(options, count, argv[i], argv[i+1])) { - i++; - continue; - } - } - } - argv[j++] = argv[i]; - } - argv[j] = NULL; - *argc = j; - - return j; -} diff --git a/shared/os-compatibility.c b/shared/os-compatibility.c deleted file mode 100644 index f761080..0000000 --- a/shared/os-compatibility.c +++ /dev/null @@ -1,407 +0,0 @@ -/* - * Copyright © 2012 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "os-compatibility.h" - -#define READONLY_SEALS (F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE) - -int -os_fd_set_cloexec(int fd) -{ - long flags; - - if (fd == -1) - return -1; - - flags = fcntl(fd, F_GETFD); - if (flags == -1) - return -1; - - if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) - return -1; - - return 0; -} - -static int -set_cloexec_or_close(int fd) -{ - if (os_fd_set_cloexec(fd) != 0) { - close(fd); - return -1; - } - return fd; -} - -int -os_socketpair_cloexec(int domain, int type, int protocol, int *sv) -{ - int ret; - -#ifdef SOCK_CLOEXEC - ret = socketpair(domain, type | SOCK_CLOEXEC, protocol, sv); - if (ret == 0 || errno != EINVAL) - return ret; -#endif - - ret = socketpair(domain, type, protocol, sv); - if (ret < 0) - return ret; - - sv[0] = set_cloexec_or_close(sv[0]); - sv[1] = set_cloexec_or_close(sv[1]); - - if (sv[0] != -1 && sv[1] != -1) - return 0; - - close(sv[0]); - close(sv[1]); - return -1; -} - -int -os_epoll_create_cloexec(void) -{ - int fd; - -#ifdef EPOLL_CLOEXEC - fd = epoll_create1(EPOLL_CLOEXEC); - if (fd >= 0) - return fd; - if (errno != EINVAL) - return -1; -#endif - - fd = epoll_create(1); - return set_cloexec_or_close(fd); -} - -static int -create_tmpfile_cloexec(char *tmpname) -{ - int fd; - -#ifdef HAVE_MKOSTEMP - fd = mkostemp(tmpname, O_CLOEXEC); - if (fd >= 0) - unlink(tmpname); -#else - fd = mkstemp(tmpname); - if (fd >= 0) { - fd = set_cloexec_or_close(fd); - unlink(tmpname); - } -#endif - - return fd; -} - -/* - * Create a new, unique, anonymous file of the given size, and - * return the file descriptor for it. The file descriptor is set - * CLOEXEC. The file is immediately suitable for mmap()'ing - * the given size at offset zero. - * - * The file should not have a permanent backing store like a disk, - * but may have if XDG_RUNTIME_DIR is not properly implemented in OS. - * - * The file name is deleted from the file system. - * - * The file is suitable for buffer sharing between processes by - * transmitting the file descriptor over Unix sockets using the - * SCM_RIGHTS methods. - * - * If the C library implements posix_fallocate(), it is used to - * guarantee that disk space is available for the file at the - * given size. If disk space is insufficient, errno is set to ENOSPC. - * If posix_fallocate() is not supported, program may receive - * SIGBUS on accessing mmap()'ed file contents instead. - * - * If the C library implements memfd_create(), it is used to create the - * file purely in memory, without any backing file name on the file - * system, and then sealing off the possibility of shrinking it. This - * can then be checked before accessing mmap()'ed file contents, to - * make sure SIGBUS can't happen. It also avoids requiring - * XDG_RUNTIME_DIR. - */ -int -os_create_anonymous_file(off_t size) -{ - static const char template[] = "/weston-shared-XXXXXX"; - const char *path; - char *name; - int fd; - int ret; - -#ifdef HAVE_MEMFD_CREATE - fd = memfd_create("weston-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING); - if (fd >= 0) { - /* We can add this seal before calling posix_fallocate(), as - * the file is currently zero-sized anyway. - * - * There is also no need to check for the return value, we - * couldn't do anything with it anyway. - */ - fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK); - } else -#endif - { - path = getenv("XDG_RUNTIME_DIR"); - if (!path) { - errno = ENOENT; - return -1; - } - - name = malloc(strlen(path) + sizeof(template)); - if (!name) - return -1; - - strcpy(name, path); - strcat(name, template); - - fd = create_tmpfile_cloexec(name); - - free(name); - - if (fd < 0) - return -1; - } - -#ifdef HAVE_POSIX_FALLOCATE - do { - ret = posix_fallocate(fd, 0, size); - } while (ret == EINTR); - if (ret != 0) { - close(fd); - errno = ret; - return -1; - } -#else - do { - ret = ftruncate(fd, size); - } while (ret < 0 && errno == EINTR); - if (ret < 0) { - close(fd); - return -1; - } -#endif - - return fd; -} - -#ifndef HAVE_STRCHRNUL -char * -strchrnul(const char *s, int c) -{ - while (*s && *s != c) - s++; - return (char *)s; -} -#endif - -struct ro_anonymous_file { - int fd; - size_t size; -}; - -/** Create a new anonymous read-only file of the given size and the given data - * - * \param size The size of \p data. - * \param data The data of the file with the size \p size. - * \return A new \c ro_anonymous_file, or NULL on failure. - * - * The intended use-case is for sending mid-sized data from the compositor - * to clients. - * If the function fails errno is set. - */ -struct ro_anonymous_file * -os_ro_anonymous_file_create(size_t size, - const char *data) -{ - struct ro_anonymous_file *file; - void *map; - - file = zalloc(sizeof *file); - if (!file) { - errno = ENOMEM; - return NULL; - } - - file->size = size; - file->fd = os_create_anonymous_file(size); - if (file->fd == -1) - goto err_free; - - map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, file->fd, 0); - if (map == MAP_FAILED) - goto err_close; - - memcpy(map, data, size); - - munmap(map, size); - -#ifdef HAVE_MEMFD_CREATE - /* try to put seals on the file to make it read-only so that we can - * return the fd later directly when support_shared is not set. - * os_ro_anonymous_file_get_fd can handle the fd even if it is not - * sealed read-only and will instead create a new anonymous file on - * each invocation. - */ - fcntl(file->fd, F_ADD_SEALS, READONLY_SEALS); -#endif - - return file; - -err_close: - close(file->fd); -err_free: - free(file); - return NULL; -} - -/** Destroy an anonymous read-only file - * - * \param file The file to destroy. - */ -void -os_ro_anonymous_file_destroy(struct ro_anonymous_file *file) -{ - close(file->fd); - free(file); -} - -/** Get the size of an anonymous read-only file - * - * \param file The file to get the size of. - * \return The size of the file. - */ -size_t -os_ro_anonymous_file_size(struct ro_anonymous_file *file) -{ - return file->size; -} - -/** Returns a file descriptor for the given file, ready to be send to a client. - * - * \param file The file for which to get a file descriptor. - * \param mapmode Describes the ways in which the returned file descriptor can - * be used with mmap. - * \return A file descriptor for the given file that can be send to a client - * or -1 on failure. - * - * The returned file descriptor must not be shared between multiple clients. - * When \p mapmode is RO_ANONYMOUS_FILE_MAPMODE_PRIVATE the file descriptor is - * only guaranteed to be mmapable with \c MAP_PRIVATE, when \p mapmode is - * RO_ANONYMOUS_FILE_MAPMODE_SHARED the file descriptor can be mmaped with - * either MAP_PRIVATE or MAP_SHARED. - * When you're done with the fd you must call \c os_ro_anonymous_file_put_fd - * instead of calling \c close. - * If the function fails errno is set. - */ -int -os_ro_anonymous_file_get_fd(struct ro_anonymous_file *file, - enum ro_anonymous_file_mapmode mapmode) -{ - void *src, *dst; - int fd; - -#ifdef HAVE_MEMFD_CREATE - int seals = fcntl(file->fd, F_GET_SEALS); - - /* file was sealed for read-only and we don't have to support MAP_SHARED - * so we can simply pass the memfd fd - */ - if (seals != -1 && mapmode == RO_ANONYMOUS_FILE_MAPMODE_PRIVATE && - (seals & READONLY_SEALS) == READONLY_SEALS) - return file->fd; -#endif - - /* for all other cases we create a new anonymous file that can be mapped - * with MAP_SHARED and copy the contents to it and return that instead - */ - fd = os_create_anonymous_file(file->size); - if (fd == -1) - return fd; - - src = mmap(NULL, file->size, PROT_READ, MAP_PRIVATE, file->fd, 0); - if (src == MAP_FAILED) { - close(fd); - return -1; - } - - dst = mmap(NULL, file->size, PROT_WRITE, MAP_SHARED, fd, 0); - if (dst == MAP_FAILED) { - close(fd); - munmap(src, file->size); - return -1; - } - - memcpy(dst, src, file->size); - munmap(src, file->size); - munmap(dst, file->size); - - return fd; -} - -/** Release a file descriptor returned by \c os_ro_anonymous_file_get_fd - * - * \param fd A file descriptor returned by \c os_ro_anonymous_file_get_fd. - * \return 0 on success, or -1 on failure. - * - * This function must be called for every file descriptor created with - * \c os_ro_anonymous_file_get_fd to not leake any resources. - * If the function fails errno is set. - */ -int -os_ro_anonymous_file_put_fd(int fd) -{ -#ifdef HAVE_MEMFD_CREATE - int seals = fcntl(fd, F_GET_SEALS); - if (seals == -1 && errno != EINVAL) - return -1; - - /* The only case in which we do NOT have to close the file is when the file - * was sealed for read-only - */ - if (seals != -1 && (seals & READONLY_SEALS) == READONLY_SEALS) - return 0; -#endif - - close(fd); - return 0; -} diff --git a/shared/os-compatibility.h b/shared/os-compatibility.h deleted file mode 100644 index 6aaa268..0000000 --- a/shared/os-compatibility.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright © 2012 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef OS_COMPATIBILITY_H -#define OS_COMPATIBILITY_H - -#include "config.h" - -#include - -int -os_fd_set_cloexec(int fd); - -int -os_socketpair_cloexec(int domain, int type, int protocol, int *sv); - -int -os_epoll_create_cloexec(void); - -int -os_create_anonymous_file(off_t size); - -#ifndef HAVE_STRCHRNUL -char * -strchrnul(const char *s, int c); -#endif - -struct ro_anonymous_file; - -enum ro_anonymous_file_mapmode { - RO_ANONYMOUS_FILE_MAPMODE_PRIVATE, - RO_ANONYMOUS_FILE_MAPMODE_SHARED, -}; - -struct ro_anonymous_file * -os_ro_anonymous_file_create(size_t size, - const char *data); - -void -os_ro_anonymous_file_destroy(struct ro_anonymous_file *file); - -size_t -os_ro_anonymous_file_size(struct ro_anonymous_file *file); - -int -os_ro_anonymous_file_get_fd(struct ro_anonymous_file *file, - enum ro_anonymous_file_mapmode mapmode); - -int -os_ro_anonymous_file_put_fd(int fd); - -#endif /* OS_COMPATIBILITY_H */ diff --git a/shared/platform.h b/shared/platform.h deleted file mode 100644 index 9264bb4..0000000 --- a/shared/platform.h +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright © 2015 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_PLATFORM_H -#define WESTON_PLATFORM_H - -#include -#include - -#ifdef ENABLE_EGL -#include -#include -#include - -#include "weston-egl-ext.h" -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef ENABLE_EGL - -static bool -weston_check_egl_extension(const char *extensions, const char *extension) -{ - size_t extlen = strlen(extension); - const char *end = extensions + strlen(extensions); - - while (extensions < end) { - size_t n = 0; - - /* Skip whitespaces, if any */ - if (*extensions == ' ') { - extensions++; - continue; - } - - n = strcspn(extensions, " "); - - /* Compare strings */ - if (n == extlen && strncmp(extension, extensions, n) == 0) - return true; /* Found */ - - extensions += n; - } - - /* Not found */ - return false; -} - -static inline void * -weston_platform_get_egl_proc_address(const char *address) -{ - const char *extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); - - if (extensions && - (weston_check_egl_extension(extensions, "EGL_EXT_platform_wayland") || - weston_check_egl_extension(extensions, "EGL_KHR_platform_wayland"))) { - return (void *) eglGetProcAddress(address); - } - - return NULL; -} - -static inline EGLDisplay -weston_platform_get_egl_display(EGLenum platform, void *native_display, - const EGLint *attrib_list) -{ - static PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display = NULL; - - if (!get_platform_display) { - get_platform_display = (PFNEGLGETPLATFORMDISPLAYEXTPROC) - weston_platform_get_egl_proc_address( - "eglGetPlatformDisplayEXT"); - } - - if (get_platform_display) - return get_platform_display(platform, - native_display, attrib_list); - - return eglGetDisplay((EGLNativeDisplayType) native_display); -} - -static inline EGLSurface -weston_platform_create_egl_surface(EGLDisplay dpy, EGLConfig config, - void *native_window, - const EGLint *attrib_list) -{ - static PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC - create_platform_window = NULL; - - if (!create_platform_window) { - create_platform_window = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC) - weston_platform_get_egl_proc_address( - "eglCreatePlatformWindowSurfaceEXT"); - } - - if (create_platform_window) - return create_platform_window(dpy, config, - native_window, - attrib_list); - - return eglCreateWindowSurface(dpy, config, - (EGLNativeWindowType) native_window, - attrib_list); -} - -static inline EGLBoolean -weston_platform_destroy_egl_surface(EGLDisplay display, - EGLSurface surface) -{ - return eglDestroySurface(display, surface); -} - -#else /* ENABLE_EGL */ - -static inline void * -weston_platform_get_egl_display(int platform, void *native_display, - const int *attrib_list) -{ - return NULL; -} - -static inline void * -weston_platform_create_egl_surface(void *dpy, void *config, - void *native_window, - const int *attrib_list) -{ - return NULL; -} - -static inline unsigned int -weston_platform_destroy_egl_surface(void *display, - void *surface) -{ - return 1; -} -#endif /* ENABLE_EGL */ - -#ifdef __cplusplus -} -#endif - -#endif /* WESTON_PLATFORM_H */ diff --git a/shared/string-helpers.h b/shared/string-helpers.h deleted file mode 100644 index c8ce449..0000000 --- a/shared/string-helpers.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright © 2016 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_STRING_HELPERS_H -#define WESTON_STRING_HELPERS_H - -#include -#include -#include -#include -#include - -/* Convert string to integer - * - * Parses a base-10 number from the given string. Checks that the - * string is not blank, contains only numerical characters, and is - * within the range of INT32_MIN to INT32_MAX. If the validation is - * successful the result is stored in *value; otherwise *value is - * unchanged and errno is set appropriately. - * - * \return true if the number parsed successfully, false on error - */ -static inline bool -safe_strtoint(const char *str, int32_t *value) -{ - long ret; - char *end; - - assert(str != NULL); - - errno = 0; - ret = strtol(str, &end, 10); - if (errno != 0) { - return false; - } else if (end == str || *end != '\0') { - errno = EINVAL; - return false; - } - - if ((long)((int32_t)ret) != ret) { - errno = ERANGE; - return false; - } - *value = (int32_t)ret; - - return true; -} - -#endif /* WESTON_STRING_HELPERS_H */ diff --git a/shared/timespec-util.h b/shared/timespec-util.h deleted file mode 100644 index f79969b..0000000 --- a/shared/timespec-util.h +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright © 2014 - 2015 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef TIMESPEC_UTIL_H -#define TIMESPEC_UTIL_H - -#include -#include -#include -#include - -#define NSEC_PER_SEC 1000000000 - -/* Subtract timespecs - * - * \param r[out] result: a - b - * \param a[in] operand - * \param b[in] operand - */ -static inline void -timespec_sub(struct timespec *r, - const struct timespec *a, const struct timespec *b) -{ - r->tv_sec = a->tv_sec - b->tv_sec; - r->tv_nsec = a->tv_nsec - b->tv_nsec; - if (r->tv_nsec < 0) { - r->tv_sec--; - r->tv_nsec += NSEC_PER_SEC; - } -} - -/* Add a nanosecond value to a timespec - * - * \param r[out] result: a + b - * \param a[in] base operand as timespec - * \param b[in] operand in nanoseconds - */ -static inline void -timespec_add_nsec(struct timespec *r, const struct timespec *a, int64_t b) -{ - r->tv_sec = a->tv_sec + (b / NSEC_PER_SEC); - r->tv_nsec = a->tv_nsec + (b % NSEC_PER_SEC); - - if (r->tv_nsec >= NSEC_PER_SEC) { - r->tv_sec++; - r->tv_nsec -= NSEC_PER_SEC; - } else if (r->tv_nsec < 0) { - r->tv_sec--; - r->tv_nsec += NSEC_PER_SEC; - } -} - -/* Add a millisecond value to a timespec - * - * \param r[out] result: a + b - * \param a[in] base operand as timespec - * \param b[in] operand in milliseconds - */ -static inline void -timespec_add_msec(struct timespec *r, const struct timespec *a, int64_t b) -{ - timespec_add_nsec(r, a, b * 1000000); -} - -/* Convert timespec to nanoseconds - * - * \param a timespec - * \return nanoseconds - */ -static inline int64_t -timespec_to_nsec(const struct timespec *a) -{ - return (int64_t)a->tv_sec * NSEC_PER_SEC + a->tv_nsec; -} - -/* Subtract timespecs and return result in nanoseconds - * - * \param a[in] operand - * \param b[in] operand - * \return to_nanoseconds(a - b) - */ -static inline int64_t -timespec_sub_to_nsec(const struct timespec *a, const struct timespec *b) -{ - struct timespec r; - timespec_sub(&r, a, b); - return timespec_to_nsec(&r); -} - -/* Convert timespec to milliseconds - * - * \param a timespec - * \return milliseconds - * - * Rounding to integer milliseconds happens always down (floor()). - */ -static inline int64_t -timespec_to_msec(const struct timespec *a) -{ - return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000; -} - -/* Subtract timespecs and return result in milliseconds - * - * \param a[in] operand - * \param b[in] operand - * \return to_milliseconds(a - b) - */ -static inline int64_t -timespec_sub_to_msec(const struct timespec *a, const struct timespec *b) -{ - return timespec_sub_to_nsec(a, b) / 1000000; -} - -/* Convert timespec to microseconds - * - * \param a timespec - * \return microseconds - * - * Rounding to integer microseconds happens always down (floor()). - */ -static inline int64_t -timespec_to_usec(const struct timespec *a) -{ - return (int64_t)a->tv_sec * 1000000 + a->tv_nsec / 1000; -} - -/* Convert timespec to protocol data - * - * \param a timespec - * \param tv_sec_hi[out] the high bytes of the seconds part - * \param tv_sec_lo[out] the low bytes of the seconds part - * \param tv_nsec[out] the nanoseconds part - * - * The input timespec must be normalized (the nanoseconds part should - * be less than 1 second) and non-negative. - */ -static inline void -timespec_to_proto(const struct timespec *a, uint32_t *tv_sec_hi, - uint32_t *tv_sec_lo, uint32_t *tv_nsec) -{ - assert(a->tv_sec >= 0); - assert(a->tv_nsec >= 0 && a->tv_nsec < NSEC_PER_SEC); - - uint64_t sec64 = a->tv_sec; - - *tv_sec_hi = sec64 >> 32; - *tv_sec_lo = sec64 & 0xffffffff; - *tv_nsec = a->tv_nsec; -} - -/* Convert nanoseconds to timespec - * - * \param a timespec - * \param b nanoseconds - */ -static inline void -timespec_from_nsec(struct timespec *a, int64_t b) -{ - a->tv_sec = b / NSEC_PER_SEC; - a->tv_nsec = b % NSEC_PER_SEC; -} - -/* Convert microseconds to timespec - * - * \param a timespec - * \param b microseconds - */ -static inline void -timespec_from_usec(struct timespec *a, int64_t b) -{ - timespec_from_nsec(a, b * 1000); -} - -/* Convert milliseconds to timespec - * - * \param a timespec - * \param b milliseconds - */ -static inline void -timespec_from_msec(struct timespec *a, int64_t b) -{ - timespec_from_nsec(a, b * 1000000); -} - -/* Convert protocol data to timespec - * - * \param a[out] timespec - * \param tv_sec_hi the high bytes of seconds part - * \param tv_sec_lo the low bytes of seconds part - * \param tv_nsec the nanoseconds part - */ -static inline void -timespec_from_proto(struct timespec *a, uint32_t tv_sec_hi, - uint32_t tv_sec_lo, uint32_t tv_nsec) -{ - a->tv_sec = ((uint64_t)tv_sec_hi << 32) + tv_sec_lo; - a->tv_nsec = tv_nsec; -} - -/* Check if a timespec is zero - * - * \param a timespec - * \return whether the timespec is zero - */ -static inline bool -timespec_is_zero(const struct timespec *a) -{ - return a->tv_sec == 0 && a->tv_nsec == 0; -} - -/* Check if two timespecs are equal - * - * \param a[in] timespec to check - * \param b[in] timespec to check - * \return whether timespecs a and b are equal - */ -static inline bool -timespec_eq(const struct timespec *a, const struct timespec *b) -{ - return a->tv_sec == b->tv_sec && - a->tv_nsec == b->tv_nsec; -} - -/* Convert milli-Hertz to nanoseconds - * - * \param mhz frequency in mHz, not zero - * \return period in nanoseconds - */ -static inline int64_t -millihz_to_nsec(uint32_t mhz) -{ - assert(mhz > 0); - return 1000000000000LL / mhz; -} - -#endif /* TIMESPEC_UTIL_H */ diff --git a/shared/weston-egl-ext.h b/shared/weston-egl-ext.h deleted file mode 100644 index 4a757c8..0000000 --- a/shared/weston-egl-ext.h +++ /dev/null @@ -1,221 +0,0 @@ -/* - * 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, 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. - */ -/* Extensions used by Weston, copied from Mesa's eglmesaext.h, */ - -#ifndef WESTON_EGL_EXT_H -#define WESTON_EGL_EXT_H - -#ifdef ENABLE_EGL - -#ifndef EGL_WL_bind_wayland_display -#define EGL_WL_bind_wayland_display 1 - -struct wl_display; - -#ifdef EGL_EGLEXT_PROTOTYPES -EGLAPI EGLBoolean EGLAPIENTRY eglBindWaylandDisplayWL(EGLDisplay dpy, struct wl_display *display); -EGLAPI EGLBoolean EGLAPIENTRY eglUnbindWaylandDisplayWL(EGLDisplay dpy, struct wl_display *display); -#endif -typedef EGLBoolean (EGLAPIENTRYP PFNEGLBINDWAYLANDDISPLAYWL) (EGLDisplay dpy, struct wl_display *display); -typedef EGLBoolean (EGLAPIENTRYP PFNEGLUNBINDWAYLANDDISPLAYWL) (EGLDisplay dpy, struct wl_display *display); -#endif - -/* - * This is a little different to the tests shipped with EGL implementations, - * which wrap the entire thing in #ifndef EGL_WL_bind_wayland_display, then go - * on to define both BindWaylandDisplay and QueryWaylandBuffer. - * - * Unfortunately, some implementations (particularly the version of Mesa shipped - * in Ubuntu 12.04) define EGL_WL_bind_wayland_display, but then only provide - * prototypes for (Un)BindWaylandDisplay, completely omitting - * QueryWaylandBuffer. - * - * Detect this, and provide our own definitions if necessary. - */ -#ifndef EGL_WAYLAND_BUFFER_WL -#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_resource; -#ifdef EGL_EGLEXT_PROTOTYPES -EGLAPI EGLBoolean EGLAPIENTRY eglQueryWaylandBufferWL(EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value); -#endif -typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYWAYLANDBUFFERWL) (EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value); -#endif - -#ifndef EGL_WL_create_wayland_buffer_from_image -#define EGL_WL_create_wayland_buffer_from_image 1 - -#ifdef EGL_EGLEXT_PROTOTYPES -EGLAPI struct wl_buffer * EGLAPIENTRY eglCreateWaylandBufferFromImageWL(EGLDisplay dpy, EGLImageKHR image); -#endif -typedef struct wl_buffer * (EGLAPIENTRYP PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWL) (EGLDisplay dpy, EGLImageKHR image); -#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 - -#ifndef GL_EXT_unpack_subimage -#define GL_EXT_unpack_subimage 1 -#define GL_UNPACK_ROW_LENGTH_EXT 0x0CF2 -#define GL_UNPACK_SKIP_ROWS_EXT 0x0CF3 -#define GL_UNPACK_SKIP_PIXELS_EXT 0x0CF4 -#endif /* GL_EXT_unpack_subimage */ - -/* 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 - -/* Define needed tokens from EGL_EXT_image_dma_buf_import extension - * here to avoid having to add ifdefs everywhere.*/ -#ifndef EGL_EXT_image_dma_buf_import -#define EGL_LINUX_DMA_BUF_EXT 0x3270 -#define EGL_LINUX_DRM_FOURCC_EXT 0x3271 -#define EGL_DMA_BUF_PLANE0_FD_EXT 0x3272 -#define EGL_DMA_BUF_PLANE0_OFFSET_EXT 0x3273 -#define EGL_DMA_BUF_PLANE0_PITCH_EXT 0x3274 -#define EGL_DMA_BUF_PLANE1_FD_EXT 0x3275 -#define EGL_DMA_BUF_PLANE1_OFFSET_EXT 0x3276 -#define EGL_DMA_BUF_PLANE1_PITCH_EXT 0x3277 -#define EGL_DMA_BUF_PLANE2_FD_EXT 0x3278 -#define EGL_DMA_BUF_PLANE2_OFFSET_EXT 0x3279 -#define EGL_DMA_BUF_PLANE2_PITCH_EXT 0x327A -#endif - -/* Define tokens from EGL_EXT_image_dma_buf_import_modifiers */ -#ifndef EGL_EXT_image_dma_buf_import_modifiers -#define EGL_EXT_image_dma_buf_import_modifiers 1 -#define EGL_DMA_BUF_PLANE3_FD_EXT 0x3440 -#define EGL_DMA_BUF_PLANE3_OFFSET_EXT 0x3441 -#define EGL_DMA_BUF_PLANE3_PITCH_EXT 0x3442 -#define EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT 0x3443 -#define EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT 0x3444 -#define EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT 0x3445 -#define EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT 0x3446 -#define EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT 0x3447 -#define EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT 0x3448 -#define EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT 0x3449 -#define EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT 0x344A -typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFFORMATSEXTPROC) (EGLDisplay dpy, EGLint max_formats, EGLint *formats, EGLint *num_formats); -typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFMODIFIERSEXTPROC) (EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR *modifiers, EGLBoolean *external_only, EGLint *num_modifiers); -#endif - -#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 /* EGL_EXT_swap_buffers_with_damage */ - -#ifndef EGL_MESA_configless_context -#define EGL_MESA_configless_context 1 -#define EGL_NO_CONFIG_MESA ((EGLConfig)0) -#endif - -#ifndef EGL_NO_CONFIG_KHR -#define EGL_NO_CONFIG_KHR ((EGLConfig)0) -#endif - -#ifndef EGL_EXT_platform_base -#define EGL_EXT_platform_base 1 -typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC) (EGLenum platform, void *native_display, const EGLint *attrib_list); -typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC) (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLint *attrib_list); -typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMPIXMAPSURFACEEXTPROC) (EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLint *attrib_list); -#endif /* EGL_EXT_platform_base */ - -#ifndef EGL_PLATFORM_GBM_KHR -#define EGL_PLATFORM_GBM_KHR 0x31D7 -#endif - -#ifndef EGL_PLATFORM_WAYLAND_KHR -#define EGL_PLATFORM_WAYLAND_KHR 0x31D8 -#endif - -#ifndef EGL_PLATFORM_X11_KHR -#define EGL_PLATFORM_X11_KHR 0x31D5 -#endif - -#ifndef EGL_PLATFORM_SURFACELESS_MESA -#define EGL_PLATFORM_SURFACELESS_MESA 0x31DD -#endif - -#ifndef EGL_KHR_cl_event2 -#define EGL_KHR_cl_event2 1 -typedef void *EGLSyncKHR; -#endif /* EGL_KHR_cl_event2 */ - -#ifndef EGL_NO_SYNC_KHR -#define EGL_NO_SYNC_KHR ((EGLSyncKHR)0) -#endif - -#ifndef EGL_KHR_fence_sync -#define EGL_KHR_fence_sync 1 -typedef EGLSyncKHR (EGLAPIENTRYP PFNEGLCREATESYNCKHRPROC) (EGLDisplay dpy, EGLenum type, const EGLint *attrib_list); -typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSYNCKHRPROC) (EGLDisplay dpy, EGLSyncKHR sync); -#endif /* EGL_KHR_fence_sync */ - -#ifndef EGL_ANDROID_native_fence_sync -#define EGL_ANDROID_native_fence_sync 1 -typedef EGLint (EGLAPIENTRYP PFNEGLDUPNATIVEFENCEFDANDROIDPROC) (EGLDisplay dpy, EGLSyncKHR sync); -#endif /* EGL_ANDROID_native_fence_sync */ - -#ifndef EGL_SYNC_NATIVE_FENCE_ANDROID -#define EGL_SYNC_NATIVE_FENCE_ANDROID 0x3144 -#endif - -#ifndef EGL_NO_NATIVE_FENCE_FD_ANDROID -#define EGL_NO_NATIVE_FENCE_FD_ANDROID -1 -#endif - -#else /* ENABLE_EGL */ - -/* EGL platform definition are keept to allow compositor-xx.c to build */ -#define EGL_PLATFORM_GBM_KHR 0x31D7 -#define EGL_PLATFORM_WAYLAND_KHR 0x31D8 -#define EGL_PLATFORM_X11_KHR 0x31D5 -#define EGL_PLATFORM_SURFACELESS_MESA 0x31DD - -#endif /* ENABLE_EGL */ - -#endif diff --git a/shared/xalloc.c b/shared/xalloc.c deleted file mode 100644 index 1b24937..0000000 --- a/shared/xalloc.c +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include - -#include "xalloc.h" - -// OHOS build -#ifndef program_invocation_short_name -#define program_invocation_short_name __func__ -#endif - -void * -fail_on_null(void *p, size_t size, char *file, int32_t line) -{ - if (p == NULL) { - fprintf(stderr, "[%s] ", program_invocation_short_name); - if (file) - fprintf(stderr, "%s:%d: ", file, line); - fprintf(stderr, "out of memory"); - if (size) - fprintf(stderr, " (%zd)", size); - fprintf(stderr, "\n"); - exit(EXIT_FAILURE); - } - - return p; -} diff --git a/shared/xalloc.h b/shared/xalloc.h deleted file mode 100644 index cd39dd8..0000000 --- a/shared/xalloc.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_XALLOC_H -#define WESTON_XALLOC_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -#include - -void * -fail_on_null(void *p, size_t size, char *file, int32_t line); - -#define xmalloc(s) (fail_on_null(malloc(s), (s), __FILE__, __LINE__)) -#define xzalloc(s) (fail_on_null(zalloc(s), (s), __FILE__, __LINE__)) -#define xstrdup(s) (fail_on_null(strdup(s), 0, __FILE__, __LINE__)) -#define xrealloc(p, s) (fail_on_null(realloc(p, s), (s), __FILE__, __LINE__)) - -#ifdef __cplusplus -} -#endif - -#endif /* WESTON_XALLOC_H */ diff --git a/test-qc.c b/test-qc.c deleted file mode 100644 index 8040ffc..0000000 --- a/test-qc.c +++ /dev/null @@ -1,2 +0,0 @@ - -this is check test 2021-7-22) diff --git a/tests/bad-buffer-test.c b/tests/bad-buffer-test.c deleted file mode 100644 index 360a81c..0000000 --- a/tests/bad-buffer-test.c +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * Copyright © 2013 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include - -#include "shared/os-compatibility.h" -#include "weston-test-client-helper.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -/* These three functions are copied from shared/os-compatibility.c in order to - * behave like older clients, and allow ftruncate() to shrink the file’s size, - * so SIGBUS can still happen. - * - * There is no reason not to use os_create_anonymous_file() otherwise. */ - -#ifndef HAVE_MKOSTEMP -static int -set_cloexec_or_close(int fd) -{ - if (os_fd_set_cloexec(fd) != 0) { - close(fd); - return -1; - } - return fd; -} -#endif - -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; -} - -static int -create_anonymous_file_without_seals(off_t size) -{ - static const char template[] = "/weston-test-XXXXXX"; - const char *path; - char *name; - int fd; - int ret; - - path = getenv("XDG_RUNTIME_DIR"); - if (!path) { - errno = ENOENT; - return -1; - } - - name = malloc(strlen(path) + sizeof(template)); - if (!name) - return -1; - - strcpy(name, path); - strcat(name, template); - - fd = create_tmpfile_cloexec(name); - - free(name); - - if (fd < 0) - return -1; - -#ifdef HAVE_POSIX_FALLOCATE - do { - ret = posix_fallocate(fd, 0, size); - } while (ret == EINTR); - if (ret != 0) { - close(fd); - errno = ret; - return -1; - } -#else - do { - ret = ftruncate(fd, size); - } while (ret < 0 && errno == EINTR); - if (ret < 0) { - close(fd); - return -1; - } -#endif - - return fd; -} - -/* tests, that attempt to crash the compositor on purpose */ - -static struct wl_buffer * -create_bad_shm_buffer(struct client *client, int width, int height) -{ - 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; - - fd = create_anonymous_file_without_seals(size); - assert(fd >= 0); - - 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); - - /* Truncate the file to a small size, so that the compositor - * will access it out-of-bounds, and hit SIGBUS. - */ - assert(ftruncate(fd, 12) == 0); - close(fd); - - return buffer; -} - -TEST(test_truncated_shm_file) -{ - struct client *client; - struct wl_buffer *bad_buffer; - struct wl_surface *surface; - int frame; - - client = create_client_and_test_surface(46, 76, 111, 134); - assert(client); - surface = client->surface->wl_surface; - - bad_buffer = create_bad_shm_buffer(client, 200, 200); - - wl_surface_attach(surface, bad_buffer, 0, 0); - wl_surface_damage(surface, 0, 0, 200, 200); - frame_callback_set(surface, &frame); - wl_surface_commit(surface); - frame_callback_wait_nofail(client, &frame); - - expect_protocol_error(client, &wl_buffer_interface, - WL_SHM_ERROR_INVALID_FD); -} diff --git a/tests/buffer-transforms-test.c b/tests/buffer-transforms-test.c deleted file mode 100644 index fdc86bb..0000000 --- a/tests/buffer-transforms-test.c +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright © 2020 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include - -#include "weston-test-client-helper.h" -#include "weston-test-fixture-compositor.h" - -#define TRANSFORM(x) WL_OUTPUT_TRANSFORM_ ## x, #x -#define RENDERERS(s, t) \ - { RENDERER_PIXMAN, s, TRANSFORM(t) }, \ - { RENDERER_GL, s, TRANSFORM(t) } - -struct setup_args { - enum renderer_type renderer; - int scale; - enum wl_output_transform transform; - const char *transform_name; -}; - -static const struct setup_args my_setup_args[] = { - RENDERERS(1, NORMAL), - RENDERERS(2, 90), -}; - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg) -{ - struct compositor_setup setup; - - /* The width and height are chosen to produce 324x240 framebuffer, to - * emulate keeping the video mode constant. - * This resolution is divisible by 2 and 3. - * Headless multiplies the given size by scale. - */ - - compositor_setup_defaults(&setup); - setup.renderer = arg->renderer; - setup.width = 324 / arg->scale; - setup.height = 240 / arg->scale; - setup.scale = arg->scale; - setup.transform = arg->transform; - setup.shell = SHELL_TEST_DESKTOP; - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args); - -struct buffer_args { - int scale; - enum wl_output_transform transform; - const char *transform_name; -}; - -static const struct buffer_args my_buffer_args[] = { - /* { 1, TRANSFORM(NORMAL) }, done in output-transforms-test.c */ - { 1, TRANSFORM(90) }, - { 1, TRANSFORM(180) }, - { 1, TRANSFORM(270) }, - { 1, TRANSFORM(FLIPPED) }, - { 1, TRANSFORM(FLIPPED_90) }, - { 1, TRANSFORM(FLIPPED_180) }, - { 1, TRANSFORM(FLIPPED_270) }, - { 2, TRANSFORM(NORMAL) }, - /* { 2, TRANSFORM(90) }, done in output-transforms-test.c */ - { 2, TRANSFORM(180) }, - { 2, TRANSFORM(FLIPPED) }, - { 3, TRANSFORM(NORMAL) }, - { 3, TRANSFORM(FLIPPED_90) }, -}; - -TEST_P(buffer_transform, my_buffer_args) -{ - const struct buffer_args *bargs = data; - const struct setup_args *oargs; - struct client *client; - bool match; - char *refname; - int ret; - - oargs = &my_setup_args[get_test_fixture_index()]; - - ret = asprintf(&refname, "output_%d-%s_buffer_%d-%s", - oargs->scale, oargs->transform_name, - bargs->scale, bargs->transform_name); - assert(ret); - - testlog("%s: %s\n", get_test_name(), refname); - - /* - * NOTE! The transform set below is a lie. - * Take that into account when analyzing screenshots. - */ - - client = create_client(); - client->surface = create_test_surface(client); - client->surface->width = 10000; /* used only for damage */ - client->surface->height = 10000; - client->surface->buffer = client_buffer_from_image_file(client, - "basic-test-card", - bargs->scale); - wl_surface_set_buffer_scale(client->surface->wl_surface, bargs->scale); - wl_surface_set_buffer_transform(client->surface->wl_surface, - bargs->transform); - move_client(client, 19, 19); - - match = verify_screen_content(client, refname, 0, NULL, 0); - assert(match); - - client_destroy(client); -} diff --git a/tests/config-parser-test.c b/tests/config-parser-test.c deleted file mode 100644 index 583c83f..0000000 --- a/tests/config-parser-test.c +++ /dev/null @@ -1,632 +0,0 @@ -/* - * Copyright © 2013 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "shared/helpers.h" -#include "zunitc/zunitc.h" - -struct fixture_data { - const char *text; - struct weston_config *config; -}; - -static struct weston_config * -load_config(const char *text) -{ - struct weston_config *config = NULL; - int len = 0; - int fd = -1; - char file[] = "/tmp/weston-config-parser-test-XXXXXX"; - - ZUC_ASSERTG_NOT_NULL(text, out); - - fd = mkstemp(file); - ZUC_ASSERTG_NE(-1, fd, out); - - len = write(fd, text, strlen(text)); - ZUC_ASSERTG_EQ((int)strlen(text), len, out_close); - - config = weston_config_parse(file); - -out_close: - close(fd); - unlink(file); -out: - return config; -} - -static void * -setup_test_config(void *data) -{ - struct weston_config *config = load_config(data); - ZUC_ASSERTG_NOT_NULL(config, out); - -out: - return config; -} - -static void * -setup_test_config_failing(void *data) -{ - struct weston_config *config = load_config(data); - ZUC_ASSERTG_NULL(config, err_free); - - return config; -err_free: - weston_config_destroy(config); - return NULL; -} - -static void -cleanup_test_config(void *data) -{ - struct weston_config *config = data; - ZUC_ASSERT_NOT_NULL(config); - weston_config_destroy(config); -} - -static struct zuc_fixture config_test_t0 = { - .data = "# nothing in this file...\n", - .set_up = setup_test_config, - .tear_down = cleanup_test_config -}; - -static struct zuc_fixture config_test_t1 = { - .data = - "# comment line here...\n" - "\n" - "[foo]\n" - "a=b\n" - "name= Roy Batty \n" - "\n" - "\n" - "[bar]\n" - "# more comments\n" - "number=5252\n" - "zero=0\n" - "negative=-42\n" - "flag=false\n" - "\n" - "[colors]\n" - "none=0x00000000\n" - "low=0x11223344\n" - "high=0xff00ff00\n" - "oct=01234567\n" - "dec=12345670\n" - "short=1234567\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", - .set_up = setup_test_config, - .tear_down = cleanup_test_config -}; - -static const char *section_names[] = { - "foo", "bar", "colors", "stuff", "bucket", "bucket" -}; - -/* - * Since these next few won't parse, we don't add the tear_down to - * attempt cleanup. - */ - -static struct zuc_fixture config_test_t2 = { - .data = - "# invalid section...\n" - "[this bracket isn't closed\n", - .set_up = setup_test_config_failing, -}; - -static struct zuc_fixture config_test_t3 = { - .data = - "# line without = ...\n" - "[bambam]\n" - "this line isn't any kind of valid\n", - .set_up = setup_test_config_failing, -}; - -static struct zuc_fixture config_test_t4 = { - .data = - "# starting with = ...\n" - "[bambam]\n" - "=not valid at all\n", - .set_up = setup_test_config_failing, -}; - -ZUC_TEST_F(config_test_t0, comment_only, data) -{ - struct weston_config *config = data; - ZUC_ASSERT_NOT_NULL(config); -} - -/** @todo individual t1 tests should have more descriptive names. */ - -ZUC_TEST_F(config_test_t1, test001, data) -{ - struct weston_config_section *section; - struct weston_config *config = data; - ZUC_ASSERT_NOT_NULL(config); - section = weston_config_get_section(config, - "mollusc", NULL, NULL); - ZUC_ASSERT_NULL(section); -} - -ZUC_TEST_F(config_test_t1, test002, data) -{ - char *s; - int r; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "foo", NULL, NULL); - r = weston_config_section_get_string(section, "a", &s, NULL); - - ZUC_ASSERTG_EQ(0, r, out_free); - ZUC_ASSERTG_STREQ("b", s, out_free); - -out_free: - free(s); -} - -ZUC_TEST_F(config_test_t1, test003, data) -{ - char *s; - int r; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "foo", NULL, NULL); - r = weston_config_section_get_string(section, "b", &s, NULL); - - ZUC_ASSERT_EQ(-1, r); - ZUC_ASSERT_EQ(ENOENT, errno); - ZUC_ASSERT_NULL(s); -} - -ZUC_TEST_F(config_test_t1, test004, data) -{ - char *s; - int r; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "foo", NULL, NULL); - r = weston_config_section_get_string(section, "name", &s, NULL); - - ZUC_ASSERTG_EQ(0, r, out_free); - ZUC_ASSERTG_STREQ("Roy Batty", s, out_free); - -out_free: - free(s); -} - -ZUC_TEST_F(config_test_t1, test005, data) -{ - char *s; - int r; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "bar", NULL, NULL); - r = weston_config_section_get_string(section, "a", &s, "boo"); - - ZUC_ASSERTG_EQ(-1, r, out_free); - ZUC_ASSERTG_EQ(ENOENT, errno, out_free); - ZUC_ASSERTG_STREQ("boo", s, out_free); - -out_free: - free(s); -} - -ZUC_TEST_F(config_test_t1, test006, data) -{ - int r; - int32_t n; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "bar", NULL, NULL); - r = weston_config_section_get_int(section, "number", &n, 600); - - ZUC_ASSERT_EQ(0, r); - ZUC_ASSERT_EQ(5252, n); - ZUC_ASSERT_EQ(0, errno); -} - -ZUC_TEST_F(config_test_t1, test007, data) -{ - int r; - int32_t n; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "bar", NULL, NULL); - r = weston_config_section_get_int(section, "+++", &n, 700); - - ZUC_ASSERT_EQ(-1, r); - ZUC_ASSERT_EQ(ENOENT, errno); - ZUC_ASSERT_EQ(700, n); -} - -ZUC_TEST_F(config_test_t1, test008, data) -{ - int r; - uint32_t u; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "bar", NULL, NULL); - r = weston_config_section_get_uint(section, "number", &u, 600); - - ZUC_ASSERT_EQ(0, r); - ZUC_ASSERT_EQ(5252, u); - ZUC_ASSERT_EQ(0, errno); -} - -ZUC_TEST_F(config_test_t1, test009, data) -{ - int r; - uint32_t u; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "bar", NULL, NULL); - r = weston_config_section_get_uint(section, "+++", &u, 600); - ZUC_ASSERT_EQ(-1, r); - ZUC_ASSERT_EQ(ENOENT, errno); - ZUC_ASSERT_EQ(600, u); -} - -ZUC_TEST_F(config_test_t1, test010, data) -{ - int r; - bool b; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "bar", NULL, NULL); - r = weston_config_section_get_bool(section, "flag", &b, true); - ZUC_ASSERT_EQ(0, r); - ZUC_ASSERT_EQ(false, b); -} - -ZUC_TEST_F(config_test_t1, test011, data) -{ - int r; - bool b; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "stuff", NULL, NULL); - r = weston_config_section_get_bool(section, "flag", &b, false); - ZUC_ASSERT_EQ(0, r); - ZUC_ASSERT_EQ(true, b); -} - -ZUC_TEST_F(config_test_t1, test012, data) -{ - int r; - bool b; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "stuff", NULL, NULL); - r = weston_config_section_get_bool(section, "bonk", &b, false); - ZUC_ASSERT_EQ(-1, r); - ZUC_ASSERT_EQ(ENOENT, errno); - ZUC_ASSERT_EQ(false, b); -} - -ZUC_TEST_F(config_test_t1, test013, data) -{ - char *s; - int r; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, - "bucket", "color", "blue"); - r = weston_config_section_get_string(section, "contents", &s, NULL); - - ZUC_ASSERTG_EQ(0, r, out_free); - ZUC_ASSERTG_STREQ("live crabs", s, out_free); - -out_free: - free(s); -} - -ZUC_TEST_F(config_test_t1, test014, data) -{ - char *s; - int r; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, - "bucket", "color", "red"); - r = weston_config_section_get_string(section, "contents", &s, NULL); - - ZUC_ASSERTG_EQ(0, r, out_free); - ZUC_ASSERTG_STREQ("sand", s, out_free); - -out_free: - free(s); -} - -ZUC_TEST_F(config_test_t1, test015, data) -{ - char *s; - int r; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, - "bucket", "color", "pink"); - ZUC_ASSERT_NULL(section); - r = weston_config_section_get_string(section, "contents", &s, "eels"); - - ZUC_ASSERTG_EQ(-1, r, out_free); - ZUC_ASSERTG_EQ(ENOENT, errno, out_free); - ZUC_ASSERTG_STREQ("eels", s, out_free); - -out_free: - free(s); -} - -ZUC_TEST_F(config_test_t1, test016, data) -{ - const char *name; - int i; - struct weston_config_section *section; - struct weston_config *config = data; - - section = NULL; - i = 0; - while (weston_config_next_section(config, §ion, &name)) - ZUC_ASSERT_STREQ(section_names[i++], name); - - ZUC_ASSERT_EQ(6, i); -} - -ZUC_TEST_F(config_test_t1, test017, data) -{ - int r; - int32_t n; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "bar", NULL, NULL); - r = weston_config_section_get_int(section, "zero", &n, 600); - - ZUC_ASSERT_EQ(0, r); - ZUC_ASSERT_EQ(0, n); - ZUC_ASSERT_EQ(0, errno); -} - -ZUC_TEST_F(config_test_t1, test018, data) -{ - int r; - uint32_t n; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "bar", NULL, NULL); - r = weston_config_section_get_uint(section, "zero", &n, 600); - - ZUC_ASSERT_EQ(0, r); - ZUC_ASSERT_EQ(0, n); - ZUC_ASSERT_EQ(0, errno); -} - -ZUC_TEST_F(config_test_t1, test019, data) -{ - int r; - uint32_t n; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "colors", NULL, NULL); - r = weston_config_section_get_color(section, "none", &n, 0xff336699); - - ZUC_ASSERT_EQ(0, r); - ZUC_ASSERT_EQ(0x000000, n); - ZUC_ASSERT_EQ(0, errno); -} - -ZUC_TEST_F(config_test_t1, test020, data) -{ - int r; - uint32_t n; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "colors", NULL, NULL); - r = weston_config_section_get_color(section, "low", &n, 0xff336699); - - ZUC_ASSERT_EQ(0, r); - ZUC_ASSERT_EQ(0x11223344, n); - ZUC_ASSERT_EQ(0, errno); -} - -ZUC_TEST_F(config_test_t1, test021, data) -{ - int r; - uint32_t n; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "colors", NULL, NULL); - r = weston_config_section_get_color(section, "high", &n, 0xff336699); - - ZUC_ASSERT_EQ(0, r); - ZUC_ASSERT_EQ(0xff00ff00, n); - ZUC_ASSERT_EQ(0, errno); -} - -ZUC_TEST_F(config_test_t1, test022, data) -{ - int r; - uint32_t n; - struct weston_config_section *section; - struct weston_config *config = data; - - // Treat colors as hex values even if missing the leading 0x - section = weston_config_get_section(config, "colors", NULL, NULL); - r = weston_config_section_get_color(section, "oct", &n, 0xff336699); - - ZUC_ASSERT_EQ(0, r); - ZUC_ASSERT_EQ(0x01234567, n); - ZUC_ASSERT_EQ(0, errno); -} - -ZUC_TEST_F(config_test_t1, test023, data) -{ - int r; - uint32_t n; - struct weston_config_section *section; - struct weston_config *config = data; - - // Treat colors as hex values even if missing the leading 0x - section = weston_config_get_section(config, "colors", NULL, NULL); - r = weston_config_section_get_color(section, "dec", &n, 0xff336699); - - ZUC_ASSERT_EQ(0, r); - ZUC_ASSERT_EQ(0x12345670, n); - ZUC_ASSERT_EQ(0, errno); -} - -ZUC_TEST_F(config_test_t1, test024, data) -{ - int r; - uint32_t n; - struct weston_config_section *section; - struct weston_config *config = data; - - // 7-digit colors are not valid (most likely typos) - section = weston_config_get_section(config, "colors", NULL, NULL); - r = weston_config_section_get_color(section, "short", &n, 0xff336699); - - ZUC_ASSERT_EQ(-1, r); - ZUC_ASSERT_EQ(0xff336699, n); - ZUC_ASSERT_EQ(EINVAL, errno); -} - -ZUC_TEST_F(config_test_t1, test025, data) -{ - int r; - uint32_t n; - struct weston_config_section *section; - struct weston_config *config = data; - - // String color names are unsupported - section = weston_config_get_section(config, "bucket", NULL, NULL); - r = weston_config_section_get_color(section, "color", &n, 0xff336699); - - ZUC_ASSERT_EQ(-1, r); - ZUC_ASSERT_EQ(0xff336699, n); - ZUC_ASSERT_EQ(EINVAL, errno); -} - -ZUC_TEST_F(config_test_t1, test026, data) -{ - int r; - int32_t n; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "bar", NULL, NULL); - r = weston_config_section_get_int(section, "negative", &n, 600); - - ZUC_ASSERT_EQ(0, r); - ZUC_ASSERT_EQ(-42, n); - ZUC_ASSERT_EQ(0, errno); -} - -ZUC_TEST_F(config_test_t1, test027, data) -{ - int r; - uint32_t n; - struct weston_config_section *section; - struct weston_config *config = data; - - section = weston_config_get_section(config, "bar", NULL, NULL); - r = weston_config_section_get_uint(section, "negative", &n, 600); - - ZUC_ASSERT_EQ(-1, r); - ZUC_ASSERT_EQ(600, n); - ZUC_ASSERT_EQ(ERANGE, errno); -} - -ZUC_TEST_F(config_test_t2, doesnt_parse, data) -{ - struct weston_config *config = data; - ZUC_ASSERT_NULL(config); -} - -ZUC_TEST_F(config_test_t3, doesnt_parse, data) -{ - struct weston_config *config = data; - ZUC_ASSERT_NULL(config); -} - -ZUC_TEST_F(config_test_t4, doesnt_parse, data) -{ - struct weston_config *config = data; - ZUC_ASSERT_NULL(config); -} - -ZUC_TEST(config_test, destroy_null) -{ - weston_config_destroy(NULL); - ZUC_ASSERT_EQ(0, weston_config_next_section(NULL, NULL, NULL)); -} - -ZUC_TEST(config_test, section_from_null) -{ - struct weston_config_section *section; - section = weston_config_get_section(NULL, "bucket", NULL, NULL); - ZUC_ASSERT_NULL(section); -} diff --git a/tests/devices-test.c b/tests/devices-test.c deleted file mode 100644 index 6b15419..0000000 --- a/tests/devices-test.c +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright © 2015 Red Hat, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include "weston-test-client-helper.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -/** - * Test (un)plugging devices - * - * At the end of each test we must return Weston to the previous state - * (add all removed devices and remove extra devices), so that - * the environment is prepared for the other tests too - */ - -#define WL_SEAT_CAPABILITY_ALL (WL_SEAT_CAPABILITY_KEYBOARD |\ - WL_SEAT_CAPABILITY_POINTER |\ - WL_SEAT_CAPABILITY_TOUCH) - -/* simply test if weston sends the right capabilities when - * some devices are removed */ -TEST(seat_capabilities_test) -{ - struct client *cl = create_client_and_test_surface(100, 100, 100, 100); - assert(cl->input->caps == WL_SEAT_CAPABILITY_ALL); - - assert(cl->input->pointer); - weston_test_device_release(cl->test->weston_test, "pointer"); - client_roundtrip(cl); - assert(!cl->input->pointer); - assert(!(cl->input->caps & WL_SEAT_CAPABILITY_POINTER)); - - assert(cl->input->keyboard); - weston_test_device_release(cl->test->weston_test, "keyboard"); - client_roundtrip(cl); - assert(!cl->input->keyboard); - assert(!(cl->input->caps & WL_SEAT_CAPABILITY_KEYBOARD)); - - assert(cl->input->touch); - weston_test_device_release(cl->test->weston_test, "touch"); - client_roundtrip(cl); - assert(!cl->input->touch); - assert(!(cl->input->caps & WL_SEAT_CAPABILITY_TOUCH)); - - /* restore previous state */ - weston_test_device_add(cl->test->weston_test, "keyboard"); - weston_test_device_add(cl->test->weston_test, "pointer"); - weston_test_device_add(cl->test->weston_test, "touch"); - client_roundtrip(cl); - - assert(cl->input->pointer); - assert(cl->input->keyboard); - assert(cl->input->touch); - - /* add extra devices */ - weston_test_device_add(cl->test->weston_test, "keyboard"); - weston_test_device_add(cl->test->weston_test, "pointer"); - weston_test_device_add(cl->test->weston_test, "touch"); - client_roundtrip(cl); - - /* remove extra devices */ - weston_test_device_release(cl->test->weston_test, "keyboard"); - weston_test_device_release(cl->test->weston_test, "pointer"); - weston_test_device_release(cl->test->weston_test, "touch"); - client_roundtrip(cl); - - /* we still should have all the capabilities, since the devices - * were doubled */ - assert(cl->input->caps == WL_SEAT_CAPABILITY_ALL); - - assert(cl->input->pointer); - assert(cl->input->keyboard); - assert(cl->input->touch); -} - -#define COUNT 15 -TEST(multiple_device_add_and_remove) -{ - int i; - struct client *cl = create_client_and_test_surface(100, 100, 100, 100); - - /* add device a lot of times */ - for (i = 0; i < COUNT; ++i) { - weston_test_device_add(cl->test->weston_test, "keyboard"); - weston_test_device_add(cl->test->weston_test, "pointer"); - weston_test_device_add(cl->test->weston_test, "touch"); - } - - client_roundtrip(cl); - - assert(cl->input->pointer); - assert(cl->input->keyboard); - assert(cl->input->touch); - - assert(cl->input->caps == WL_SEAT_CAPABILITY_ALL); - - /* release all new devices */ - for (i = 0; i < COUNT; ++i) { - weston_test_device_release(cl->test->weston_test, "keyboard"); - weston_test_device_release(cl->test->weston_test, "pointer"); - weston_test_device_release(cl->test->weston_test, "touch"); - } - - client_roundtrip(cl); - - /* there is still one from each device left */ - assert(cl->input->caps == WL_SEAT_CAPABILITY_ALL); - - assert(cl->input->pointer); - assert(cl->input->keyboard); - assert(cl->input->touch); -} - -TEST(device_release_before_destroy) -{ - struct client *cl = create_client_and_test_surface(100, 100, 100, 100); - - /* we can release pointer when we won't be using it anymore. - * Do it and see what happens if the device is destroyed - * right after that */ - wl_pointer_release(cl->input->pointer->wl_pointer); - /* we must free and set to NULL the structures, otherwise - * seat capabilities will double-free them */ - free(cl->input->pointer); - cl->input->pointer = NULL; - - wl_keyboard_release(cl->input->keyboard->wl_keyboard); - free(cl->input->keyboard); - cl->input->keyboard = NULL; - - wl_touch_release(cl->input->touch->wl_touch); - free(cl->input->touch); - cl->input->touch = NULL; - - weston_test_device_release(cl->test->weston_test, "pointer"); - weston_test_device_release(cl->test->weston_test, "keyboard"); - weston_test_device_release(cl->test->weston_test, "touch"); - client_roundtrip(cl); - - assert(cl->input->caps == 0); - - /* restore previous state */ - weston_test_device_add(cl->test->weston_test, "pointer"); - weston_test_device_add(cl->test->weston_test, "keyboard"); - weston_test_device_add(cl->test->weston_test, "touch"); - client_roundtrip(cl); - - assert(cl->input->caps == WL_SEAT_CAPABILITY_ALL); -} - -TEST(device_release_before_destroy_multiple) -{ - int i; - - /* if weston crashed during this test, then there is - * some inconsistency */ - for (i = 0; i < 30; ++i) { - /* Fifty times run the previous test. This will create - * fifty clients, because we don't have any - * way how to destroy them (worth of adding!). Only one - * client will run at a time though and so should have no - * effect on the result of the test (after the client - * finishes its body, it just 'is' and does nothing). */ - device_release_before_destroy(); - } -} - -/* normal work-flow test */ -TEST(device_release_after_destroy) -{ - struct client *cl = create_client_and_test_surface(100, 100, 100, 100); - - weston_test_device_release(cl->test->weston_test, "pointer"); - wl_pointer_release(cl->input->pointer->wl_pointer); - /* we must free the memory manually, otherwise seat.capabilities - * will try to free it and will use invalid proxy */ - free(cl->input->pointer); - cl->input->pointer = NULL; - - client_roundtrip(cl); - - weston_test_device_release(cl->test->weston_test, "keyboard"); - wl_keyboard_release(cl->input->keyboard->wl_keyboard); - free(cl->input->keyboard); - cl->input->keyboard = NULL; - - client_roundtrip(cl); - - weston_test_device_release(cl->test->weston_test, "touch"); - wl_touch_release(cl->input->touch->wl_touch); - free(cl->input->touch); - cl->input->touch = NULL; - - client_roundtrip(cl); - - assert(cl->input->caps == 0); - - /* restore previous state */ - weston_test_device_add(cl->test->weston_test, "pointer"); - weston_test_device_add(cl->test->weston_test, "keyboard"); - weston_test_device_add(cl->test->weston_test, "touch"); - client_roundtrip(cl); - - assert(cl->input->caps == WL_SEAT_CAPABILITY_ALL); -} - -TEST(device_release_after_destroy_multiple) -{ - int i; - - /* if weston crashed during this test, then there is - * some inconsistency */ - for (i = 0; i < 30; ++i) { - device_release_after_destroy(); - } -} - -/* see https://bugzilla.gnome.org/show_bug.cgi?id=745008 - * It is a mutter bug, but highly relevant. Weston does not - * suffer from this bug atm, but it is worth of testing. */ -TEST(get_device_after_destroy) -{ - struct client *cl = create_client_and_test_surface(100, 100, 100, 100); - struct wl_pointer *wl_pointer; - struct wl_keyboard *wl_keyboard; - struct wl_touch *wl_touch; - - /* There's a race: - * 1) compositor destroys device - * 2) client asks for the device, because it has not received - * the new capabilities yet - * 3) compositor gets the request with a new_id for the - * destroyed device - * 4) client uses the new_id - * 5) client gets new capabilities, destroying the objects - * - * If the compositor just bails out in step 3) and does not - * create the resource, then the client gets an error in step 4) - * - even though it followed the protocol (it just didn't know - * about new capabilities). - * - * This test simulates this situation - */ - - /* connection is buffered, so after calling client_roundtrip(), - * this whole batch will be delivered to compositor and will - * exactly simulate our situation */ - weston_test_device_release(cl->test->weston_test, "pointer"); - wl_pointer = wl_seat_get_pointer(cl->input->wl_seat); - assert(wl_pointer); - - /* this should be ignored */ - wl_pointer_set_cursor(wl_pointer, 0, NULL, 0, 0); - - /* this should not be ignored */ - wl_pointer_release(wl_pointer); - client_roundtrip(cl); - - weston_test_device_release(cl->test->weston_test, "keyboard"); - wl_keyboard = wl_seat_get_keyboard(cl->input->wl_seat); - assert(wl_keyboard); - wl_keyboard_release(wl_keyboard); - client_roundtrip(cl); - - weston_test_device_release(cl->test->weston_test, "touch"); - wl_touch = wl_seat_get_touch(cl->input->wl_seat); - assert(wl_touch); - wl_touch_release(wl_touch); - client_roundtrip(cl); - - /* get weston to the previous state */ - weston_test_device_add(cl->test->weston_test, "pointer"); - weston_test_device_add(cl->test->weston_test, "keyboard"); - weston_test_device_add(cl->test->weston_test, "touch"); - client_roundtrip(cl); - - assert(cl->input->caps == WL_SEAT_CAPABILITY_ALL); -} - -TEST(get_device_after_destroy_multiple) -{ - int i; - - /* if weston crashed during this test, then there is - * some inconsistency */ - for (i = 0; i < 30; ++i) { - get_device_after_destroy(); - } -} - -TEST(seats_have_names) -{ - struct client *cl = create_client_and_test_surface(100, 100, 100, 100); - struct input *input; - - wl_list_for_each(input, &cl->inputs, link) { - assert(input->seat_name); - } -} - -TEST(seat_destroy_and_recreate) -{ - struct client *cl = create_client_and_test_surface(100, 100, 100, 100); - - weston_test_device_release(cl->test->weston_test, "seat"); - /* Roundtrip to receive and handle the seat global removal event */ - client_roundtrip(cl); - - assert(!cl->input); - - weston_test_device_add(cl->test->weston_test, "seat"); - /* First roundtrip to send request and receive new seat global */ - client_roundtrip(cl); - /* Second roundtrip to handle seat events and set up input devices */ - client_roundtrip(cl); - - assert(cl->input); - assert(cl->input->pointer); - assert(cl->input->keyboard); - assert(cl->input->touch); -} diff --git a/tests/drm-smoke-test.c b/tests/drm-smoke-test.c deleted file mode 100644 index 4c5b6a5..0000000 --- a/tests/drm-smoke-test.c +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright © 2020 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include "weston-test-client-helper.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - setup.shell = SHELL_TEST_DESKTOP; - setup.backend = WESTON_BACKEND_DRM; - setup.renderer = RENDERER_PIXMAN; - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -TEST(drm_smoke) { - - struct client *client; - struct buffer *buffer; - struct wl_surface *surface; - pixman_color_t red; - int i, frame; - - color_rgb888(&red, 255, 0, 0); - - client = create_client_and_test_surface(0, 0, 200, 200); - assert(client); - - surface = client->surface->wl_surface; - buffer = create_shm_buffer_a8r8g8b8(client, 200, 200); - - fill_image_with_color(buffer->image, &red); - - for (i = 0; i < 5; i++) { - wl_surface_attach(surface, buffer->proxy, 0, 0); - wl_surface_damage(surface, 0, 0, 200, 200); - frame_callback_set(surface, &frame); - wl_surface_commit(surface); - frame_callback_wait(client, &frame); - } - - client_destroy(client); -} diff --git a/tests/event-test.c b/tests/event-test.c deleted file mode 100644 index 75419a6..0000000 --- a/tests/event-test.c +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * Copyright © 2013 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include "weston-test-client-helper.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -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 = create_client_and_test_surface(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 buffer *buf1; - struct buffer *buf2; - struct buffer *buf3; - int buf1_released = 0; - int buf2_released = 0; - int buf3_released = 0; - int frame; - - client = create_client_and_test_surface(100, 100, 100, 100); - assert(client); - surface = client->surface->wl_surface; - - buf1 = create_shm_buffer_a8r8g8b8(client, 100, 100); - wl_buffer_add_listener(buf1->proxy, &buffer_listener, &buf1_released); - - buf2 = create_shm_buffer_a8r8g8b8(client, 100, 100); - wl_buffer_add_listener(buf2->proxy, &buffer_listener, &buf2_released); - - buf3 = create_shm_buffer_a8r8g8b8(client, 100, 100); - wl_buffer_add_listener(buf3->proxy, &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->proxy, 0, 0); - wl_surface_attach(surface, buf2->proxy, 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->proxy, 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->buffer->proxy, 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/input-timestamps-helper.c b/tests/input-timestamps-helper.c deleted file mode 100644 index 05cafec..0000000 --- a/tests/input-timestamps-helper.c +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright © 2017 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include - -#include "input-timestamps-helper.h" -#include "input-timestamps-unstable-v1-client-protocol.h" -#include "shared/timespec-util.h" -#include -#include "weston-test-client-helper.h" - -struct input_timestamps { - struct zwp_input_timestamps_v1 *proxy; -}; - -static struct zwp_input_timestamps_manager_v1 * -get_input_timestamps_manager(struct client *client) -{ - struct global *g; - struct global *global_ts = NULL; - struct zwp_input_timestamps_manager_v1 *ts = NULL; - - wl_list_for_each(g, &client->global_list, link) { - if (strcmp(g->interface, zwp_input_timestamps_manager_v1_interface.name)) - continue; - - if (global_ts) - assert(!"Multiple input timestamp managers"); - - global_ts = g; - } - - assert(global_ts); - assert(global_ts->version == 1); - - ts = wl_registry_bind(client->wl_registry, global_ts->name, - &zwp_input_timestamps_manager_v1_interface, 1); - assert(ts); - - return ts; -} - -static void -input_timestamp(void *data, - struct zwp_input_timestamps_v1 *zwp_input_timestamps_v1, - uint32_t tv_sec_hi, - uint32_t tv_sec_lo, - uint32_t tv_nsec) -{ - struct timespec *timestamp = data; - - timespec_from_proto(timestamp, tv_sec_hi, tv_sec_lo, - tv_nsec); - - testlog("test-client: got input timestamp %ld.%ld\n", - timestamp->tv_sec, timestamp->tv_nsec); -} - -static const struct zwp_input_timestamps_v1_listener -input_timestamps_listener = { - .timestamp = input_timestamp, -}; - -struct input_timestamps * -input_timestamps_create_for_keyboard(struct client *client) -{ - struct zwp_input_timestamps_manager_v1 *manager = - get_input_timestamps_manager(client); - struct timespec *timestamp= &client->input->keyboard->input_timestamp; - struct input_timestamps *input_ts; - - input_ts = zalloc(sizeof *input_ts); - assert(input_ts); - - input_ts->proxy = - zwp_input_timestamps_manager_v1_get_keyboard_timestamps( - manager, client->input->keyboard->wl_keyboard); - assert(input_ts->proxy); - - zwp_input_timestamps_v1_add_listener(input_ts->proxy, - &input_timestamps_listener, - timestamp); - - zwp_input_timestamps_manager_v1_destroy(manager); - - client_roundtrip(client); - - return input_ts; -} - -struct input_timestamps * -input_timestamps_create_for_pointer(struct client *client) -{ - struct zwp_input_timestamps_manager_v1 *manager = - get_input_timestamps_manager(client); - struct timespec *timestamp= &client->input->pointer->input_timestamp; - struct input_timestamps *input_ts; - - input_ts = zalloc(sizeof *input_ts); - assert(input_ts); - - input_ts->proxy = - zwp_input_timestamps_manager_v1_get_pointer_timestamps( - manager, client->input->pointer->wl_pointer); - assert(input_ts->proxy); - - zwp_input_timestamps_v1_add_listener(input_ts->proxy, - &input_timestamps_listener, - timestamp); - - zwp_input_timestamps_manager_v1_destroy(manager); - - client_roundtrip(client); - - return input_ts; -} - -struct input_timestamps * -input_timestamps_create_for_touch(struct client *client) -{ - struct zwp_input_timestamps_manager_v1 *manager = - get_input_timestamps_manager(client); - struct timespec *timestamp= &client->input->touch->input_timestamp; - struct input_timestamps *input_ts; - - input_ts = zalloc(sizeof *input_ts); - assert(input_ts); - - input_ts->proxy = - zwp_input_timestamps_manager_v1_get_touch_timestamps( - manager, client->input->touch->wl_touch); - assert(input_ts->proxy); - - zwp_input_timestamps_v1_add_listener(input_ts->proxy, - &input_timestamps_listener, - timestamp); - - zwp_input_timestamps_manager_v1_destroy(manager); - - client_roundtrip(client); - - return input_ts; -} - -void -input_timestamps_destroy(struct input_timestamps *input_ts) -{ - zwp_input_timestamps_v1_destroy(input_ts->proxy); - free(input_ts); -} diff --git a/tests/input-timestamps-helper.h b/tests/input-timestamps-helper.h deleted file mode 100644 index 5301df0..0000000 --- a/tests/input-timestamps-helper.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright © 2017 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef INPUT_TIMESTAMPS_HELPER_H -#define INPUT_TIMESTAMPS_HELPER_H - -#include "config.h" - -struct client; -struct input_timestamps; - -struct input_timestamps * -input_timestamps_create_for_keyboard(struct client *client); - -struct input_timestamps * -input_timestamps_create_for_pointer(struct client *client); - -struct input_timestamps * -input_timestamps_create_for_touch(struct client *client); - -void -input_timestamps_destroy(struct input_timestamps *input_ts); - -#endif /* INPUT_TIMESTAMPS_HELPER_H */ diff --git a/tests/internal-screenshot-test.c b/tests/internal-screenshot-test.c deleted file mode 100644 index 6c1b774..0000000 --- a/tests/internal-screenshot-test.c +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include "weston-test-client-helper.h" -#include "weston-test-fixture-compositor.h" -#include "test-config.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - setup.renderer = RENDERER_PIXMAN; - setup.width = 320; - setup.height = 240; - setup.shell = SHELL_DESKTOP; - setup.config_file = TESTSUITE_INTERNAL_SCREENSHOT_CONFIG_PATH; - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -static void -draw_stuff(pixman_image_t *image) -{ - int w, h; - int stride; /* bytes */ - int x, y; - uint32_t r, g, b; - uint32_t *pixels; - uint32_t *pixel; - pixman_format_code_t fmt; - - fmt = pixman_image_get_format(image); - w = pixman_image_get_width(image); - h = pixman_image_get_height(image); - stride = pixman_image_get_stride(image); - pixels = pixman_image_get_data(image); - - assert(PIXMAN_FORMAT_BPP(fmt) == 32); - - for (x = 0; x < w; x++) - for (y = 0; y < h; y++) { - b = x; - g = x + y; - r = y; - pixel = pixels + (y * stride / 4) + x; - *pixel = (255U << 24) | (r << 16) | (g << 8) | b; - } -} - -TEST(internal_screenshot) -{ - struct buffer *buf; - struct client *client; - struct wl_surface *surface; - struct buffer *screenshot = NULL; - pixman_image_t *reference_good = NULL; - pixman_image_t *reference_bad = NULL; - pixman_image_t *diffimg; - struct rectangle clip; - const char *fname; - bool match = false; - bool dump_all_images = true; - - /* Create the client */ - testlog("Creating client for test\n"); - client = create_client_and_test_surface(100, 100, 100, 100); - assert(client); - surface = client->surface->wl_surface; - - /* - * We are racing our screenshooting against weston-desktop-shell - * setting the cursor. If w-d-s wins, our screenshot will have a cursor - * shown, which makes the image comparison fail. Our window and the - * default pointer position are accidentally causing an overlap that - * intersects our test clip rectangle. - * - * w-d-s wins very rarely though, so the race is easy to miss. You can - * make it happen by putting a delay before the call to - * create_client_and_test_surface(). - * - * The weston_test_move_pointer() below makes the race irrelevant, as - * the cursor won't overlap with anything we care about. - */ - - /* Move the pointer away from the screenshot area. */ - weston_test_move_pointer(client->test->weston_test, 0, 1, 0, 0, 0); - - buf = create_shm_buffer_a8r8g8b8(client, 100, 100); - draw_stuff(buf->image); - wl_surface_attach(surface, buf->proxy, 0, 0); - wl_surface_damage(surface, 0, 0, 100, 100); - wl_surface_commit(surface); - - /* Take a snapshot. Result will be in screenshot->wl_buffer. */ - testlog("Taking a screenshot\n"); - screenshot = capture_screenshot_of_output(client); - assert(screenshot); - - /* Load good reference image */ - fname = screenshot_reference_filename("internal-screenshot-good", 0); - testlog("Loading good reference image %s\n", fname); - reference_good = load_image_from_png(fname); - assert(reference_good); - - /* Load bad reference image */ - fname = screenshot_reference_filename("internal-screenshot-bad", 0); - testlog("Loading bad reference image %s\n", fname); - reference_bad = load_image_from_png(fname); - assert(reference_bad); - - /* Test check_images_match() without a clip. - * We expect this to fail since we use a bad reference image - */ - match = check_images_match(screenshot->image, reference_bad, NULL, NULL); - testlog("Screenshot %s reference image\n", match? "equal to" : "different from"); - assert(!match); - pixman_image_unref(reference_bad); - - /* Test check_images_match() with clip. - * Alpha-blending and other effects can cause irrelevant discrepancies, so look only - * at a small portion of the solid-colored background - */ - clip.x = 100; - clip.y = 100; - clip.width = 100; - clip.height = 100; - testlog("Clip: %d,%d %d x %d\n", clip.x, clip.y, clip.width, clip.height); - match = check_images_match(screenshot->image, reference_good, &clip, NULL); - testlog("Screenshot %s reference image in clipped area\n", match? "matches" : "doesn't match"); - if (!match) { - diffimg = visualize_image_difference(screenshot->image, reference_good, &clip, NULL); - fname = screenshot_output_filename("internal-screenshot-error", 0); - write_image_as_png(diffimg, fname); - pixman_image_unref(diffimg); - } - pixman_image_unref(reference_good); - - /* Test dumping of non-matching images */ - if (!match || dump_all_images) { - fname = screenshot_output_filename("internal-screenshot", 0); - write_image_as_png(screenshot->image, fname); - } - - buffer_destroy(screenshot); - - testlog("Test complete\n"); - assert(match); -} diff --git a/tests/internal-screenshot.ini b/tests/internal-screenshot.ini deleted file mode 100644 index abc046e..0000000 --- a/tests/internal-screenshot.ini +++ /dev/null @@ -1,3 +0,0 @@ -[shell] -startup-animation=none -background-color=0xCC336699 diff --git a/tests/ivi-layout-internal-test.c b/tests/ivi-layout-internal-test.c deleted file mode 100644 index 8f2d6be..0000000 --- a/tests/ivi-layout-internal-test.c +++ /dev/null @@ -1,1022 +0,0 @@ -/* - * Copyright © 2013 DENSO CORPORATION - * Copyright © 2015 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include - -#include -#include "compositor/weston.h" -#include "ivi-shell/ivi-layout-export.h" -#include "ivi-shell/ivi-layout-private.h" -#include "ivi-test.h" -#include "shared/helpers.h" -#include "weston-test-runner.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - setup.shell = SHELL_IVI; - - return weston_test_harness_execute_as_plugin(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -struct test_context { - struct weston_compositor *compositor; - const struct ivi_layout_interface *layout_interface; - uint32_t user_flags; - - struct wl_listener layer_property_changed; - struct wl_listener layer_created; - struct wl_listener layer_removed; -}; - -static void -iassert_fail(const char *cond, const char *file, int line, - const char *func, struct test_context *ctx) -{ - weston_log("Assert failure in %s:%d, %s: '%s'\n", - file, line, func, cond); - weston_compositor_exit_with_code(ctx->compositor, EXIT_FAILURE); -} - -#define iassert(cond) ({ \ - bool b_ = (cond); \ - if (!b_) \ - iassert_fail(#cond, __FILE__, __LINE__, __func__, ctx); \ - b_; \ -}) - -/************************ tests begin ******************************/ - -/* - * These are all internal ivi_layout API tests that do not require - * any client objects. - */ -static void -test_surface_bad_visibility(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->surface_set_visibility(NULL, true) == IVI_FAILED); - - lyt->commit_changes(); -} - -static void -test_surface_bad_destination_rectangle(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->surface_set_destination_rectangle(NULL, 20, 30, 200, 300) == IVI_FAILED); -} - -static void -test_surface_bad_source_rectangle(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->surface_set_source_rectangle(NULL, 20, 30, 200, 300) == IVI_FAILED); -} - -static void -test_surface_bad_properties(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->get_properties_of_surface(NULL) == NULL); -} - -static void -test_layer_create(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - uint32_t id1; - uint32_t id2; - struct ivi_layout_layer *ivilayer; - struct ivi_layout_layer *new_ivilayer; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - iassert(IVI_TEST_LAYER_ID(0) == lyt->get_id_of_layer(ivilayer)); - - new_ivilayer = lyt->get_layer_from_id(IVI_TEST_LAYER_ID(0)); - iassert(ivilayer == new_ivilayer); - - id1 = lyt->get_id_of_layer(ivilayer); - id2 = lyt->get_id_of_layer(new_ivilayer); - iassert(id1 == id2); - - lyt->layer_destroy(ivilayer); - iassert(lyt->get_layer_from_id(IVI_TEST_LAYER_ID(0)) == NULL); -} - -static void -test_layer_visibility(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - const struct ivi_layout_layer_properties *prop; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - prop = lyt->get_properties_of_layer(ivilayer); - - iassert(prop->visibility == false); - - iassert(lyt->layer_set_visibility(ivilayer, true) == IVI_SUCCEEDED); - - iassert(prop->visibility == false); - - lyt->commit_changes(); - - iassert(prop->visibility == true); - - lyt->layer_destroy(ivilayer); -} - -static void -test_layer_opacity(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - const struct ivi_layout_layer_properties *prop; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - prop = lyt->get_properties_of_layer(ivilayer); - iassert(prop->opacity == wl_fixed_from_double(1.0)); - - iassert(lyt->layer_set_opacity( - ivilayer, wl_fixed_from_double(0.5)) == IVI_SUCCEEDED); - - iassert(prop->opacity == wl_fixed_from_double(1.0)); - - lyt->commit_changes(); - - iassert(prop->opacity == wl_fixed_from_double(0.5)); - - lyt->layer_destroy(ivilayer); -} - -static void -test_layer_dimension(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - const struct ivi_layout_layer_properties *prop; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - prop = lyt->get_properties_of_layer(ivilayer); - iassert(prop->dest_width == 200); - iassert(prop->dest_height == 300); - - iassert(lyt->layer_set_destination_rectangle(ivilayer, prop->dest_x, prop->dest_y, - 400, 600) == IVI_SUCCEEDED); - - iassert(prop->dest_width == 200); - iassert(prop->dest_height == 300); - - lyt->commit_changes(); - - iassert(prop->dest_width == 400); - iassert(prop->dest_height == 600); - - lyt->layer_destroy(ivilayer); -} - -static void -test_layer_position(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - const struct ivi_layout_layer_properties *prop; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - prop = lyt->get_properties_of_layer(ivilayer); - iassert(prop->dest_x == 0); - iassert(prop->dest_y == 0); - - iassert(lyt->layer_set_destination_rectangle(ivilayer, 20, 30, - prop->dest_width, prop->dest_height) == IVI_SUCCEEDED); - - iassert(prop->dest_x == 0); - iassert(prop->dest_y == 0); - - lyt->commit_changes(); - - prop = lyt->get_properties_of_layer(ivilayer); - iassert(prop->dest_x == 20); - iassert(prop->dest_y == 30); - - lyt->layer_destroy(ivilayer); -} - -static void -test_layer_destination_rectangle(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - const struct ivi_layout_layer_properties *prop; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - prop = lyt->get_properties_of_layer(ivilayer); - iassert(prop->dest_width == 200); - iassert(prop->dest_height == 300); - iassert(prop->dest_x == 0); - iassert(prop->dest_y == 0); - - iassert(lyt->layer_set_destination_rectangle( - ivilayer, 20, 30, 400, 600) == IVI_SUCCEEDED); - - prop = lyt->get_properties_of_layer(ivilayer); - iassert(prop->dest_width == 200); - iassert(prop->dest_height == 300); - iassert(prop->dest_x == 0); - iassert(prop->dest_y == 0); - - lyt->commit_changes(); - - iassert(prop->dest_width == 400); - iassert(prop->dest_height == 600); - iassert(prop->dest_x == 20); - iassert(prop->dest_y == 30); - - lyt->layer_destroy(ivilayer); -} - -static void -test_layer_source_rectangle(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - const struct ivi_layout_layer_properties *prop; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - prop = lyt->get_properties_of_layer(ivilayer); - iassert(prop->source_width == 200); - iassert(prop->source_height == 300); - iassert(prop->source_x == 0); - iassert(prop->source_y == 0); - - iassert(lyt->layer_set_source_rectangle( - ivilayer, 20, 30, 400, 600) == IVI_SUCCEEDED); - - prop = lyt->get_properties_of_layer(ivilayer); - iassert(prop->source_width == 200); - iassert(prop->source_height == 300); - iassert(prop->source_x == 0); - iassert(prop->source_y == 0); - - lyt->commit_changes(); - - prop = lyt->get_properties_of_layer(ivilayer); - iassert(prop->source_width == 400); - iassert(prop->source_height == 600); - iassert(prop->source_x == 20); - iassert(prop->source_y == 30); - - lyt->layer_destroy(ivilayer); -} - -static void -test_layer_bad_remove(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - lyt->layer_destroy(NULL); -} - -static void -test_layer_bad_visibility(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->layer_set_visibility(NULL, true) == IVI_FAILED); - - lyt->commit_changes(); -} - -static void -test_layer_bad_opacity(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - const struct ivi_layout_layer_properties *prop; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - iassert(lyt->layer_set_opacity( - NULL, wl_fixed_from_double(0.3)) == IVI_FAILED); - - iassert(lyt->layer_set_opacity( - ivilayer, wl_fixed_from_double(0.3)) == IVI_SUCCEEDED); - - iassert(lyt->layer_set_opacity( - ivilayer, wl_fixed_from_double(-1)) == IVI_FAILED); - - lyt->commit_changes(); - - prop = lyt->get_properties_of_layer(ivilayer); - iassert(prop->opacity == wl_fixed_from_double(0.3)); - - iassert(lyt->layer_set_opacity( - ivilayer, wl_fixed_from_double(1.1)) == IVI_FAILED); - - lyt->commit_changes(); - - iassert(prop->opacity == wl_fixed_from_double(0.3)); - - iassert(lyt->layer_set_opacity( - NULL, wl_fixed_from_double(0.5)) == IVI_FAILED); - - lyt->commit_changes(); - - lyt->layer_destroy(ivilayer); -} - -static void -test_layer_bad_destination_rectangle(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->layer_set_destination_rectangle( - NULL, 20, 30, 200, 300) == IVI_FAILED); -} - -static void -test_layer_bad_source_rectangle(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->layer_set_source_rectangle( - NULL, 20, 30, 200, 300) == IVI_FAILED); -} - -static void -test_layer_bad_properties(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->get_properties_of_layer(NULL) == NULL); -} - -static void -test_commit_changes_after_visibility_set_layer_destroy(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - iassert(lyt->layer_set_visibility(ivilayer, true) == IVI_SUCCEEDED); - lyt->layer_destroy(ivilayer); - lyt->commit_changes(); -} - -static void -test_commit_changes_after_opacity_set_layer_destroy(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - iassert(lyt->layer_set_opacity( - ivilayer, wl_fixed_from_double(0.5)) == IVI_SUCCEEDED); - lyt->layer_destroy(ivilayer); - lyt->commit_changes(); -} - -static void -test_commit_changes_after_source_rectangle_set_layer_destroy(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - iassert(lyt->layer_set_source_rectangle( - ivilayer, 20, 30, 200, 300) == IVI_SUCCEEDED); - lyt->layer_destroy(ivilayer); - lyt->commit_changes(); -} - -static void -test_commit_changes_after_destination_rectangle_set_layer_destroy(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - iassert(lyt->layer_set_destination_rectangle( - ivilayer, 20, 30, 200, 300) == IVI_SUCCEEDED); - lyt->layer_destroy(ivilayer); - lyt->commit_changes(); -} - -static void -test_layer_create_duplicate(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - struct ivi_layout_layer *duplicatelayer; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - if (ivilayer != NULL) - iassert(ivilayer->ref_count == 1); - - duplicatelayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer == duplicatelayer); - - if (ivilayer != NULL) - iassert(ivilayer->ref_count == 2); - - lyt->layer_destroy(ivilayer); - - if (ivilayer != NULL) - iassert(ivilayer->ref_count == 1); - - lyt->layer_destroy(ivilayer); -} - -static void -test_get_layer_after_destory_layer(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - lyt->layer_destroy(ivilayer); - - ivilayer = lyt->get_layer_from_id(IVI_TEST_LAYER_ID(0)); - iassert(ivilayer == NULL); -} - -static void -test_screen_render_order(struct test_context *ctx) -{ -#define LAYER_NUM (3) - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct weston_output *output; - struct ivi_layout_layer *ivilayers[LAYER_NUM] = {}; - struct ivi_layout_layer **array; - int32_t length = 0; - uint32_t i; - - if (!iassert(!wl_list_empty(&ctx->compositor->output_list))) - return; - - output = wl_container_of(ctx->compositor->output_list.next, output, link); - - for (i = 0; i < LAYER_NUM; i++) - ivilayers[i] = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(i), 200, 300); - - iassert(lyt->screen_set_render_order(output, ivilayers, LAYER_NUM) == IVI_SUCCEEDED); - - lyt->commit_changes(); - - iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); - iassert(length == LAYER_NUM); - for (i = 0; i < LAYER_NUM; i++) - iassert(array[i] == ivilayers[i]); - - if (length > 0) - free(array); - - array = NULL; - - iassert(lyt->screen_set_render_order(output, NULL, 0) == IVI_SUCCEEDED); - - lyt->commit_changes(); - - iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); - iassert(length == 0 && array == NULL); - - for (i = 0; i < LAYER_NUM; i++) - lyt->layer_destroy(ivilayers[i]); - -#undef LAYER_NUM -} - -static void -test_screen_bad_render_order(struct test_context *ctx) -{ -#define LAYER_NUM (3) - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct weston_output *output; - struct ivi_layout_layer *ivilayers[LAYER_NUM] = {}; - struct ivi_layout_layer **array; - int32_t length = 0; - uint32_t i; - - if (!iassert(!wl_list_empty(&ctx->compositor->output_list))) - return; - - output = wl_container_of(ctx->compositor->output_list.next, output, link); - - for (i = 0; i < LAYER_NUM; i++) - ivilayers[i] = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(i), 200, 300); - - iassert(lyt->screen_set_render_order(NULL, ivilayers, LAYER_NUM) == IVI_FAILED); - - lyt->commit_changes(); - - iassert(lyt->get_layers_on_screen(NULL, &length, &array) == IVI_FAILED); - iassert(lyt->get_layers_on_screen(output, NULL, &array) == IVI_FAILED); - iassert(lyt->get_layers_on_screen(output, &length, NULL) == IVI_FAILED); - - for (i = 0; i < LAYER_NUM; i++) - lyt->layer_destroy(ivilayers[i]); - -#undef LAYER_NUM -} - -static void -test_screen_add_layers(struct test_context *ctx) -{ -#define LAYER_NUM (3) - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct weston_output *output; - struct ivi_layout_layer *ivilayers[LAYER_NUM] = {}; - struct ivi_layout_layer **array; - int32_t length = 0; - uint32_t i; - - if (!iassert(!wl_list_empty(&ctx->compositor->output_list))) - return; - - output = wl_container_of(ctx->compositor->output_list.next, output, link); - - for (i = 0; i < LAYER_NUM; i++) { - ivilayers[i] = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(i), 200, 300); - iassert(lyt->screen_add_layer(output, ivilayers[i]) == IVI_SUCCEEDED); - } - - lyt->commit_changes(); - - iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); - iassert(length == LAYER_NUM); - for (i = 0; i < (uint32_t)length; i++) - iassert(array[i] == ivilayers[i]); - - if (length > 0) - free(array); - - array = NULL; - - iassert(lyt->screen_set_render_order(output, NULL, 0) == IVI_SUCCEEDED); - for (i = LAYER_NUM; i-- > 0;) - iassert(lyt->screen_add_layer(output, ivilayers[i]) == IVI_SUCCEEDED); - - lyt->commit_changes(); - - iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); - iassert(length == LAYER_NUM); - for (i = 0; i < (uint32_t)length; i++) - iassert(array[i] == ivilayers[LAYER_NUM - (i + 1)]); - - if (length > 0) - free(array); - - for (i = 0; i < LAYER_NUM; i++) - lyt->layer_destroy(ivilayers[i]); - -#undef LAYER_NUM -} - -static void -test_screen_remove_layer(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - struct weston_output *output; - struct ivi_layout_layer **array; - int32_t length = 0; - - if (wl_list_empty(&ctx->compositor->output_list)) - return; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - output = wl_container_of(ctx->compositor->output_list.next, output, link); - - iassert(lyt->screen_add_layer(output, ivilayer) == IVI_SUCCEEDED); - lyt->commit_changes(); - - iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); - iassert(length == 1); - iassert(array[0] == ivilayer); - - iassert(lyt->screen_remove_layer(output, ivilayer) == IVI_SUCCEEDED); - lyt->commit_changes(); - - if (length > 0) - free(array); - - array = NULL; - - iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); - iassert(length == 0); - iassert(array == NULL); - - lyt->layer_destroy(ivilayer); -} - -static void -test_screen_bad_remove_layer(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - struct weston_output *output; - - if (wl_list_empty(&ctx->compositor->output_list)) - return; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - output = wl_container_of(ctx->compositor->output_list.next, output, link); - - iassert(lyt->screen_remove_layer(NULL, ivilayer) == IVI_FAILED); - lyt->commit_changes(); - - iassert(lyt->screen_remove_layer(output, NULL) == IVI_FAILED); - lyt->commit_changes(); - - iassert(lyt->screen_remove_layer(NULL, NULL) == IVI_FAILED); - lyt->commit_changes(); - - lyt->layer_destroy(ivilayer); -} - - -static void -test_commit_changes_after_render_order_set_layer_destroy( - struct test_context *ctx) -{ -#define LAYER_NUM (3) - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct weston_output *output; - struct ivi_layout_layer *ivilayers[LAYER_NUM] = {}; - uint32_t i; - - if (!iassert(!wl_list_empty(&ctx->compositor->output_list))) - return; - - output = wl_container_of(ctx->compositor->output_list.next, output, link); - - for (i = 0; i < LAYER_NUM; i++) - ivilayers[i] = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(i), 200, 300); - - iassert(lyt->screen_set_render_order(output, ivilayers, LAYER_NUM) == IVI_SUCCEEDED); - - lyt->layer_destroy(ivilayers[1]); - - lyt->commit_changes(); - - lyt->layer_destroy(ivilayers[0]); - lyt->layer_destroy(ivilayers[2]); -#undef LAYER_NUM -} - -static void -test_layer_properties_changed_notification_callback(struct wl_listener *listener, void *data) -{ - struct test_context *ctx = - container_of(listener, struct test_context, - layer_property_changed); - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer = data; - const struct ivi_layout_layer_properties *prop = lyt->get_properties_of_layer(ivilayer); - - iassert(lyt->get_id_of_layer(ivilayer) == IVI_TEST_LAYER_ID(0)); - iassert(prop->source_width == 200); - iassert(prop->source_height == 300); - - if (lyt->get_id_of_layer(ivilayer) == IVI_TEST_LAYER_ID(0) && - prop->source_width == 200 && prop->source_height == 300) - ctx->user_flags = 1; -} - -static void -test_layer_properties_changed_notification(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - - ctx->user_flags = 0; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - - ctx->layer_property_changed.notify = test_layer_properties_changed_notification_callback; - - iassert(lyt->layer_add_listener(ivilayer, &ctx->layer_property_changed) == IVI_SUCCEEDED); - - lyt->commit_changes(); - - iassert(ctx->user_flags == 0); - - iassert(lyt->layer_set_destination_rectangle( - ivilayer, 20, 30, 200, 300) == IVI_SUCCEEDED); - - lyt->commit_changes(); - - iassert(ctx->user_flags == 1); - - ctx->user_flags = 0; - iassert(lyt->layer_set_destination_rectangle( - ivilayer, 20, 30, 200, 300) == IVI_SUCCEEDED); - - lyt->commit_changes(); - - iassert(ctx->user_flags == 0); - - // remove layer property changed listener. - wl_list_remove(&ctx->layer_property_changed.link); - - ctx->user_flags = 0; - lyt->commit_changes(); - - iassert(ctx->user_flags == 0); - - lyt->layer_destroy(ivilayer); -} - -static void -test_layer_create_notification_callback(struct wl_listener *listener, void *data) -{ - struct test_context *ctx = - container_of(listener, struct test_context, - layer_created); - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer = data; - const struct ivi_layout_layer_properties *prop = lyt->get_properties_of_layer(ivilayer); - - iassert(lyt->get_id_of_layer(ivilayer) == IVI_TEST_LAYER_ID(0)); - iassert(prop->source_width == 200); - iassert(prop->source_height == 300); - - if (lyt->get_id_of_layer(ivilayer) == IVI_TEST_LAYER_ID(0) && - prop->source_width == 200 && prop->source_height == 300) - ctx->user_flags = 1; -} - -static void -test_layer_create_notification(struct test_context *ctx) -{ -#define LAYER_NUM (2) - const struct ivi_layout_interface *lyt = ctx->layout_interface; - static const uint32_t layers[LAYER_NUM] = {IVI_TEST_LAYER_ID(0), IVI_TEST_LAYER_ID(1)}; - struct ivi_layout_layer *ivilayers[LAYER_NUM] = {}; - - ctx->user_flags = 0; - ctx->layer_created.notify = test_layer_create_notification_callback; - - iassert(lyt->add_listener_create_layer(&ctx->layer_created) == IVI_SUCCEEDED); - ivilayers[0] = lyt->layer_create_with_dimension(layers[0], 200, 300); - - iassert(ctx->user_flags == 1); - - ctx->user_flags = 0; - // remove layer created listener. - wl_list_remove(&ctx->layer_created.link); - - ivilayers[1] = lyt->layer_create_with_dimension(layers[1], 400, 500); - - iassert(ctx->user_flags == 0); - - lyt->layer_destroy(ivilayers[0]); - lyt->layer_destroy(ivilayers[1]); -#undef LAYER_NUM -} - -static void -test_layer_remove_notification_callback(struct wl_listener *listener, void *data) -{ - struct test_context *ctx = - container_of(listener, struct test_context, - layer_removed); - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer = data; - const struct ivi_layout_layer_properties *prop = - lyt->get_properties_of_layer(ivilayer); - - iassert(lyt->get_id_of_layer(ivilayer) == IVI_TEST_LAYER_ID(0)); - iassert(prop->source_width == 200); - iassert(prop->source_height == 300); - - if (lyt->get_id_of_layer(ivilayer) == IVI_TEST_LAYER_ID(0) && - prop->source_width == 200 && prop->source_height == 300) - ctx->user_flags = 1; -} - -static void -test_layer_remove_notification(struct test_context *ctx) -{ -#define LAYER_NUM (2) - const struct ivi_layout_interface *lyt = ctx->layout_interface; - static const uint32_t layers[LAYER_NUM] = {IVI_TEST_LAYER_ID(0), IVI_TEST_LAYER_ID(1)}; - struct ivi_layout_layer *ivilayers[LAYER_NUM] = {}; - - ctx->user_flags = 0; - ctx->layer_removed.notify = test_layer_remove_notification_callback; - - ivilayers[0] = lyt->layer_create_with_dimension(layers[0], 200, 300); - iassert(lyt->add_listener_remove_layer(&ctx->layer_removed) == IVI_SUCCEEDED); - lyt->layer_destroy(ivilayers[0]); - - iassert(ctx->user_flags == 1); - - ctx->user_flags = 0; - ivilayers[1] = lyt->layer_create_with_dimension(layers[1], 250, 350); - - // remove layer property changed listener. - wl_list_remove(&ctx->layer_removed.link); - lyt->layer_destroy(ivilayers[1]); - - iassert(ctx->user_flags == 0); -#undef LAYER_NUM -} - -static void -test_layer_bad_properties_changed_notification_callback(struct wl_listener *listener, void *data) -{ -} - -static void -test_layer_bad_properties_changed_notification(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - - ctx->layer_property_changed.notify = test_layer_bad_properties_changed_notification_callback; - - iassert(lyt->layer_add_listener(NULL, &ctx->layer_property_changed) == IVI_FAILED); - iassert(lyt->layer_add_listener(ivilayer, NULL) == IVI_FAILED); - - lyt->layer_destroy(ivilayer); -} - -static void -test_surface_bad_configure_notification(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->add_listener_configure_surface(NULL) == IVI_FAILED); -} - -static void -test_layer_bad_create_notification(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->add_listener_create_layer(NULL) == IVI_FAILED); -} - -static void -test_surface_bad_create_notification(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->add_listener_create_surface(NULL) == IVI_FAILED); -} - -static void -test_layer_bad_remove_notification(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->add_listener_remove_layer(NULL) == IVI_FAILED); -} - -static void -test_surface_bad_remove_notification(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->add_listener_remove_surface(NULL) == IVI_FAILED); -} - -/************************ tests end ********************************/ - -static void -run_internal_tests(struct test_context *ctx) -{ - test_surface_bad_visibility(ctx); - test_surface_bad_destination_rectangle(ctx); - test_surface_bad_source_rectangle(ctx); - test_surface_bad_properties(ctx); - - test_layer_create(ctx); - test_layer_visibility(ctx); - test_layer_opacity(ctx); - test_layer_dimension(ctx); - test_layer_position(ctx); - test_layer_destination_rectangle(ctx); - test_layer_source_rectangle(ctx); - test_layer_bad_remove(ctx); - test_layer_bad_visibility(ctx); - test_layer_bad_opacity(ctx); - test_layer_bad_destination_rectangle(ctx); - test_layer_bad_source_rectangle(ctx); - test_layer_bad_properties(ctx); - test_commit_changes_after_visibility_set_layer_destroy(ctx); - test_commit_changes_after_opacity_set_layer_destroy(ctx); - test_commit_changes_after_source_rectangle_set_layer_destroy(ctx); - test_commit_changes_after_destination_rectangle_set_layer_destroy(ctx); - test_layer_create_duplicate(ctx); - test_get_layer_after_destory_layer(ctx); - - test_screen_render_order(ctx); - test_screen_bad_render_order(ctx); - test_screen_add_layers(ctx); - test_screen_remove_layer(ctx); - test_screen_bad_remove_layer(ctx); - test_commit_changes_after_render_order_set_layer_destroy(ctx); - - test_layer_properties_changed_notification(ctx); - test_layer_create_notification(ctx); - test_layer_remove_notification(ctx); - test_layer_bad_properties_changed_notification(ctx); - test_surface_bad_configure_notification(ctx); - test_layer_bad_create_notification(ctx); - test_surface_bad_create_notification(ctx); - test_layer_bad_remove_notification(ctx); - test_surface_bad_remove_notification(ctx); -} - -PLUGIN_TEST(ivi_layout_internal) -{ - /* struct weston_compositor *compositor; */ - struct test_context ctx = {}; - const struct ivi_layout_interface *iface; - - iface = ivi_layout_get_api(compositor); - - if (!iface) { - weston_log("fatal: cannot use ivi_layout_interface.\n"); - weston_compositor_exit_with_code(compositor, RESULT_HARD_ERROR); - return; - } - - ctx.compositor = compositor; - ctx.layout_interface = iface; - - run_internal_tests(&ctx); -} diff --git a/tests/ivi-layout-test-client.c b/tests/ivi-layout-test-client.c deleted file mode 100644 index b7edf8e..0000000 --- a/tests/ivi-layout-test-client.c +++ /dev/null @@ -1,509 +0,0 @@ -/* - * Copyright © 2015 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include - -#include "shared/helpers.h" -#include "shared/xalloc.h" -#include "weston-test-client-helper.h" -#include "ivi-application-client-protocol.h" -#include "ivi-test.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - setup.shell = SHELL_IVI; - setup.extra_module = "test-ivi-layout.so"; - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -struct runner { - struct client *client; - struct weston_test_runner *test_runner; - int done; -}; - -static void -runner_finished_handler(void *data, struct weston_test_runner *test_runner) -{ - struct runner *runner = data; - - runner->done = 1; -} - -static const struct weston_test_runner_listener test_runner_listener = { - runner_finished_handler -}; - -static struct runner * -client_create_runner(struct client *client) -{ - struct runner *runner; - struct global *g; - struct global *global_runner = NULL; - - runner = xzalloc(sizeof(*runner)); - runner->client = client; - - wl_list_for_each(g, &client->global_list, link) { - if (strcmp(g->interface, "weston_test_runner")) - continue; - - if (global_runner) - assert(0 && "multiple weston_test_runner objects"); - - global_runner = g; - } - - assert(global_runner && "no weston_test_runner found"); - assert(global_runner->version == 1); - - runner->test_runner = wl_registry_bind(client->wl_registry, - global_runner->name, - &weston_test_runner_interface, - 1); - assert(runner->test_runner); - - weston_test_runner_add_listener(runner->test_runner, - &test_runner_listener, runner); - - return runner; -} - -static void -runner_destroy(struct runner *runner) -{ - weston_test_runner_destroy(runner->test_runner); - client_roundtrip(runner->client); - free(runner); -} - -static void -runner_run(struct runner *runner, const char *test_name) -{ - testlog("weston_test_runner.run(\"%s\")\n", test_name); - - runner->done = 0; - weston_test_runner_run(runner->test_runner, test_name); - - while (!runner->done) { - if (wl_display_dispatch(runner->client->wl_display) < 0) - assert(0 && "runner wait"); - } -} - -static struct ivi_application * -get_ivi_application(struct client *client) -{ - struct global *g; - struct global *global_iviapp = NULL; - struct ivi_application *iviapp; - - wl_list_for_each(g, &client->global_list, link) { - if (strcmp(g->interface, "ivi_application")) - continue; - - if (global_iviapp) - assert(0 && "multiple ivi_application objects"); - - global_iviapp = g; - } - - assert(global_iviapp && "no ivi_application found"); - - assert(global_iviapp->version == 1); - - iviapp = wl_registry_bind(client->wl_registry, global_iviapp->name, - &ivi_application_interface, 1); - assert(iviapp); - - return iviapp; -} - -struct ivi_window { - struct wl_surface *wl_surface; - struct ivi_surface *ivi_surface; - uint32_t ivi_id; -}; - -static struct ivi_window * -client_create_ivi_window(struct client *client, - struct ivi_application *iviapp, - uint32_t ivi_id) -{ - struct ivi_window *wnd; - - wnd = xzalloc(sizeof(*wnd)); - wnd->wl_surface = wl_compositor_create_surface(client->wl_compositor); - wnd->ivi_surface = ivi_application_surface_create(iviapp, ivi_id, - wnd->wl_surface); - wnd->ivi_id = ivi_id; - - return wnd; -} - -static void -ivi_window_destroy(struct ivi_window *wnd) -{ - ivi_surface_destroy(wnd->ivi_surface); - wl_surface_destroy(wnd->wl_surface); - free(wnd); -} - -/******************************** tests ********************************/ - -/* - * These tests make use of weston_test_runner global interface exposed by - * ivi-layout-test-plugin.c. This allows these tests to trigger compositor-side - * checks. - * - * See ivi-layout-test-plugin.c for further details. - */ - -/** - * RUNNER_TEST() names are defined in ivi-layout-test-plugin.c. - * Each RUNNER_TEST name listed here uses the same simple initial client setup. - */ -const char * const basic_test_names[] = { - "surface_visibility", - "surface_opacity", - "surface_dimension", - "surface_position", - "surface_destination_rectangle", - "surface_source_rectangle", - "surface_bad_opacity", - "surface_properties_changed_notification", - "surface_bad_properties_changed_notification", - "surface_on_many_layer", -}; - -const char * const surface_property_commit_changes_test_names[] = { - "commit_changes_after_visibility_set_surface_destroy", - "commit_changes_after_opacity_set_surface_destroy", - "commit_changes_after_source_rectangle_set_surface_destroy", - "commit_changes_after_destination_rectangle_set_surface_destroy", -}; - -const char * const render_order_test_names[] = { - "layer_render_order", - "layer_bad_render_order", - "layer_add_surfaces", -}; - -TEST_P(ivi_layout_runner, basic_test_names) -{ - /* an element from basic_test_names */ - const char * const *test_name = data; - struct client *client; - struct runner *runner; - struct ivi_application *iviapp; - struct ivi_window *wnd; - - client = create_client(); - runner = client_create_runner(client); - iviapp = get_ivi_application(client); - - wnd = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); - - runner_run(runner, *test_name); - - ivi_window_destroy(wnd); - runner_destroy(runner); -} - -TEST(ivi_layout_surface_create) -{ - struct client *client; - struct runner *runner; - struct ivi_application *iviapp; - struct ivi_window *winds[2]; - - client = create_client(); - runner = client_create_runner(client); - iviapp = get_ivi_application(client); - - winds[0] = client_create_ivi_window(client, iviapp, - IVI_TEST_SURFACE_ID(0)); - winds[1] = client_create_ivi_window(client, iviapp, - IVI_TEST_SURFACE_ID(1)); - - runner_run(runner, "surface_create_p1"); - - ivi_window_destroy(winds[0]); - - runner_run(runner, "surface_create_p2"); - - ivi_window_destroy(winds[1]); - runner_destroy(runner); -} - -TEST_P(commit_changes_after_properties_set_surface_destroy, surface_property_commit_changes_test_names) -{ - /* an element from surface_property_commit_changes_test_names */ - const char * const *test_name = data; - struct client *client; - struct runner *runner; - struct ivi_application *iviapp; - struct ivi_window *wnd; - - client = create_client(); - runner = client_create_runner(client); - iviapp = get_ivi_application(client); - - wnd = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); - - runner_run(runner, *test_name); - - ivi_window_destroy(wnd); - - runner_run(runner, "ivi_layout_commit_changes"); - - runner_destroy(runner); -} - -TEST(get_surface_after_destroy_ivi_surface) -{ - struct client *client; - struct runner *runner; - struct ivi_application *iviapp; - struct ivi_window *wnd; - - client = create_client(); - runner = client_create_runner(client); - iviapp = get_ivi_application(client); - - wnd = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); - - ivi_surface_destroy(wnd->ivi_surface); - - runner_run(runner, "get_surface_after_destroy_surface"); - - wl_surface_destroy(wnd->wl_surface); - free(wnd); - runner_destroy(runner); -} - -TEST(get_surface_after_destroy_wl_surface) -{ - struct client *client; - struct runner *runner; - struct ivi_application *iviapp; - struct ivi_window *wnd; - - client = create_client(); - runner = client_create_runner(client); - iviapp = get_ivi_application(client); - - wnd = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); - - wl_surface_destroy(wnd->wl_surface); - - runner_run(runner, "get_surface_after_destroy_surface"); - - ivi_surface_destroy(wnd->ivi_surface); - free(wnd); - runner_destroy(runner); -} - -TEST_P(ivi_layout_layer_render_order_runner, render_order_test_names) -{ - /* an element from render_order_test_names */ - const char * const *test_name = data; - struct client *client; - struct runner *runner; - struct ivi_application *iviapp; - struct ivi_window *winds[3]; - - client = create_client(); - runner = client_create_runner(client); - iviapp = get_ivi_application(client); - - winds[0] = client_create_ivi_window(client, iviapp, - IVI_TEST_SURFACE_ID(0)); - winds[1] = client_create_ivi_window(client, iviapp, - IVI_TEST_SURFACE_ID(1)); - winds[2] = client_create_ivi_window(client, iviapp, - IVI_TEST_SURFACE_ID(2)); - - runner_run(runner, *test_name); - - ivi_window_destroy(winds[0]); - ivi_window_destroy(winds[1]); - ivi_window_destroy(winds[2]); - runner_destroy(runner); -} - -TEST(destroy_surface_after_layer_render_order) -{ - struct client *client; - struct runner *runner; - struct ivi_application *iviapp; - struct ivi_window *winds[3]; - - client = create_client(); - runner = client_create_runner(client); - iviapp = get_ivi_application(client); - - winds[0] = client_create_ivi_window(client, iviapp, - IVI_TEST_SURFACE_ID(0)); - winds[1] = client_create_ivi_window(client, iviapp, - IVI_TEST_SURFACE_ID(1)); - winds[2] = client_create_ivi_window(client, iviapp, - IVI_TEST_SURFACE_ID(2)); - - runner_run(runner, "test_layer_render_order_destroy_one_surface_p1"); - - ivi_window_destroy(winds[1]); - - runner_run(runner, "test_layer_render_order_destroy_one_surface_p2"); - - ivi_window_destroy(winds[0]); - ivi_window_destroy(winds[2]); - runner_destroy(runner); -} - -TEST(commit_changes_after_render_order_set_surface_destroy) -{ - struct client *client; - struct runner *runner; - struct ivi_application *iviapp; - struct ivi_window *winds[3]; - - client = create_client(); - runner = client_create_runner(client); - iviapp = get_ivi_application(client); - - winds[0] = client_create_ivi_window(client, iviapp, - IVI_TEST_SURFACE_ID(0)); - winds[1] = client_create_ivi_window(client, iviapp, - IVI_TEST_SURFACE_ID(1)); - winds[2] = client_create_ivi_window(client, iviapp, - IVI_TEST_SURFACE_ID(2)); - - runner_run(runner, "commit_changes_after_render_order_set_surface_destroy"); - - ivi_window_destroy(winds[1]); - - runner_run(runner, "ivi_layout_commit_changes"); - runner_run(runner, "cleanup_layer"); - - ivi_window_destroy(winds[0]); - ivi_window_destroy(winds[2]); - runner_destroy(runner); -} - -TEST(ivi_layout_surface_configure_notification) -{ - struct client *client; - struct runner *runner; - struct ivi_application *iviapp; - struct ivi_window *wind; - struct buffer *buffer; - - client = create_client(); - runner = client_create_runner(client); - iviapp = get_ivi_application(client); - - runner_run(runner, "surface_configure_notification_p1"); - - wind = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); - - buffer = create_shm_buffer_a8r8g8b8(client, 200, 300); - - wl_surface_attach(wind->wl_surface, buffer->proxy, 0, 0); - wl_surface_damage(wind->wl_surface, 0, 0, 20, 30); - wl_surface_commit(wind->wl_surface); - - runner_run(runner, "surface_configure_notification_p2"); - - wl_surface_attach(wind->wl_surface, buffer->proxy, 0, 0); - wl_surface_damage(wind->wl_surface, 0, 0, 40, 50); - wl_surface_commit(wind->wl_surface); - - runner_run(runner, "surface_configure_notification_p3"); - - buffer_destroy(buffer); - ivi_window_destroy(wind); - runner_destroy(runner); -} - -TEST(ivi_layout_surface_create_notification) -{ - struct client *client; - struct runner *runner; - struct ivi_application *iviapp; - struct ivi_window *wind; - - client = create_client(); - runner = client_create_runner(client); - iviapp = get_ivi_application(client); - - runner_run(runner, "surface_create_notification_p1"); - - wind = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); - - runner_run(runner, "surface_create_notification_p2"); - - ivi_window_destroy(wind); - wind = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); - runner_run(runner, "surface_create_notification_p3"); - - ivi_window_destroy(wind); - runner_destroy(runner); -} - -TEST(ivi_layout_surface_remove_notification) -{ - struct client *client; - struct runner *runner; - struct ivi_application *iviapp; - struct ivi_window *wind; - - client = create_client(); - runner = client_create_runner(client); - iviapp = get_ivi_application(client); - - wind = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); - runner_run(runner, "surface_remove_notification_p1"); - ivi_window_destroy(wind); - - runner_run(runner, "surface_remove_notification_p2"); - - wind = client_create_ivi_window(client, iviapp, IVI_TEST_SURFACE_ID(0)); - ivi_window_destroy(wind); - runner_run(runner, "surface_remove_notification_p3"); - - runner_destroy(runner); -} diff --git a/tests/ivi-layout-test-plugin.c b/tests/ivi-layout-test-plugin.c deleted file mode 100644 index cad2fa5..0000000 --- a/tests/ivi-layout-test-plugin.c +++ /dev/null @@ -1,1018 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * Copyright © 2013 DENSO CORPORATION - * Copyright © 2015 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include "compositor/weston.h" -#include "weston-test-server-protocol.h" -#include "ivi-test.h" -#include "ivi-shell/ivi-layout-export.h" -#include "shared/helpers.h" - -struct test_context; - -struct runner_test { - const char *name; - void (*run)(struct test_context *); -} __attribute__ ((aligned (32))); - -#define RUNNER_TEST(name) \ - static void runner_func_##name(struct test_context *); \ - \ - const struct runner_test runner_test_##name \ - __attribute__ ((section ("plugin_test_section"))) = \ - { \ - #name, runner_func_##name \ - }; \ - \ - static void runner_func_##name(struct test_context *ctx) - -extern const struct runner_test __start_plugin_test_section; -extern const struct runner_test __stop_plugin_test_section; - -static const struct runner_test * -find_runner_test(const char *name) -{ - const struct runner_test *t; - - for (t = &__start_plugin_test_section; - t < &__stop_plugin_test_section; t++) { - if (strcmp(t->name, name) == 0) - return t; - } - - return NULL; -} - -struct test_context { - const struct ivi_layout_interface *layout_interface; - struct wl_resource *runner_resource; - uint32_t user_flags; - - struct wl_listener surface_property_changed; - struct wl_listener surface_created; - struct wl_listener surface_removed; - struct wl_listener surface_configured; -}; - -struct test_launcher { - struct weston_compositor *compositor; - struct test_context context; - const struct ivi_layout_interface *layout_interface; -}; - -static void -destroy_runner(struct wl_resource *resource) -{ - struct test_launcher *launcher = wl_resource_get_user_data(resource); - struct test_context *ctx = &launcher->context; - - assert(ctx->runner_resource == NULL || - ctx->runner_resource == resource); - - ctx->layout_interface = NULL; - ctx->runner_resource = NULL; -} - -static void -runner_destroy_handler(struct wl_client *client, struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -runner_run_handler(struct wl_client *client, struct wl_resource *resource, - const char *test_name) -{ - struct test_launcher *launcher; - const struct runner_test *t; - struct test_context *ctx; - - launcher = wl_resource_get_user_data(resource); - ctx = &launcher->context; - - assert(ctx->runner_resource == NULL || - ctx->runner_resource == resource); - - ctx->layout_interface = launcher->layout_interface; - ctx->runner_resource = resource; - - t = find_runner_test(test_name); - if (!t) { - weston_log("Error: runner test \"%s\" not found.\n", - test_name); - wl_resource_post_error(resource, - WESTON_TEST_RUNNER_ERROR_UNKNOWN_TEST, - "weston_test_runner: unknown: '%s'", - test_name); - return; - } - - weston_log("weston_test_runner.run(\"%s\")\n", test_name); - - t->run(ctx); - - weston_test_runner_send_finished(resource); -} - -static const struct weston_test_runner_interface runner_implementation = { - runner_destroy_handler, - runner_run_handler -}; - -static void -bind_runner(struct wl_client *client, void *data, - uint32_t version, uint32_t id) -{ - struct test_launcher *launcher = data; - struct wl_resource *resource; - - resource = wl_resource_create(client, &weston_test_runner_interface, - 1, id); - if (!resource) { - wl_client_post_no_memory(client); - return; - } - - wl_resource_set_implementation(resource, &runner_implementation, - launcher, destroy_runner); - - if (launcher->context.runner_resource != NULL) { - weston_log("test FATAL: " - "attempting to run several tests in parallel.\n"); - wl_resource_post_error(resource, - WESTON_TEST_RUNNER_ERROR_TEST_FAILED, - "attempt to run parallel tests"); - } -} - -WL_EXPORT int -wet_module_init(struct weston_compositor *compositor, - int *argc, char *argv[]) -{ - struct test_launcher *launcher; - const struct ivi_layout_interface *iface; - - iface = ivi_layout_get_api(compositor); - - if (!iface) { - weston_log("fatal: cannot use ivi_layout_interface.\n"); - return -1; - } - - launcher = zalloc(sizeof *launcher); - if (!launcher) - return -1; - - launcher->compositor = compositor; - launcher->layout_interface = iface; - - if (wl_global_create(compositor->wl_display, - &weston_test_runner_interface, 1, - launcher, bind_runner) == NULL) - return -1; - - return 0; -} - -static void -runner_assert_fail(const char *cond, const char *file, int line, - const char *func, struct test_context *ctx) -{ - weston_log("Assert failure in %s:%d, %s: '%s'\n", - file, line, func, cond); - - assert(ctx->runner_resource); - wl_resource_post_error(ctx->runner_resource, - WESTON_TEST_RUNNER_ERROR_TEST_FAILED, - "Assert failure in %s:%d, %s: '%s'\n", - file, line, func, cond); -} - -#define runner_assert(cond) ({ \ - bool b_ = (cond); \ - if (!b_) \ - runner_assert_fail(#cond, __FILE__, __LINE__, \ - __func__, ctx); \ - b_; \ -}) - -#define runner_assert_or_return(cond) do { \ - bool b_ = (cond); \ - if (!b_) { \ - runner_assert_fail(#cond, __FILE__, __LINE__, \ - __func__, ctx); \ - return; \ - } \ -} while (0) - - -/*************************** tests **********************************/ - -/* - * This is a IVI controller module requiring ivi-shell.so. - * This module is specially written to execute tests that target the - * ivi_layout API. - * - * The test program containing the fixture setup and initiating the tests is - * test-ivi-layout-client (ivi-layout-test-client.c). - * That program uses the weston-test-runner framework to execute each TEST() - * in ivi-layout-test-client.c with a fresh connection to the single - * compositor instance. - * - * Each TEST() in ivi-layout-test-client.c will bind to weston_test_runner global - * interface. A TEST() will set up the client state, and issue - * weston_test_runner.run request to execute the compositor-side of the test. - * - * The compositor-side parts of the tests are in this file. They are specified - * by RUNNER_TEST() macro, where the name argument matches the name string - * passed to weston_test_runner.run. - * - * A RUNNER_TEST() function simply returns when it succeeds. If it fails, - * a fatal protocol error is sent to the client from runner_assert() or - * runner_assert_or_return(). - * - * A single TEST() in ivi-layout-test-client.c may use multiple RUNNER_TEST()s to - * achieve multiple test points over a client action sequence. - */ - -RUNNER_TEST(surface_create_p1) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf[2]; - uint32_t ivi_id; - - ivisurf[0] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf[0]); - - ivisurf[1] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(1)); - runner_assert(ivisurf[1]); - - ivi_id = lyt->get_id_of_surface(ivisurf[0]); - runner_assert(ivi_id == IVI_TEST_SURFACE_ID(0)); - - ivi_id = lyt->get_id_of_surface(ivisurf[1]); - runner_assert(ivi_id == IVI_TEST_SURFACE_ID(1)); -} - -RUNNER_TEST(surface_create_p2) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - - /* the ivi_surface was destroyed by the client */ - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf == NULL); -} - -RUNNER_TEST(surface_visibility) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - int32_t ret; - const struct ivi_layout_surface_properties *prop; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf); - - ret = lyt->surface_set_visibility(ivisurf, true); - runner_assert(ret == IVI_SUCCEEDED); - - lyt->commit_changes(); - - prop = lyt->get_properties_of_surface(ivisurf); - runner_assert(prop->visibility == true); -} - -RUNNER_TEST(surface_opacity) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - int32_t ret; - const struct ivi_layout_surface_properties *prop; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf); - - prop = lyt->get_properties_of_surface(ivisurf); - runner_assert(prop->opacity == wl_fixed_from_double(1.0)); - - ret = lyt->surface_set_opacity(ivisurf, wl_fixed_from_double(0.5)); - runner_assert(ret == IVI_SUCCEEDED); - - runner_assert(prop->opacity == wl_fixed_from_double(1.0)); - - lyt->commit_changes(); - - runner_assert(prop->opacity == wl_fixed_from_double(0.5)); -} - -RUNNER_TEST(surface_dimension) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - const struct ivi_layout_surface_properties *prop; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf != NULL); - - prop = lyt->get_properties_of_surface(ivisurf); - runner_assert_or_return(prop); - runner_assert(prop->dest_width == 1); - runner_assert(prop->dest_height == 1); - - runner_assert(IVI_SUCCEEDED == - lyt->surface_set_destination_rectangle(ivisurf, prop->dest_x, - prop->dest_y, 200, 300)); - - runner_assert(prop->dest_width == 1); - runner_assert(prop->dest_height == 1); - - lyt->commit_changes(); - - prop = lyt->get_properties_of_surface(ivisurf); - runner_assert_or_return(prop); - runner_assert(prop->dest_width == 200); - runner_assert(prop->dest_height == 300); -} - -RUNNER_TEST(surface_position) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - const struct ivi_layout_surface_properties *prop; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf != NULL); - - prop = lyt->get_properties_of_surface(ivisurf); - runner_assert_or_return(prop); - runner_assert(prop->dest_x == 0); - runner_assert(prop->dest_y == 0); - - runner_assert(lyt->surface_set_destination_rectangle( - ivisurf, 20, 30, - prop->dest_width, prop->dest_height) == IVI_SUCCEEDED); - - runner_assert(prop->dest_x == 0); - runner_assert(prop->dest_y == 0); - - lyt->commit_changes(); - - prop = lyt->get_properties_of_surface(ivisurf); - runner_assert_or_return(prop); - runner_assert(prop->dest_x == 20); - runner_assert(prop->dest_y == 30); -} - -RUNNER_TEST(surface_destination_rectangle) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - const struct ivi_layout_surface_properties *prop; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf != NULL); - - prop = lyt->get_properties_of_surface(ivisurf); - runner_assert_or_return(prop); - runner_assert(prop->dest_width == 1); - runner_assert(prop->dest_height == 1); - runner_assert(prop->dest_x == 0); - runner_assert(prop->dest_y == 0); - - runner_assert(lyt->surface_set_destination_rectangle( - ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); - - prop = lyt->get_properties_of_surface(ivisurf); - runner_assert_or_return(prop); - runner_assert(prop->dest_width == 1); - runner_assert(prop->dest_height == 1); - runner_assert(prop->dest_x == 0); - runner_assert(prop->dest_y == 0); - - lyt->commit_changes(); - - prop = lyt->get_properties_of_surface(ivisurf); - runner_assert_or_return(prop); - runner_assert(prop->dest_width == 200); - runner_assert(prop->dest_height == 300); - runner_assert(prop->dest_x == 20); - runner_assert(prop->dest_y == 30); -} - -RUNNER_TEST(surface_source_rectangle) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - const struct ivi_layout_surface_properties *prop; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf != NULL); - - prop = lyt->get_properties_of_surface(ivisurf); - runner_assert_or_return(prop); - runner_assert(prop->source_width == 0); - runner_assert(prop->source_height == 0); - runner_assert(prop->source_x == 0); - runner_assert(prop->source_y == 0); - - runner_assert(lyt->surface_set_source_rectangle( - ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); - - prop = lyt->get_properties_of_surface(ivisurf); - runner_assert_or_return(prop); - runner_assert(prop->source_width == 0); - runner_assert(prop->source_height == 0); - runner_assert(prop->source_x == 0); - runner_assert(prop->source_y == 0); - - lyt->commit_changes(); - - prop = lyt->get_properties_of_surface(ivisurf); - runner_assert_or_return(prop); - runner_assert(prop->source_width == 200); - runner_assert(prop->source_height == 300); - runner_assert(prop->source_x == 20); - runner_assert(prop->source_y == 30); -} - -RUNNER_TEST(surface_bad_opacity) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - const struct ivi_layout_surface_properties *prop; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf != NULL); - - runner_assert(lyt->surface_set_opacity( - NULL, wl_fixed_from_double(0.3)) == IVI_FAILED); - - runner_assert(lyt->surface_set_opacity( - ivisurf, wl_fixed_from_double(0.3)) == IVI_SUCCEEDED); - - runner_assert(lyt->surface_set_opacity( - ivisurf, wl_fixed_from_double(-1)) == IVI_FAILED); - - lyt->commit_changes(); - - prop = lyt->get_properties_of_surface(ivisurf); - runner_assert(prop->opacity == wl_fixed_from_double(0.3)); - - runner_assert(lyt->surface_set_opacity( - ivisurf, wl_fixed_from_double(1.1)) == IVI_FAILED); - - lyt->commit_changes(); - - runner_assert(prop->opacity == wl_fixed_from_double(0.3)); - - runner_assert(lyt->surface_set_opacity( - NULL, wl_fixed_from_double(0.5)) == IVI_FAILED); - - lyt->commit_changes(); -} - -RUNNER_TEST(surface_on_many_layer) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - struct ivi_layout_layer *ivilayers[IVI_TEST_LAYER_COUNT] = {}; - struct ivi_layout_layer **array; - int32_t length = 0; - uint32_t i; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf != NULL); - - for (i = 0; i < IVI_TEST_LAYER_COUNT; i++) { - ivilayers[i] = lyt->layer_create_with_dimension( - IVI_TEST_LAYER_ID(i), 200, 300); - runner_assert(lyt->layer_add_surface( - ivilayers[i], ivisurf) == IVI_SUCCEEDED); - } - - lyt->commit_changes(); - - runner_assert(lyt->get_layers_under_surface( - ivisurf, &length, &array) == IVI_SUCCEEDED); - runner_assert(IVI_TEST_LAYER_COUNT == length); - for (i = 0; i < IVI_TEST_LAYER_COUNT; i++) - runner_assert(array[i] == ivilayers[i]); - - if (length > 0) - free(array); - - for (i = 0; i < IVI_TEST_LAYER_COUNT; i++) - lyt->layer_remove_surface(ivilayers[i], ivisurf); - - array = NULL; - - lyt->commit_changes(); - - runner_assert(lyt->get_layers_under_surface( - ivisurf, &length, &array) == IVI_SUCCEEDED); - runner_assert(length == 0 && array == NULL); - - for (i = 0; i < IVI_TEST_LAYER_COUNT; i++) - lyt->layer_destroy(ivilayers[i]); -} - -RUNNER_TEST(ivi_layout_commit_changes) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - lyt->commit_changes(); -} - -RUNNER_TEST(commit_changes_after_visibility_set_surface_destroy) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf != NULL); - runner_assert(lyt->surface_set_visibility( - ivisurf, true) == IVI_SUCCEEDED); -} - -RUNNER_TEST(commit_changes_after_opacity_set_surface_destroy) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf != NULL); - runner_assert(lyt->surface_set_opacity( - ivisurf, wl_fixed_from_double(0.5)) == IVI_SUCCEEDED); -} - -RUNNER_TEST(commit_changes_after_source_rectangle_set_surface_destroy) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf != NULL); - runner_assert(lyt->surface_set_source_rectangle( - ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); -} - -RUNNER_TEST(commit_changes_after_destination_rectangle_set_surface_destroy) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf != NULL); - runner_assert(lyt->surface_set_destination_rectangle( - ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); -} - -RUNNER_TEST(get_surface_after_destroy_surface) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf == NULL); -} - -RUNNER_TEST(layer_render_order) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - struct ivi_layout_surface *ivisurfs[IVI_TEST_SURFACE_COUNT] = {}; - struct ivi_layout_surface **array; - int32_t length = 0; - uint32_t i; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - - for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) - ivisurfs[i] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(i)); - - runner_assert(lyt->layer_set_render_order( - ivilayer, ivisurfs, IVI_TEST_SURFACE_COUNT) == IVI_SUCCEEDED); - - lyt->commit_changes(); - - runner_assert(lyt->get_surfaces_on_layer( - ivilayer, &length, &array) == IVI_SUCCEEDED); - runner_assert(IVI_TEST_SURFACE_COUNT == length); - for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) - runner_assert(array[i] == ivisurfs[i]); - - if (length > 0) - free(array); - - runner_assert(lyt->layer_set_render_order( - ivilayer, NULL, 0) == IVI_SUCCEEDED); - - array = NULL; - - lyt->commit_changes(); - - runner_assert(lyt->get_surfaces_on_layer( - ivilayer, &length, &array) == IVI_SUCCEEDED); - runner_assert(length == 0 && array == NULL); - - lyt->layer_destroy(ivilayer); -} - -RUNNER_TEST(test_layer_render_order_destroy_one_surface_p1) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - struct ivi_layout_surface *ivisurfs[IVI_TEST_SURFACE_COUNT] = {}; - struct ivi_layout_surface **array; - int32_t length = 0; - int32_t i; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - - for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) - ivisurfs[i] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(i)); - - runner_assert(lyt->layer_set_render_order( - ivilayer, ivisurfs, IVI_TEST_SURFACE_COUNT) == IVI_SUCCEEDED); - - lyt->commit_changes(); - - runner_assert(lyt->get_surfaces_on_layer( - ivilayer, &length, &array) == IVI_SUCCEEDED); - runner_assert(IVI_TEST_SURFACE_COUNT == length); - for (i = 0; i < length; i++) - runner_assert(array[i] == ivisurfs[i]); - - if (length > 0) - free(array); -} - -RUNNER_TEST(test_layer_render_order_destroy_one_surface_p2) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - struct ivi_layout_surface *ivisurfs[2] = {}; - struct ivi_layout_surface **array; - int32_t length = 0; - int32_t i; - - ivilayer = lyt->get_layer_from_id(IVI_TEST_LAYER_ID(0)); - ivisurfs[0] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - ivisurfs[1] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(2)); - - runner_assert(lyt->get_surfaces_on_layer( - ivilayer, &length, &array) == IVI_SUCCEEDED); - runner_assert(2 == length); - for (i = 0; i < length; i++) - runner_assert(array[i] == ivisurfs[i]); - - if (length > 0) - free(array); - - lyt->layer_destroy(ivilayer); -} - -RUNNER_TEST(layer_bad_render_order) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - struct ivi_layout_surface *ivisurfs[IVI_TEST_SURFACE_COUNT] = {}; - struct ivi_layout_surface **array = NULL; - int32_t length = 0; - uint32_t i; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - - for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) - ivisurfs[i] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(i)); - - runner_assert(lyt->layer_set_render_order( - NULL, ivisurfs, IVI_TEST_SURFACE_COUNT) == IVI_FAILED); - - lyt->commit_changes(); - - runner_assert(lyt->get_surfaces_on_layer( - NULL, &length, &array) == IVI_FAILED); - runner_assert(length == 0 && array == NULL); - - runner_assert(lyt->get_surfaces_on_layer( - ivilayer, NULL, &array) == IVI_FAILED); - runner_assert(array == NULL); - - runner_assert(lyt->get_surfaces_on_layer( - ivilayer, &length, NULL) == IVI_FAILED); - runner_assert(length == 0); - - lyt->layer_destroy(ivilayer); -} - -RUNNER_TEST(layer_add_surfaces) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - struct ivi_layout_surface *ivisurfs[IVI_TEST_SURFACE_COUNT] = {}; - struct ivi_layout_surface **array; - int32_t length = 0; - uint32_t i; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - - for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) { - ivisurfs[i] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(i)); - runner_assert(lyt->layer_add_surface( - ivilayer, ivisurfs[i]) == IVI_SUCCEEDED); - } - - lyt->commit_changes(); - - runner_assert(lyt->get_surfaces_on_layer( - ivilayer, &length, &array) == IVI_SUCCEEDED); - runner_assert(IVI_TEST_SURFACE_COUNT == length); - for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) - runner_assert(array[i] == ivisurfs[i]); - - if (length > 0) - free(array); - - runner_assert(lyt->layer_set_render_order( - ivilayer, NULL, 0) == IVI_SUCCEEDED); - - for (i = IVI_TEST_SURFACE_COUNT; i-- > 0;) - runner_assert(lyt->layer_add_surface( - ivilayer, ivisurfs[i]) == IVI_SUCCEEDED); - - lyt->commit_changes(); - - runner_assert(lyt->get_surfaces_on_layer( - ivilayer, &length, &array) == IVI_SUCCEEDED); - runner_assert(IVI_TEST_SURFACE_COUNT == length); - for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) - runner_assert(array[i] == ivisurfs[IVI_TEST_SURFACE_COUNT - (i + 1)]); - - if (length > 0) - free(array); - - lyt->layer_destroy(ivilayer); -} - -RUNNER_TEST(commit_changes_after_render_order_set_surface_destroy) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - struct ivi_layout_surface *ivisurfs[IVI_TEST_SURFACE_COUNT] = {}; - int i; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - - for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) - ivisurfs[i] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(i)); - - runner_assert(lyt->layer_set_render_order( - ivilayer, ivisurfs, IVI_TEST_SURFACE_COUNT) == IVI_SUCCEEDED); -} - -RUNNER_TEST(cleanup_layer) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - - ivilayer = lyt->get_layer_from_id(IVI_TEST_LAYER_ID(0)); - lyt->layer_destroy(ivilayer); -} - -static void -test_surface_properties_changed_notification_callback(struct wl_listener *listener, void *data) - -{ - struct test_context *ctx = - container_of(listener, struct test_context, - surface_property_changed); - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf = data; - - runner_assert_or_return(lyt->get_id_of_surface(ivisurf) == IVI_TEST_SURFACE_ID(0)); - - ctx->user_flags = 1; -} - -RUNNER_TEST(surface_properties_changed_notification) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - const uint32_t id_surface = IVI_TEST_SURFACE_ID(0); - struct ivi_layout_surface *ivisurf; - - ctx->user_flags = 0; - - ivisurf = lyt->get_surface_from_id(id_surface); - runner_assert(ivisurf != NULL); - - ctx->surface_property_changed.notify = test_surface_properties_changed_notification_callback; - - runner_assert(lyt->surface_add_listener( - ivisurf, &ctx->surface_property_changed) == IVI_SUCCEEDED); - - lyt->commit_changes(); - - runner_assert(ctx->user_flags == 0); - - runner_assert(lyt->surface_set_destination_rectangle( - ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); - - lyt->commit_changes(); - - runner_assert(ctx->user_flags == 1); - - ctx->user_flags = 0; - runner_assert(lyt->surface_set_destination_rectangle( - ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); - - lyt->commit_changes(); - - runner_assert(ctx->user_flags == 0); - - // remove surface property changed listener. - wl_list_remove(&ctx->surface_property_changed.link); - ctx->user_flags = 0; - runner_assert(lyt->surface_set_destination_rectangle( - ivisurf, 40, 50, 400, 500) == IVI_SUCCEEDED); - - lyt->commit_changes(); - - runner_assert(ctx->user_flags == 0); -} - -static void -test_surface_configure_notification_callback(struct wl_listener *listener, void *data) -{ - struct test_context *ctx = - container_of(listener, struct test_context, - surface_configured); - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf = data; - - runner_assert_or_return(lyt->get_id_of_surface(ivisurf) == IVI_TEST_SURFACE_ID(0)); - - ctx->user_flags = 1; -} - -RUNNER_TEST(surface_configure_notification_p1) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - ctx->surface_configured.notify = test_surface_configure_notification_callback; - runner_assert(IVI_SUCCEEDED == lyt->add_listener_configure_surface(&ctx->surface_configured)); - lyt->commit_changes(); - - ctx->user_flags = 0; -} - -RUNNER_TEST(surface_configure_notification_p2) -{ - runner_assert(ctx->user_flags == 1); - - // remove surface configured listener. - wl_list_remove(&ctx->surface_configured.link); - ctx->user_flags = 0; -} - -RUNNER_TEST(surface_configure_notification_p3) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - lyt->commit_changes(); - runner_assert(ctx->user_flags == 0); -} - -static void -test_surface_create_notification_callback(struct wl_listener *listener, void *data) -{ - struct test_context *ctx = - container_of(listener, struct test_context, - surface_created); - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf = data; - - runner_assert_or_return(lyt->get_id_of_surface(ivisurf) == IVI_TEST_SURFACE_ID(0)); - - ctx->user_flags = 1; -} - -RUNNER_TEST(surface_create_notification_p1) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - ctx->surface_created.notify = test_surface_create_notification_callback; - runner_assert(lyt->add_listener_create_surface( - &ctx->surface_created) == IVI_SUCCEEDED); - - ctx->user_flags = 0; -} - -RUNNER_TEST(surface_create_notification_p2) -{ - runner_assert(ctx->user_flags == 1); - - // remove surface created listener. - wl_list_remove(&ctx->surface_created.link); - ctx->user_flags = 0; -} - -RUNNER_TEST(surface_create_notification_p3) -{ - runner_assert(ctx->user_flags == 0); -} - -static void -test_surface_remove_notification_callback(struct wl_listener *listener, void *data) -{ - struct test_context *ctx = - container_of(listener, struct test_context, - surface_removed); - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf = data; - - runner_assert_or_return(lyt->get_id_of_surface(ivisurf) == IVI_TEST_SURFACE_ID(0)); - - ctx->user_flags = 1; -} - -RUNNER_TEST(surface_remove_notification_p1) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - ctx->surface_removed.notify = test_surface_remove_notification_callback; - runner_assert(lyt->add_listener_remove_surface(&ctx->surface_removed) - == IVI_SUCCEEDED); - - ctx->user_flags = 0; -} - -RUNNER_TEST(surface_remove_notification_p2) -{ - runner_assert(ctx->user_flags == 1); - - // remove surface removed listener. - wl_list_remove(&ctx->surface_removed.link); - ctx->user_flags = 0; -} - -RUNNER_TEST(surface_remove_notification_p3) -{ - runner_assert(ctx->user_flags == 0); -} - -static void -test_surface_bad_properties_changed_notification_callback(struct wl_listener *listener, void *data) -{ -} - -RUNNER_TEST(surface_bad_properties_changed_notification) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf != NULL); - - ctx->surface_property_changed.notify = test_surface_bad_properties_changed_notification_callback; - - runner_assert(lyt->surface_add_listener( - NULL, &ctx->surface_property_changed) == IVI_FAILED); - runner_assert(lyt->surface_add_listener( - ivisurf, NULL) == IVI_FAILED); -} diff --git a/tests/ivi-shell-app-test.c b/tests/ivi-shell-app-test.c deleted file mode 100644 index 5652c0d..0000000 --- a/tests/ivi-shell-app-test.c +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright © 2015 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include "weston-test-client-helper.h" -#include "ivi-application-client-protocol.h" -#include "weston-test-fixture-compositor.h" -#include "test-config.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - setup.shell = SHELL_IVI; - setup.config_file = TESTSUITE_IVI_CONFIG_PATH; - setup.logging_scopes = "log,test-harness-plugin,proto"; - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -static struct ivi_application * -get_ivi_application(struct client *client) -{ - struct global *g; - struct global *global_iviapp = NULL; - struct ivi_application *iviapp; - - wl_list_for_each(g, &client->global_list, link) { - if (strcmp(g->interface, "ivi_application")) - continue; - - if (global_iviapp) - assert(0 && "multiple ivi_application objects"); - - global_iviapp = g; - } - - assert(global_iviapp && "no ivi_application found"); - - assert(global_iviapp->version == 1); - - iviapp = wl_registry_bind(client->wl_registry, global_iviapp->name, - &ivi_application_interface, 1); - assert(iviapp); - - return iviapp; -} - -TEST(ivi_application_exists) -{ - struct client *client; - struct ivi_application *iviapp; - - client = create_client(); - iviapp = get_ivi_application(client); - client_roundtrip(client); - - testlog("Successful bind: %p\n", iviapp); - - client_destroy(client); -} diff --git a/tests/ivi-test.h b/tests/ivi-test.h deleted file mode 100644 index 5706ed8..0000000 --- a/tests/ivi-test.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright © 2015 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef IVI_TEST_H -#define IVI_TEST_H - -/* - * IVI_TEST_SURFACE_ID_BASE is an arbitrary number picked for the - * IVI tests. The only requirement is that it does not clash with - * any other ivi-id range used in Weston. - */ -#define IVI_TEST_SURFACE_ID_BASE 0xffc01200 -#define IVI_TEST_SURFACE_ID(i) (IVI_TEST_SURFACE_ID_BASE + i) -#define IVI_TEST_LAYER_ID_BASE 0xeef01200 -#define IVI_TEST_LAYER_ID(i) (IVI_TEST_LAYER_ID_BASE + i) - -#define IVI_TEST_SURFACE_COUNT (3) -#define IVI_TEST_LAYER_COUNT (3) - -#endif /* IVI_TEST_H */ diff --git a/tests/keyboard-test.c b/tests/keyboard-test.c deleted file mode 100644 index f2a769e..0000000 --- a/tests/keyboard-test.c +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include - -#include "input-timestamps-helper.h" -#include "shared/timespec-util.h" -#include "weston-test-client-helper.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -static const struct timespec t1 = { .tv_sec = 1, .tv_nsec = 1000001 }; -static const struct timespec t2 = { .tv_sec = 2, .tv_nsec = 2000001 }; -static const struct timespec t_other = { .tv_sec = 123, .tv_nsec = 456 }; - -static struct client * -create_client_with_keyboard_focus(void) -{ - struct client *cl = create_client_and_test_surface(10, 10, 1, 1); - assert(cl); - - weston_test_activate_surface(cl->test->weston_test, - cl->surface->wl_surface); - client_roundtrip(cl); - - return cl; -} - -static void -send_key(struct client *client, const struct timespec *time, - uint32_t key, uint32_t state) -{ - uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; - - timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); - weston_test_send_key(client->test->weston_test, tv_sec_hi, tv_sec_lo, - tv_nsec, key, state); - client_roundtrip(client); -} - -TEST(simple_keyboard_test) -{ - struct client *client = create_client_with_keyboard_focus(); - struct keyboard *keyboard = client->input->keyboard; - struct surface *expect_focus = client->surface; - uint32_t expect_key = 0; - uint32_t expect_state = 0; - - 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; - send_key(client, &t1, expect_key, expect_state); - } else if (keyboard->focus) { - expect_focus = NULL; - weston_test_activate_surface( - client->test->weston_test, NULL); - client_roundtrip(client); - } else if (expect_key < 10) { - expect_key++; - expect_focus = client->surface; - expect_state = WL_KEYBOARD_KEY_STATE_PRESSED; - weston_test_activate_surface( - client->test->weston_test, - expect_focus->wl_surface); - send_key(client, &t1, expect_key, expect_state); - } else { - break; - } - } -} - -TEST(keyboard_key_event_time) -{ - struct client *client = create_client_with_keyboard_focus(); - struct keyboard *keyboard = client->input->keyboard; - struct input_timestamps *input_ts = - input_timestamps_create_for_keyboard(client); - - send_key(client, &t1, 1, WL_KEYBOARD_KEY_STATE_PRESSED); - assert(keyboard->key_time_msec == timespec_to_msec(&t1)); - assert(timespec_eq(&keyboard->key_time_timespec, &t1)); - - send_key(client, &t2, 1, WL_KEYBOARD_KEY_STATE_RELEASED); - assert(keyboard->key_time_msec == timespec_to_msec(&t2)); - assert(timespec_eq(&keyboard->key_time_timespec, &t2)); - - input_timestamps_destroy(input_ts); -} - -TEST(keyboard_timestamps_stop_after_input_timestamps_object_is_destroyed) -{ - struct client *client = create_client_with_keyboard_focus(); - struct keyboard *keyboard = client->input->keyboard; - struct input_timestamps *input_ts = - input_timestamps_create_for_keyboard(client); - - send_key(client, &t1, 1, WL_KEYBOARD_KEY_STATE_PRESSED); - assert(keyboard->key_time_msec == timespec_to_msec(&t1)); - assert(timespec_eq(&keyboard->key_time_timespec, &t1)); - - input_timestamps_destroy(input_ts); - - send_key(client, &t2, 1, WL_KEYBOARD_KEY_STATE_RELEASED); - assert(keyboard->key_time_msec == timespec_to_msec(&t2)); - assert(timespec_is_zero(&keyboard->key_time_timespec)); -} - -TEST(keyboard_timestamps_stop_after_client_releases_wl_keyboard) -{ - struct client *client = create_client_with_keyboard_focus(); - struct keyboard *keyboard = client->input->keyboard; - struct input_timestamps *input_ts = - input_timestamps_create_for_keyboard(client); - - send_key(client, &t1, 1, WL_KEYBOARD_KEY_STATE_PRESSED); - assert(keyboard->key_time_msec == timespec_to_msec(&t1)); - assert(timespec_eq(&keyboard->key_time_timespec, &t1)); - - wl_keyboard_release(client->input->keyboard->wl_keyboard); - - /* Set input_timestamp to an arbitrary value (different from t1, t2 - * and 0) and check that it is not changed by sending the event. - * This is preferred over just checking for 0, since 0 is used - * internally for resetting the timestamp after handling an input - * event and checking for it here may lead to false negatives. */ - keyboard->input_timestamp = t_other; - send_key(client, &t2, 1, WL_KEYBOARD_KEY_STATE_RELEASED); - assert(timespec_eq(&keyboard->input_timestamp, &t_other)); - - input_timestamps_destroy(input_ts); -} diff --git a/tests/linux-explicit-synchronization-test.c b/tests/linux-explicit-synchronization-test.c deleted file mode 100644 index 4d4c24b..0000000 --- a/tests/linux-explicit-synchronization-test.c +++ /dev/null @@ -1,498 +0,0 @@ -/* - * Copyright © 2018 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include "linux-explicit-synchronization-unstable-v1-client-protocol.h" -#include "weston-test-client-helper.h" -#include "wayland-server-protocol.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - - /* We need to use the pixman renderer, since a few of the tests depend - * on the renderer holding onto a surface buffer until the next one - * is committed, which the noop renderer doesn't do. */ - setup.renderer = RENDERER_PIXMAN; - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -static struct zwp_linux_explicit_synchronization_v1 * -get_linux_explicit_synchronization(struct client *client) -{ - struct global *g; - struct global *global_sync = NULL; - struct zwp_linux_explicit_synchronization_v1 *sync = NULL; - - wl_list_for_each(g, &client->global_list, link) { - if (strcmp(g->interface, - zwp_linux_explicit_synchronization_v1_interface.name)) - continue; - - if (global_sync) - assert(!"Multiple linux explicit sync objects"); - - global_sync = g; - } - - assert(global_sync); - assert(global_sync->version == 2); - - sync = wl_registry_bind( - client->wl_registry, global_sync->name, - &zwp_linux_explicit_synchronization_v1_interface, 2); - assert(sync); - - return sync; -} - -static struct client * -create_test_client(void) -{ - struct client *cl = create_client_and_test_surface(0, 0, 100, 100); - assert(cl); - return cl; -} - -TEST(second_surface_synchronization_on_surface_raises_error) -{ - struct client *client = create_test_client(); - struct zwp_linux_explicit_synchronization_v1 *sync = - get_linux_explicit_synchronization(client); - struct zwp_linux_surface_synchronization_v1 *surface_sync1; - struct zwp_linux_surface_synchronization_v1 *surface_sync2; - - surface_sync1 = - zwp_linux_explicit_synchronization_v1_get_synchronization( - sync, client->surface->wl_surface); - client_roundtrip(client); - - /* Second surface_synchronization creation should fail */ - surface_sync2 = - zwp_linux_explicit_synchronization_v1_get_synchronization( - sync, client->surface->wl_surface); - expect_protocol_error( - client, - &zwp_linux_explicit_synchronization_v1_interface, - ZWP_LINUX_EXPLICIT_SYNCHRONIZATION_V1_ERROR_SYNCHRONIZATION_EXISTS); - - zwp_linux_surface_synchronization_v1_destroy(surface_sync2); - zwp_linux_surface_synchronization_v1_destroy(surface_sync1); - zwp_linux_explicit_synchronization_v1_destroy(sync); -} - -TEST(set_acquire_fence_with_invalid_fence_raises_error) -{ - struct client *client = create_test_client(); - struct zwp_linux_explicit_synchronization_v1 *sync = - get_linux_explicit_synchronization(client); - struct zwp_linux_surface_synchronization_v1 *surface_sync = - zwp_linux_explicit_synchronization_v1_get_synchronization( - sync, client->surface->wl_surface); - int pipefd[2] = { -1, -1 }; - - assert(pipe(pipefd) == 0); - - zwp_linux_surface_synchronization_v1_set_acquire_fence(surface_sync, - pipefd[0]); - expect_protocol_error( - client, - &zwp_linux_surface_synchronization_v1_interface, - ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_INVALID_FENCE); - - close(pipefd[0]); - close(pipefd[1]); - zwp_linux_surface_synchronization_v1_destroy(surface_sync); - zwp_linux_explicit_synchronization_v1_destroy(sync); -} - -TEST(set_acquire_fence_on_destroyed_surface_raises_error) -{ - struct client *client = create_test_client(); - struct zwp_linux_explicit_synchronization_v1 *sync = - get_linux_explicit_synchronization(client); - struct zwp_linux_surface_synchronization_v1 *surface_sync = - zwp_linux_explicit_synchronization_v1_get_synchronization( - sync, client->surface->wl_surface); - int pipefd[2] = { -1, -1 }; - - assert(pipe(pipefd) == 0); - - wl_surface_destroy(client->surface->wl_surface); - zwp_linux_surface_synchronization_v1_set_acquire_fence(surface_sync, - pipefd[0]); - expect_protocol_error( - client, - &zwp_linux_surface_synchronization_v1_interface, - ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_SURFACE); - - close(pipefd[0]); - close(pipefd[1]); - zwp_linux_surface_synchronization_v1_destroy(surface_sync); - zwp_linux_explicit_synchronization_v1_destroy(sync); -} - -TEST(second_buffer_release_in_commit_raises_error) -{ - struct client *client = create_test_client(); - struct zwp_linux_explicit_synchronization_v1 *sync = - get_linux_explicit_synchronization(client); - struct zwp_linux_surface_synchronization_v1 *surface_sync = - zwp_linux_explicit_synchronization_v1_get_synchronization( - sync, client->surface->wl_surface); - struct zwp_linux_buffer_release_v1 *buffer_release1; - struct zwp_linux_buffer_release_v1 *buffer_release2; - - buffer_release1 = - zwp_linux_surface_synchronization_v1_get_release(surface_sync); - client_roundtrip(client); - - /* Second buffer_release creation should fail */ - buffer_release2 = - zwp_linux_surface_synchronization_v1_get_release(surface_sync); - expect_protocol_error( - client, - &zwp_linux_surface_synchronization_v1_interface, - ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_DUPLICATE_RELEASE); - - zwp_linux_buffer_release_v1_destroy(buffer_release2); - zwp_linux_buffer_release_v1_destroy(buffer_release1); - zwp_linux_surface_synchronization_v1_destroy(surface_sync); - zwp_linux_explicit_synchronization_v1_destroy(sync); -} - -TEST(get_release_without_buffer_raises_commit_error) -{ - struct client *client = create_test_client(); - struct zwp_linux_explicit_synchronization_v1 *sync = - get_linux_explicit_synchronization(client); - struct zwp_linux_surface_synchronization_v1 *surface_sync = - zwp_linux_explicit_synchronization_v1_get_synchronization( - sync, client->surface->wl_surface); - struct wl_surface *surface = client->surface->wl_surface; - struct zwp_linux_buffer_release_v1 *buffer_release; - - buffer_release = - zwp_linux_surface_synchronization_v1_get_release(surface_sync); - wl_surface_commit(surface); - expect_protocol_error( - client, - &zwp_linux_surface_synchronization_v1_interface, - ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_BUFFER); - - zwp_linux_buffer_release_v1_destroy(buffer_release); - zwp_linux_surface_synchronization_v1_destroy(surface_sync); - zwp_linux_explicit_synchronization_v1_destroy(sync); -} - -TEST(get_release_on_destroyed_surface_raises_error) -{ - struct client *client = create_test_client(); - struct zwp_linux_explicit_synchronization_v1 *sync = - get_linux_explicit_synchronization(client); - struct zwp_linux_surface_synchronization_v1 *surface_sync = - zwp_linux_explicit_synchronization_v1_get_synchronization( - sync, client->surface->wl_surface); - struct zwp_linux_buffer_release_v1 *buffer_release; - - wl_surface_destroy(client->surface->wl_surface); - buffer_release = - zwp_linux_surface_synchronization_v1_get_release(surface_sync); - expect_protocol_error( - client, - &zwp_linux_surface_synchronization_v1_interface, - ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_SURFACE); - - zwp_linux_buffer_release_v1_destroy(buffer_release); - zwp_linux_surface_synchronization_v1_destroy(surface_sync); - zwp_linux_explicit_synchronization_v1_destroy(sync); -} - -TEST(get_release_after_commit_succeeds) -{ - struct client *client = create_test_client(); - struct zwp_linux_explicit_synchronization_v1 *sync = - get_linux_explicit_synchronization(client); - struct wl_surface *surface = client->surface->wl_surface; - struct zwp_linux_surface_synchronization_v1 *surface_sync = - zwp_linux_explicit_synchronization_v1_get_synchronization( - sync, surface); - struct buffer *buf1 = create_shm_buffer_a8r8g8b8(client, 100, 100); - struct zwp_linux_buffer_release_v1 *buffer_release1; - struct zwp_linux_buffer_release_v1 *buffer_release2; - - buffer_release1 = - zwp_linux_surface_synchronization_v1_get_release(surface_sync); - client_roundtrip(client); - - wl_surface_attach(surface, buf1->proxy, 0, 0); - wl_surface_commit(surface); - - buffer_release2 = - zwp_linux_surface_synchronization_v1_get_release(surface_sync); - client_roundtrip(client); - - buffer_destroy(buf1); - zwp_linux_buffer_release_v1_destroy(buffer_release2); - zwp_linux_buffer_release_v1_destroy(buffer_release1); - zwp_linux_surface_synchronization_v1_destroy(surface_sync); - zwp_linux_explicit_synchronization_v1_destroy(sync); -} - -static void -buffer_release_fenced_handler(void *data, - struct zwp_linux_buffer_release_v1 *buffer_release, - int32_t fence) -{ - assert(!"Fenced release not supported yet"); -} - -static void -buffer_release_immediate_handler(void *data, - struct zwp_linux_buffer_release_v1 *buffer_release) -{ - int *released = data; - - *released += 1; -} - -struct zwp_linux_buffer_release_v1_listener buffer_release_listener = { - buffer_release_fenced_handler, - buffer_release_immediate_handler -}; - -/* The following release event tests depend on the behavior of the used - * backend, in this case the pixman backend. This doesn't limit their - * usefulness, though, since it allows them to check if, given a typical - * backend implementation, weston core supports the per commit nature of the - * release events. - */ - -TEST(get_release_events_are_emitted_for_different_buffers) -{ - struct client *client = create_test_client(); - struct zwp_linux_explicit_synchronization_v1 *sync = - get_linux_explicit_synchronization(client); - struct zwp_linux_surface_synchronization_v1 *surface_sync = - zwp_linux_explicit_synchronization_v1_get_synchronization( - sync, client->surface->wl_surface); - struct buffer *buf1 = create_shm_buffer_a8r8g8b8(client, 100, 100); - struct buffer *buf2 = create_shm_buffer_a8r8g8b8(client, 100, 100); - struct wl_surface *surface = client->surface->wl_surface; - struct zwp_linux_buffer_release_v1 *buffer_release1; - struct zwp_linux_buffer_release_v1 *buffer_release2; - int buf_released1 = 0; - int buf_released2 = 0; - int frame; - - buffer_release1 = - zwp_linux_surface_synchronization_v1_get_release(surface_sync); - zwp_linux_buffer_release_v1_add_listener(buffer_release1, - &buffer_release_listener, - &buf_released1); - wl_surface_attach(surface, buf1->proxy, 0, 0); - frame_callback_set(surface, &frame); - wl_surface_commit(surface); - frame_callback_wait(client, &frame); - /* No release event should have been emitted yet (we are using the - * pixman renderer, which holds buffers until they are replaced). */ - assert(buf_released1 == 0); - - buffer_release2 = - zwp_linux_surface_synchronization_v1_get_release(surface_sync); - zwp_linux_buffer_release_v1_add_listener(buffer_release2, - &buffer_release_listener, - &buf_released2); - wl_surface_attach(surface, buf2->proxy, 0, 0); - frame_callback_set(surface, &frame); - wl_surface_commit(surface); - frame_callback_wait(client, &frame); - /* Check that exactly one buffer_release event was emitted for the - * previous commit (buf1). */ - assert(buf_released1 == 1); - assert(buf_released2 == 0); - - wl_surface_attach(surface, buf1->proxy, 0, 0); - frame_callback_set(surface, &frame); - wl_surface_commit(surface); - frame_callback_wait(client, &frame); - /* Check that exactly one buffer_release event was emitted for the - * previous commit (buf2). */ - assert(buf_released1 == 1); - assert(buf_released2 == 1); - - buffer_destroy(buf2); - buffer_destroy(buf1); - zwp_linux_buffer_release_v1_destroy(buffer_release2); - zwp_linux_buffer_release_v1_destroy(buffer_release1); - zwp_linux_surface_synchronization_v1_destroy(surface_sync); - zwp_linux_explicit_synchronization_v1_destroy(sync); -} - -TEST(get_release_events_are_emitted_for_same_buffer_on_surface) -{ - struct client *client = create_test_client(); - struct zwp_linux_explicit_synchronization_v1 *sync = - get_linux_explicit_synchronization(client); - struct zwp_linux_surface_synchronization_v1 *surface_sync = - zwp_linux_explicit_synchronization_v1_get_synchronization( - sync, client->surface->wl_surface); - struct buffer *buf = create_shm_buffer_a8r8g8b8(client, 100, 100); - struct wl_surface *surface = client->surface->wl_surface; - struct zwp_linux_buffer_release_v1 *buffer_release1; - struct zwp_linux_buffer_release_v1 *buffer_release2; - int buf_released1 = 0; - int buf_released2 = 0; - int frame; - - buffer_release1 = - zwp_linux_surface_synchronization_v1_get_release(surface_sync); - zwp_linux_buffer_release_v1_add_listener(buffer_release1, - &buffer_release_listener, - &buf_released1); - wl_surface_attach(surface, buf->proxy, 0, 0); - frame_callback_set(surface, &frame); - wl_surface_commit(surface); - frame_callback_wait(client, &frame); - /* No release event should have been emitted yet (we are using the - * pixman renderer, which holds buffers until they are replaced). */ - assert(buf_released1 == 0); - - buffer_release2 = - zwp_linux_surface_synchronization_v1_get_release(surface_sync); - zwp_linux_buffer_release_v1_add_listener(buffer_release2, - &buffer_release_listener, - &buf_released2); - wl_surface_attach(surface, buf->proxy, 0, 0); - frame_callback_set(surface, &frame); - wl_surface_commit(surface); - frame_callback_wait(client, &frame); - /* Check that exactly one buffer_release event was emitted for the - * previous commit (buf). */ - assert(buf_released1 == 1); - assert(buf_released2 == 0); - - wl_surface_attach(surface, buf->proxy, 0, 0); - frame_callback_set(surface, &frame); - wl_surface_commit(surface); - frame_callback_wait(client, &frame); - /* Check that exactly one buffer_release event was emitted for the - * previous commit (buf again). */ - assert(buf_released1 == 1); - assert(buf_released2 == 1); - - buffer_destroy(buf); - zwp_linux_buffer_release_v1_destroy(buffer_release2); - zwp_linux_buffer_release_v1_destroy(buffer_release1); - zwp_linux_surface_synchronization_v1_destroy(surface_sync); - zwp_linux_explicit_synchronization_v1_destroy(sync); -} - -TEST(get_release_events_are_emitted_for_same_buffer_on_different_surfaces) -{ - struct client *client = create_test_client(); - struct surface *other_surface = create_test_surface(client); - struct wl_surface *surface1 = client->surface->wl_surface; - struct wl_surface *surface2 = other_surface->wl_surface; - struct zwp_linux_explicit_synchronization_v1 *sync = - get_linux_explicit_synchronization(client); - struct zwp_linux_surface_synchronization_v1 *surface_sync1 = - zwp_linux_explicit_synchronization_v1_get_synchronization( - sync, surface1); - struct zwp_linux_surface_synchronization_v1 *surface_sync2 = - zwp_linux_explicit_synchronization_v1_get_synchronization( - sync, surface2); - struct buffer *buf1 = create_shm_buffer_a8r8g8b8(client, 100, 100); - struct buffer *buf2 = create_shm_buffer_a8r8g8b8(client, 100, 100); - struct zwp_linux_buffer_release_v1 *buffer_release1; - struct zwp_linux_buffer_release_v1 *buffer_release2; - int buf_released1 = 0; - int buf_released2 = 0; - int frame; - - weston_test_move_surface(client->test->weston_test, surface2, 0, 0); - - /* Attach buf1 to both surface1 and surface2. */ - buffer_release1 = - zwp_linux_surface_synchronization_v1_get_release(surface_sync1); - zwp_linux_buffer_release_v1_add_listener(buffer_release1, - &buffer_release_listener, - &buf_released1); - wl_surface_attach(surface1, buf1->proxy, 0, 0); - frame_callback_set(surface1, &frame); - wl_surface_commit(surface1); - frame_callback_wait(client, &frame); - - buffer_release2 = - zwp_linux_surface_synchronization_v1_get_release(surface_sync2); - zwp_linux_buffer_release_v1_add_listener(buffer_release2, - &buffer_release_listener, - &buf_released2); - wl_surface_attach(surface2, buf1->proxy, 0, 0); - frame_callback_set(surface2, &frame); - wl_surface_commit(surface2); - frame_callback_wait(client, &frame); - - assert(buf_released1 == 0); - assert(buf_released2 == 0); - - /* Attach buf2 to surface1, and check that a buffer_release event for - * the previous commit (buf1) for that surface is emitted. */ - wl_surface_attach(surface1, buf2->proxy, 0, 0); - frame_callback_set(surface1, &frame); - wl_surface_commit(surface1); - frame_callback_wait(client, &frame); - - assert(buf_released1 == 1); - assert(buf_released2 == 0); - - /* Attach buf2 to surface2, and check that a buffer_release event for - * the previous commit (buf1) for that surface is emitted. */ - wl_surface_attach(surface2, buf2->proxy, 0, 0); - frame_callback_set(surface2, &frame); - wl_surface_commit(surface2); - frame_callback_wait(client, &frame); - - assert(buf_released1 == 1); - assert(buf_released2 == 1); - - buffer_destroy(buf2); - buffer_destroy(buf1); - zwp_linux_buffer_release_v1_destroy(buffer_release2); - zwp_linux_buffer_release_v1_destroy(buffer_release1); - zwp_linux_surface_synchronization_v1_destroy(surface_sync2); - zwp_linux_surface_synchronization_v1_destroy(surface_sync1); - zwp_linux_explicit_synchronization_v1_destroy(sync); -} diff --git a/tests/matrix-test.c b/tests/matrix-test.c deleted file mode 100644 index 03b9216..0000000 --- a/tests/matrix-test.c +++ /dev/null @@ -1,424 +0,0 @@ -/* - * Copyright © 2012 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include - -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 ns/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 ns/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/meson.build b/tests/meson.build deleted file mode 100644 index 1b9ecdc..0000000 --- a/tests/meson.build +++ /dev/null @@ -1,346 +0,0 @@ -plugin_test_shell_desktop = shared_library( - 'weston-test-desktop-shell', - 'weston-test-desktop-shell.c', - include_directories: common_inc, - dependencies: [ dep_lib_desktop, dep_libweston_public ], - name_prefix: '', - install: false -) -env_modmap += 'weston-test-desktop-shell.so=@0@;'.format(plugin_test_shell_desktop.full_path()) - -lib_test_runner = static_library( - 'test-runner', - 'weston-test-runner.c', - dependencies: [ - dep_libweston_private_h_deps, - dep_wayland_client, - ], - include_directories: common_inc, - install: false, -) -dep_test_runner = declare_dependency( - dependencies: dep_wayland_client, - link_with: lib_test_runner -) - -lib_test_client = static_library( - 'test-client', - [ - 'weston-test-client-helper.c', - 'weston-test-fixture-compositor.c', - weston_test_client_protocol_h, - weston_test_protocol_c, - viewporter_client_protocol_h, - viewporter_protocol_c, - ], - include_directories: common_inc, - dependencies: [ - dep_libshared, - dep_wayland_client, - dep_libexec_weston, - dep_pixman, - dependency('cairo'), - ], - install: false, -) -dep_test_client = declare_dependency( - link_with: lib_test_client, - sources: [ - viewporter_client_protocol_h, - ], - dependencies: [ - dep_wayland_client, - dep_test_runner, - dep_pixman, - dependency('libudev', version: '>= 136'), - ] -) - -exe_plugin_test = shared_library( - 'test-plugin', - 'weston-test.c', - weston_test_server_protocol_h, - weston_test_protocol_c, - include_directories: common_inc, - dependencies: [ - dep_libexec_weston, - dep_libweston_private, - dep_threads - ], - name_prefix: '', - install: false, -) - -deps_zuc = [ dep_libshared ] -if get_option('test-junit-xml') - d = dependency('libxml-2.0', version: '>= 2.6', required: false) - if not d.found() - error('JUnit XML support requires libxml-2.0 >= 2.6 which was not found. Or, you can use \'-Dtest-junit-xml=false\'.') - endif - deps_zuc += d - config_h.set('ENABLE_JUNIT_XML', '1') -endif - -lib_zuc = static_library( - 'zunitc', - '../tools/zunitc/inc/zunitc/zunitc.h', - '../tools/zunitc/inc/zunitc/zunitc_impl.h', - '../tools/zunitc/src/zuc_base_logger.c', - '../tools/zunitc/src/zuc_base_logger.h', - '../tools/zunitc/src/zuc_collector.c', - '../tools/zunitc/src/zuc_collector.h', - '../tools/zunitc/src/zuc_context.h', - '../tools/zunitc/src/zuc_event.h', - '../tools/zunitc/src/zuc_event_listener.h', - '../tools/zunitc/src/zuc_junit_reporter.c', - '../tools/zunitc/src/zuc_junit_reporter.h', - '../tools/zunitc/src/zuc_types.h', - '../tools/zunitc/src/zunitc_impl.c', - include_directories: [ common_inc, include_directories('../tools/zunitc/inc') ], - dependencies: deps_zuc, -) -dep_zuc = declare_dependency( - link_with: lib_zuc, - dependencies: dep_libshared, - include_directories: include_directories('../tools/zunitc/inc') -) - -lib_zucmain = static_library( - 'zunitcmain', - '../tools/zunitc/src/main.c', - include_directories: [ common_inc, include_directories('../tools/zunitc/inc') ], - dependencies: [ dep_libshared, dep_zuc ], -) -dep_zucmain = declare_dependency( - link_with: lib_zucmain, - dependencies: dep_zuc -) - -tests = [ - { 'name': 'bad-buffer', }, - { 'name': 'drm-smoke', }, - { 'name': 'buffer-transforms', }, - { 'name': 'devices', }, - { 'name': 'event', }, - { 'name': 'internal-screenshot', }, - { - 'name': 'keyboard', - 'sources': [ - 'keyboard-test.c', - 'input-timestamps-helper.c', - input_timestamps_unstable_v1_client_protocol_h, - input_timestamps_unstable_v1_protocol_c, - ], - }, - { - 'name': 'linux-explicit-synchronization', - 'sources': [ - 'linux-explicit-synchronization-test.c', - linux_explicit_synchronization_unstable_v1_client_protocol_h, - linux_explicit_synchronization_unstable_v1_protocol_c, - ], - }, - { 'name': 'output-transforms', }, - { 'name': 'plugin-registry', }, - { - 'name': 'pointer', - 'sources': [ - 'pointer-test.c', - 'input-timestamps-helper.c', - input_timestamps_unstable_v1_client_protocol_h, - input_timestamps_unstable_v1_protocol_c, - ], - }, - { - 'name': 'presentation', - 'sources': [ - 'presentation-test.c', - presentation_time_client_protocol_h, - presentation_time_protocol_c, - ], - }, - { 'name': 'roles', }, - { 'name': 'string', }, - { 'name': 'subsurface', }, - { 'name': 'subsurface-shot', }, - { 'name': 'surface', }, - { 'name': 'surface-global', }, - { - 'name': 'text', - 'sources': [ - 'text-test.c', - text_input_unstable_v1_client_protocol_h, - text_input_unstable_v1_protocol_c, - ], - }, - { - 'name': 'touch', - 'sources': [ - 'touch-test.c', - 'input-timestamps-helper.c', - input_timestamps_unstable_v1_client_protocol_h, - input_timestamps_unstable_v1_protocol_c, - ], - }, - { - 'name': 'vertex-clip', - 'dep_objs': dep_vertex_clipping, - }, - { 'name': 'viewporter', }, - { 'name': 'viewporter-shot', }, -] - -tests_standalone = [ - ['config-parser', [], [ dep_zucmain ]], - ['matrix', [], [ dep_libm, dep_matrix_c ]], - ['timespec', [], [ dep_zucmain ]], - ['zuc', - [ - '../tools/zunitc/test/fixtures_test.c', - '../tools/zunitc/test/zunitc_test.c' - ], - [ dep_zucmain ] - ], -] - -if get_option('xwayland') - d = dependency('x11', required: false) - if not d.found() - error('Xwayland tests require libX11 which was not found. Or, you can use \'-Dxwayland=false\'.') - endif - tests += { - 'name': 'xwayland', - 'dep_objs': d, - } -endif - -# Manual test plugin, not used in the automatic suite -surface_screenshot_test = shared_library( - 'test-surface-screenshot', - 'surface-screenshot-test.c', - include_directories: common_inc, - dependencies: [ dep_libweston_private, dep_libshared ], - name_prefix: '', - install: false, -) - -if get_option('shell-ivi') - ivi_layout_test_plugin = shared_library( - 'test-ivi-layout', - [ - 'ivi-layout-test-plugin.c', - weston_test_server_protocol_h, - weston_test_protocol_c, - ], - include_directories: common_inc, - dependencies: [ dep_libweston_private, dep_libexec_weston ], - name_prefix: '', - install: false, - ) - env_modmap += 'test-ivi-layout.so=' + ivi_layout_test_plugin.full_path() + ';' - - tests += [ - { - 'name': 'ivi-layout-client', - 'sources': [ - 'ivi-layout-test-client.c', - ivi_application_client_protocol_h, - ivi_application_protocol_c, - ], - 'test_deps': [ ivi_layout_test_plugin ], - }, - { 'name': 'ivi-layout-internal', }, - { - 'name': 'ivi-shell-app', - 'sources': [ - 'ivi-shell-app-test.c', - ivi_application_client_protocol_h, - ivi_application_protocol_c, - ], - }, - ] -endif - -test_config_h = configuration_data() -test_config_h.set_quoted('WESTON_TEST_REFERENCE_PATH', meson.current_source_dir() + '/reference') -test_config_h.set_quoted('WESTON_MODULE_MAP', env_modmap) -test_config_h.set_quoted('WESTON_DATA_DIR', join_paths(meson.current_source_dir(), '..', 'data')) -test_config_h.set_quoted('TESTSUITE_PLUGIN_PATH', exe_plugin_test.full_path()) -test_config_h.set_quoted('TESTSUITE_IVI_CONFIG_PATH', join_paths(meson.current_build_dir(), '../ivi-shell/weston-ivi-test.ini')) -test_config_h.set_quoted('TESTSUITE_INTERNAL_SCREENSHOT_CONFIG_PATH', join_paths(meson.current_source_dir(), 'internal-screenshot.ini')) -configure_file(output: 'test-config.h', configuration: test_config_h) - -foreach t : tests - t_name = 'test-' + t.get('name') - t_sources = t.get('sources', [t.get('name') + '-test.c']) - t_sources += weston_test_client_protocol_h - - t_deps = [ dep_test_client, dep_libweston_private_h ] - t_deps += t.get('dep_objs', []) - - t_exe = executable( - t_name, - t_sources, - c_args: [ - '-DUNIT_TEST', - '-DTHIS_TEST_NAME="' + t_name + '"', - ], - build_by_default: true, - include_directories: common_inc, - dependencies: t_deps, - install: false, - ) - - test(t.get('name'), t_exe, depends: t.get('test_deps', [])) -endforeach - -# FIXME: the multiple loops is lame. rethink this. -foreach t : tests_standalone - if t[0] != 'zuc' - srcs_t = [ - '@0@-test.c'.format(t.get(0)), - weston_test_client_protocol_h, - ] - else - srcs_t = [] - endif - - if t.length() > 1 - srcs_t += t.get(1) - endif - - if t.length() > 2 - deps_t = t[2] - else - deps_t = [ dep_test_client ] - endif - - exe_t = executable( - 'test-@0@'.format(t.get(0)), - srcs_t, - c_args: [ '-DUNIT_TEST' ], - build_by_default: true, - include_directories: common_inc, - dependencies: deps_t, - install: false, - ) - - # matrix-test is a manual test - if t[0] != 'matrix' - test(t.get(0), exe_t) - endif -endforeach - -if get_option('backend-drm') - executable( - 'setbacklight', - 'setbacklight.c', - dependencies: [ - dep_backlight, - dep_libdrm, - dependency('libudev') - ], - include_directories: common_inc, - install: false - ) -endif diff --git a/tests/output-transforms-test.c b/tests/output-transforms-test.c deleted file mode 100644 index 3ed19e3..0000000 --- a/tests/output-transforms-test.c +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright © 2020 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include - -#include "weston-test-client-helper.h" -#include "weston-test-fixture-compositor.h" - -#define TRANSFORM(x) WL_OUTPUT_TRANSFORM_ ## x, #x -#define RENDERERS(s, t) \ - { RENDERER_PIXMAN, s, TRANSFORM(t) }, \ - { RENDERER_GL, s, TRANSFORM(t) } - -struct setup_args { - enum renderer_type renderer; - int scale; - enum wl_output_transform transform; - const char *transform_name; -}; - -static const struct setup_args my_setup_args[] = { - RENDERERS(1, NORMAL), - RENDERERS(1, 90), - RENDERERS(1, 180), - RENDERERS(1, 270), - RENDERERS(1, FLIPPED), - RENDERERS(1, FLIPPED_90), - RENDERERS(1, FLIPPED_180), - RENDERERS(1, FLIPPED_270), - RENDERERS(2, NORMAL), - RENDERERS(3, NORMAL), - RENDERERS(2, 90), - RENDERERS(2, 180), - RENDERERS(2, FLIPPED), - RENDERERS(3, FLIPPED_270), -}; - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg) -{ - struct compositor_setup setup; - - /* The width and height are chosen to produce 324x240 framebuffer, to - * emulate keeping the video mode constant. - * This resolution is divisible by 2 and 3. - * Headless multiplies the given size by scale. - */ - - compositor_setup_defaults(&setup); - setup.renderer = arg->renderer; - setup.width = 324 / arg->scale; - setup.height = 240 / arg->scale; - setup.scale = arg->scale; - setup.transform = arg->transform; - setup.shell = SHELL_TEST_DESKTOP; - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args); - -struct buffer_args { - int scale; - enum wl_output_transform transform; - const char *transform_name; -}; - -static const struct buffer_args my_buffer_args[] = { - { 1, TRANSFORM(NORMAL) }, - { 2, TRANSFORM(90) }, -}; - -TEST_P(output_transform, my_buffer_args) -{ - const struct buffer_args *bargs = data; - const struct setup_args *oargs; - struct client *client; - bool match; - char *refname; - int ret; - - oargs = &my_setup_args[get_test_fixture_index()]; - - ret = asprintf(&refname, "output_%d-%s_buffer_%d-%s", - oargs->scale, oargs->transform_name, - bargs->scale, bargs->transform_name); - assert(ret); - - testlog("%s: %s\n", get_test_name(), refname); - - /* - * NOTE! The transform set below is a lie. - * Take that into account when analyzing screenshots. - */ - - client = create_client(); - client->surface = create_test_surface(client); - client->surface->width = 10000; /* used only for damage */ - client->surface->height = 10000; - client->surface->buffer = client_buffer_from_image_file(client, - "basic-test-card", - bargs->scale); - wl_surface_set_buffer_scale(client->surface->wl_surface, bargs->scale); - wl_surface_set_buffer_transform(client->surface->wl_surface, - bargs->transform); - move_client(client, 19, 19); - - match = verify_screen_content(client, refname, 0, NULL, 0); - assert(match); - - client_destroy(client); -} diff --git a/tests/plugin-registry-test.c b/tests/plugin-registry-test.c deleted file mode 100644 index 85de5f8..0000000 --- a/tests/plugin-registry-test.c +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2016 DENSO CORPORATION - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include - -#include -#include "compositor/weston.h" -#include - -#include "weston-test-runner.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - - return weston_test_harness_execute_as_plugin(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -static void -dummy_func(void) -{ -} - -static const struct my_api { - void (*func1)(void); - void (*func2)(void); - void (*func3)(void); -} my_test_api = { - dummy_func, - dummy_func, - dummy_func, -}; - -#define MY_API_NAME "test_my_api_v1" - -static void -init_tests(struct weston_compositor *compositor) -{ - assert(weston_plugin_api_get(compositor, MY_API_NAME, - sizeof(my_test_api)) == NULL); - - assert(weston_plugin_api_register(compositor, MY_API_NAME, &my_test_api, - sizeof(my_test_api)) == 0); - - assert(weston_plugin_api_register(compositor, MY_API_NAME, &my_test_api, - sizeof(my_test_api)) == -2); - - assert(weston_plugin_api_get(compositor, MY_API_NAME, - sizeof(my_test_api)) == &my_test_api); - - assert(weston_plugin_api_register(compositor, "another", &my_test_api, - sizeof(my_test_api)) == 0); -} - -PLUGIN_TEST(plugin_registry_test) -{ - /* struct weston_compositor *compositor; */ - const struct my_api *api; - size_t sz = sizeof(struct my_api); - - init_tests(compositor); - - assert(weston_plugin_api_get(compositor, MY_API_NAME, sz) == - &my_test_api); - - assert(weston_plugin_api_get(compositor, MY_API_NAME, sz - 4) == - &my_test_api); - - assert(weston_plugin_api_get(compositor, MY_API_NAME, sz + 4) == NULL); - - api = weston_plugin_api_get(compositor, MY_API_NAME, sz); - assert(api && api->func2 == dummy_func); -} diff --git a/tests/pointer-test.c b/tests/pointer-test.c deleted file mode 100644 index 5d618c2..0000000 --- a/tests/pointer-test.c +++ /dev/null @@ -1,467 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * Copyright © 2013 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include - -#include "input-timestamps-helper.h" -#include "shared/timespec-util.h" -#include "weston-test-client-helper.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -static const struct timespec t0 = { .tv_sec = 0, .tv_nsec = 100000000 }; -static const struct timespec t1 = { .tv_sec = 1, .tv_nsec = 1000001 }; -static const struct timespec t2 = { .tv_sec = 2, .tv_nsec = 2000001 }; -static const struct timespec t_other = { .tv_sec = 123, .tv_nsec = 456 }; - -static void -send_motion(struct client *client, const struct timespec *time, int x, int y) -{ - uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; - - timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); - weston_test_move_pointer(client->test->weston_test, tv_sec_hi, tv_sec_lo, - tv_nsec, x, y); - client_roundtrip(client); -} - -static void -send_button(struct client *client, const struct timespec *time, - uint32_t button, uint32_t state) -{ - uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; - - timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); - weston_test_send_button(client->test->weston_test, tv_sec_hi, tv_sec_lo, - tv_nsec, button, state); - client_roundtrip(client); -} - -static void -send_axis(struct client *client, const struct timespec *time, uint32_t axis, - double value) -{ - uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; - - timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); - weston_test_send_axis(client->test->weston_test, tv_sec_hi, tv_sec_lo, - tv_nsec, axis, wl_fixed_from_double(value)); - client_roundtrip(client); -} - -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) -{ - send_motion(client, &t0, x, y); - check_pointer(client, x, y); -} - -static struct client * -create_client_with_pointer_focus(int x, int y, int w, int h) -{ - struct client *cl = create_client_and_test_surface(x, y, w, h); - assert(cl); - /* Move the pointer inside the surface to ensure that the surface - * has the pointer focus. */ - check_pointer_move(cl, x, y); - return cl; -} - -TEST(test_pointer_top_left) -{ - struct client *client; - int x, y; - - client = create_client_and_test_surface(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 = create_client_and_test_surface(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 = create_client_and_test_surface(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 = create_client_and_test_surface(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 = create_client_and_test_surface(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 = create_client_and_test_surface(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 = create_client_and_test_surface(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 = create_client_and_test_surface(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 = create_client_and_test_surface(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); -} - -TEST(pointer_motion_events) -{ - struct client *client = create_client_with_pointer_focus(100, 100, - 100, 100); - struct pointer *pointer = client->input->pointer; - struct input_timestamps *input_ts = - input_timestamps_create_for_pointer(client); - - send_motion(client, &t1, 150, 150); - assert(pointer->x == 50); - assert(pointer->y == 50); - assert(pointer->motion_time_msec == timespec_to_msec(&t1)); - assert(timespec_eq(&pointer->motion_time_timespec, &t1)); - - input_timestamps_destroy(input_ts); -} - -TEST(pointer_button_events) -{ - struct client *client = create_client_with_pointer_focus(100, 100, - 100, 100); - struct pointer *pointer = client->input->pointer; - struct input_timestamps *input_ts = - input_timestamps_create_for_pointer(client); - - assert(pointer->button == 0); - assert(pointer->state == 0); - - send_button(client, &t1, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); - assert(pointer->button == BTN_LEFT); - assert(pointer->state == WL_POINTER_BUTTON_STATE_PRESSED); - assert(pointer->button_time_msec == timespec_to_msec(&t1)); - assert(timespec_eq(&pointer->button_time_timespec, &t1)); - - send_button(client, &t2, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); - assert(pointer->button == BTN_LEFT); - assert(pointer->state == WL_POINTER_BUTTON_STATE_RELEASED); - assert(pointer->button_time_msec == timespec_to_msec(&t2)); - assert(timespec_eq(&pointer->button_time_timespec, &t2)); - - input_timestamps_destroy(input_ts); -} - -TEST(pointer_axis_events) -{ - struct client *client = create_client_with_pointer_focus(100, 100, - 100, 100); - struct pointer *pointer = client->input->pointer; - struct input_timestamps *input_ts = - input_timestamps_create_for_pointer(client); - - send_axis(client, &t1, 1, 1.0); - assert(pointer->axis == 1); - assert(pointer->axis_value == 1.0); - assert(pointer->axis_time_msec == timespec_to_msec(&t1)); - assert(timespec_eq(&pointer->axis_time_timespec, &t1)); - - send_axis(client, &t2, 2, 0.0); - assert(pointer->axis == 2); - assert(pointer->axis_stop_time_msec == timespec_to_msec(&t2)); - assert(timespec_eq(&pointer->axis_stop_time_timespec, &t2)); - - input_timestamps_destroy(input_ts); -} - -TEST(pointer_timestamps_stop_after_input_timestamps_object_is_destroyed) -{ - struct client *client = create_client_with_pointer_focus(100, 100, - 100, 100); - struct pointer *pointer = client->input->pointer; - struct input_timestamps *input_ts = - input_timestamps_create_for_pointer(client); - - send_button(client, &t1, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); - assert(pointer->button == BTN_LEFT); - assert(pointer->state == WL_POINTER_BUTTON_STATE_PRESSED); - assert(pointer->button_time_msec == timespec_to_msec(&t1)); - assert(timespec_eq(&pointer->button_time_timespec, &t1)); - - input_timestamps_destroy(input_ts); - - send_button(client, &t2, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); - assert(pointer->button == BTN_LEFT); - assert(pointer->state == WL_POINTER_BUTTON_STATE_RELEASED); - assert(pointer->button_time_msec == timespec_to_msec(&t2)); - assert(timespec_is_zero(&pointer->button_time_timespec)); -} - -TEST(pointer_timestamps_stop_after_client_releases_wl_pointer) -{ - struct client *client = create_client_with_pointer_focus(100, 100, - 100, 100); - struct pointer *pointer = client->input->pointer; - struct input_timestamps *input_ts = - input_timestamps_create_for_pointer(client); - - send_motion(client, &t1, 150, 150); - assert(pointer->x == 50); - assert(pointer->y == 50); - assert(pointer->motion_time_msec == timespec_to_msec(&t1)); - assert(timespec_eq(&pointer->motion_time_timespec, &t1)); - - wl_pointer_release(client->input->pointer->wl_pointer); - - /* Set input_timestamp to an arbitrary value (different from t1, t2 - * and 0) and check that it is not changed by sending the event. - * This is preferred over just checking for 0, since 0 is used - * internally for resetting the timestamp after handling an input - * event and checking for it here may lead to false negatives. */ - pointer->input_timestamp = t_other; - send_motion(client, &t2, 175, 175); - assert(timespec_eq(&pointer->input_timestamp, &t_other)); - - input_timestamps_destroy(input_ts); -} diff --git a/tests/presentation-test.c b/tests/presentation-test.c deleted file mode 100644 index 5e9d991..0000000 --- a/tests/presentation-test.c +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright © 2014 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "shared/helpers.h" -#include "shared/xalloc.h" -#include "shared/timespec-util.h" -#include "weston-test-client-helper.h" -#include "presentation-time-client-protocol.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -static struct wp_presentation * -get_presentation(struct client *client) -{ - struct global *g; - struct global *global_pres = NULL; - struct wp_presentation *pres; - - wl_list_for_each(g, &client->global_list, link) { - if (strcmp(g->interface, wp_presentation_interface.name)) - continue; - - if (global_pres) - assert(0 && "multiple presentation objects"); - - global_pres = g; - } - - assert(global_pres && "no presentation found"); - - assert(global_pres->version == 1); - - pres = wl_registry_bind(client->wl_registry, global_pres->name, - &wp_presentation_interface, 1); - assert(pres); - - return pres; -} - -struct feedback { - struct client *client; - struct wp_presentation_feedback *obj; - - enum { - FB_PENDING = 0, - FB_PRESENTED, - FB_DISCARDED - } result; - - struct wl_output *sync_output; - uint64_t seq; - struct timespec time; - uint32_t refresh_nsec; - uint32_t flags; -}; - -static void -feedback_sync_output(void *data, - struct wp_presentation_feedback *presentation_feedback, - struct wl_output *output) -{ - struct feedback *fb = data; - - assert(fb->result == FB_PENDING); - - if (output) - fb->sync_output = output; -} - -static void -feedback_presented(void *data, - struct wp_presentation_feedback *presentation_feedback, - uint32_t tv_sec_hi, - uint32_t tv_sec_lo, - uint32_t tv_nsec, - uint32_t refresh_nsec, - uint32_t seq_hi, - uint32_t seq_lo, - uint32_t flags) -{ - struct feedback *fb = data; - - assert(fb->result == FB_PENDING); - fb->result = FB_PRESENTED; - fb->seq = ((uint64_t)seq_hi << 32) + seq_lo; - timespec_from_proto(&fb->time, tv_sec_hi, tv_sec_lo, tv_nsec); - fb->refresh_nsec = refresh_nsec; - fb->flags = flags; -} - -static void -feedback_discarded(void *data, - struct wp_presentation_feedback *presentation_feedback) -{ - struct feedback *fb = data; - - assert(fb->result == FB_PENDING); - fb->result = FB_DISCARDED; -} - -static const struct wp_presentation_feedback_listener feedback_listener = { - feedback_sync_output, - feedback_presented, - feedback_discarded -}; - -static struct feedback * -feedback_create(struct client *client, - struct wl_surface *surface, - struct wp_presentation *pres) -{ - struct feedback *fb; - - fb = xzalloc(sizeof *fb); - fb->client = client; - fb->obj = wp_presentation_feedback(pres, surface); - wp_presentation_feedback_add_listener(fb->obj, &feedback_listener, fb); - - return fb; -} - -static void -feedback_wait(struct feedback *fb) -{ - while (fb->result == FB_PENDING) { - assert(wl_display_dispatch(fb->client->wl_display) >= 0); - } -} - -static char * -pflags_to_str(uint32_t flags, char *str, unsigned len) -{ - static const struct { - uint32_t flag; - char sym; - } desc[] = { - { WP_PRESENTATION_FEEDBACK_KIND_VSYNC, 's' }, - { WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK, 'c' }, - { WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION, 'e' }, - { WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY, 'z' }, - }; - unsigned i; - - *str = '\0'; - if (len < ARRAY_LENGTH(desc) + 1) - return str; - - for (i = 0; i < ARRAY_LENGTH(desc); i++) - str[i] = flags & desc[i].flag ? desc[i].sym : '_'; - str[ARRAY_LENGTH(desc)] = '\0'; - - return str; -} - -static void -feedback_print(struct feedback *fb) -{ - char str[10]; - - switch (fb->result) { - case FB_PENDING: - testlog("pending"); - return; - case FB_DISCARDED: - testlog("discarded"); - return; - case FB_PRESENTED: - break; - } - - pflags_to_str(fb->flags, str, sizeof str); - testlog("presented %lld.%09lld, refresh %u us, [%s] seq %" PRIu64, - (long long)fb->time.tv_sec, (long long)fb->time.tv_nsec, - fb->refresh_nsec / 1000, str, fb->seq); -} - -static void -feedback_destroy(struct feedback *fb) -{ - wp_presentation_feedback_destroy(fb->obj); - free(fb); -} - -TEST(test_presentation_feedback_simple) -{ - struct client *client; - struct feedback *fb; - struct wp_presentation *pres; - - client = create_client_and_test_surface(100, 50, 123, 77); - assert(client); - pres = get_presentation(client); - - wl_surface_attach(client->surface->wl_surface, - client->surface->buffer->proxy, 0, 0); - fb = feedback_create(client, client->surface->wl_surface, pres); - wl_surface_damage(client->surface->wl_surface, 0, 0, 100, 100); - wl_surface_commit(client->surface->wl_surface); - - client_roundtrip(client); - - feedback_wait(fb); - - testlog("%s feedback:", __func__); - feedback_print(fb); - testlog("\n"); - - feedback_destroy(fb); -} diff --git a/tests/reference/basic-test-card.png b/tests/reference/basic-test-card.png deleted file mode 100644 index 027ca852e94d2c58ce735e6a042515a6171b1fe3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8347 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 G3;P%Myjjfv diff --git a/tests/reference/internal-screenshot-bad-00.png b/tests/reference/internal-screenshot-bad-00.png deleted file mode 100644 index eb90de6b0934b9a8bae7d98f32f11700a1ba08f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4204 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 diff --git a/tests/reference/internal-screenshot-good-00.png b/tests/reference/internal-screenshot-good-00.png deleted file mode 100644 index 728b738e7dc41298744783bdba93063b0a38f21e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 733 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fO!OFUg1Ln;{GUSZ^AV&pk+ zfcL*xoc4)?pZ8R^UU&#pHeev(xzxVR*6x=+zu6-;-?H_tv*u|ALx)sJ1-2DB%tsie zrZz|{*u*2i95RhDso|8715ZFCTL+^S(TYleCTRfmubD=vNz`-G2ry|sBgrZ12V>Iv VKn2Bhr*?z1db;|#taD0e0sw)Ws$u{D diff --git a/tests/reference/output_1-180_buffer_1-NORMAL-00.png b/tests/reference/output_1-180_buffer_1-NORMAL-00.png deleted file mode 100644 index cd60739060c7fd950d4731ded23234190a487d0b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4642 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 N(>2zqyz@BnzW|DDxuF06 diff --git a/tests/reference/output_1-180_buffer_2-90-00.png b/tests/reference/output_1-180_buffer_2-90-00.png deleted file mode 100644 index 8aa9a1fc2a2c5dca686931711dec8934d15a7a8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4869 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 diff --git a/tests/reference/output_1-90_buffer_1-NORMAL-00.png b/tests/reference/output_1-90_buffer_1-NORMAL-00.png deleted file mode 100644 index f17d21d76a333f328830d7d8373ac0f9a699d904..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4880 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 a!N-kUKUcm;>n4A30Wb|+XyyH<5&r=JBP<*M diff --git a/tests/reference/output_1-90_buffer_2-90-00.png b/tests/reference/output_1-90_buffer_2-90-00.png deleted file mode 100644 index f3bb62c8eed8ac8584595afecc791c53f78f9c17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4655 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 diff --git a/tests/reference/output_1-FLIPPED_180_buffer_2-90-00.png b/tests/reference/output_1-FLIPPED_180_buffer_2-90-00.png deleted file mode 100644 index 381ef746671be79dd6a8f3ff46d90bac4c4ffa6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4884 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 diff --git a/tests/reference/output_1-FLIPPED_270_buffer_1-NORMAL-00.png b/tests/reference/output_1-FLIPPED_270_buffer_1-NORMAL-00.png deleted file mode 100644 index 91c5cdb925841c6db739bbc15112f2ef8908f34f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4789 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 Z(J`64iF3utvA};Qkby1)T&?XK`!C&;6Sn{W diff --git a/tests/reference/output_1-FLIPPED_270_buffer_2-90-00.png b/tests/reference/output_1-FLIPPED_270_buffer_2-90-00.png deleted file mode 100644 index 15333fc17098aa9ef78ab5665d0b195871d5ef20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4570 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 PV1U+ReYF}D`S!dmQoqg?dU)R|Ob>Gs2=d{>q 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 Dc+|Ba diff --git a/tests/reference/output_1-FLIPPED_buffer_2-90-00.png b/tests/reference/output_1-FLIPPED_buffer_2-90-00.png deleted file mode 100644 index 0e32a86a4fc4561d11a05a4df10d10794fb53c70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4892 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 diff --git a/tests/reference/output_1-NORMAL_buffer_1-180-00.png b/tests/reference/output_1-NORMAL_buffer_1-180-00.png deleted file mode 100644 index 3d3a0653a46f08d3a7e250f453e927698d6fe0de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4674 zcmeHLXH-*bmkvl#0TJO!M--7Fohu+ENE0JXfeTzZL;-0ED7^^SfCQvU35k~~HS``p zArvVQDWQapASuKUS|GsW&iAcZYyQoA-;en->zuXEdCxv?IeR~6KYPCk56q3Z&I_Ff z003N|dxn+(0Fxo({?}P%hTJboyvw+-xtkap0#5%vKB7v~0RWzTkfEM+D2y~8^?)w~ z-cNDC7;vk8yl5oKjxb8)dM{v+ii*>g3v6U^G_4Mtk(BfFd#)o}HotnJ^qY+uJNm|! zb3n+9+>d3+2oW^U+KKaK&5O5LA+c@J_IS1yan>2v6-F*|2prD}i^mOQ5u)coWXV-7ujS%IOesJ@g{N*I^v+?oqo*1~FmzUko zOjbL`mS+laiy`GVu90&%&Y!<<1=!((RY)J?Td^AUp#B*8Kp+qt92|OFVp*j?moB{2 z%f1{s z1;(2zD=Qrx9cQbByVL5G*t=zAW#7DgyHA?qPenaXUfm#JIzK-mFf=f5&&0&U%*+e~nrZUO8}s6fE*N}NUR}LV z3pNIU66#_A-35H4YzqX?$<9tdd!Dch*Ny3HZEbC8QVXpXKT9H!(z%C*hLRE!>+0%$ z&qY@0_6I1Ub}SFMKBQ)*a#Umu^L!NIcX>dP#8Y85p# zHMO+?Cxr7gEJiZxiHV6TEABJBa5y|)E5yde<_-00W@e^V$QE&PGu2E{L7|VL6#B_f zX1%UXWrhc7@8zJfp?j5e$JnnrbHr5v4~-A=ZECnzGKoj6g=7*Ho- z&oDTHAqSc;H_~N()d&Lo68nj|^P0~R#m`=aFJ0|W+2u{_=+K@i0jjGvH#Gdd#(=^X zz7*O(szjYAFZV}QH_EH3NOT%iwUP}0I^V}Yn5@>jc^s}1_e_=ZkxI@p0nPy{ zrS+xw>8tQ6!Oq#)*`=P@F+m~MzoZ3%0s{k=dSYXle)OHcA#C2H0@#rjs3#^R9v>fn zh5lx$hHF^NW%+3MIh*&=CFU!8ySp;#HcB(k>N1%=8h<9M3Uj{cev3k(J`cSL^~P43 z2rZ^sq;g3)2m-?SATu$UIM9jD8$+nC#>`h1D?#{#Ah*{}o;BVsAq=HZA5=8yN$b|d zzW@HwlCp=1DRnv3fd&^_G~Dh7?%+=Fp$psnvrbtztMibwE8aG!N(UMw*J8<9IUPwB zAUxwn@sxF45T9OY^WS4$!2&m^kU9Tj`#|Sa_i^_rZQ*9hQ(qCqXxb4nYpLO|(P(se zKPuT`mvBMIM%PD_yf=gS>TF!UJ~`dUj%(8EhwevCDfCsAzdAFdh7uxmv5NuE_YoRZ`K$)WU8)*p%gC-SKMKXQIIK9gxEBdH+rbU#D$3}XtL=StP@DMh*IwRyE%Kh?r zdt)x%wmPIXnUp`9uUVyaBx4rb;RK!oRe*kp>u_+pj?#KVL&K8db+#@n7vBLeY+W+G zxVU)G#={@8pB&+f1+RoClP2HjH`P%k_E|&I zb+k&&?hfge$6_KPxb_h8T94mc(?8(A2#gH7ynue%C6tI&*!pk^qZDFk{+K6@0raT3 z_g`zaj_6ag#a<3qF+Q9MLg%P4-r#OwZAXV=>oRlZhYv&Mw6rQyCG7mEB{iXKM0evr z*9sjySF>woE=mx2l0%)Im48#@4tj2eJD3b5$5M24z{kW7LD4)fTN%)P^MCs9^GG{Dr zT@*nWXIeL#!Q?2qbc!6v4^&oG(&>@KUEg5{1R^t2(gFGa{!=t6#C>P}iRaAGkHwF$ zgEhs{nBNcrU+n-qr9|{OJb@tj1mBW+xA1oO&ZhzimA+ ih-UAy67al_~Z-O(WR( z5~;1%_XVB96s76C$}BA$D9Iy!m*_I+~lw-Mnwla+p6 z^;7glU9Z11)Z)~SzPj=3kl*m_N@);WBS1e`ao6DOg~`cDnEdfuJE44C6aEGU2EW`$7m;R9BR%m_e2ifJnZ4FiXNUEU z!S7pMsUJQ_X;tRK;n9>8^gwbTL+W?w6wIW%QUyEg=3+_>DuJvWICPR+UXh{9c^sfUkmP!Qzy&I z%S%d1QeA9cN=k<-tM=`Y7q8${!E#z!?F@PM)2&OH(s9FYhrzpVrvWKwRw7 z#l2v)^KKDkUq6j{*=cpFF7M0hf`3w^1s{u>tLw+Et}Y}J+1!kc*+nq;f$?KbOiW~C zWH8{Z^E1EcI*7rSCINYJAWM8u%Ps9!kC_yNtt~ABNwbZAGt9999dfsD%nKHruBBQ( z<7jL9c7O2Kw?w@EUJ=6vMMXubo}&X70x&7pL7t;9(Ea=RgTIsC;tm4sU$8eU7P1b35HnBG?CuhXtH1==Gf~~T4Umh$DW@Hw} zi?5~SRY0;9uF?8M}nD!I>IcQ1Ndo*(Q>_L4DO zYr{dMQJ-%iD|iu;Kd}|J0z~?QfN$4YWRhWlDIGE~mjO&kKLOK>&RdF+61r4e`=5d-4UJif>=<{|ED|nzF{~LPBm#z*Ir5I^9^(UZ3g3&BkoiE(MPWf&{2a;UesEUnUa(h{)BmcK=sYbI1pb${?v$_YlJCpo^%?Qgw4AfHjHM<2mwj&A22 z^j0i>oB}Fuw5ql|T;6MyT&33Is&BtfTurWW=`t!E4}@i{HsN=XPp5y49PRf;pdZbO z@;6%+js?Qr)MncI`Rjk_Lb`OB7rR4lL6h%8Q17!fnj2ZN+l?9?pOonM&u-H1js_X! zhHb4G_&1&A*a|2NE?Tag>(stQ@?s09$xBvZ|`-uE-d>Xb!J#V zm>*5BA+T2LmXDXE7tOQv)Zeu6{l4k!(1_1WO}){M>R!HwCEUDp$=jnnO!SUMfl&S0 zBbYxvF9k1eT7?Mz8FYV@UporZCRJJlU*@d~HSYSI;HV3XFLUqQ8~NHB6o0!<+?Qyc~cu_n%S_NP7G z$30V3g0;0LGURh#6xIA>?ON-?BI&2sDCkQspBUZpNI{n_7QiFDWUcC7?!XKRfMMR% z;T%bGe+4Y?MInZPSj<6unQnjZ{lftzWs>(j1`xvZJE^aFHmDznA$okqI%0)3Ljpq^ zwADN{vm-f5bhD>6d^BPoo}6;}mP=U3*m#Z@jB8-axH!2#3_jhQ?5k*s>84r)mdNf( z+_oPBHWpvX#kDo!Mg2OirZ`Z}Ocr*fJXG8mCb#M5RLqW={((2HyA`W=AfEdJhLLYY z8oEksp7k?1!Ve4-CnW4@&Jzez$K*W{YOa}_n0MlIrn|%F#ZY9sJ;WraDm2%H0+|Xk zpf`Nkg|hXmIWBJiCsq1q$=fL?jEVDLn2AW`bKJ4#Vr9@ca6&%9arMAds*@o7e{b>`e z1gLt`p^5mgNA4!u#S*V80O5n1++ZsNC0KHnc_TL^VI9nU@M-gNPz2(+^4{c)Hp0XN zM@hpkYz%(Skv99qVPWkLZK)33W=sxumGkkt`!7cQ7X^8#s9r<(YFoZ}-?Ro;-Km-s zeyrs)1v^|x;z90XR#!Uf-Cye!w}qb8e+upE>g`I2<1&MLDpN>tyrkGqk zl74yx_oAZ>jzg2&8YXpu?Vm4Xtpr*3Z8Q!cz9l!c=+ocNJay!Yf}nhV?~IH|9NDPo ziol*8J>6Q+$<5fYj-*>rDCri;?T$~O$%io*b%B)$yj$~8f5?{M8ksr!@{ep}*RmtE zAovF`dV~tQ-=7dX_`;;a*^hrAcWx5~1cigWej58={l9GrfSVnw_gR(;kEEdVQGZ)* z?yl_Y?4t29!TLn^l=+jCkgdq*7Y9FJDy>qW7kL$l`V4kgr z^>;YA^MEh>Q}Rwsu9R+nwf}9Q{O^_7|KIyx*_`;lw%S3A`We}%gI}W;8Jj`?kde7z JwZ3cIe*h4`{ht5; diff --git a/tests/reference/output_1-NORMAL_buffer_1-270-00.png b/tests/reference/output_1-NORMAL_buffer_1-270-00.png deleted file mode 100644 index 332b79cae8a0d4c2ab26732c7657f6e49b8223a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4877 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%0A`97^} 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 LQy*BT?hyTNC(A*F diff --git a/tests/reference/output_1-NORMAL_buffer_1-FLIPPED_90-00.png b/tests/reference/output_1-NORMAL_buffer_1-FLIPPED_90-00.png deleted file mode 100644 index bdb129bbeb23e70e0c122af182e32b3e7230ca94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4914 zcmeHLXHXRHlO8~c5>#?@S;;C%aukr9GYd%065J&R34-7v!2ps`at{r&6e?%UPX-F>{7s+sQheW&N`uI}fV=~ssO8q}1mlmGxwYiX((0|1d4 zLEa}PCR|U8;Oc}MsiTgD8gTW``LUz)9RS=yYpFdl4bH(Vco~{n#E|X7F7%{Q%t1Cr zuUHctqfGr7G6ctwKsg7eXr`5TL!U7(kC)Le=kzRF@n@=m^_oA!>pZ0d`g=2HT`X@& zHM$y#%8}^u@-&icRZye?(R2(RY-(#Dy>C8CxDg7Q2QecHWape$-&RzYdN42=F8utI z3>uo9AC?P59hRrJlK^zg%P%N9?KGS^S`~o z{qhWo&{&V&(h$)J#_vOei2i)Pq!b6muC_!Sf!$vfw9MNAmUKC;BqH5Q;0EMycMsF& zxo^oy*PCnsa=(nk;OC%r5E39_`Xdr%+Zp7$g>4!!w4wk;lzEVGj05B769m_XG(x;} z{7LRPdSnp=CtR0C`WW4f-I?_4$X>eIRc-psJpB?-=IwIe3}~kSg_&3F`=BCDKiv`B zqL|FrB)L}$*ooU^X7oMG7Du9%*pk>mUjDM#2yEP7lfuKJDK@Feg;o%?!}8Y5X?K?F-UGQ9n^sCxtz46s3P9S-#;|?uK`!c+9rdLB0FmZ zJJ)G7KGjT)t0kpjxVeq!{e8k_?iPLgres#FFP6zWzYCL`;|Im2T9A^8#mC#No&88n z&98offn`vDR;Lzo&)J*s44Wm*KhRl3^hy1N<&2NqGbQd7eP8$7*dCB##TFJ^md}eH z%Ke%Y5|^R?k&DY0rH4KZOd34t*`C6cDc{}F=){#BDDG3fyOex7i3ez@1T|c18wDr4 zjEoF2bcH57Rr<>Ae9_mv+&aNzbEXUjz+-GV+N_yzsdNRBE_5oU4gz@x>U+Ls4gv)T z&V)`9e%fjgr5fXoW*^(vPkgb#f_3t6=i6R1 z2uSYku~}t!&7`6qq?hIOWrI52O24>%;j&utu4MSYLEVqBW$WZ^-Dk;i`PVjt0Yhep z2I_9%8#-ucaPZb0dCfjVPfu^9g_4WdnN79Z&d%QtQsb4Q3EG%WtkRDOx#fe6!pO48 ztn28HPJd%APHIW8WB@;PwMcyVL~QC+u8h^z+d*~mSATt8m-?zL!%qL8O|rW;1m-pb zA_cG6lT?pA-CLH1h_B|yy;vn@Tp{}SV)9Mv((<&ui2OI`Iv)v%Nt^-8jo4ICyS?W( zRgO~dThSNNHQ!J$9MJz*2+0@!@_D`MU`;}TiR^-@gQq1ksAy{UVhr37vQ2XO>@Sv? zO}XAZuJxzu?emK(SuwF8`wT&w;@?Mdxm?uLXEvB=v{m#52k?TQhE>L+A@}Pua7?N< z#G4#(Q|08uf#w=;pT1{DLc5RefkjuPgo5Jg&G(4aqn6z^pQ~R^UN?fR0l2a#a%pT6 z&JA9@LAvKu@$At?{hUzzOZ<-ixt&a@sE(9`U{XK5uSn@M={wuz^sFe{)Y=i8= zc6~i>TE76?{6iw$t>Ov%V|VwM-+w2Q*h+Cj*bd>ydqT?(vsE4KB&%lQM0$E&O~O!2 zA&W)E$8x#79Ua3q?I*}aFM@>AFV!jz6_w3WPb_GM9iqNfj(5@ulNdgPI3!POQ-Jnp zsT;#jOO&ze;b&#$uvvs!N?gk&j*RG|d-M%#%^o9z73&s*FmnM+A~erW zP(|&#qKJ$iq~*d+XND~5PyKi$j1(^GSSxVCllF6s-Ip(RT0{7D(h&W{+y2XWXb`o1 z2)_g}RB=vWMr!e(`CW$+U#xWJS$tToa4RO_E^F7Jp=X9f=;Gqlk{WZ)18B-D znd0PzQ+lllFrZcLfJ)HzUr1+w-^Q@xLhOO>gdxWHpa8FveoC1h*A$WZdpIJ?Ky8KO z8Q-h#^jH(`ZY_sHlY*@TJC0v~?Pui@{1I*s62>JPh z=pMNEWhNc>e8^Iu+s`kQ7gMzlEZl(fT;tkIB4yHlrY)sD3Lc{-J|4`+^e1rQ<9>D-Ll$*_k|E--=fXIKhu@KFxhF#q*AOy{*g!H!U0& z-K+q!0Dwf*ZBoml6n`fRo7@@kx4Pudn%>sfpr@}|3-YH156po5>(wILP7{+RusluL z9B7<;MalTXE<(b4G|l!U<>fzP^wvc0=q=R+9<)D*A_;fEcc7kudfG2MILySY?bi?#1Tt&#=Ye zSxtR^I4t~_2nvP5;qWH!ImX+ttMUQaT>a9q%t6%g_Ec#}$?jb9UUl?mFc>yd+zkqYp|uYqdVl;_L!t6yU?r`s zt#xz*p~Ttl9GQCoErn2job^JD&{$kbKK`V{V#D>=d1-}HVrpv2&Dq&6XO9ofjwmiE zDJd#iL!;eXmWwf6tOQ(+e)r(}1D{JYOLG-iJYbQlNA`FGv3uP-;P3u29*;*NbtB)( z$E~&?kw}8t6Hj|#$ELjhF~FI#oUzu_KwC#~iO-5e?e6Vw)*~K)0RaKepW|&rn;_K- z3k$Zkwj3NB7!0Pmx;i#>ZEfwxj~}tIv3l8(y$A$WUg;uHR8&+WnSJJK2<*|L@7-7Y zQT9##*i1e}$kF4^r-#9Tt#cS3AD^1)YK6cZ#ac&#+2iA5lWZOyp0JZ0EghXPch}2$ zb_8awS(uxfTUgk)o`Zy3Wgdloa(Xtv!Ih@8KOQu|bqvF84RA@L&Q@%+d^|m6LytCE zVQ9u_t=gTP9o2Z6;?mM#{KQOy8~RrRKb&HCHI|(|>YTo>j!(kDz!T{Kfn`Zv3M2)M zU5F1iS5#KI@y&QPa>_uR(L=e3dSalp`Bs=*(5^gmEn&hl4JRli)!fz=TF*dyjcu!R z2J^V7C31Yp8)_Z7nof<$8CdR#$6~QAao*nER#sLGg)RKW#l;K^3u%ZqH&w3+oGQJXT^l&}{AOd?IPDQq%p|gZBSCV)Z7v7hP;QBHX5SJkA6XU{cMm z67}|_jHm$LZCg{spw`xkii$LKva+(Yb#yiZs=j>rQeLhFc}qCny*Sm3;U+k+R#`#q z`_jF;^^GeZ$oa2H>1>yGxVZDgz+p)u99|Uf(mWS&v|%*6AQyU6Hm9Ia_kOjdpn!We ziFia=0qJEoKGS)tX6JK&vn&NDcV=jK)Vqn(0rUJ!8$8$K{mw^HO6p=?KBRsqS8i&4 zUfw_ocpnp*L0!LO-*e7#`z{p~m5WrpmzS5vDW;Q}P=bFXnfY%X zi@;ci7Wuf+6ed>=9(2mNoapUbB)69=PuDv&_<9NJjS>Dd6p%wipcAv|Dv|K^eg#=4 zqfad@(wnY9K~k9H!LghwaRSvVAe$BaYNpHQ&doHt5o1ihg!0S{{0Et}xUpFBpZTPaMRJ%eeW~-w5d0O>kj#~0rI*xZ*Fe7s;ChEhiSTd;4WvUI!W(Y zwSU5*;W5esn0*%`t|~3nXk+2xGWchdZ~I0R_dk#S{HealJB%g3#g_||nXxf9+ab?C zbB44H>(tBHV9DVk=_~TOiVDHRUhew@0HDQmE;NG@dt)rRVs72<)do)n$>%G0_*%(` z9&1ktNB&{HFnkV*I6HLNbcH}5fq{~kP8T<~(&FO5F>0XL5-uWa=;Bh$ug_;w_?unU zKOwL%o`&OXXOlX8Z-2k`zj6~NgYC96FtM-*TJDY;A0ID&dvtX4`LkH)KvY^<+OH>; zu;uRSUsE~g;h`BFwV#aGWVmlR7zcXZ&-ZzD`S;-cd-_dndomjKva+&|2UQ8^GZHcy zO&v8O`RA9xU2MdbtmAgyl#8qX1GCOf4^BS)6j>f*Nq91U}BsSF#WyFn)V`du3&1VPRoM$3pwL0fE#V9UWCvx=Zx3 zS5{ZiZKI5j>q|;d(Fo`HLDFK&Hi@oq6GOw1nHeh^n|onPaZJNl`QfG0R5_)@USlJp z=xt|vEjns+j!sC(S(bzsD=VvWb4vsm?x?ETL(o&5?Ozjj?-25!G4cwZp3ayLz7KcQ z(HX$wE)L{bGKw-pZmU#ORE&;}ewF&^OrnEwtGE0r)ke@oI&NAr>`WmWCMG6A&?@wd zjIPekKYDu))J$_`m1|;klk_(pM&$F>eu? PV1U+ReYF}D`TfRJ?eFh@q{S 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 tfctkEzuXEdCxv?IeR~6KYPCk56q3Z&I_Ff z003N|dxn+(0Fxo({?}P%hTJboyvw+-xtkap0#5%vKB7v~0RWzTkfEM+D2y~8^?)w~ z-cNDC7;vk8yl5oKjxb8)dM{v+ii*>g3v6U^G_4Mtk(BfFd#)o}HotnJ^qY+uJNm|! zb3n+9+>d3+2oW^U+KKaK&5O5LA+c@J_IS1yan>2v6-F*|2prD}i^mOQ5u)coWXV-7ujS%IOesJ@g{N*I^v+?oqo*1~FmzUko zOjbL`mS+laiy`GVu90&%&Y!<<1=!((RY)J?Td^AUp#B*8Kp+qt92|OFVp*j?moB{2 z%f1{s z1;(2zD=Qrx9cQbByVL5G*t=zAW#7DgyHA?qPenaXUfm#JIzK-mFf=f5&&0&U%*+e~nrZUO8}s6fE*N}NUR}LV z3pNIU66#_A-35H4YzqX?$<9tdd!Dch*Ny3HZEbC8QVXpXKT9H!(z%C*hLRE!>+0%$ z&qY@0_6I1Ub}SFMKBQ)*a#Umu^L!NIcX>dP#8Y85p# zHMO+?Cxr7gEJiZxiHV6TEABJBa5y|)E5yde<_-00W@e^V$QE&PGu2E{L7|VL6#B_f zX1%UXWrhc7@8zJfp?j5e$JnnrbHr5v4~-A=ZECnzGKoj6g=7*Ho- z&oDTHAqSc;H_~N()d&Lo68nj|^P0~R#m`=aFJ0|W+2u{_=+K@i0jjGvH#Gdd#(=^X zz7*O(szjYAFZV}QH_EH3NOT%iwUP}0I^V}Yn5@>jc^s}1_e_=ZkxI@p0nPy{ zrS+xw>8tQ6!Oq#)*`=P@F+m~MzoZ3%0s{k=dSYXle)OHcA#C2H0@#rjs3#^R9v>fn zh5lx$hHF^NW%+3MIh*&=CFU!8ySp;#HcB(k>N1%=8h<9M3Uj{cev3k(J`cSL^~P43 z2rZ^sq;g3)2m-?SATu$UIM9jD8$+nC#>`h1D?#{#Ah*{}o;BVsAq=HZA5=8yN$b|d zzW@HwlCp=1DRnv3fd&^_G~Dh7?%+=Fp$psnvrbtztMibwE8aG!N(UMw*J8<9IUPwB zAUxwn@sxF45T9OY^WS4$!2&m^kU9Tj`#|Sa_i^_rZQ*9hQ(qCqXxb4nYpLO|(P(se zKPuT`mvBMIM%PD_yf=gS>TF!UJ~`dUj%(8EhwevCDfCsAzdAFdh7uxmv5NuE_YoRZ`K$)WU8)*p%gC-SKMKXQIIK9gxEBdH+rbU#D$3}XtL=StP@DMh*IwRyE%Kh?r zdt)x%wmPIXnUp`9uUVyaBx4rb;RK!oRe*kp>u_+pj?#KVL&K8db+#@n7vBLeY+W+G zxVU)G#={@8pB&+f1+RoClP2HjH`P%k_E|&I zb+k&&?hfge$6_KPxb_h8T94mc(?8(A2#gH7ynue%C6tI&*!pk^qZDFk{+K6@0raT3 z_g`zaj_6ag#a<3qF+Q9MLg%P4-r#OwZAXV=>oRlZhYv&Mw6rQyCG7mEB{iXKM0evr z*9sjySF>woE=mx2l0%)Im48#@4tj2eJD3b5$5M24z{kW7LD4)fTN%)P^MCs9^GG{Dr zT@*nWXIeL#!Q?2qbc!6v4^&oG(&>@KUEg5{1R^t2(gFGa{!=t6#C>P}iRaAGkHwF$ zgEhs{nBNcrU+n-qr9|{OJb@tj1mBW+xA1oO&ZhzimA+ ih-UAy67al_~Z-O(WR( z5~;1%_XVB96s76C$}BA$D9Iy!m*_I+~lw-Mnwla+p6 z^;7glU9Z11)Z)~SzPj=3kl*m_N@);WBS1e`ao6DOg~`cDnEdfuJE44C6aEGU2EW`$7m;R9BR%m_e2ifJnZ4FiXNUEU z!S7pMsUJQ_X;tRK;n9>8^gwbTL+W?w6wIW%QUyEg=3+_>DuJvWICPR+UXh{9c^sfUkmP!Qzy&I z%S%d1QeA9cN=k<-tM=`Y7q8${!E#z!?F@PM)2&OH(s9FYhrzpVrvWKwRw7 z#l2v)^KKDkUq6j{*=cpFF7M0hf`3w^1s{u>tLw+Et}Y}J+1!kc*+nq;f$?KbOiW~C zWH8{Z^E1EcI*7rSCINYJAWM8u%Ps9!kC_yNtt~ABNwbZAGt9999dfsD%nKHruBBQ( z<7jL9c7O2Kw?w@EUJ=6vMMXubo}&X70x&7pL7t;9(Ea=RgTIsC;tm4sU$8eU7P1b35HnBG?CuhXtH1==Gf~~T4Umh$DW@Hw} zi?5~SRY0;9uF?8M}nD!I>IcQ1Ndo*(Q>_L4DO zYr{dMQJ-%iD|iu;Kd}|J0z~?QfN$4YWRhWlDIGE~mjO&kKLOK>&RdF+61r4e`=5d-4UJif>=<{|ED|nzF{~LPBm#z*Ir5I^9^(UZ3g3&BkoiE(MPWf&{2a;UesEUnUa(h{)BmcK=sYbI1pb${?v$_YlJCpo^%?Qgw4AfHjHM<2mwj&A22 z^j0i>oB}Fuw5ql|T;6MyT&33Is&BtfTurWW=`t!E4}@i{HsN=XPp5y49PRf;pdZbO z@;6%+js?Qr)MncI`Rjk_Lb`OB7rR4lL6h%8Q17!fnj2ZN+l?9?pOonM&u-H1js_X! zhHb4G_&1&A*a|2NE?Tag>(stQ@?s09$xBvZ|`-uE-d>Xb!J#V zm>*5BA+T2LmXDXE7tOQv)Zeu6{l4k!(1_1WO}){M>R!HwCEUDp$=jnnO!SUMfl&S0 zBbYxvF9k1eT7?Mz8FYV@UporZCRJJlU*@d~HSYSI;HV3XFLUqQ8~NHB6o0!<+?Qyc~cu_n%S_NP7G z$30V3g0;0LGURh#6xIA>?ON-?BI&2sDCkQspBUZpNI{n_7QiFDWUcC7?!XKRfMMR% z;T%bGe+4Y?MInZPSj<6unQnjZ{lftzWs>(j1`xvZJE^aFHmDznA$okqI%0)3Ljpq^ zwADN{vm-f5bhD>6d^BPoo}6;}mP=U3*m#Z@jB8-axH!2#3_jhQ?5k*s>84r)mdNf( z+_oPBHWpvX#kDo!Mg2OirZ`Z}Ocr*fJXG8mCb#M5RLqW={((2HyA`W=AfEdJhLLYY z8oEksp7k?1!Ve4-CnW4@&Jzez$K*W{YOa}_n0MlIrn|%F#ZY9sJ;WraDm2%H0+|Xk zpf`Nkg|hXmIWBJiCsq1q$=fL?jEVDLn2AW`bKJ4#Vr9@ca6&%9arMAds*@o7e{b>`e z1gLt`p^5mgNA4!u#S*V80O5n1++ZsNC0KHnc_TL^VI9nU@M-gNPz2(+^4{c)Hp0XN zM@hpkYz%(Skv99qVPWkLZK)33W=sxumGkkt`!7cQ7X^8#s9r<(YFoZ}-?Ro;-Km-s zeyrs)1v^|x;z90XR#!Uf-Cye!w}qb8e+upE>g`I2<1&MLDpN>tyrkGqk zl74yx_oAZ>jzg2&8YXpu?Vm4Xtpr*3Z8Q!cz9l!c=+ocNJay!Yf}nhV?~IH|9NDPo ziol*8J>6Q+$<5fYj-*>rDCri;?T$~O$%io*b%B)$yj$~8f5?{M8ksr!@{ep}*RmtE zAovF`dV~tQ-=7dX_`;;a*^hrAcWx5~1cigWej58={l9GrfSVnw_gR(;kEEdVQGZ)* z?yl_Y?4t29!TLn^l=+jCkgdq*7Y9FJDy>qW7kL$l`V4kgr z^>;YA^MEh>Q}Rwsu9R+nwf}9Q{O^_7|KIyx*_`;lw%S3A`We}%gI}W;8Jj`?kde7z JwZ3cIe*h4`{ht5; diff --git a/tests/reference/output_1-NORMAL_buffer_2-90-00.png b/tests/reference/output_1-NORMAL_buffer_2-90-00.png deleted file mode 100644 index f95cec5709ecd52edaa20187489ed87db1a8e0d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4854 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%0A`97^} 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 PV1U+ReYF}D`TfRJ?eFh@q{S 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 tfctkEU97}*e2K= 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 diff --git a/tests/reference/output_2-180_buffer_2-90-00.png b/tests/reference/output_2-180_buffer_2-90-00.png deleted file mode 100644 index edd425e569c24d9475d72431a9fe9675e355eb11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5270 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 diff --git a/tests/reference/output_2-90_buffer_1-180-00.png b/tests/reference/output_2-90_buffer_1-180-00.png deleted file mode 100644 index 85579ddd3fcb15fc3a5228f2a2f8a09ae121476f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14282 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 diff --git a/tests/reference/output_2-90_buffer_1-270-00.png b/tests/reference/output_2-90_buffer_1-270-00.png deleted file mode 100644 index 86e4ba0eaa6a35325c1725bebe2fc28ec9d53a04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14385 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

=!T6s8P@0B0>UQedjg3egSyQ@1BB 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 diff --git a/tests/reference/output_2-90_buffer_1-90-00.png b/tests/reference/output_2-90_buffer_1-90-00.png deleted file mode 100644 index a26f923841a132dc7ef4408e8502bf5ef8839929..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14482 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 diff --git a/tests/reference/output_2-90_buffer_1-FLIPPED-00.png b/tests/reference/output_2-90_buffer_1-FLIPPED-00.png deleted file mode 100644 index 95730615ed7bf8cf10b6f48af9118e230e4c50c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14336 zcmdsdbySpL_w5h@0@97Z5F(vJHv-ZnAYBq8Dcy|%(%m75lynJ_(lWGksl?C-BOp@u z@%w)Fu66&vf8B8{nSpuV_la}%K6{@hMqBgg16&$h2n6y#MOi@?0zp#%?|R^;&P8{Vk>-mwgY5#o2o_nQG-aLlqVQt9 zhEaq#ND5n&I+WxR33xGpb_DB?HmQ(@KEMKvsSq@kukCt!Y}YcA*Sp; zrCfh*of^ds{U!p}t+*0)h|Nft?5Xbe?Ffyz^z8`7alK%*mW!St6Xc7ab{k|CSa`JP ze<)`oESLhBJfFImPa?+l4!+BVA)`=N?8FeB9tWatwDD!tbN2@Q#f}B=W9wzV(lHu@ zTbf1FF@DuGD0L#b390$~^Q!L+zqL_GvU0cWBhiU$yF3&{k4QBIW-t5tYkcpUrDwa& zUFQLIwunFa5ct)O<2)QT^yB(%HUvG?tk`UQ{nrhXbs~wNwX7b6wGfVXv}9=JRK|`X zkzRPw&npS}G&CMHzbQU{eCBb(uYBr*) z!Nw3;1#(6%c4{VKKZUUy)!^57b*#1nsO{G~KCAa<9yWip8mhn}?l5&JccltxzLYjN7L+$2tT1Cfb>gs#DB(fp}kLR{r< zjBFSgBtk_Hh_W8zg7mM__caxjja2Zuc+mgZ*;MI(|^ccZsO$ZtkJBu0aT zLRfTUH;IMbf5g!tVYPF&tj2c;XrJ+V*JlfeLPAFzhDv{9~HpYU4OcGhoqF8haUwTt3#d*kcVah%G+6*l( z*gi`8{l4|}Hcw-KXd=3}`ZqL)Uz~}NWd^=?7>nRE5jB=h<{M)%j5?M^Y+swm>{S-C z-RH6e0~~Us!e3anS&;K|_5vikf*P^)AJXs&F7!KH7;l-f^{ja=Zwo8+wZ2aLJRx58 zrrH;YBYgN&)#!0$fwg?D!V_3#SXmaay#ZmvaMhv!y1?#pg$*&tubsLd`0j!l#hTOi zAT|W2qB$=~YF=3k8_3<5mx$%?#fW`f2(>k9I83ur4y}gzt~p4#uVNzaMyrbVbJ7~C z4fLbYW)6Nzx*EM4kK%UZ#G&_Jo0 z&u@RYSelry645sN5mtOZ8d^%`wYWq!CZ-btT}472%f#I^M8Ot{S;wKMUOcQd}Qu}XQqwg{r>Y^0uxdL5d3f8OgYi`MUQMpF4W{E z9WrMeh@ElksWG|PsY3^{2B;*Q`8g$voN@OQ6csQa(dbEInY20$a%(AMwBP*E;T36; zXlVCXj9F-*-V@e{9BmR-er5=UWi$>H%~*k(-ByAhTfvKcU$?8WFj7!g0HO?`9DZGN z$kU2x#AqazT1=r4<%U&;zD~1r!GIkNRifHB)Ki6^%R+<83I)XkN~iLetllD0hM7;_ zH&Hbb1>lzq@y*=`%f&N`$BD(cDWDT6l$qV$gX|Pd-Qzl~nPAQ(JAmx2vX9Z?q-NdI zc@q65?IHiy%SX#R_fI-jdzJ9&qfqqY+zRNwVgi}OSHH(8QO4dy8r>ZKk(@S3ZFU{z z%^NO<(Wz(kuP||329I25^y9psw_jbR%%{C&m4)be&2;7@otyX~{D&!orVB*&SV zxdi^!Ow7b-dl>Up3c0eVTi4*7bjzv~3Hb?f2JR%1+Uf^g!Y z5h|rfe%h()V9i|Sr8u{Kim8NS-cLvFRkNaI%g|l&n?qg<`j8@BrO$7utTFiq3<+PjDG=1L_G=OyC|_Ef{S6Vx#qVLR#MMbXZ5sGEmB;gg zw2@Gol>-k=o)3ZtCHSR_h!$e9jqd*Da(5YLMQmpmsz~UU1;L5NVNouO#A|4qz|t=9 z!}vjp3T6q#uEU#5nU+-ul@&WJP|M2ZPbuUJs5>mdBvB^0$6~Vi{QYN5^RRFsXpoJn z2iKR!b_ABpO2njjPpJfnE~~)()1Z=6u65U>k!hASbn5HqGnT2($4Hc zsg|4~jo9K|3b)DMeW-WX(nZNM{2Zq+%emit5KF@Y(Q?JjIpP<|?=gXa-}Bf(wT-hs z2RYVcJlbU;{us~BCX|A&>?UFlR{?@$fR%^$g%*cK46ZpkNQOyUCa$M}12R2*#DxJ_ zk|AWbOqwv`nsa|#-*H%qUoA8P3~COaxz{k9e-t(M8lG44&Vp9qNlS)BHhL=tb#V+= zxMYIl$YC%#)~1eQa#NO&E0el)0uzCN_iTf7+|bj9Ww9u>7YDO$%C^E^h`IaOUJ$(C z9~bQvJKxPrr-(vqgg{Hj5>j z0W|lx_@#5~dVud@ab9lH6fpUcV$t8d3!zhE0N-YIH}piwmngerYX$afrn%NMgK zcOW>{VXH`uidOBCIc_J+9RnpFR`OmD&AnkJ;Zk+?Bj~x2K^m&V_U!(TJjIZ0h6GG_ z@<+Kk&Ip?1yRQjyAe^j7P{B{S{fQ)9n+w{HP|$^<<8x=d$4F%&8&i=lhT*8_mxH2- zZfkR?n|a}^zc7DMB|{d`SEuAX@a2g=ntvhUR*2=tDq0bH9vGtb6{}}WxM6RzgMF*v zxb`wQb*JJYer3$P8arBg#F~aPJbJDk5M2-mQhv5!}M7UZsH)DslSak`5 z)O;OV4hDjnzqq8QWZEqA$iq3jR*c}^7zz0>zeBEVs|WU>kf(Zys*uECW{3R^zsfo` z!Ny_2sxc)9W`UAp(qaUn4I;aFymzm0J1bHJ5nj%tV5XX|1KobW>`JA`&WWwJ$M=EW zi|FEZ55Xy(oh>w7=n+tU(>DweYrr{WslzEkBeO)vdfsHTJecmt=8I`#-@=<1r5y~l zHHxNG_ZV#viIH5d9m$L*(FhfNL5crjp@GLRcn z7JU))hFGSKJ5N@fe-Mo&Ic&k13g^4ZE?>s^4L3VlQB*zW2QfRNOzx^tJ{AbE8zdpx zK7WjLG3jWU&Ve9PO5Ric3+h2$(iok6FEv$+9yxWs3}JlzhoLoVT0;pgH1A=H5vlhRb|&z$Udkme=@tCOBcoX^3CVxNZ7# z9Mak_yfOUHcbAMyS(v?C)s^`b09@rYrsz|nL_<=7@scb@-EluUu?bk%K$c&!V(sZL z`H`uO9ZmLqIAbFu%qI3d;q+5MY)S4c=AOrivcjH$a~;WGqzQ@xn>&%&pT_d=hSj0$ zH@HQ;A6=j%YZS>7zGT&DRti(m#P{#Vb=VT6H_~@*ca#hS`a=^10+Pg-;af@6)11lO z+!1Cll3{KL$(SDrxqvR>z{((DQH-7%YcZ*p^(DXKS|kN(H%#&iGiCM+HdN6=to*js zlTziIuTAu_7$m9rj$-WIcby}A>XExJy9_ZuB3t*uJ)@TGcsPF{6T;enQ1It8@91`M zxlHO2dQ$8P<|5|o^G-251p4l~Z2KL)0!S6Mr`q!|e$jpLw+y7w5B)HeziwBSPdux; zw|OLJpsk!le~*01H2Fvz0!?JvJ2oB5owAnJpel}0m|o6U!-^4?ovhI_Nr4X===kR< zN5XeQSjXHZ3saumLxZp=XTA?eYs~!3wh&f?Pi7f#cQ1b1t0_y1lqm%gt%M(s+0pwT zH&lc;C&GY(j@vKP9L1 z3g3GGn}K^KU*u;cFCvcQ1Qj<^%k%w)BsAD2t)-=uzmJeN~kh8H$wIs1=xqvUgm1D}hvLWtXNH{u#~KK!`BH~7 zkKTG_s=$|_|5+-eUtt(tNg?P}lr(Te>x zQ4TPOF~UCd<1gF-G2qQ*4ZLE?cCu0cdz z%sR{(BOc8WBcQ$iaj1H#L*xsGUBR1S?l|?j;YW67Oj=NoupREBxM^=x^hP*!JXC}W zN47C=-`Yz$#c$sL>D|aiWfUFPxG==!pRdUCZm};510Gn5CP=I+JAE%iEe8Yc$%wI- zm(h#0<1&mrlnem4@arOJ$JsSe_6sNF{s;6NrP_$RFq*<>KXrBvnS2uqP zNN`qK^c@wJwtCK86isztgCesjtPCnJNWyUX2OEK4Ligo;->uP05F?&U$O3^RVdV6@ zfP221ink*)zKLeVfYd@7*cBCP3x)EPvicFvh}vT63#|^YL>j3MQm#x5?o41vWv9@O zq{ErcS;S~mbyTNZqR;tGNa@=U2(H+*p1Jn4$fGB?s`f!LG-EL&9Q===0&kM<+j&Mf z$!;WnT%(+IwQ&sF)XaSmzC*N+?n+mI)_@g5_-XN8^8Ibs)H~ByqnVeHst6rV9)5|$ zFQs!GE0^60hh6yI%5z~$UZIZ&Hwl^wlG(mm(tUbxjWcT+hre{9KOSX5jr?Oz5dv4mRi2@RnS{i zVSHwq_(AJ>@Q@ez)_4>Qy^mn2aJb!2+~;YBd^( zb#4kgT#Pa{MC%O?nqPXC>awS+=H}*_n3!;KataCx;^E;{RaJfd{JG4tFvmkSs~;be z4Dw1_(X7`YcLA|MMg0TPv$5`Ioxn= z1|_fg0hn&7?iBnRY)nLEiu0F775e%GZM;&c?&8vtv6P9_xEQVnvJXX z$j=hPv}26WFC(#*C?{$A=MD(9Sv-0WVT-ZwQ2d=vCA(%019W<7YU+m%4-OAkbM311 zPe!_!kfuR$EF>|eJR-uv1{~=bnVCo=vaGDk-roM~?cX6V4Ry3=akO{oPwk_H{D(~* z|LV$YHmIXo$mDN8naMW(cYbSA%;pquui={@xk6ZOU^oKxuJ( zU@t>UM+a7C#72N7eyQa~+I%1WaA?G?5p3F**Lcrs&GYl}uK(`Wc3+>4zJC3Bh9Pd- z6DK{(3|_4kfEQ0@46?b;63>K(?x5f1J)`hQ-srlbyj@AMJZ`yu+AYP1_g0~6l=Jeg zr+BtHD@n}$d~L|>CDP|;Wdw++O2-( zc$O5N+RBu#simoD-W?nSmekYp_vzNCs@UUm^ka1vlHKED5sk*yRv&L~Z-0M(2Zy~M zeNh9&!sfed0DX<3=Fs^~1mv0HR2)FoKz}5Mn z4W+MbBAxV|2@+a_bM$1FJGnfs$5$X5VP6<@KJ}C#=vOcHlP6C&EP@c~%|zCSdCM;gU|&u*WF2sxMK9PsFY?QET$xKoD8|H#*`QJhOVy1o12?u zg~5M+L@@w3@bU8M{lRI&gbvZ)m~s*Z8Q5lOZeg(wpkZ}YU)3OObW4i=a}6FOVdEgi zh}|ezoi%mD25hs1g@t&2f>MdY{wQBu3$qTjYaida@Q5g~**M3eExfx%76zVV8YkOEn-@08%(KTi2JfE?*%RKD^vUU5<>5)YsSd_R2Nmk;e|KQI&6;{Q32( z&Weg-_ohtdX7>_>1gQ)kPkR-cJi2QA4Mg~|?@Ir2HfB5i0SXej#Rm`E#`Y3lSV|1&zxw-d0FSSgb z3R$|uA-W+Q9e)`PYmdsSAH@P}XfBg8I^`tYOv9=(faZ ztXqRXePHw7!yn6Bj`Z}%!f1l}9NS##4NVOVeShyvHQG;XBayKK#kehxbwxWrf0hsu zYHn&Gi&ye!TNxf6?(gp(%N2==iUJ>}p1py?#QFG&G9as~tK!m8`LALZm92hbC9YdR zAUL7C6T;JLIqmtDMwpI16=xV~;`&Ga?(V0lTGC?TtX1mGxWj;CKuBv9Ncw?iudJ-Z zFa*P!jcI9VIlk0>{P?l9mNItW@bEA`KK|t7cd|YT%nn$) z+1YLPMquCl8Il3>l(?eNt`GIygA!-nK$a?Mhxa z>kg$)+`snrz2BNuaqpJ((qvkh(U{EyYDOZIWQZ zM=0~~;X_F-Ry;<@iDkesrlxuM`9Xhw?`{kw9(%5p)NbRU%uOLGzFoI6*Hq=GWh0!U6$j=>TWuNSv z#%YsBZ4aW-{bF#NGD2ismGw7D-RJxI`krzrNKJrZAv-7MZy&?$ufx`L7bhntXXo_M zEe3y!W6d_xu8_Oi)6>(=fD3?9H?bDiKexwn&CJY_mERwpadfXbGz*J}pvlfhy-t1= zOU;r*SgdKT;;#stwCy+&!6vWiV2PBG&9nV^R{(M_*!%NT!eU8T+}B@HA8Q@0bzXs< zrPKKd-^YYHDiUy?eLZ>iV1` zePBS5dwY`Dg`f7N`kM7Q)mt-nWV!eA77ArOIpJlAm)&$R3f2I+t*zzr^Wm{bKX6}= z$k&IiApys0o!0dWPQv<9+tkGd$AHzlFUMRB3=D$Lsw}cUWRKVoyfp$v0xK))-2D9A zX=ZocskYFu=BEKf$qv3iBQ~dp+(x#i{ji0^2=X~Hwbr7$CIP0zlpK++?~rfGMn*3k z_-FRH#cF2&ponu8>=Oba9U{!i0(G3^5gUl5yL&@-_g(W@V|%SKOUeZD>C>m+7Un#e zXP3xVU`i!uN)L&9&E9NF#-!eUuRx6y0?OMO%`BOMFSk23YX+Zgj<|+gEr0eoroeqo zO5cB9hm9=w;`6$*iwn)0cYB?LHHG>&=irKv%TbF#YyO~HG1EvBgsd#O!h>s}(|IkY zWc=E|jyF+JA?J;St8E^SbI0f<@k-BATU%NH!hoImw@5yTBPw`UDM{j%_S!Fdzw#V( z^)0b!r~fj55Do|$qaD5Gn#d^Z(Nk{Ir>yng?Q5xVrYodnpKK@D~yKS zcE1Vll{ekANamAW~yi$e)LX?K{t%VJP z4^2as6v$>M!_wbKIt`{|Dmps--Sxgx^GZDruno(u-TH0&J{@!r<;7Rbc!j2L)Fgvu z@wW!!YE7-r*`;+52xY~;vjAE{8z%tGO?l?$=Y3uczSA_=LR~t}*MN!_ApXEZ+%MdA zuK8a>lJ-K(Z@*%yW2m8Np)o~^592nS-It(A@1In5#PLp*?El(*AU$`gU(4=By&)&TckRJ~7=ZmO*%K%Vgu}R3EG#3;LT7XL)pwOC>0c#PL?h?yt=30p08fLvaxwzTkC3Tt9UUadIw?yw*V6< zuJo8s;Oik?oDXB-yBxbGkYdeVf@T9-4<%SZvV>x9GLk@_5Vv$wopol;XHdn> z#U<%?s^AsxJK^JZTK>20^78VrRsq}7wTSVxJP$A50PuD407w2p53oNOm`ED%DBZ%7 zVH|tmP)yfn0(c+Iu`sK>ROo++=19R=@C(RvA=C|Las7nSeqEyjR!@{9K>i|BqnI4VjO90`KtZWlv7f{Q3@N*y*0|O%} ze{wR90ZfxQY)XfM+8X+fpWElwVk(ehKzN|$bQp>zw{@3s_BgzZ&p+Smrgmj-Xqbrf zkwdV?8sj!*i}MB0TfcudnQ^xoSL;?ZH8sIxWV+kiY4$v>_pVXb*Voh2(*v=T%P!C8 zZd~Bw zWuR}-+1UxAJ6*N8x!XlJ_iJ*kA=jIr2NmNoAg{N!wt!^OwYD7p8aw`6X)onU`PxOh{UKZFxDs z@R4`Ply*YkyM&P|vA+p=%`FZ@AzNS5H~u6!jK7_X&0fSI>0t8XRW^V~hRz3KWxUp=tNro$meJwS7(G!jA!gCMirobz~}aIGcOcp&_Kh*qf7cAI)c3E-o$@ zbYeOIkN^77S5Z+>H&K6~eOmxX$kf6@=3~N`Ti)&MZJ)z8A!H<|331pUHkK??%FOhu z8!_fL^LCy1zbe&#*4bzgVV?O11q6K7W&`Bx8K;low|4!%ZOw|ZF1NjoM z#~=)$vh9(qibF;R9D*rP=UR%i2>hH?2+lOKgFV^@0xFi&krt(g0p5Oe*=VOh7OQ3^ z{IqZ0`OneY=@g3*MqaHewG1yOZ7bR#lU+_M>3ma$AKO#;;hzVP^U;1ZhXo{I#wcCg zZ?mGRq^LZ6I(HE?`=gp!Ep7I>eyQ$=9ijJRV4uXlfq)x*d)Eeyf2j1-@0uWMb%z|d zVu+&bC-uKpZF0omTT=&ei^Z0i!>)o8+j&o!lKaHPDi`!C^u&b(s%k4+e0+ZY=RPKg zULK(WRf?y~3FiO$C9$heJ9y3fCZSs?wWPA=@ zMb5oRuJhfWBAT3j_-uH|@SH-2fgx+#PV z()q3s1eZ5Uz2Er+<7qh1h9zUC$1Sd`m(W zrv7K;J;SrQha`MM8H-PIq?~es)cK>5OOSL=y9IMRH`GubJ-v@vGX;t`Ke|)$7 z$Z>WwKP+w9Yn9pQexk2E3>m3Ogc|V=eFL!oru(%e_Q~vTJ-SJs)l|nw59vBCH4vw2Z#sRH_Ae!Y zekuHjO+|d4F_Orc8MQWa6YmV?;P$<45xl&Za@;$?(p&TFcRW7gsu~l%Sd#$lhXO!7 z;O6Q3su;qfR3*Pi2@sAmOiqwKd!1gT&24ihmYTXM~S-cA6n6AFz>Z|)LfYu zU>|!~_nB0_+(#+)KeNQhdyk#PfhYFy)fX5L){BeQnxLYHxs3jwnEW@Rwm`b=h~^8o zW98Ndw)mb4&u6EO@N_B7jiLpFlP)9vmIH*i(5fwdAX_qAmk{d@ihbEUFEFB6IZcXcTm5 zd-mVeM0l;rzpHC$nQQ4$OZPZGJJZnA1X2{BTztGH+bJ09e62-cUf#j}zQo78XI>e+ zWVrdy|IVoZkb1n2W>7m1G%W=sW%>^+WMFsw8*z**&9b4v!MV9PpmoKcAS&4>78VSb zgV@vpx$6Fp_|9Y%_JS zwJn{3M~XbQp;Cg;jBUAt-(CiGIb+*x1-7)c8F8co5vCcEl(PZCkW=$3hWfh{Dd0xN zDEzdnYjFPD+Ij(~1h|So#Q??l!&#g>UWwPdLsUS(eRsMPm<@e@b*g-xly!~E~w72JsZcXpPAO|uErS40>p(G_G9k8x9<%xQZaA=m~=bxIN z2kdEX@~m8t925;+US43xi;{0nQnuCaUrM6_ij1qr5eRiHt>;yBqx8#cz6nOLKiP1T5P&(;DY_ zTYdRPaOHn$m!)Jo6*{F)15WSx{3C{r`M@#6p3KK=(Fumq7_pXzJk11thUq*twY3uy z6NLEqz#mP=3UJ zKOmODIs$v_^whm^QSxqCuQGu0k=x`CzwXmiX!l@Q*SpfLHUMUEKtMpPd1ua;9dq(^?!UmuTl9+zr(Ht(f0+~seYl$s4=_Cvk2N2+P_V&$Lee$0M4xGQ9 zT0|5LkhF?wg+6EUQ4uakzWb>yXa*r7hjVxTNb0P_QY+O>O-)^NY5~%TCYu5`%Rk z0xp5l+M7rcSi#6(lan;F>w|LKUj)uJdO{0dzI>Ut2sso@c$rh_it2SZ1W^h?*uS8) zp@AuqjSTnrbT{PbT^)1u(8gupEpSn;cS;gn);bpFvbFWCU7eh+!5Qj*elsNnh3D+^ zXh1FTFxDeJd$I`N5L9?SZ%46I%8p@ZTDmIM8v;+7cmK@a)zwu?OKaJ|d27iJI1Uy! zd(X4gw|%;=RzU>c?>JXUoj~55S=^m9PDp?DK3uZm$*Ux}0*+qy-Ocgcd24s)l%62~ z{?;r%{CqXU!NB%gI6}>*;CePSFt{0+r}@v_=QaH_xxQZ8QgLqIGHWAII8i0r+SWEA z#!C?kkQ#wn=J3f^W&x&B6hrWzMf<{=&BrppRmUE!yV&deh8f`C;9zC-gX_&lzW|U; zu(Vm$7U6RJ&=zdkb-MJ$qy2p~&{j|=J`~EZwH3%78Byuu17?30nBwm4uHb}g6>m;* zax!n%pPwa(((T@d#;mDgUpPJ)S4#(+tJ~K-QT;T4j{|4&2z{EY2w84b@qg~^<5TwG z13(Q`M<9tK{Q2TD(Umbq?mbPX{z|2S~h=%i>KhDC|J-5G@tTeT74lqX)Ap^QBYihl7 zfUoZ-(9$_fcen|>`Fm!_B`hX(`FnQ;$k;-apsdQ1cDIee*{bItn4{jjAuxH&>Cn6c zq6PrP-R(uUOyHk7Q=YiRpbygs~Kv0P1Z|4EL0Ctmc^||?kOciPbwEfam zCJJRF4+zWa@)5Fogk`RaR!w19W(lh?fCe!{tm3QW2N64=ZI zOCiD<&mJMBcRhu3508$j}21w{XhBxWSh29$Ie46^biHnOKA%?dh#WFKppUZOmEHp^=E#L=56kZm%e80o)xvV zy!o0{p@F~GE3>Z0MAom7A-6~n0f2HO2$Vcp0D}HIfUo?VB&mDk0MF)qvQhN58nUS6 zli;_x$zxH_9zsqOV#xaVwC=@={>S#$50v)hFwC!gJ78VO`Wwh>z;j?80R4A8+qK!s zyHamCB`2QAT(mdw4|0t*aE-z=&YpqR@5`Laz+iWGcPA(HOW(8ebB>9m7VZx}W`i5P zn(^}TR>jSOfz4{Xl-Kw)@R7(CE?G3!VMA<55&D;9iUo=*YlahbPm={GD*q!=YXO*A7oA zv>&jwfBvk`2Kpb#%Ka^eeUeYkOWfx{FkWpi-2M6W;|X1=M8tm;fxPz03^IVXUH8NY zhd6QQH4!N9;BdBN|pX2l5=!dfAsrj{r;!F zwLIs_pkUjP-U6=ZCKMVv53*=B-OQ`He|{@=zNT(+m`Z2gRCxrJTQcDM0Qhw`SL=7u zQc~3(^ZDRV&~k}xF9)+(g&r_vLHhGrbmfiNUF7umjnx8~ z5NQbs2`MQlA)zxc{m#M_D>#2QXQi~0o7)L5utk9-{m&d-1Kwxh1cSjq(BJY6QqT*M z3Awvk*V53a(9CQftU^s%2S3*f%6jX*%Ih!kuH`@yi_pks%1@tiDX+`uN%zgiE$(kB7U|l9jGVhh% zx(!SiK*RJh!5wvVMabFwcnXwC@wm+K!GRt}x^9IY8<~=l5{Ll6_4+l-EzUDA(CJve zmtJum)IIA^5$IK+r@NK|nk9{mjleEi5`4b_PAd7Og&5Ww2LAdk2O4L9`geAA{`~m^ z{G0%Pf8C02{DNykPi-qX)4zghdvC9jq|SY$8np5q^sjtxsKBWpaDCR>NYG=i3=4Rt zO+`huv%3q32pBpQdSBfhF1K8?Wa@ny3_6fvJbZG`ictX^b|7jp=AQSORj>rZ0Aj(` z))qLQz@z=9aR_q_qd;9Lo(+M55m;tAGY6m&23-(f#75JB+y)`f}04NL?RU%_G^ZV`C1Bnl~w=ZJ%65r{5-4g-c5y0`6 zFaP&>iFyl!gW{?I!a={o0x|301%!(>8y7eCu6{ibS4}#cd-CsoEx~d-e@4G-2^?^o zIRIh)e1L{~d&chWt#OH%oM(t#{D&u#)^l)iOz!_TZ$(E@h+WUW=ZDMoqL48O_p9?m zPw~V2Gd|ecMl#go%&~-)0UD sdmK4#=Ln+Px# diff --git a/tests/reference/output_2-90_buffer_1-FLIPPED_180-00.png b/tests/reference/output_2-90_buffer_1-FLIPPED_180-00.png deleted file mode 100644 index ce944d91e5b695476675ea370b295d64b8ede337..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14762 zcmdtJg;!Kx^getE0g(nN5r&WwX^<`lq!FY;Qc6k&M0%vVyHgsZrBS*=0cnwLY3bkL z^Zl;hdf)d?crR-)v*yk{_nfo$e)hAUeJA*p@=IK7N^A&%aOGs-Y7m412k-x4p@RQs zc_A|31;a$)B^!S$2j&}#=OT~Vd)jUfN|JjlJO#y_!m3-5Ek!2W|C<&d0HK@xFr=juRWJhG>|JEY5mJyC%%wo-er&h`hd;JW=8hvV=B8Js(L zaQ%pwm{?Cw@9ER0p`oGG)zwEwM-Lu6xV*fyKZ~^HeYwfc)w6uyAto*)FnRe{n4xuZ1vp zo;q}Tc6SIzysntyNmi`Y6%`O*P8?|6&pfQPYD}^tKrqH8XJllgq@<*!QPI$}Ic>(M z4eJ;UeZz*Uk-LlSdrCmUfZ9JJ(W=)5H{x z_%Su*vh>{_*xE@57XmvjEG!%@vs@B*omzTa~OB{%oe|yU^WKIm3RBDgB9^Z6gjFl_+QKT+^9oDm4sa=|$ z6q&=l;Opz#XI9nLCZ0O9(CT?P;(c?}-`}4qh86x9(M1*61J-$be9V;Tarh0BCM+W2 z^!Qlfl7gEM1zCJx$)5`RO`qM~&Tema_c0X}^OL9jtB2W1q9bTf&wISC(zsGt5IE|^ z_4O`BMzGkvujM5PtvDT)u1{E(ZOOZr=qT2kHA~(P*Vol8FboXE8sXBa)b`;KKq!Pr z1`4E&Phm7O+U9+Cm$4#~;Z;ZR%jprZn}5q^i{5S8Y>Y&~WUbQj(>DCza(;e(O-;?U zwY9zZruxcC!hof*=sHTHlIXfPB8W0vrcCo`yu9@nPp2^36!Yv?Nx-;CNzA07)(=^! zBilG%F%boijj48Us*hhrdS5Ls_f!~4zQr|OyyY~}nhi-te}%=JRj7DVzcMt?Z4h$o z*V42h!c~%Vb#uAzeb~n&esd5R7#KKlRsC3%#5F^!WI~+JI#E`I+uGB-DOb)9ye2cgT)|6#R1{?T(gVBfW) z^VmR~@@+hP{1O%kd;5KG`xpt7-a118Bu={oJ%$X?E-C_}uRnMz(cy07?BNl6#nj(qnoXJ=lB>080m3 zQKso$uq_cTV`^(#rq4byGO`E)H#btuZNKG=k&*H6{OUf1{lm-&Cs?#>vy7Lpc0tQ6 zfe6bi^(Q0_@)J+M=54pBR#LRNCQ1^$WN3rl1`byVWd)c!zWCK&Uwkp(O}i#l1aw$F z^YimeDj9<#BjO(a{@z?)y6nwO&&`SWZ-?c`u69pNO#$rjCs=iKefRF2hlhumSzblO zNLA_bQ;Wa@!4Nvy&dJGy{mTk<8pe0OhsFkax#uJ&F*l_C2tAJpN2B_!+C&7 z>*BZP;g6mJYXM{!Sk~~GJ-bd%e++$md15?R4c>jfBX;wN zOc6f5d|gV@%3lZltb8g9zkYoM?viZVDi^{h=Xq?C{B-1yF7cODRR#^3U^@=&=FA!%A3tgob zvEm>RDI~Htl+RY}=?=xxh*$Z;~ZWGp&vzVK3rttwlgI85;XbjF(4{9B?`sk!re*YYm=G+kDi*ZF7pWqUBqTy ztxT5bU0+}KxM%-#I|O6{Bqv~@wu`Nj=aGFZ4+36CiJ4oxIxs$g@O-Ak5oMaGnVIWp z`1tsG3BbhhBkP%Z-kK+~V2FCV`9l$vWZaff@vwq+cJ77G1bEMkSU5Sd-0!7_4)$jk z`m`xq#wa75iPyCT`+K%8PPVO)^Ht(HL$aA7u7{V4w>KQ#=L33ueopxXWbvFX@*=(v3&;z@8fT56VKH0Qvf+J^uDg` zH)uOwRGM{Yp%H5x{gc1ghRZW;T`MFkoGQ!kxuD?a{HndZ{eUoVtCW=ld#c=Ec4me( zUY;p5`AK_2*q-~YTQLf*1V$FN_`R%PbMX|x?}(M(S+1{#Ym=D%J6;#5HI>S*VzP>!hsKyv_T^0$^(Xn7@+cv4(Xxux%sblrhH#BU!^ZLs(7Z;ZiYteHJ z)$?5dESG2dfZ_mlV8(r}?;YQ0IOQDL^#_C_Skl&(b?%s@OQfnl<(a=tt1kb5UYx2l zQ+s3J5wrP}b6`L~Tuk+}je@-rUwcM+`W-ax%{3qx`QF(36yqA>%Uk9H{O$0t;XBYeUS3>ZR+U0aDtz>N%7)oPjUU{$z?v1Calf& zfw$UIo0y-Mmsdn&uKWcW)Q(PyNsXBS$EKZQ(7}X$TYcf+roa9vD2qIn{VAs|sa}Jr zUH_iC%Vze2;eW@#bk+VPV8a6g1Aq+!^nSeiy5u60jEpQXF%fL9F8k~BD_#;dyfrKV zQ}WD}9K}^}S-!10j;gs^?my4T??;IR3*CD%tsgU_W4%ydxjny-z7{8FXrifM*r3mz zI`sL+X2R*zkojxD!+808e)0HGaR zb=+mGd=wtwxU#*G4~C0Yt}rTwEsEy*A+_kQbtBy+pBz7Rh}ZixIsT7 zHFf)V`cU=fpsSr-xjuWK@jrb=MO*e{$Ew*r|TH zcwTkkU+TruB8`e#(*Eapl6ySGZfPCgN2Zz6Y@;jiW0l_geIOudieo3&p zpWJ75M+^bT)|O0CQ&HJy;~npj%3<;^0- z_rnl$Ad5Ers_>1p&*I0=9J~c0zC9u+7>ySLGT3`#t|@NHZ~daJe8~g1TEkZopqo|a zd&@)@M{%P-qJZqirUbs!0OLZ@cY0zJX6nyd-*7Y(2NkO>TS)StbX?ANdfU1Ft;@C_a7<5^l{bz;rEjBIdYbOZ?6kLIm zmJ+`GD6pJiS2ogj^|84j2D@T=>BCD}8Uszj=Y0f4-7=ijMyK?322mI*?aRFe@XzyC zjp5*m?xbY_>)XKPisDFjm(Yg!`S-&Uu~xfz9FiA)R67p_S#qK=I3xWnF^o_Yd;tX$ zK}Fjnc9s|^Tzf0%VxsAuQh(K3>|C@*TCJNdbhFi`5`lV9Qy z{sW)ieif{6b>ZGRlImmTUvZj*Lpa?`9~~YfJ(KoNQ(buvps8 zMzyhI>UEAg@vbWj27UZGuz8>r|6u**=H>xCLYSr^QPPvJMtFUj;-V$YTbn#!Yfslp zgM|w*tS6RSD06bJ4`I3Rr}BQcNz#=>K$F>NPV!3$S?}&YG+D{gJUu%P$R~=<;rwfF z)hA5$sE2ctWFntEt+Ec;6H> zd5_)NkMu7uB;}FP2Zeo2VfAgJd$icQ#umJ_y%@dL{2ti6*?bna+E8sUy|-^U0#w2x z&@G`c0|n-LiOPYwotIBXl5htlp)VK7#~RW6e5BIX<)xmqEv_*JYVZOS0ece+2yS!k z>R+&$ma$&Dy}WOk7niY~Zlv(>du%q;iT6)-Lslo(;n5DQ`7tb#o*W&W#HTR!7t^iY zbQ)Rs%N35OkEXLmLk)lCf_08&g3$h&GwWy1kafC8uOrW33Xp6NE9^A*gJhhXIsnOy zq&ep_VJ>1q!de{3Vu$bV)5@*9L_%~;?X^fzRWb+fTfk^?Y=i1hkT982d~M2)R6!Ez z5}L?usZf#=c2K(dFzbS5;Y&X5j(r%0bV6 zX1bvb`Iy?oN%u~pPNBzoC+2M zz*F1M&|c?ve!n0l1_mHpK#Nf;(zNY)EXx`XB$bqm3~F+6OReFe4+)FEep&wCYqw7> z4i67wnN;GIWCmIu1No`5^G>CTv>*7HU8v*=sI0r4Ku`nUN=QuHK5+|u4OTVe zs(^fK_r9^VcEXZZe@b*OX1USszw)|#JEKp!)mgcfo)(@v_G@M)B|Y7?o@L}UKBv>m zB(~VwpzlO|>y4^+sUFeVS-OK+S62s?e}}Ryk3of${R$Lz=N?|p&b4EfPUYcs#^mMO zpiBm*m`QZ^+DpG50%Z^Adk4>!N&eSau;X{8=jW9eV|#q#ZD$+w_x^N+nwpxXrltbU zklwTzx83;S#S8EWDE{xV@oBE6O0M(P1L$rOq2Yq0)S)#X6wb`(ie-+NaYGI@!Sbs$ zU^C108`Qw!R~^?wbcPZt^c%Q2ISC?rGE-j{W`vkmH-Nb}HqO}a&!)cw{`K1FDxto0qf%8V$*O%k2YO-@hkW+{86&v=h`MwiCSGy zbfu@JmT1nhs{HRrZz?#RJ;TMrv$M4|`0DWOeOj(U1*`mj!pRsQzD+Lsz%vpDR)?#e zI#2;#9u^*M(DcF9$%!{wmdbuf^W+y`PJp2UNuuoN7nB8j6><>knzQ5un+HmCEltgU zz(AlZMoy^;M8uA)1FkK8u|ZGUnJ(kFBhjiDlYPKlv0`2WwmGl;HtthoFEQSh@FR0WIuFJGBgi#V=QwX%io}B{Z zNY=^f_kiFTQXiAmdmqId*Jy2>?FeCxyWutrShb9~G}!md%#l^cKwZ(+wNi}P<>lq) z_6wyG)_`vAYMK~ke7G^@SYAHiW{B+rQZT5Z0SB*tWCW1PhW|M~Kefn5PI2*DArn$a zDQm4VMhU~nC%WUG5*})?VbosA7Ugh7k_Ml)P&%l>fwgF;s6^P!AFj&C$avixWj2*x zZX4cS40X*7i|@SkXnqsmA$B`$k?DD{SpbAaCUp~g8yg^NEVg;K8B9#QGeP}~eEV&X z_Fwy(-)(uD4F3)O3uxPkhF+Tj5mqVIi)*v!%p}+}9)G_D(s+FY4wx|PrzTI^d3t(!C#eB}*AbKzLv0xm->En;+Nt0f5JgK? zm;4VMQ2RXvsw*fW-&9N?8bG;ZIACqNI^()KQ)_s0m^f{7THkiP3w%6Tf47eW+u!>< z(fb@Ir*7a2AK251i7pS8d)hAN_wN*h)=~9;8cXkk!n0qa(*;?>({-^*tiHLDRqG;# z&A5Tn-s#p6seZPI7W`etv#Ef)HmEl@u)l-JN9q>0rO{m#5bciEu@Y0gY%%ddONW z%E=jZr}Uc*n1Xa-UJW#d0An0{eD9m{bs(kkE)h<#8 zb-nPex8`*p=(?mUM}~(3w0Q|6!iCOGIDni6<#Mc!R}bAiPbReL?H55A?K#m*rhr6H z%%AB7Q4k_g4)=FjWl`@-pbd%fRy+B8i~76_q{62FZ*H#E#iy+KCfvA0MADS&sdcCu z{hts8gJK>iVwm{UK&X8Vnk)M?w>l?GhOHjw0FZ$^k(!pKK9ORrS~lNA8Qu-@0>HzY zzk%YQbhofrkJsvEMmj|L3~6$awq2g5Cuu}up+J7_@QPaej5UvOITOvLP{hLGVq_3J zuk=I2OOe_eo0QM;NpHE6K~e|C_7H0WD`$=WPbL=@5D>^-zI{?@D;zKXgy{Y%1^53~ z9}e%wGd*0qy>a1d1Om5f_`4w1pI_y08v3XvX% z?iz|WcXATV?E%f5k~-ry44j`Dpk00} zEJ^-jukCro^wfAb$ z>U@!)OVjMMsn+sFOI}laVAU~SO^~FzY96!z15GrW%2}V3WFCCH@>4ufRiAeT9VN{Y zl?;&v#QHFZ93UefxgD)o>mF%ipdiwa?g^^pCn$6 z3R#M>xiO#YR18q9t*q45excbFG#R9_N2|Op1z4BMYUURgX{aT`D?x3m#}3+!(B%gC zr9)oXWZ#=LYmNC6Ac=ymMeEJQHh^(^0DcwGO?K}&tQm>*K0iO7o}LDH4FrakGBNCo&R+tdD!IbnN~vOL zzY)5)^L`R!uKUz9;V&3@EIde1`KLb(R8Ns2MbbrLv>s^Yy1{ zZS}(aW~DdlLqoc)85b>i@$m4}>3@QBd_L87b5eF9_4t#H(J8Hpk$SeJirc`5)2m+{ zgARH@IzPX;E6UK^d)_CTI~{lmnMRcF?Wlu>>Pyw@5dcY36^2@T+Fd4GsYBh%2d`U+ zMvmg@*8rgUV&d;Dwt+tG@#JOfL(43}m*_U-LET}c-g4hA&PG$xfv1FP!gv+K88Nf1Qw`tDhP zyPh=Y3@xI^7vXh`o_WSsEX>TPEu;XK0Ern)&VGt#g}L{WAQsV;v$aS}I3`KC=WX%y zi>mz9I>=J0Mqz>23NLE{UnJs}ZDtS+!Th;O!Bqz!5A#hffKvlU>+b0RWD^K@fDeyv z9xIqP8&by?m8-LYE(&1m^6mXk>Olv+WTJj^*_jI6941X*r>c#5&bpgXg>r+f(?QaP zVVPqWvZLSldaz$yQTqGDghuVBOEB`^zYc(!fbPhucUykHs?)6EX)X~v$qsNLaA?M82-eEwsZ)gzY8w8Eg*w|RW*Fd+z)6+B1pC)2iJHfed zw9urs{XwrcIPfkytpP3P&GN0N$A={HYn3RCPT|J}3MC!L4 zU&S=tTI7M->d7H3eJoKSnD1*jLhFV5^Bm^2;sNq3C5xa%94DW@y1EMdaL7EiZ$*`> zgu+>Yt3>BOl?yaOK`jC73(mU;J@w*3Z~r9NP9<@pi2bx!;yMEz8Y@m9rpUsz+)kQ1 zaB546%r*ZB8Y3gP)zhz)$;}4}M4k@4W%O+%7cZ1x5>}F%d2Orede{E(=~@1#LBzgyAwZ5P>)IL zD%b|8}zPPwyq|2Z~tnwc#h-`dF?0YxL7;^aqp8XM6?Wq=@G* z*5C8=0cQAE=G@hpPZrmeqov(Rc4G^P{TH8!(PQ#{>KWURtR@>0WAJg%o7^9`Nc?#P z1ZzVpA=GDUc1Vw)y7tuA2@VwK&(v5W^7NiLe)O><2AmcNNl9Q>qE<&uLw@7AM(JB; zZ%>|%rNxfvadM2OQRoiNzo@~*ynWK4^tZk*-J~+q@|0^0imc9cHh@bI%He-E7}NiJ z2MDyO)!c5cGu4JDa=Ye4doj=lV4^Iaay+rMlH=(!;M6ZkU!15Y5%x0n2?ev>)BCBP zF!Zv@H8IC!Jf%obqb8{dS2bYG06W3#8M_V+7luf<4C&}SIoKnZiv%?RpCrHBPf2yT zX1)R9Asw)two&-1_j8C)AWl?bZPYL}qn)CpGo&?STykQe!~|wAA&Vq1dzb?u(FlWr6iOdsFh+0Vf`lp}z>=#TWW=bCwND{^zlE(&iP^&MrZNkiA zzdMhcOs3t_9W0aNC0Bt`NHp`>6vo9bSfwv>PU9^H(I^M0*ZML!FHx(jVC=mzim8@X z!|2b}K5&}h!3s8zIi67?pNrnU#@HU$gCHX3-z`Xfmw@DCN#2n)H$L%?$3lolQ7DvH z=zoy#RX;u^BImJ>bdR1g)59OV5?33V&JuUEl~hmA*}2!m*%y?IJBVPnqxA7af)s+gq0B2}}QKif@6Q*5P>`E;BA&f(T>WkzFg&_D$ z+Y5v_utfkf-53O|;r#u@Noi#?S=rMCtaI_vBdjX`q6lUbtrx74LFjJSe0XFA^EKc^ z49c36$}XXEK7jk`__mus?WGS$9z-)G@q9O>xBUE8J@#8U|H)|DL$Fb_VIg3``= zI9xm(Ltga-M1QrZH6-_TCiVG!Ry!k= zg>YY|e6~iMO#n}ZCrd$JkWW5!q3A>RtFYBEtsyvv$dFZxk|nuA1rkg8gXGJp#-vujK`!kbsvKoE<`?lXQy7QJpIM0D-MCVo%XA+k4s)Z07s@ag;^!=xH9Czj5#pbw>~) z0zy$$3PNKUP`iLbM)c<73S0@l=Oo~eO1~qCM*eY7L?kb%kAzCni1=L)@Vv{r2^Q?q za!Wkc%<*lDYCDW5-+Eh!^}ha%g;X_$1Lp$YQcf zV=-$d3HOnOU?y;N;y-c(fahIi)DuWwg0b;US2>UKYRKu4=<7JDkh)*e1D_bY@9Dv$ zh)1uK1aTz-EMe79v>6r&VekhxnH3AM7DdW#jj8mIt_xgx23=gAWb>o)eQ~j>I`vpb z+K{|`;h-=}5z+z5Zs(~Ji26Zx*IYJm9LWHZ1Qxd3XjmKrB`lQzPnQ2vC*sCyv8~TA zpt>9i<qo-JZ zdEK%Sz7+A8+BB?QE6eKgK$Ruw|ALc4yM>=$FT2Fr>s#GgOx8~D4E`V*g#ZZ=*pjBV zx<5ohHDilJbn*lppVmXiwiS^fXM!=PG=+>X^Qix0S9PP9^>!zJ<{BnAd({?xThwMB>$6TMXJOzko2HElez;%A1T%y zpoAFZH4P$yMRCJoIG8bhq!}J_HsSV0O(Ut`kKwhxAN+{n&a_)n4xIw)xpnh#j!Ypb zQZ~dz+5Whrkstdw%Y}xY%tj#CgSONr{RwmjABU16gw-YU`ojEp)nLR7o=Che{|@u! zaC~rwaXZP^uRjaQIKkjyl`(OA5F`t&P&}&N|VR~f!;`5F~QN0IX29goNM@TcfhlOw>JIGPt{nBNR65BVP@1S%% zUj2{&4>yb5IDbqVN!Q$?dM=)SyFyZN>`DoLs)ivsS~$V~{cp%JPKtR9B9-fMvp*I+ z3c_V6ktz*bq8~{;^3+J0AMM!rpJBJYyW?2W%G#|WnMP%oUDDfZ#rNQ-|4R6DTb${T zXgRV$?Pn!wn)y^Ur6lWI-Gc?Tkwh>CYk5~#ryIfG`Heieoz)W;0RS5Wm{nY8OYG-Q zjLDLDkTR4>!M&Nb|GY`Dm4jvrf>mmsynTkH=^x~paj@h3^aUyk0U=+0DfSrbur@}R z>e(896#iHpiBgCM#a?xxpE+gnwZXP@>WS-SMH=xD3}qB%O|UDHj`j#*&q0xZ2&3@{ z(roPT{ED>?O=94PcD4FaT2*7bsJ|i+DC3_!KiJk*1Cft?RLBp!a zXu^8778{JThFM#WBu9`36l0|svGq0hGox6m*}YVasg#?gUeSk`xwP_Zh$b|mgX5gH z()!7jp5E~&viC`qTmy}N8bt#LiJoD6^Pb$6>-I{M)vvcj#X|AOX^hjL}BZH2faOc5>EvM>%u5bj`+@-#og8gdo8;r!G z9NS1C!|GFP#yu@g7YD{fT36dL*O-l{JgwGm(eH?I8EuEq#btQh{rqu6E^e1H* zM_V)INVR7={xFpjP@Q4G7Wo*ga~l4wT~_xSrNej2^v4yxqnLzskuL{{jg=ELsUGV~ z%D(7lGXDB7K(Le7)T%=kk-;;RW;HWYm}PpG{iQFfPF?z_n>T~aVsozR-%E2mpi2p2 z%&BTyd}yK-lr_Qom3GyUU`2qYa`#P#0|lZMQ~N(#95Q?S;5to4LR3h4Iwb5c?>OQ~ zI|<5|9m%eYssw0*G(xtIeuj%YWI%PGTlj3GM8KDC!`gF2e`_T~@;F%q{n>98Gj_*E zqY@Gj3_Rc&d`DR^kPs(wy4@rQ10s-ydBAQ<`=30CMmK^2s^;GgT0Tss_~YUkt)jC&Ib7L3Y*#zJArZ>rPL>)5)`@7OLgNFg)KYul~<+N==wT8IKfQPw@SZ#$tP zUg16LJi-GXsiIH_Rf8lcTr+={qf1tOnuMy^vv|Tp?!gHr@^o^H3<3s7ij-(oMkZhH za%d2LmvAD$RIE1AQIH~k88C`v1Y^8&r2h2Rk1m{1?zqAIHO0UG_~^O1b|uXiVQ4W( zKciez9{Rp8vmh)IN0%63U&}l~TZoRrr|^OOv(o&xh?}^wBea|r=9y5s#7gq@7j-Sq zI?)zgB6N4@go0WE-uKl7+RmZq&nBJj&{LAyVpw`DjJukQ!0d}sj?6e3 zWQKPra0pt3`ic8ubPa13H}C*96U;#`L5FYTsW7mQ*_ZH?5z^d zw}y^ig|`RCXt*SYsY^TakIsUz%V-y}fGctCjCNTKwjuS2+He z+CN13O<3l?l(~Jf%h^m33hvR(`6rDy8jcQtifF>W5Iz=#2B+CdCZ&Y;;|^jOAy(J zz^0QVA>hj^#Tv`|kv8>i0aU8C5*j2dvYKr)M&2suRq|EX1gNyZ+(F!- z+VRgm7>;kMkWo*9J|nmi=u3JOob^fMzKs1gnJdvK1g!)w_rN02C=1%iY+{a{)*0}I ziFM+v+mV;Q1Qfe5ZlMwd1MLfB5=2Tz=T%?N6OEsxRVai9Y6D|-Z8FuGqTDa+sECQ% z8UdLWR@VJf>_}r+w;)YTX`C_6%P}~K=;^h;`H>t!srdZ3Mt0nsuYegj#V{^h1GCQE zjLAX?GrP-!sFzrTKR{&uP{aIfyr;#0>CY0UEfc)_Qn>1mDz&r{x-V>|6o8`qP_1F_ z%pmY#?$Zvaq#ni;py3ar9>(oPWjV{WR0y)qXS-iXBaKU!tyd%jFh15ure9J2$+Vk_ zxfwr?8Z>%Heo~Cp>bb6U&W0%^V#99>t!=Elisr<&$j z_7`Fts@J}XKaAcFgwLwsrLG~3%>?$J57mH|dV+WGjJW4)+r`WJAJW7++6xpkvDyhm z!Na{vbdx!<1Z59YCs=BfB|vhr_BQ{%0@B;XlbC{;as7BlcAU0{fA%F`@rIq*x{ucd z+6aF;M_M_;Lo1^SEmH)43_3&vWyH}ELP_gy(x}bf`&fPyW7Y4VkoBessu|W`2N?*@bM|hG>X|WB7P(;T3=RhBLE*D+ktO z?nd;!VdZXS=W<6~Iv38{#pyUl9xUpMo5hr&T4q!vZO1_0btgJ!oD9LxdO~(|#s! z7l=$Y_c=me4-NA!-HFJ51M3WzI_{IYc!PT!_;a2*e`l-nTVsil%fH(;%(lf`C1^?M49g=cv40Qx6 z3*!}i2=sHNx~^*hqj%2sYmm;c!Q{m|w_aX+Lr03IDds0hzJ>?A zTq$@`K+fuQyiqCFg( z$=O@-dv}N*Zc3DJVFeRJR!3pgASM*-jsPi&theGKBe zPKc|S*~;S0?qdHc56_>`X4+&$b2BqDJKNh*4mg^Frb+o^%S#o02nwr7wcV936TC%- z5brJ{Gi=ni3#@eut@@^e&S%T67q1p?WxE%D87m<03MX6a>zy3L+=rRDpJ&LJhszu# sj85Bxe^wYZnY3&~e`fywgSURSG?qpxj`u%kv_asIlU9b8OBwn69|`sGJ^%m! diff --git a/tests/reference/output_2-90_buffer_1-FLIPPED_270-00.png b/tests/reference/output_2-90_buffer_1-FLIPPED_270-00.png deleted file mode 100644 index 6ade0281ebb056d1db7095c9feba961d0796e85b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14394 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 diff --git a/tests/reference/output_2-90_buffer_1-FLIPPED_90-00.png b/tests/reference/output_2-90_buffer_1-FLIPPED_90-00.png deleted file mode 100644 index f40570dc51ff377260e279da8d4a3e1651a80cd3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14446 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 diff --git a/tests/reference/output_2-90_buffer_2-FLIPPED-00.png b/tests/reference/output_2-90_buffer_2-FLIPPED-00.png deleted file mode 100644 index 312448901a19ce9ff4c9c8c80cb87a1053a735fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5258 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 diff --git a/tests/reference/output_2-90_buffer_2-NORMAL-00.png b/tests/reference/output_2-90_buffer_2-NORMAL-00.png deleted file mode 100644 index 53fdb7b5e6b81927fbf8248f229a97f667a8e54e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5288 zcmcgwXH-*Lw@m_w7o;di5k!&#HRZ11V2w&$8#0gmLH=%-& zutpeq84o>ZPj8sHxc=j-Tsja(T7UTGUfs@eJ@QVG6;RH-8=x0`E@>loU|;~i$RPH z9ZmE@(lN3EVzu^_Kfrd#*EH%lK_ZxNq1WBkw`>+VZ27GOwA-J=DBg7?rBWuZYjr&~{2o-#D!F%YD*Z8acW$*SI#SkWhX>p*yBfKl)>lsG78+(m^6kseKbHg{ z?#|R4NKU)ZssCIauSb544OsE9ww$oWlP>h)7`4cZ?V?b-$!tB`B zpj*}`Uu#n*_jo@E? zn%=$vKPK$(3%N&gVTg*O6SQFfami9L49)%QmcEPRTmyo$b~W|Xv) ze8)}}->XfS&}-=7MPd54svBu=e$YfUiZ_pHMX~+9+08FYf>mIZCIX+4t@}jLq|dv$ zj>KS8{HkYvK4sCbclp=1Tn~6^=HW)T{0>=*3pn?<9ht@wfmABicelbEG%Tbzz1#&{nWLN%6#MOh7 zap9C6fSLte!%QP_u{p8zf?vlr zB(1JLztl~XX#MV9Z+)*!|9TLK7*C-{KpDCRzxHhNfZ1s@`WxN-tfiPaY2FtD%qSfE_QDDb$-jsweO{^LV(6&aA}SesLo4e5a22keZR^0MFEa6_93 z&Cw1~fUw78^%K5CbqcOcu}ZJ(Ytf|U{zhJpO>`+3 zlHzGnzv69b)bwvf&}IOo(ip%7PO0B=qpU9)pad5M$zzx9wqSB{K0%7CweWZLA#$mmrk5sf(zvy`deL%E<39R|AF`8b;uTZiKOJl;1)9 z4rFQ5*2KtewC>`1|5unzTe-sQr@kO=qGLg4p{jFmmUM2Qa{)&)YDJMQ1)lJSQt3b9 z2Tv_jUXnP#kg(ueCy$#a8X7BtgvNeGG?xHAC#t{MxXl!fO~6%0%)F$vdyFu&ZpI1m zr4=sM2##o<))xN05h3Za{bzo}N~C_J8}H^^8pU~tEWaVqXAt1+<#h=PHqTT|BA-1I z?1=*my*Rg6l@GoONr|Wh-}-udEXJ0uxU@8;n-o`If=VcH=N3G`$lcXD(wHGJXn;2Y z7604Tw>Gc6BvI7#oP{lO`T@RH_2tT$<0>u=0fI?AN=T~4Zhm}36~{f#91$ui_jr%g zkV<2G36MMn#R0pLr57F)6+Wv9Sh5*=pLM_YE4DM`{|3`tJhHml%9a@PxsWD

5w)5#rSQuKu=!?#^J`r-CCf@NumWR824}enV9m2 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 A7XSbN diff --git a/tests/reference/output_2-90_buffer_3-FLIPPED_90-00.png b/tests/reference/output_2-90_buffer_3-FLIPPED_90-00.png deleted file mode 100644 index fe5507dc639806e744e1b37bcca19dcc52645c80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5167 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

5w)5#rSQuKu=!?#^J`r-CCf@NumWR824}enV9m2 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 A7XSbN diff --git a/tests/reference/output_2-FLIPPED_buffer_1-NORMAL-00.png b/tests/reference/output_2-FLIPPED_buffer_1-NORMAL-00.png deleted file mode 100644 index 8c456b51434b95d4d02a7a4bceb25ed31c5b8284..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14407 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 diff --git a/tests/reference/output_2-FLIPPED_buffer_2-90-00.png b/tests/reference/output_2-FLIPPED_buffer_2-90-00.png deleted file mode 100644 index 58206d1eac41b1849c2fe9037a8fcd43afb71197..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5360 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 OfYxn2wNe%9r~d`06IdAl diff --git a/tests/reference/output_2-NORMAL_buffer_1-NORMAL-00.png b/tests/reference/output_2-NORMAL_buffer_1-NORMAL-00.png deleted file mode 100644 index b1d2a3774b94ca3b1e7604ae5753e614d319ff95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14482 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 I@#6LW06h`QH2?qr diff --git a/tests/reference/output_2-NORMAL_buffer_2-90-00.png b/tests/reference/output_2-NORMAL_buffer_2-90-00.png deleted file mode 100644 index 26b5789d2002a7df71bcd38120ba22327808fd41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5313 zcmc&&dpOho-`)WuIaG=ezR95yLgcI>IV(9QLR8Mp*_d@86h#g*Os65{SlD9C_Zz~p z@O4;*73SEc$$5CT_&v|_&-3?l?Yg#IpXbp`+eW9eNyag&4u6>@NXEkt*)`rJ0PkBH!`iJfMUhi$i zJ#K$L?km}@U0-4$U^81L5P3Qs>mtgp1Kf8}^g(cWf6>U+uh`a$^SW*paq%%m@LO7O z-3$V=##5#cI=UDa-^YmO*>^l)3#MaYs&V1I>! zTR_NfpVck(G?(-L|4ULbVew(((BZ|U{>-pG=itt$1Ew7+-f2w=b>A1)Si^wBFQW~L zn2Mh_p8xMwSg>vt%EFpd@Ml`dW>@yf!)&b%83j&{&q;^eoSk!9>>M=21$Qs#4UIBt zB9^OfY%-8d8Djsr>;`2Nv=1bqLKsoxiTKwp%1918zJ&CvQy+ktj+PvZl#x~PRwquZ zH?eBGM280Z`ZhZ6c~!5hlomZYabkCYV8a@WIQvX~qlo}p4fPs}V=fVDy;m1T$J}4_ z6uh(5xWH*gK%uVu>w_xa3FuK9$ufVIZy|{N`Mmsoc-qK$?m4S84GM=i*pybD)Wi^vN z=`QGdco0|eQHX6zJi4h4D-?~#KKZmc)tR}UR+uhKb575iyV&*g_#bc>=JPB&g9dmv z6wN-iIKv30%?OGomgwsnE(h|`4wL^IMTYZ1K#*z`*#3%;Xi)RIrI*;#vmsyJ+ZE_# z=Q6X|S2Q@{>zF?wBM9Dli!-SR3PEG(5%Tsp(NH%yY$^MOU}EEe?gUMbCo3BWwRC9# z(%TB2w_wC5Hdh+5YGV#rn0_coM7pS`{d{hHd1tfElxuvJb4I-34>Q2Or6K$SD+C#+ zULq?D4a9e~uMir-Kf&ZAx8f_MgovdkfYt5?0>|iX4Z6ajm)xLry-diVtIlAZ+-x8^ z=ng2M9H#2F!>~t$ClzuveZxvFdQMf25e_7n=$aVtg=@u&mr6A3DJ}g_veFz@a$wqE z`+T>%(88slQz_O)lo25+d{(Slt&C3ZJ)o4x%#anaK5>e7E8$;d8Z#fY_|?0EXNQ-{ zm*NC)_-YrTS858B0M`}$c?X0ie?0$VPo(?Cx#HK$c0g}o+BK@kr$W!EHjjOxpDL}2mgNExFceQ`EPbZEF3?pD{P9|KBHqZ>l|_A;TIi) zDUa=tIQ@kfxy#9>skxaPhp_Y$Y_(|)K#NS<;u~Pt9tG!Z$kzj;R@+_K_F824O+fK> z@u%g{S%j16;D2+T17uZBf=44=lVc{I=qzs~)PK-d!e#~T67lvO{lek30!D%HHJE`k z-<@P3Fx}A+Xm>q}WL%^UY{WH5Pp7ma{N3H|QaN|o1)-818%98>Un0MM2k^P@*M9%* z!Tc*ud$VE-Ft+i% z%7vfDd{k)l{y)Hv#uGL0_K5NSyoeErJETj5#q>6Dlz@(hF+BP*v~lDk8gol=t_$BYb#UxYQ$aktv+b z3{FW5B!fKr%=BRQB?DnCB`io+LxRW1AW%ZR`6Xy5Y+Y;N9t(GCr-R5zk*lDe{sg;U zzO`Z7(>=#D)&h@#V7RgrCqHY(dP>}&Ej@J#bUnhjVtpzb*{*(v(*w=Ju3i2Z4Y1-@Z+@H zw~@UM$V^~?Fv*d^9cJNCG!kPfi$<1j!ph_}cC#-rn!GQ=@WQwe@7W&X3CxY7$9F3^_)|}SCz;HLAHF_a_a4U5 zNi@<%oJj1{H7afgK59A)xz*)^I(Bl01ozTl zD?sUnuc$Ig48VR-ULMqTG)xZG3CxF6!CE2xcn59!Kb{_#T}I|ZJnOQwI2Jm18xra^ zZbF6F-@PxQjFte*>f`_%8zRBx#v@rJPF8+9&R73wT5Zk3m(?1-kM;?}FB+r2wDrOj zu;eMSL@586Cj6CsYj&yN&BCsge45Vwi`Kcl1TfDrTOS!aw)|aTpI2MCk=Jjf3nq)N zz6WYM{KqX6I+b`6$35pHKgE3+p|7iD`#0KTx^u;Mt)!#L?~A0@_rD(!pC5Nf#N+jf zF&Mtm(o(>^7BA+Op`oFPzrRxG8tY~;4tMx$8GDbcia<9i8hZato8|;VNlZTl+APBHupq)Tfj_*of zAruK&(K*|~;VUX!PO7z$UtE9P7ct@|73zyGtkuTZ-s&A?YrzGj&xQhq-Uo(piC()f-adPly(qkpvsd>th(xtvY0VT zI|TG67x6|ir~-jw1jGAdy(~+y#u?czM6CcqYs_)GUzGM>oVez{WB?6w;K=3Wbke(?erz45a=14Pb0BbayEhfh6zJ)D-~9t_0T z7Fv2QyXcWTg@)^cz!)18U&Zz2Sd*YgXhJgholYd+b-YAx48d}T3bO)|8e#Qlbs(iy zYbE32E8^DX24V^gmx*ToYVU(pb%a6-*=|zWfdHop7a%oes?fwDWe{-&el9P@r@z0S z$eQfx3ruN9M^9h>wPeLTgJbXFWIMXVb&MJcTSg%=w-i6F&z2K?G7R~e*sQMmt8Kqe zxz_RwHHOKn!?)O4WAFXRGD$h)hOMS>QD*GQ_{jaOi}ErVV9xX``D5zySFW@#`%aPQ z-y-yof!%k}(LV&dx^hlYf?d?I*z~CZtUKa;z+ms#!H_=rz7H)PW0iQrCAcM8mgpI| zq$XFQxz-e0+M)80JGrL3wD8kx@1oi*FL&!RpXTufv2;C2o;-epT-<&mDBU_^(}pQ5 zYeXVWB_EOK`Cik1wWBhwm`B3sBhT68GdF94xcXAgFQ@o9)vYwm>5N8$8^_c2lc@N> zn9Zd@gf5gGJr8A2b-k#KVXk-n8Nvd+{xv*D!wx6ADY!e=2_|y|W)|8V*sa~HNhweg zzpE&PdFVILFuYbbQ#KQ!T?7x_ih|_*epve$UOVw;sNpRnb@XFL^5*)HHq~@8CZU-z zj9i2DeY=n>{5@ft^81@Kf;1qKwt1-7Cc>=Z701g=&neZFVQ1o7he^LWnVS;`%c8=1 z&bfNNk#`*>uiogZz0`a@3uZF-?5t?q%IrYWhj27Sn=4+iTN)^QAExq+)S}8&)hQ4E zoZD;ncm+e@gidK7RIArj*5@~$v3g|CaeJfF0H*Vily<=1CDJr%)Ca_3M#=ST%biJ# zH=pF^q1;e+(?Ro-0s7j!wHkFcD^K1lJt<0KFkwLCUpP*B8Yc!(-8>4SMn~~CHW`EV zqMShNj@&L_`wYm9PzJ02gBEuu>_r_+x^sO%(nQkC!WdC=V6u44H)08|x_S z&m7X$Q;|ki`Bs%}GU`WS)iL8aAIoK~XI~EOFu2(%mVxCo?~WYBDb%ezOQ~;5l%lU9 z(n6vq7eA@-AU>~!4~FJ=b3@0pMWHS#{&tT6sH87MU(Dtj=ZtLc{#We2ca$ z`IXW80haANi4NP;Y{Vg}zqo_xOIjulgK6_AcQvLsjW#bDY4~rQ0zKXyw(rFDFWxh0 z>;T#Z&hg}LTh!GC{ihEzvxl62h)GFW#_ibjLHoUvntf%m^bvigJap1Lj5Z>>gq3Bv znZ<2;syJ~Yi*plQiM_FbXAYJGO>&oIdBe$)Uf#01Xx>!53Z9R}>~sOOvl9{B^M>H5 z7ciibinWYf=k${&KYsBHkvkux+_R#0ciU34V%i=bKIU}kxAyah7q!g;&!zhGb?BcP zFAJ&f_O-_Glt%J}BBS`&U{sdFXtSny+mkzqDcpm@n@gfwE^#u#A-H)m$NlcNd@74k zxN-*84>+(}RK^i~J`zetZE~Q@p@d$qtIMez>Ps!<%iE==i5pgg4Tcr2|KFBx)i=PA zj$~~^Rwe6Z`eUj8+2>2DkbdMmF}}6z;7KPhXAP7LPB)4n%?NOncnehgb5gEpNI^to zJ{cmJO^dc(9hnMZ+b6zY#4+ZKh_Ey-O9LmY8$O{q7K+q<$*O-L+HQ zoG2|6^2aQLqF2V_$u)1HcYhi<|LwhCo_>^SS$Dw`jw6EfqZqHF@7zZsuVW^INt9Y$ z&`c?QWX||zF%`meo(TK>ayaTbLXKRPC^pr zsy+%+!`hGN296N(4&@KvPixlLRB5(e-M+I9frQqBsPCG^qUUZpPQBYP#iyhs-1X+5 zVQp$vAQ`Ed{K+j0>QQsQ(f~R3xNL6#cQ%EYfg82!s=K zNpVN^|MPn6#f9WVrK0G2>@Omk&?eHRFEe1UtDuk**)c{G1Hnl{rXHopFz(Xt;Lu=W7kW*< z^DOw@dm}lQpAgmg%UZ)1x%xh8<>$kZE-_7wa0$K^nD;=d-(ZiL_vEXdBIZ)vW zHdv+I|Ltt1B4sU!`j*de>h&`hO!>^Y^MGI$Mcv|7LVosXPEtciMTRKEwO_Vq+?Ak# nhZ+I)uN|0f#~iq+x(auKodAY|Ung*$Ndhd)Y)vcAd)@ml(FjI= diff --git a/tests/reference/output_3-FLIPPED_270_buffer_1-NORMAL-00.png b/tests/reference/output_3-FLIPPED_270_buffer_1-NORMAL-00.png deleted file mode 100644 index 878307d7e01de9104ed1faf2efef19703fb31cfd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12107 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

p3eGp`f^+w8~dOhmwz+!}gF$pcy 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 diff --git a/tests/reference/output_3-NORMAL_buffer_2-90-00.png b/tests/reference/output_3-NORMAL_buffer_2-90-00.png deleted file mode 100644 index b1133ef66834d5b75086273e8172d058a00711e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11702 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 bZlZRDxsLyw_xfQkVgqzE41x7(cCr5p=p_>5 diff --git a/tests/reference/subsurface_z_order-00.png b/tests/reference/subsurface_z_order-00.png deleted file mode 100644 index 581fef61ef57236d4399186fde708ee29f901d48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKQJ-_N$!f>gTe~DWM4f;<_cq diff --git a/tests/reference/subsurface_z_order-01.png b/tests/reference/subsurface_z_order-01.png deleted file mode 100644 index 0a591811b7de92edc55ed0f34d36fd0f7482b5a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKQJ)^Ngn6WA3%yTz$e62Be3c}kj?Oak;F%K zAnoJn;uuoF`1XvWAcFt{gMsSQ5TQMc{@Yn2et--UNN;g^=&d2bb-5=1NVYmHM5Q(e hbMbSuIw^vTWDrqjS)O+1*~C{Mj;E`i%Q~loCIHZxCsY6c diff --git a/tests/reference/subsurface_z_order-02.png b/tests/reference/subsurface_z_order-02.png deleted file mode 100644 index 737e2f9ccb3e5452a4ab2fd402e5165d8042c4f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKQJ)^Ngn6WA0R5gC&X1Fu+Z$!6vS5kQJRz$e62Be3c}kj?NP2pRtW zf7EdLH;^~g)5S5Qg7NLSPKJg65rz#SK^}^&&(DiEe&7G!Z9}UONCyxk)cOVhfdRWC z7#w`NwX(Sh1b$~XHT~z{;^I2CG!P;J7AA@K{>TzyEW|E`2Mh-8ciLoL)_Z^iJYD@< J);T3K0RT(dRFVJy diff --git a/tests/reference/subsurface_z_order-04.png b/tests/reference/subsurface_z_order-04.png deleted file mode 100644 index 1c415d59c8be04e5fc038a8eb7077444557dd6db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 237 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKd>+Z$!6vS5kQJRz$e62Be3c}kj?NP2pRtW zf7EdLH;^~e)5S5Qg7NJ+L&gRM4h945h=W{V3+mPE_4pqgIIMfHPZp#H2oBs-(*S}W zsR0o1!RtRS7YN+*EO0CE0}2|%afoq%h42>!lo?z#HK^!lg}M0I`kX0Q0=;^Jfe$ R7w3ZnJYD@<);T3K0RWzeIwJr8 diff --git a/tests/reference/viewport_upscale_solid-00.png b/tests/reference/viewport_upscale_solid-00.png deleted file mode 100644 index 71f438e79b8d6e1d5a8b89013516a6173a4a7b81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 829 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX5Ps$$$P@Hb9Ck$=lt9;Xep2*t>i(0|V11 zPZ!6KiaBrZY~*YS5O8qx*Kt$M-dU#e;Fs~PvNsQvUo_9GNt}E%bFgnj@Q1m$Q*~61z7~+SUMIeqiu)^>bP0l+XkK&mQXW diff --git a/tests/roles-test.c b/tests/roles-test.c deleted file mode 100644 index fbce053..0000000 --- a/tests/roles-test.c +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright © 2014 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include - -#include "weston-test-client-helper.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - setup.logging_scopes = "log,proto,test-harness-plugin"; - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -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 struct wl_shell * -get_wl_shell(struct client *client) -{ - struct global *g; - struct global *global = NULL; - struct wl_shell *shell; - - wl_list_for_each(g, &client->global_list, link) { - if (strcmp(g->interface, "wl_shell")) - continue; - - if (global) - assert(0 && "multiple wl_shell objects"); - - global = g; - } - - assert(global && "no wl_shell found"); - - shell = wl_registry_bind(client->wl_registry, global->name, - &wl_shell_interface, 1); - assert(shell); - - return shell; -} - -TEST(test_role_conflict_sub_wlshell) -{ - struct client *client; - struct wl_subcompositor *subco; - struct wl_surface *child; - struct wl_subsurface *sub; - struct wl_shell *shell; - struct wl_shell_surface *shsurf; - - client = create_client_and_test_surface(100, 50, 123, 77); - assert(client); - - subco = get_subcompositor(client); - shell = get_wl_shell(client); - - child = wl_compositor_create_surface(client->wl_compositor); - assert(child); - sub = wl_subcompositor_get_subsurface(subco, child, - client->surface->wl_surface); - assert(sub); - - shsurf = wl_shell_get_shell_surface(shell, child); - assert(shsurf); - - expect_protocol_error(client, &wl_shell_interface, - WL_SHELL_ERROR_ROLE); -} - -TEST(test_role_conflict_wlshell_sub) -{ - struct client *client; - struct wl_subcompositor *subco; - struct wl_surface *child; - struct wl_subsurface *sub; - struct wl_shell *shell; - struct wl_shell_surface *shsurf; - - client = create_client_and_test_surface(100, 50, 123, 77); - assert(client); - - subco = get_subcompositor(client); - shell = get_wl_shell(client); - - child = wl_compositor_create_surface(client->wl_compositor); - assert(child); - shsurf = wl_shell_get_shell_surface(shell, child); - assert(shsurf); - - sub = wl_subcompositor_get_subsurface(subco, child, - client->surface->wl_surface); - assert(sub); - - expect_protocol_error(client, &wl_subcompositor_interface, - WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE); -} diff --git a/tests/setbacklight.c b/tests/setbacklight.c deleted file mode 100644 index 3bfc7d1..0000000 --- a/tests/setbacklight.c +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * 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 "config.h" - -#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/string-test.c b/tests/string-test.c deleted file mode 100644 index 5571b52..0000000 --- a/tests/string-test.c +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright © 2016 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include - -#include "shared/string-helpers.h" - -#include "weston-test-client-helper.h" - -TEST(strtol_conversions) -{ - bool ret; - int32_t val = -1; - char *str = NULL; - - str = ""; val = -1; - ret = safe_strtoint(str, &val); - assert(ret == false); - assert(val == -1); - - str = "."; val = -1; - ret = safe_strtoint(str, &val); - assert(ret == false); - assert(val == -1); - - str = "42"; val = -1; - ret = safe_strtoint(str, &val); - assert(ret == true); - assert(val == 42); - - str = "-42"; val = -1; - ret = safe_strtoint(str, &val); - assert(ret == true); - assert(val == -42); - - str = "0042"; val = -1; - ret = safe_strtoint(str, &val); - assert(ret == true); - assert(val == 42); - - str = "x42"; val = -1; - ret = safe_strtoint(str, &val); - assert(ret == false); - assert(val == -1); - - str = "42x"; val = -1; - ret = safe_strtoint(str, &val); - assert(ret == false); - assert(val == -1); - - str = "0x42424242"; val = -1; - ret = safe_strtoint(str, &val); - assert(ret == false); - assert(val == -1); - - str = "424748364789L"; val = -1; - ret = safe_strtoint(str, &val); - assert(ret == false); - assert(val == -1); -} diff --git a/tests/subsurface-shot-test.c b/tests/subsurface-shot-test.c deleted file mode 100644 index 4df9d3d..0000000 --- a/tests/subsurface-shot-test.c +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * Copyright © 2016 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include - -#include "weston-test-client-helper.h" -#include "weston-test-fixture-compositor.h" - -static const enum renderer_type renderers[] = { - RENDERER_PIXMAN, - RENDERER_GL, -}; - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness, const enum renderer_type *arg) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - setup.renderer = *arg; - setup.width = 320; - setup.height = 240; - setup.shell = SHELL_TEST_DESKTOP; - setup.logging_scopes = "log,test-harness-plugin"; - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, renderers); - -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 int -check_screen(struct client *client, - const char *ref_image, - int ref_seq_no, - const struct rectangle *clip, - int seq_no) -{ - bool match; - - match = verify_screen_content(client, ref_image, ref_seq_no, clip, - seq_no); - - return match ? 0 : -1; -} - -static struct buffer * -surface_commit_color(struct client *client, struct wl_surface *surface, - pixman_color_t *color, int width, int height) -{ - struct buffer *buf; - - buf = create_shm_buffer_a8r8g8b8(client, width, height); - fill_image_with_color(buf->image, color); - wl_surface_attach(surface, buf->proxy, 0, 0); - wl_surface_damage(surface, 0, 0, width, height); - wl_surface_commit(surface); - - return buf; -} - -TEST(subsurface_z_order) -{ - struct client *client; - struct wl_subcompositor *subco; - struct buffer *bufs[5] = { 0 }; - struct wl_surface *surf[5] = { 0 }; - struct wl_subsurface *sub[5] = { 0 }; - struct rectangle clip = { 40, 40, 280, 200 }; - int fail = 0; - unsigned i; - pixman_color_t red; - pixman_color_t blue; - pixman_color_t cyan; - pixman_color_t green; - - color_rgb888(&red, 255, 0, 0); - color_rgb888(&blue, 0, 0, 255); - color_rgb888(&cyan, 0, 255, 255); - color_rgb888(&green, 0, 255, 0); - - client = create_client_and_test_surface(100, 50, 100, 100); - assert(client); - subco = get_subcompositor(client); - - /* move the pointer clearly away from our screenshooting area */ - weston_test_move_pointer(client->test->weston_test, 0, 1, 0, 2, 30); - - /* make the parent surface red */ - surf[0] = client->surface->wl_surface; - bufs[0] = surface_commit_color(client, surf[0], &red, 100, 100); - /* sub[0] is not used */ - - fail += check_screen(client, "subsurface_z_order", 0, &clip, 0); - - /* create a blue sub-surface above red */ - surf[1] = wl_compositor_create_surface(client->wl_compositor); - sub[1] = wl_subcompositor_get_subsurface(subco, surf[1], surf[0]); - bufs[1] = surface_commit_color(client, surf[1], &blue, 100, 100); - - wl_subsurface_set_position(sub[1], 20, 20); - wl_surface_commit(surf[0]); - - fail += check_screen(client, "subsurface_z_order", 1, &clip, 1); - - /* create a cyan sub-surface above blue */ - surf[2] = wl_compositor_create_surface(client->wl_compositor); - sub[2] = wl_subcompositor_get_subsurface(subco, surf[2], surf[1]); - bufs[2] = surface_commit_color(client, surf[2], &cyan, 100, 100); - - wl_subsurface_set_position(sub[2], 20, 20); - wl_surface_commit(surf[1]); - wl_surface_commit(surf[0]); - - fail += check_screen(client, "subsurface_z_order", 2, &clip, 2); - - /* create a green sub-surface above blue, sibling to cyan */ - surf[3] = wl_compositor_create_surface(client->wl_compositor); - sub[3] = wl_subcompositor_get_subsurface(subco, surf[3], surf[1]); - bufs[3] = surface_commit_color(client, surf[3], &green, 100, 100); - - wl_subsurface_set_position(sub[3], -40, 10); - wl_surface_commit(surf[1]); - wl_surface_commit(surf[0]); - - fail += check_screen(client, "subsurface_z_order", 3, &clip, 3); - - /* stack blue below red, which brings also cyan and green below red */ - wl_subsurface_place_below(sub[1], surf[0]); - wl_surface_commit(surf[0]); - - fail += check_screen(client, "subsurface_z_order", 4, &clip, 4); - - assert(fail == 0); - - for (i = 0; i < ARRAY_LENGTH(sub); i++) - if (sub[i]) - wl_subsurface_destroy(sub[i]); - - for (i = 0; i < ARRAY_LENGTH(surf); i++) - if (surf[i]) - wl_surface_destroy(surf[i]); - - for (i = 0; i < ARRAY_LENGTH(bufs); i++) - if (bufs[i]) - buffer_destroy(bufs[i]); -} diff --git a/tests/subsurface-test.c b/tests/subsurface-test.c deleted file mode 100644 index 1f9c90c..0000000 --- a/tests/subsurface-test.c +++ /dev/null @@ -1,768 +0,0 @@ -/* - * Copyright © 2012 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include "weston-test-client-helper.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -#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 = create_client_and_test_surface(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 = create_client_and_test_surface(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 = create_client_and_test_surface(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); -} - -TEST(test_subsurface_paradox) -{ - struct client *client; - struct wl_surface *parent; - struct wl_subcompositor *subco; - - client = create_client_and_test_surface(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); - - expect_protocol_error(client, &wl_subcompositor_interface, - WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE); -} - -TEST(test_subsurface_identical_link) -{ - struct client *client; - struct compound_surface com; - - client = create_client_and_test_surface(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); - - expect_protocol_error(client, &wl_subcompositor_interface, - WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE); -} - -TEST(test_subsurface_change_link) -{ - struct client *client; - struct compound_surface com; - struct wl_surface *stranger; - - client = create_client_and_test_surface(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); - - expect_protocol_error(client, &wl_subcompositor_interface, - WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE); -} - -TEST(test_subsurface_nesting) -{ - struct client *client; - struct compound_surface com; - struct wl_surface *stranger; - - client = create_client_and_test_surface(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 = create_client_and_test_surface(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); -} - -TEST(test_subsurface_loop_paradox) -{ - struct client *client; - struct wl_surface *surface[3]; - struct wl_subcompositor *subco; - - client = create_client_and_test_surface(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]); - - expect_protocol_error(client, &wl_subcompositor_interface, - WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE); -} - -TEST(test_subsurface_place_above_nested_parent) -{ - struct client *client; - struct compound_surface com; - struct wl_surface *grandchild; - struct wl_subcompositor *subco; - struct wl_subsurface *sub; - - client = create_client_and_test_surface(100, 50, 123, 77); - assert(client); - - populate_compound_surface(&com, client); - - subco = get_subcompositor(client); - grandchild = wl_compositor_create_surface(client->wl_compositor); - sub = wl_subcompositor_get_subsurface(subco, grandchild, com.child[0]); - - wl_subsurface_place_above(sub, com.child[0]); - - client_roundtrip(client); -} - -TEST(test_subsurface_place_above_grandparent) -{ - struct client *client; - struct compound_surface com; - struct wl_surface *grandchild; - struct wl_subsurface *sub; - struct wl_subcompositor *subco; - - client = create_client_and_test_surface(100, 50, 123, 77); - assert(client); - - populate_compound_surface(&com, client); - - subco = get_subcompositor(client); - grandchild = wl_compositor_create_surface(client->wl_compositor); - sub = wl_subcompositor_get_subsurface(subco, grandchild, com.child[0]); - - /* can't place a subsurface above its grandparent */ - wl_subsurface_place_above(sub, com.parent); - - expect_protocol_error(client, &wl_subsurface_interface, - WL_SUBSURFACE_ERROR_BAD_SURFACE); -} - -TEST(test_subsurface_place_above_great_aunt) -{ - struct client *client; - struct compound_surface com; - struct wl_surface *grandchild; - struct wl_subsurface *sub; - struct wl_subcompositor *subco; - - client = create_client_and_test_surface(100, 50, 123, 77); - assert(client); - - populate_compound_surface(&com, client); - - subco = get_subcompositor(client); - grandchild = wl_compositor_create_surface(client->wl_compositor); - sub = wl_subcompositor_get_subsurface(subco, grandchild, com.child[0]); - - /* can't place a subsurface above its parents' siblings */ - wl_subsurface_place_above(sub, com.child[1]); - - expect_protocol_error(client, &wl_subsurface_interface, - WL_SUBSURFACE_ERROR_BAD_SURFACE); -} - -TEST(test_subsurface_place_above_child) -{ - struct client *client; - struct compound_surface com; - struct wl_surface *grandchild; - struct wl_subcompositor *subco; - - client = create_client_and_test_surface(100, 50, 123, 77); - assert(client); - - populate_compound_surface(&com, client); - - subco = get_subcompositor(client); - grandchild = wl_compositor_create_surface(client->wl_compositor); - wl_subcompositor_get_subsurface(subco, grandchild, com.child[0]); - - /* can't place a subsurface above its own child subsurface */ - wl_subsurface_place_above(com.sub[0], grandchild); - - expect_protocol_error(client, &wl_subsurface_interface, - WL_SUBSURFACE_ERROR_BAD_SURFACE); -} - -TEST(test_subsurface_place_below_nested_parent) -{ - struct client *client; - struct compound_surface com; - struct wl_subcompositor *subco; - struct wl_surface *grandchild; - struct wl_subsurface *sub; - - client = create_client_and_test_surface(100, 50, 123, 77); - assert(client); - - populate_compound_surface(&com, client); - - subco = get_subcompositor(client); - grandchild = wl_compositor_create_surface(client->wl_compositor); - sub = wl_subcompositor_get_subsurface(subco, grandchild, com.child[0]); - - wl_subsurface_place_below(sub, com.child[0]); - - client_roundtrip(client); -} - -TEST(test_subsurface_place_below_grandparent) -{ - struct client *client; - struct compound_surface com; - struct wl_surface *grandchild; - struct wl_subsurface *sub; - struct wl_subcompositor *subco; - - client = create_client_and_test_surface(100, 50, 123, 77); - assert(client); - - populate_compound_surface(&com, client); - - subco = get_subcompositor(client); - grandchild = wl_compositor_create_surface(client->wl_compositor); - sub = wl_subcompositor_get_subsurface(subco, grandchild, com.child[0]); - - /* can't place a subsurface below its grandparent */ - wl_subsurface_place_below(sub, com.parent); - - expect_protocol_error(client, &wl_subsurface_interface, - WL_SUBSURFACE_ERROR_BAD_SURFACE); -} - -TEST(test_subsurface_place_below_great_aunt) -{ - struct client *client; - struct compound_surface com; - struct wl_surface *grandchild; - struct wl_subsurface *sub; - struct wl_subcompositor *subco; - - client = create_client_and_test_surface(100, 50, 123, 77); - assert(client); - - populate_compound_surface(&com, client); - - subco = get_subcompositor(client); - grandchild = wl_compositor_create_surface(client->wl_compositor); - sub = wl_subcompositor_get_subsurface(subco, grandchild, com.child[0]); - - /* can't place a subsurface below its parents' siblings */ - wl_subsurface_place_below(sub, com.child[1]); - - expect_protocol_error(client, &wl_subsurface_interface, - WL_SUBSURFACE_ERROR_BAD_SURFACE); -} - -TEST(test_subsurface_place_below_child) -{ - struct client *client; - struct compound_surface com; - struct wl_surface *grandchild; - struct wl_subcompositor *subco; - - client = create_client_and_test_surface(100, 50, 123, 77); - assert(client); - - populate_compound_surface(&com, client); - - subco = get_subcompositor(client); - grandchild = wl_compositor_create_surface(client->wl_compositor); - wl_subcompositor_get_subsurface(subco, grandchild, com.child[0]); - - /* can't place a subsurface below its own child subsurface */ - wl_subsurface_place_below(com.sub[0], grandchild); - - expect_protocol_error(client, &wl_subsurface_interface, - WL_SUBSURFACE_ERROR_BAD_SURFACE); -} - -TEST(test_subsurface_place_above_stranger) -{ - struct client *client; - struct compound_surface com; - struct wl_surface *stranger; - - client = create_client_and_test_surface(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); - - expect_protocol_error(client, &wl_subsurface_interface, - WL_SUBSURFACE_ERROR_BAD_SURFACE); -} - -TEST(test_subsurface_place_below_stranger) -{ - struct client *client; - struct compound_surface com; - struct wl_surface *stranger; - - client = create_client_and_test_surface(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); - - expect_protocol_error(client, &wl_subsurface_interface, - WL_SUBSURFACE_ERROR_BAD_SURFACE); -} - -TEST(test_subsurface_place_above_foreign) -{ - struct client *client; - struct compound_surface com1; - struct compound_surface com2; - - client = create_client_and_test_surface(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]); - - expect_protocol_error(client, &wl_subsurface_interface, - WL_SUBSURFACE_ERROR_BAD_SURFACE); -} - -TEST(test_subsurface_place_below_foreign) -{ - struct client *client; - struct compound_surface com1; - struct compound_surface com2; - - client = create_client_and_test_surface(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]); - - expect_protocol_error(client, &wl_subsurface_interface, - WL_SUBSURFACE_ERROR_BAD_SURFACE); -} - -TEST(test_subsurface_destroy_protocol) -{ - struct client *client; - struct compound_surface com; - - client = create_client_and_test_surface(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); - /* fallthrough */ - case 10: - SUB_LINK(9, 2); - /* fallthrough */ - case 9: - SUB_LINK(8, 6); - /* fallthrough */ - case 8: - SUB_LINK(7, 6); - /* fallthrough */ - case 7: - SUB_LINK(6, 2); - /* fallthrough */ - case 6: - SUB_LINK(5, 1); - /* fallthrough */ - case 5: - SUB_LINK(4, 3); - /* fallthrough */ - case 4: - SUB_LINK(3, 1); - /* fallthrough */ - case 3: - SUB_LINK(2, 0); - /* fallthrough */ - 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) { - testlog(" [sub %2d]", h); - wl_subsurface_destroy(subs[h]); - subs[h] = NULL; - } else { - testlog(" [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 = create_client_and_test_surface(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); - - testlog("permu"); - - for (i = 0; i < NSTEPS; i++) - testlog(" %2d", per.cnt[i]); - - for (i = 0; i < NSTEPS; i++) - destroy_permu_object(surfs, subs, per.cnt[i]); - - testlog("\n"); - client_roundtrip(client); - - destroy_subsurface_tree(surfs, subs, test_size); - counter++; - } - - client_roundtrip(client); - testlog("tried %d destroy permutations\n", counter); -} diff --git a/tests/surface-global-test.c b/tests/surface-global-test.c deleted file mode 100644 index cd9b784..0000000 --- a/tests/surface-global-test.c +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include -#include "libweston-internal.h" -#include "compositor/weston.h" -#include "weston-test-runner.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - - return weston_test_harness_execute_as_plugin(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -PLUGIN_TEST(surface_to_from_global) -{ - /* struct weston_compositor *compositor; */ - struct weston_surface *surface; - struct weston_view *view; - float x, y; - wl_fixed_t fx, fy; - int32_t ix, iy; - - surface = weston_surface_create(compositor); - assert(surface); - view = weston_view_create(surface); - assert(view); - surface->width = 50; - surface->height = 50; - weston_view_set_position(view, 5, 10); - weston_view_update_transform(view); - - weston_view_to_global_float(view, 33, 22, &x, &y); - assert(x == 38 && y == 32); - - weston_view_to_global_float(view, -8, -2, &x, &y); - assert(x == -3 && y == 8); - - weston_view_to_global_fixed(view, 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_view_from_global_float(view, 38, 32, &x, &y); - assert(x == 33 && y == 22); - - weston_view_from_global_float(view, 42, 5, &x, &y); - assert(x == 37 && y == -5); - - weston_view_from_global_fixed(view, 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_view_from_global(view, 0, 0, &ix, &iy); - assert(ix == -5 && iy == -10); - - weston_view_from_global(view, 5, 10, &ix, &iy); - assert(ix == 0 && iy == 0); -} diff --git a/tests/surface-screenshot-test.c b/tests/surface-screenshot-test.c deleted file mode 100644 index 3d93ac5..0000000 --- a/tests/surface-screenshot-test.c +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright © 2015 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include "compositor/weston.h" -#include "shared/file-util.h" -#include "libweston-internal.h" - -static char * -encode_PAM_comment_line(const char *comment) -{ - size_t len = strlen(comment); - char *str = malloc(len + 2); - char *dst = str; - const char *src = comment; - const char *end = src + len; - - *dst++ = '#'; - *dst++ = ' '; - for (; src < end; src++, dst++) { - if (*src == '\n' || !isprint(*src)) - *dst = '_'; - else - *dst = *src; - } - - return str; -} - -/* - * PAM image format: - * http://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format - * RGBA is in byte address order. - * - * ImageMagick 'convert' can convert a PAM image to a more common format. - * To view the image metadata: $ head -n7 image.pam - */ -static int -write_PAM_image_rgba(FILE *fp, int width, int height, - void *pixels, size_t size, const char *comment) -{ - char *str; - int ret; - - assert(size == (size_t)4 * width * height); - - ret = fprintf(fp, "P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 255\n" - "TUPLTYPE RGB_ALPHA\n", width, height); - if (ret < 0) - return -1; - - if (comment) { - str = encode_PAM_comment_line(comment); - ret = fprintf(fp, "%s\n", str); - free(str); - - if (ret < 0) - return -1; - } - - ret = fprintf(fp, "ENDHDR\n"); - if (ret < 0) - return -1; - - if (fwrite(pixels, 1, size, fp) != size) - return -1; - - if (ferror(fp)) - return -1; - - return 0; -} - -static uint32_t -unmult(uint32_t c, uint32_t a) -{ - return (c * 255 + a / 2) / a; -} - -static void -unpremultiply_and_swap_a8b8g8r8_to_PAMrgba(void *pixels, size_t size) -{ - uint32_t *p = pixels; - uint32_t *end; - - for (end = p + size / 4; p < end; p++) { - uint32_t v = *p; - uint32_t a; - - a = (v & 0xff000000) >> 24; - if (a == 0) { - *p = 0; - } else { - uint8_t *dst = (uint8_t *)p; - - dst[0] = unmult((v & 0x000000ff) >> 0, a); - dst[1] = unmult((v & 0x0000ff00) >> 8, a); - dst[2] = unmult((v & 0x00ff0000) >> 16, a); - dst[3] = a; - } - } -} - -static void -trigger_binding(struct weston_keyboard *keyboard, const struct timespec *time, - uint32_t key, void *data) -{ - const char *prefix = "surfaceshot-"; - const char *suffix = ".pam"; - char fname[1024]; - struct weston_surface *surface; - struct weston_seat *seat = keyboard->seat; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - int width, height; - char desc[512]; - void *pixels; - const size_t bytespp = 4; /* PIXMAN_a8b8g8r8 */ - size_t sz; - int ret; - FILE *fp; - - if (!pointer || !pointer->focus) - return; - - surface = pointer->focus->surface; - - weston_surface_get_content_size(surface, &width, &height); - - if (!surface->get_label || - surface->get_label(surface, desc, sizeof(desc)) < 0) - snprintf(desc, sizeof(desc), "(unknown)"); - - weston_log("surface screenshot of %p: '%s', %dx%d\n", - surface, desc, width, height); - - sz = width * bytespp * height; - if (sz == 0) { - weston_log("no content for %p\n", surface); - return; - } - - pixels = malloc(sz); - if (!pixels) { - weston_log("%s: failed to malloc %zu B\n", __func__, sz); - return; - } - - ret = weston_surface_copy_content(surface, pixels, sz, - 0, 0, width, height); - if (ret < 0) { - weston_log("shooting surface %p failed\n", surface); - goto out; - } - - unpremultiply_and_swap_a8b8g8r8_to_PAMrgba(pixels, sz); - - fp = file_create_dated(NULL, prefix, suffix, fname, sizeof(fname)); - if (!fp) { - const char *msg; - - switch (errno) { - case ETIME: - msg = "failure in datetime formatting"; - break; - default: - msg = strerror(errno); - } - - weston_log("Cannot open '%s*%s' for writing: %s\n", - prefix, suffix, msg); - goto out; - } - - ret = write_PAM_image_rgba(fp, width, height, pixels, sz, desc); - if (fclose(fp) != 0 || ret < 0) - weston_log("writing surface %p screenshot failed.\n", surface); - else - weston_log("successfully shot surface %p into '%s'\n", - surface, fname); - -out: - free(pixels); -} - -WL_EXPORT int -wet_module_init(struct weston_compositor *ec, - int *argc, char *argv[]) -{ - weston_compositor_add_debug_binding(ec, KEY_H, trigger_binding, ec); - - return 0; -} diff --git a/tests/surface-test.c b/tests/surface-test.c deleted file mode 100644 index fca74c9..0000000 --- a/tests/surface-test.c +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include -#include "compositor/weston.h" -#include "weston-test-runner.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - - return weston_test_harness_execute_as_plugin(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -PLUGIN_TEST(surface_transform) -{ - /* struct weston_compositor *compositor; */ - struct weston_surface *surface; - struct weston_view *view; - float x, y; - - surface = weston_surface_create(compositor); - assert(surface); - view = weston_view_create(surface); - assert(view); - surface->width = 200; - surface->height = 200; - weston_view_set_position(view, 100, 100); - weston_view_update_transform(view); - weston_view_to_global_float(view, 20, 20, &x, &y); - - fprintf(stderr, "20,20 maps to %f, %f\n", x, y); - assert(x == 120 && y == 120); - - weston_view_set_position(view, 150, 300); - weston_view_update_transform(view); - weston_view_to_global_float(view, 50, 40, &x, &y); - assert(x == 200 && y == 340); -} diff --git a/tests/text-test.c b/tests/text-test.c deleted file mode 100644 index 84988f3..0000000 --- a/tests/text-test.c +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include - -#include "weston-test-client-helper.h" -#include "text-input-unstable-v1-client-protocol.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -struct text_input_state { - int activated; - int deactivated; -}; - -static void -text_input_commit_string(void *data, - struct zwp_text_input_v1 *text_input, - uint32_t serial, - const char *text) -{ -} - -static void -text_input_preedit_string(void *data, - struct zwp_text_input_v1 *text_input, - uint32_t serial, - const char *text, - const char *commit) -{ -} - -static void -text_input_delete_surrounding_text(void *data, - struct zwp_text_input_v1 *text_input, - int32_t index, - uint32_t length) -{ -} - -static void -text_input_cursor_position(void *data, - struct zwp_text_input_v1 *text_input, - int32_t index, - int32_t anchor) -{ -} - -static void -text_input_preedit_styling(void *data, - struct zwp_text_input_v1 *text_input, - uint32_t index, - uint32_t length, - uint32_t style) -{ -} - -static void -text_input_preedit_cursor(void *data, - struct zwp_text_input_v1 *text_input, - int32_t index) -{ -} - -static void -text_input_modifiers_map(void *data, - struct zwp_text_input_v1 *text_input, - struct wl_array *map) -{ -} - -static void -text_input_keysym(void *data, - struct zwp_text_input_v1 *text_input, - uint32_t serial, - uint32_t time, - uint32_t sym, - uint32_t state, - uint32_t modifiers) -{ -} - -static void -text_input_enter(void *data, - struct zwp_text_input_v1 *text_input, - struct wl_surface *surface) - -{ - struct text_input_state *state = data; - - testlog("%s\n", __FUNCTION__); - - state->activated += 1; -} - -static void -text_input_leave(void *data, - struct zwp_text_input_v1 *text_input) -{ - struct text_input_state *state = data; - - state->deactivated += 1; -} - -static void -text_input_input_panel_state(void *data, - struct zwp_text_input_v1 *text_input, - uint32_t state) -{ -} - -static void -text_input_language(void *data, - struct zwp_text_input_v1 *text_input, - uint32_t serial, - const char *language) -{ -} - -static void -text_input_text_direction(void *data, - struct zwp_text_input_v1 *text_input, - uint32_t serial, - uint32_t direction) -{ -} - -static const struct zwp_text_input_v1_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 zwp_text_input_manager_v1 *factory; - struct zwp_text_input_v1 *text_input; - struct text_input_state state; - - client = create_client_and_test_surface(100, 100, 100, 100); - assert(client); - - factory = NULL; - wl_list_for_each(global, &client->global_list, link) { - if (strcmp(global->interface, "zwp_text_input_manager_v1") == 0) - factory = wl_registry_bind(client->wl_registry, - global->name, - &zwp_text_input_manager_v1_interface, 1); - } - - assert(factory); - - memset(&state, 0, sizeof state); - text_input = zwp_text_input_manager_v1_create_text_input(factory); - zwp_text_input_v1_add_listener(text_input, - &text_input_listener, - &state); - - /* Make sure our test surface has keyboard focus. */ - weston_test_activate_surface(client->test->weston_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. */ - zwp_text_input_v1_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. */ - zwp_text_input_v1_deactivate(text_input, client->input->wl_seat); - client_roundtrip(client); - assert(state.activated == 1 && state.deactivated == 1); - - /* Activate test model again. */ - zwp_text_input_v1_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. */ - weston_test_activate_surface(client->test->weston_test, NULL); - client_roundtrip(client); - assert(state.activated == 2 && state.deactivated == 2); -} diff --git a/tests/timespec-test.c b/tests/timespec-test.c deleted file mode 100644 index fa1e6a1..0000000 --- a/tests/timespec-test.c +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright © 2016 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "shared/timespec-util.h" - -#include "shared/helpers.h" -#include "zunitc/zunitc.h" - -ZUC_TEST(timespec_test, timespec_sub) -{ - struct timespec a, b, r; - - a.tv_sec = 1; - a.tv_nsec = 1; - b.tv_sec = 0; - b.tv_nsec = 2; - timespec_sub(&r, &a, &b); - ZUC_ASSERT_EQ(r.tv_sec, 0); - ZUC_ASSERT_EQ(r.tv_nsec, NSEC_PER_SEC - 1); -} - -ZUC_TEST(timespec_test, timespec_to_nsec) -{ - struct timespec a; - - a.tv_sec = 4; - a.tv_nsec = 4; - ZUC_ASSERT_EQ(timespec_to_nsec(&a), (NSEC_PER_SEC * 4ULL) + 4); -} - -ZUC_TEST(timespec_test, timespec_to_usec) -{ - struct timespec a; - - a.tv_sec = 4; - a.tv_nsec = 4000; - ZUC_ASSERT_EQ(timespec_to_usec(&a), (4000000ULL) + 4); -} - -ZUC_TEST(timespec_test, timespec_to_msec) -{ - struct timespec a; - - a.tv_sec = 4; - a.tv_nsec = 4000000; - ZUC_ASSERT_EQ(timespec_to_msec(&a), (4000ULL) + 4); -} - -ZUC_TEST(timespec_test, timespec_to_proto) -{ - struct timespec a; - uint32_t tv_sec_hi; - uint32_t tv_sec_lo; - uint32_t tv_nsec; - - a.tv_sec = 0; - a.tv_nsec = 0; - timespec_to_proto(&a, &tv_sec_hi, &tv_sec_lo, &tv_nsec); - ZUC_ASSERT_EQ(0, tv_sec_hi); - ZUC_ASSERT_EQ(0, tv_sec_lo); - ZUC_ASSERT_EQ(0, tv_nsec); - - a.tv_sec = 1234; - a.tv_nsec = NSEC_PER_SEC - 1; - timespec_to_proto(&a, &tv_sec_hi, &tv_sec_lo, &tv_nsec); - ZUC_ASSERT_EQ(0, tv_sec_hi); - ZUC_ASSERT_EQ(1234, tv_sec_lo); - ZUC_ASSERT_EQ(NSEC_PER_SEC - 1, tv_nsec); - - a.tv_sec = (time_t)0x7000123470005678LL; - a.tv_nsec = 1; - timespec_to_proto(&a, &tv_sec_hi, &tv_sec_lo, &tv_nsec); - ZUC_ASSERT_EQ((uint64_t)a.tv_sec >> 32, tv_sec_hi); - ZUC_ASSERT_EQ(0x70005678, tv_sec_lo); - ZUC_ASSERT_EQ(1, tv_nsec); -} - -ZUC_TEST(timespec_test, millihz_to_nsec) -{ - ZUC_ASSERT_EQ(millihz_to_nsec(60000), 16666666); -} - -ZUC_TEST(timespec_test, timespec_add_nsec) -{ - struct timespec a, r; - - a.tv_sec = 0; - a.tv_nsec = NSEC_PER_SEC - 1; - timespec_add_nsec(&r, &a, 1); - ZUC_ASSERT_EQ(1, r.tv_sec); - ZUC_ASSERT_EQ(0, r.tv_nsec); - - timespec_add_nsec(&r, &a, 2); - ZUC_ASSERT_EQ(1, r.tv_sec); - ZUC_ASSERT_EQ(1, r.tv_nsec); - - timespec_add_nsec(&r, &a, (NSEC_PER_SEC * 2ULL)); - ZUC_ASSERT_EQ(2, r.tv_sec); - ZUC_ASSERT_EQ(NSEC_PER_SEC - 1, r.tv_nsec); - - timespec_add_nsec(&r, &a, (NSEC_PER_SEC * 2ULL) + 2); - ZUC_ASSERT_EQ(r.tv_sec, 3); - ZUC_ASSERT_EQ(r.tv_nsec, 1); - - a.tv_sec = 1; - a.tv_nsec = 1; - timespec_add_nsec(&r, &a, -2); - ZUC_ASSERT_EQ(r.tv_sec, 0); - ZUC_ASSERT_EQ(r.tv_nsec, NSEC_PER_SEC - 1); - - a.tv_nsec = 0; - timespec_add_nsec(&r, &a, -NSEC_PER_SEC); - ZUC_ASSERT_EQ(0, r.tv_sec); - ZUC_ASSERT_EQ(0, r.tv_nsec); - - a.tv_nsec = 0; - timespec_add_nsec(&r, &a, -NSEC_PER_SEC + 1); - ZUC_ASSERT_EQ(0, r.tv_sec); - ZUC_ASSERT_EQ(1, r.tv_nsec); - - a.tv_nsec = 50; - timespec_add_nsec(&r, &a, (-NSEC_PER_SEC * 10ULL)); - ZUC_ASSERT_EQ(-9, r.tv_sec); - ZUC_ASSERT_EQ(50, r.tv_nsec); - - r.tv_sec = 4; - r.tv_nsec = 0; - timespec_add_nsec(&r, &r, NSEC_PER_SEC + 10ULL); - ZUC_ASSERT_EQ(5, r.tv_sec); - ZUC_ASSERT_EQ(10, r.tv_nsec); - - timespec_add_nsec(&r, &r, (NSEC_PER_SEC * 3ULL) - 9ULL); - ZUC_ASSERT_EQ(8, r.tv_sec); - ZUC_ASSERT_EQ(1, r.tv_nsec); - - timespec_add_nsec(&r, &r, (NSEC_PER_SEC * 7ULL) + (NSEC_PER_SEC - 1ULL)); - ZUC_ASSERT_EQ(16, r.tv_sec); - ZUC_ASSERT_EQ(0, r.tv_nsec); -} - -ZUC_TEST(timespec_test, timespec_add_msec) -{ - struct timespec a, r; - - a.tv_sec = 1000; - a.tv_nsec = 1; - timespec_add_msec(&r, &a, 2002); - ZUC_ASSERT_EQ(1002, r.tv_sec); - ZUC_ASSERT_EQ(2000001, r.tv_nsec); -} - -ZUC_TEST(timespec_test, timespec_sub_to_nsec) -{ - struct timespec a, b; - - a.tv_sec = 1000; - a.tv_nsec = 1; - b.tv_sec = 1; - b.tv_nsec = 2; - ZUC_ASSERT_EQ((999LL * NSEC_PER_SEC) - 1, timespec_sub_to_nsec(&a, &b)); -} - -ZUC_TEST(timespec_test, timespec_sub_to_msec) -{ - struct timespec a, b; - - a.tv_sec = 1000; - a.tv_nsec = 2000000L; - b.tv_sec = 2; - b.tv_nsec = 1000000L; - ZUC_ASSERT_EQ((998 * 1000) + 1, timespec_sub_to_msec(&a, &b)); -} - -ZUC_TEST(timespec_test, timespec_from_nsec) -{ - struct timespec a; - - timespec_from_nsec(&a, 0); - ZUC_ASSERT_EQ(0, a.tv_sec); - ZUC_ASSERT_EQ(0, a.tv_nsec); - - timespec_from_nsec(&a, NSEC_PER_SEC - 1); - ZUC_ASSERT_EQ(0, a.tv_sec); - ZUC_ASSERT_EQ(NSEC_PER_SEC - 1, a.tv_nsec); - - timespec_from_nsec(&a, NSEC_PER_SEC); - ZUC_ASSERT_EQ(1, a.tv_sec); - ZUC_ASSERT_EQ(0, a.tv_nsec); - - timespec_from_nsec(&a, (5LL * NSEC_PER_SEC) + 1); - ZUC_ASSERT_EQ(5, a.tv_sec); - ZUC_ASSERT_EQ(1, a.tv_nsec); -} - -ZUC_TEST(timespec_test, timespec_from_usec) -{ - struct timespec a; - - timespec_from_usec(&a, 0); - ZUC_ASSERT_EQ(0, a.tv_sec); - ZUC_ASSERT_EQ(0, a.tv_nsec); - - timespec_from_usec(&a, 999999); - ZUC_ASSERT_EQ(0, a.tv_sec); - ZUC_ASSERT_EQ(999999 * 1000, a.tv_nsec); - - timespec_from_usec(&a, 1000000); - ZUC_ASSERT_EQ(1, a.tv_sec); - ZUC_ASSERT_EQ(0, a.tv_nsec); - - timespec_from_usec(&a, 5000001); - ZUC_ASSERT_EQ(5, a.tv_sec); - ZUC_ASSERT_EQ(1000, a.tv_nsec); -} - -ZUC_TEST(timespec_test, timespec_from_msec) -{ - struct timespec a; - - timespec_from_msec(&a, 0); - ZUC_ASSERT_EQ(0, a.tv_sec); - ZUC_ASSERT_EQ(0, a.tv_nsec); - - timespec_from_msec(&a, 999); - ZUC_ASSERT_EQ(0, a.tv_sec); - ZUC_ASSERT_EQ(999 * 1000000, a.tv_nsec); - - timespec_from_msec(&a, 1000); - ZUC_ASSERT_EQ(1, a.tv_sec); - ZUC_ASSERT_EQ(0, a.tv_nsec); - - timespec_from_msec(&a, 5001); - ZUC_ASSERT_EQ(5, a.tv_sec); - ZUC_ASSERT_EQ(1000000, a.tv_nsec); -} - -ZUC_TEST(timespec_test, timespec_from_proto) -{ - struct timespec a; - - timespec_from_proto(&a, 0, 0, 0); - ZUC_ASSERT_EQ(0, a.tv_sec); - ZUC_ASSERT_EQ(0, a.tv_nsec); - - timespec_from_proto(&a, 0, 1234, 9999); - ZUC_ASSERT_EQ(1234, a.tv_sec); - ZUC_ASSERT_EQ(9999, a.tv_nsec); - - timespec_from_proto(&a, 0x1234, 0x5678, 1); - ZUC_ASSERT_EQ((time_t)0x0000123400005678LL, a.tv_sec); - ZUC_ASSERT_EQ(1, a.tv_nsec); -} - -ZUC_TEST(timespec_test, timespec_is_zero) -{ - struct timespec zero = { 0 }; - struct timespec non_zero_sec = { .tv_sec = 1, .tv_nsec = 0 }; - struct timespec non_zero_nsec = { .tv_sec = 0, .tv_nsec = 1 }; - - ZUC_ASSERT_TRUE(timespec_is_zero(&zero)); - ZUC_ASSERT_FALSE(timespec_is_zero(&non_zero_nsec)); - ZUC_ASSERT_FALSE(timespec_is_zero(&non_zero_sec)); -} - -ZUC_TEST(timespec_test, timespec_eq) -{ - struct timespec a = { .tv_sec = 2, .tv_nsec = 1 }; - struct timespec b = { .tv_sec = -1, .tv_nsec = 2 }; - - ZUC_ASSERT_TRUE(timespec_eq(&a, &a)); - ZUC_ASSERT_TRUE(timespec_eq(&b, &b)); - - ZUC_ASSERT_FALSE(timespec_eq(&a, &b)); - ZUC_ASSERT_FALSE(timespec_eq(&b, &a)); -} diff --git a/tests/touch-test.c b/tests/touch-test.c deleted file mode 100644 index b2133bd..0000000 --- a/tests/touch-test.c +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright © 2017 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include - -#include "input-timestamps-helper.h" -#include "shared/timespec-util.h" -#include "weston-test-client-helper.h" -#include "wayland-server-protocol.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -static const struct timespec t1 = { .tv_sec = 1, .tv_nsec = 1000001 }; -static const struct timespec t2 = { .tv_sec = 2, .tv_nsec = 2000001 }; -static const struct timespec t3 = { .tv_sec = 3, .tv_nsec = 3000001 }; -static const struct timespec t_other = { .tv_sec = 123, .tv_nsec = 456 }; - -static struct client * -create_touch_test_client(void) -{ - struct client *cl = create_client_and_test_surface(0, 0, 100, 100); - assert(cl); - return cl; -} - -static void -send_touch(struct client *client, const struct timespec *time, - uint32_t touch_type) -{ - uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; - - timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); - weston_test_send_touch(client->test->weston_test, tv_sec_hi, tv_sec_lo, - tv_nsec, 1, 1, 1, touch_type); - client_roundtrip(client); -} - -TEST(touch_events) -{ - struct client *client = create_touch_test_client(); - struct touch *touch = client->input->touch; - struct input_timestamps *input_ts = - input_timestamps_create_for_touch(client); - - send_touch(client, &t1, WL_TOUCH_DOWN); - assert(touch->down_time_msec == timespec_to_msec(&t1)); - assert(timespec_eq(&touch->down_time_timespec, &t1)); - - send_touch(client, &t2, WL_TOUCH_MOTION); - assert(touch->motion_time_msec == timespec_to_msec(&t2)); - assert(timespec_eq(&touch->motion_time_timespec, &t2)); - - send_touch(client, &t3, WL_TOUCH_UP); - assert(touch->up_time_msec == timespec_to_msec(&t3)); - assert(timespec_eq(&touch->up_time_timespec, &t3)); - - input_timestamps_destroy(input_ts); -} - -TEST(touch_timestamps_stop_after_input_timestamps_object_is_destroyed) -{ - struct client *client = create_touch_test_client(); - struct touch *touch = client->input->touch; - struct input_timestamps *input_ts = - input_timestamps_create_for_touch(client); - - send_touch(client, &t1, WL_TOUCH_DOWN); - assert(touch->down_time_msec == timespec_to_msec(&t1)); - assert(timespec_eq(&touch->down_time_timespec, &t1)); - - input_timestamps_destroy(input_ts); - - send_touch(client, &t2, WL_TOUCH_UP); - assert(touch->up_time_msec == timespec_to_msec(&t2)); - assert(timespec_is_zero(&touch->up_time_timespec)); -} - -TEST(touch_timestamps_stop_after_client_releases_wl_touch) -{ - struct client *client = create_touch_test_client(); - struct touch *touch = client->input->touch; - struct input_timestamps *input_ts = - input_timestamps_create_for_touch(client); - - send_touch(client, &t1, WL_TOUCH_DOWN); - assert(touch->down_time_msec == timespec_to_msec(&t1)); - assert(timespec_eq(&touch->down_time_timespec, &t1)); - - wl_touch_release(client->input->touch->wl_touch); - - /* Set input_timestamp to an arbitrary value (different from t1, t2 - * and 0) and check that it is not changed by sending the event. - * This is preferred over just checking for 0, since 0 is used - * internally for resetting the timestamp after handling an input - * event and checking for it here may lead to false negatives. */ - touch->input_timestamp = t_other; - send_touch(client, &t2, WL_TOUCH_UP); - assert(timespec_eq(&touch->input_timestamp, &t_other)); - - input_timestamps_destroy(input_ts); -} diff --git a/tests/vertex-clip-test.c b/tests/vertex-clip-test.c deleted file mode 100644 index ce876ce..0000000 --- a/tests/vertex-clip-test.c +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright © 2013 Sam Spilsbury - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include - -#include "weston-test-runner.h" - -#include "shared/helpers.h" -#include "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, - float *vertices_x, - float *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; - float vertices_x[8]; - float 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; - float vertices_x[8]; - float 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/viewporter-shot-test.c b/tests/viewporter-shot-test.c deleted file mode 100644 index b4f389c..0000000 --- a/tests/viewporter-shot-test.c +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2014, 2016, 2020 Collabora, Ltd. - * Copyright 2020 Zodiac Inflight Innovations - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include - -#include "weston-test-client-helper.h" -#include "weston-test-fixture-compositor.h" - -static const enum renderer_type renderers[] = { - RENDERER_PIXMAN, - RENDERER_GL, -}; - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness, - const enum renderer_type *renderer) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - setup.renderer = *renderer; - setup.shell = SHELL_TEST_DESKTOP; - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, renderers); - - -TEST(viewport_upscale_solid) -{ - struct client *client; - struct wp_viewport *viewport; - pixman_color_t color; - const int width = 256; - const int height = 100; - bool match; - - color_rgb888(&color, 255, 128, 0); - - client = create_client(); - client->surface = create_test_surface(client); - viewport = client_create_viewport(client); - - client->surface->buffer = create_shm_buffer_a8r8g8b8(client, 2, 2); - fill_image_with_color(client->surface->buffer->image, &color); - - /* Needs output scale != buffer scale to hit bilinear filter. */ - wl_surface_set_buffer_scale(client->surface->wl_surface, 2); - - wp_viewport_set_destination(viewport, width, height); - client->surface->width = width; - client->surface->height = height; - - move_client(client, 19, 19); - - match = verify_screen_content(client, "viewport_upscale_solid", 0, - NULL, 0); - assert(match); - - wp_viewport_destroy(viewport); - client_destroy(client); -} diff --git a/tests/viewporter-test.c b/tests/viewporter-test.c deleted file mode 100644 index 68e9364..0000000 --- a/tests/viewporter-test.c +++ /dev/null @@ -1,519 +0,0 @@ -/* - * Copyright © 2014, 2016 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include - -#include "shared/helpers.h" -#include "shared/xalloc.h" -#include "weston-test-client-helper.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -static void -set_source(struct wp_viewport *vp, int x, int y, int w, int h) -{ - wp_viewport_set_source(vp, wl_fixed_from_int(x), wl_fixed_from_int(y), - wl_fixed_from_int(w), wl_fixed_from_int(h)); -} - -TEST(test_viewporter_double_create) -{ - struct wp_viewporter *viewporter; - struct client *client; - - client = create_client_and_test_surface(100, 50, 123, 77); - - viewporter = bind_to_singleton_global(client, - &wp_viewporter_interface, 1); - wp_viewporter_get_viewport(viewporter, client->surface->wl_surface); - wp_viewporter_get_viewport(viewporter, client->surface->wl_surface); - - expect_protocol_error(client, &wp_viewporter_interface, - WP_VIEWPORTER_ERROR_VIEWPORT_EXISTS); -} - -struct bad_source_rect_args { - int x, y, w, h; -}; - -static const struct bad_source_rect_args bad_source_rect_args[] = { - { -5, 0, 20, 10 }, - { 0, -5, 20, 10 }, - { 5, 6, 0, 10 }, - { 5, 6, 20, 0 }, - { 5, 6, -20, 10 }, - { 5, 6, 20, -10 }, - { -1, -1, 20, 10 }, - { 5, 6, -1, -1 }, -}; - -TEST_P(test_viewporter_bad_source_rect, bad_source_rect_args) -{ - const struct bad_source_rect_args *args = data; - struct client *client; - struct wp_viewport *vp; - - client = create_client_and_test_surface(100, 50, 123, 77); - - vp = client_create_viewport(client); - - testlog("wp_viewport.set_source x=%d, y=%d, w=%d, h=%d\n", - args->x, args->y, args->w, args->h); - set_source(vp, args->x, args->y, args->w, args->h); - - expect_protocol_error(client, &wp_viewport_interface, - WP_VIEWPORT_ERROR_BAD_VALUE); -} - -TEST(test_viewporter_unset_source_rect) -{ - struct client *client; - struct wp_viewport *vp; - - client = create_client_and_test_surface(100, 50, 123, 77); - - vp = client_create_viewport(client); - set_source(vp, -1, -1, -1, -1); - wl_surface_commit(client->surface->wl_surface); - - client_roundtrip(client); -} - -struct bad_destination_args { - int w, h; -}; - -static const struct bad_destination_args bad_destination_args[] = { - { 0, 10 }, - { 20, 0 }, - { -20, 10 }, - { -1, 10 }, - { 20, -10 }, - { 20, -1 }, -}; - -TEST_P(test_viewporter_bad_destination_size, bad_destination_args) -{ - const struct bad_destination_args *args = data; - struct client *client; - struct wp_viewport *vp; - - client = create_client_and_test_surface(100, 50, 123, 77); - - vp = client_create_viewport(client); - - testlog("wp_viewport.set_destination w=%d, h=%d\n", args->w, args->h); - wp_viewport_set_destination(vp, args->w, args->h); - - expect_protocol_error(client, &wp_viewport_interface, - WP_VIEWPORT_ERROR_BAD_VALUE); -} - -TEST(test_viewporter_unset_destination_size) -{ - struct client *client; - struct wp_viewport *vp; - - client = create_client_and_test_surface(100, 50, 123, 77); - - vp = client_create_viewport(client); - wp_viewport_set_destination(vp, -1, -1); - wl_surface_commit(client->surface->wl_surface); - - client_roundtrip(client); -} - -struct nonint_destination_args { - wl_fixed_t w, h; -}; - -static const struct nonint_destination_args nonint_destination_args[] = { -#define F(i,f) ((i) * 256 + (f)) - { F(20, 0), F(10, 1) }, - { F(20, 0), F(10, -1) }, - { F(20, 1), F(10, 0) }, - { F(20, -1), F(10, 0) }, - { F(20, 128), F(10, 128) }, -#undef F -}; - -TEST_P(test_viewporter_non_integer_destination_size, nonint_destination_args) -{ - const struct nonint_destination_args *args = data; - struct client *client; - struct wp_viewport *vp; - - client = create_client_and_test_surface(100, 50, 123, 77); - - vp = client_create_viewport(client); - - testlog("non-integer size w=%f, h=%f\n", - wl_fixed_to_double(args->w), wl_fixed_to_double(args->h)); - wp_viewport_set_source(vp, 5, 6, args->w, args->h); - wp_viewport_set_destination(vp, -1, -1); - wl_surface_commit(client->surface->wl_surface); - - expect_protocol_error(client, &wp_viewport_interface, - WP_VIEWPORT_ERROR_BAD_SIZE); -} - -struct source_buffer_args { - wl_fixed_t x, y; - wl_fixed_t w, h; - int buffer_scale; - enum wl_output_transform buffer_transform; -}; - -static int -get_surface_width(struct surface *surface, - int buffer_scale, - enum wl_output_transform buffer_transform) -{ - switch (buffer_transform) { - case WL_OUTPUT_TRANSFORM_NORMAL: - case WL_OUTPUT_TRANSFORM_180: - case WL_OUTPUT_TRANSFORM_FLIPPED: - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - return surface->width / buffer_scale; - case WL_OUTPUT_TRANSFORM_90: - case WL_OUTPUT_TRANSFORM_270: - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - return surface->height / buffer_scale; - } - - return -1; -} - -static int -get_surface_height(struct surface *surface, - int buffer_scale, - enum wl_output_transform buffer_transform) -{ - switch (buffer_transform) { - case WL_OUTPUT_TRANSFORM_NORMAL: - case WL_OUTPUT_TRANSFORM_180: - case WL_OUTPUT_TRANSFORM_FLIPPED: - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - return surface->height / buffer_scale; - case WL_OUTPUT_TRANSFORM_90: - case WL_OUTPUT_TRANSFORM_270: - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - return surface->width / buffer_scale; - } - - return -1; -} - -static void -setup_source_vs_buffer(struct client *client, - const struct source_buffer_args *args) -{ - struct wl_surface *surf; - struct wp_viewport *vp; - - surf = client->surface->wl_surface; - vp = client_create_viewport(client); - - testlog("surface %dx%d\n", - get_surface_width(client->surface, - args->buffer_scale, args->buffer_transform), - get_surface_height(client->surface, - args->buffer_scale, args->buffer_transform)); - testlog("source x=%f, y=%f, w=%f, h=%f; " - "buffer scale=%d, transform=%d\n", - wl_fixed_to_double(args->x), wl_fixed_to_double(args->y), - wl_fixed_to_double(args->w), wl_fixed_to_double(args->h), - args->buffer_scale, args->buffer_transform); - - wl_surface_set_buffer_scale(surf, args->buffer_scale); - wl_surface_set_buffer_transform(surf, args->buffer_transform); - wl_surface_attach(surf, client->surface->buffer->proxy, 0, 0); - wp_viewport_set_source(vp, args->x, args->y, args->w, args->h); - wp_viewport_set_destination(vp, 99, 99); - wl_surface_commit(surf); -} - -/* buffer dimensions */ -#define WIN_W 124 -#define WIN_H 78 - -/* source rect base size */ -#define SRC_W 20 -#define SRC_H 10 - -/* margin */ -#define MRG 10 -/* epsilon of wl_fixed_t */ -#define EPS 1 - -TEST(test_viewporter_source_buffer_params) -{ - const int max_scale = 2; - - /* buffer_scale requirement */ - assert(WIN_W % max_scale == 0); - assert(WIN_H % max_scale == 0); - - /* source rect must fit inside regardless of scale and transform */ - assert(SRC_W < WIN_W / max_scale); - assert(SRC_H < WIN_H / max_scale); - assert(SRC_W < WIN_H / max_scale); - assert(SRC_H < WIN_W / max_scale); - - /* If buffer scale was ignored, source rect should be inside instead */ - assert(WIN_W / max_scale + SRC_W + MRG < WIN_W); - assert(WIN_H / max_scale + SRC_H + MRG < WIN_H); - assert(WIN_W / max_scale + SRC_H + MRG < WIN_W); - assert(WIN_H / max_scale + SRC_W + MRG < WIN_H); -} - -static const struct source_buffer_args bad_source_buffer_args[] = { -#define F(i) ((i) * 256) - -/* Flush right-top, but epsilon too far right. */ - { F(WIN_W - SRC_W) + EPS, F(0), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, - { F(WIN_W - SRC_W), F(0), F(SRC_W) + EPS, F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, -/* Flush left-bottom, but epsilon too far down. */ - { F(0), F(WIN_H - SRC_H) + EPS, F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, - { F(0), F(WIN_H - SRC_H), F(SRC_W), F(SRC_H) + EPS, 1, WL_OUTPUT_TRANSFORM_NORMAL }, -/* Completely outside on the right. */ - { F(WIN_W + MRG), F(0), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, -/* Competely outside on the bottom. */ - { F(0), F(WIN_H + MRG), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, - -/* - * buffer_scale=2, so the surface size will be halved. - * If buffer_scale was not taken into account, these would all be inside. - * These are the same as above, but adapted to buffer_scale=2. - */ - { F(WIN_W / 2 - SRC_W) + EPS, F(0), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, - { F(WIN_W / 2 - SRC_W), F(0), F(SRC_W) + EPS, F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, - - { F(0), F(WIN_H / 2 - SRC_H) + EPS, F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, - { F(0), F(WIN_H / 2 - SRC_H), F(SRC_W), F(SRC_H) + EPS, 2, WL_OUTPUT_TRANSFORM_NORMAL }, - - { F(WIN_W / 2 + MRG), F(0), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, - - { F(0), F(WIN_H / 2 + MRG), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, - -/* Exceeding bottom-right corner by epsilon: */ -/* non-dimension-swapping transforms */ - { F(WIN_W - SRC_W), F(WIN_H - SRC_H), F(SRC_W), F(SRC_H) + EPS, 1, WL_OUTPUT_TRANSFORM_FLIPPED_180 }, - { F(WIN_W - SRC_W), F(WIN_H - SRC_H) + EPS, F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_FLIPPED }, - { F(WIN_W - SRC_W), F(WIN_H - SRC_H), F(SRC_W) + EPS, F(SRC_H), 1, WL_OUTPUT_TRANSFORM_180 }, - -/* dimension-swapping transforms */ - { F(WIN_H - SRC_W) + EPS, F(WIN_W - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_90 }, - { F(WIN_H - SRC_W), F(WIN_W - SRC_H) + EPS, F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_270 }, - { F(WIN_H - SRC_W), F(WIN_W - SRC_H), F(SRC_W) + EPS, F(SRC_H), 1, WL_OUTPUT_TRANSFORM_FLIPPED_90 }, - { F(WIN_H - SRC_W), F(WIN_W - SRC_H), F(SRC_W), F(SRC_H) + EPS, 1, WL_OUTPUT_TRANSFORM_FLIPPED_270 }, - -/* non-dimension-swapping transforms, buffer_scale=2 */ - { F(WIN_W / 2 - SRC_W), F(WIN_H / 2 - SRC_H) + EPS, F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_FLIPPED_180 }, - { F(WIN_W / 2 - SRC_W), F(WIN_H / 2 - SRC_H), F(SRC_W) + EPS, F(SRC_H), 2, WL_OUTPUT_TRANSFORM_FLIPPED }, - { F(WIN_W / 2 - SRC_W), F(WIN_H / 2 - SRC_H), F(SRC_W), F(SRC_H) + EPS, 2, WL_OUTPUT_TRANSFORM_180 }, - -/* dimension-swapping transforms, buffer_scale=2 */ - { F(WIN_H / 2 - SRC_W) + EPS, F(WIN_W / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_90 }, - { F(WIN_H / 2 - SRC_W), F(WIN_W / 2 - SRC_H) + EPS, F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_270 }, - { F(WIN_H / 2 - SRC_W), F(WIN_W / 2 - SRC_H), F(SRC_W) + EPS, F(SRC_H), 2, WL_OUTPUT_TRANSFORM_FLIPPED_90 }, - { F(WIN_H / 2 - SRC_W), F(WIN_W / 2 - SRC_H), F(SRC_W), F(SRC_H) + EPS, 2, WL_OUTPUT_TRANSFORM_FLIPPED_270 }, - -#undef F -}; - -TEST_P(test_viewporter_source_outside_buffer, bad_source_buffer_args) -{ - const struct source_buffer_args *args = data; - struct client *client; - - client = create_client_and_test_surface(100, 50, WIN_W, WIN_H); - setup_source_vs_buffer(client, args); - - expect_protocol_error(client, &wp_viewport_interface, - WP_VIEWPORT_ERROR_OUT_OF_BUFFER); -} - -static const struct source_buffer_args good_source_buffer_args[] = { -#define F(i) ((i) * 256) - -/* top-left, top-right, bottom-left, and bottom-right corner */ - { F(0), F(0), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, - { F(WIN_W - SRC_W), F(0), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, - { F(0), F(WIN_H - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, - { F(WIN_W - SRC_W), F(WIN_H - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_NORMAL }, - -/* buffer_scale=2, so the surface size will be halved */ - { F(0), F(0), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, - { F(WIN_W / 2 - SRC_W), F(0), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, - { F(0), F(WIN_H / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, - { F(WIN_W / 2 - SRC_W), F(WIN_H / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_NORMAL }, - -/* with half pixel offset */ - { F(WIN_W / 2 - SRC_W) + 128, F(WIN_H / 2 - SRC_H) + 128, F(SRC_W) - 128, F(SRC_H) - 128, 2, WL_OUTPUT_TRANSFORM_NORMAL }, - -/* Flushed to bottom-right corner: */ -/* non-dimension-swapping transforms */ - { F(WIN_W - SRC_W), F(WIN_H - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_FLIPPED_180 }, - { F(WIN_W - SRC_W), F(WIN_H - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_FLIPPED }, - { F(WIN_W - SRC_W), F(WIN_H - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_180 }, - -/* dimension-swapping transforms */ - { F(WIN_H - SRC_W), F(WIN_W - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_90 }, - { F(WIN_H - SRC_W), F(WIN_W - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_270 }, - { F(WIN_H - SRC_W), F(WIN_W - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_FLIPPED_90 }, - { F(WIN_H - SRC_W), F(WIN_W - SRC_H), F(SRC_W), F(SRC_H), 1, WL_OUTPUT_TRANSFORM_FLIPPED_270 }, - -/* non-dimension-swapping transforms, buffer_scale=2 */ - { F(WIN_W / 2 - SRC_W), F(WIN_H / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_FLIPPED_180 }, - { F(WIN_W / 2 - SRC_W), F(WIN_H / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_FLIPPED }, - { F(WIN_W / 2 - SRC_W), F(WIN_H / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_180 }, - -/* dimension-swapping transforms, buffer_scale=2 */ - { F(WIN_H / 2 - SRC_W), F(WIN_W / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_90 }, - { F(WIN_H / 2 - SRC_W), F(WIN_W / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_270 }, - { F(WIN_H / 2 - SRC_W), F(WIN_W / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_FLIPPED_90 }, - { F(WIN_H / 2 - SRC_W), F(WIN_W / 2 - SRC_H), F(SRC_W), F(SRC_H), 2, WL_OUTPUT_TRANSFORM_FLIPPED_270 }, - -#undef F -}; - -TEST_P(test_viewporter_source_inside_buffer, good_source_buffer_args) -{ - const struct source_buffer_args *args = data; - struct client *client; - - client = create_client_and_test_surface(100, 50, WIN_W, WIN_H); - setup_source_vs_buffer(client, args); - client_roundtrip(client); -} - -#undef WIN_W -#undef WIN_H -#undef SRC_W -#undef SRC_H -#undef MRG -#undef EPS - -TEST(test_viewporter_outside_null_buffer) -{ - struct client *client; - struct wp_viewport *vp; - struct wl_surface *surf; - - client = create_client_and_test_surface(100, 50, 123, 77); - surf = client->surface->wl_surface; - - /* If buffer is NULL, does not matter what the source rect is. */ - vp = client_create_viewport(client); - wl_surface_attach(surf, NULL, 0, 0); - set_source(vp, 1000, 1000, 20, 10); - wp_viewport_set_destination(vp, 99, 99); - wl_surface_commit(surf); - client_roundtrip(client); - - /* Try again, with all old values. */ - wl_surface_commit(surf); - client_roundtrip(client); - - /* Try once more with old NULL buffer. */ - set_source(vp, 1200, 1200, 20, 10); - wl_surface_commit(surf); - client_roundtrip(client); - - /* When buffer comes back, source rect matters again. */ - wl_surface_attach(surf, client->surface->buffer->proxy, 0, 0); - wl_surface_commit(surf); - expect_protocol_error(client, &wp_viewport_interface, - WP_VIEWPORT_ERROR_OUT_OF_BUFFER); -} - -TEST(test_viewporter_no_surface_set_source) -{ - struct client *client; - struct wp_viewport *vp; - - client = create_client_and_test_surface(100, 50, 123, 77); - vp = client_create_viewport(client); - wl_surface_destroy(client->surface->wl_surface); - client->surface->wl_surface = NULL; - - /* But the wl_surface does not exist anymore. */ - set_source(vp, 1000, 1000, 20, 10); - - expect_protocol_error(client, &wp_viewport_interface, - WP_VIEWPORT_ERROR_NO_SURFACE); -} - -TEST(test_viewporter_no_surface_set_destination) -{ - struct client *client; - struct wp_viewport *vp; - - client = create_client_and_test_surface(100, 50, 123, 77); - vp = client_create_viewport(client); - wl_surface_destroy(client->surface->wl_surface); - client->surface->wl_surface = NULL; - - /* But the wl_surface does not exist anymore. */ - wp_viewport_set_destination(vp, 99, 99); - - expect_protocol_error(client, &wp_viewport_interface, - WP_VIEWPORT_ERROR_NO_SURFACE); -} - -TEST(test_viewporter_no_surface_destroy) -{ - struct client *client; - struct wp_viewport *vp; - - client = create_client_and_test_surface(100, 50, 123, 77); - vp = client_create_viewport(client); - wl_surface_destroy(client->surface->wl_surface); - client->surface->wl_surface = NULL; - - /* But the wl_surface does not exist anymore. */ - wp_viewport_destroy(vp); - - client_roundtrip(client); -} diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c deleted file mode 100644 index 94b93ea..0000000 --- a/tests/weston-test-client-helper.c +++ /dev/null @@ -1,1899 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * Copyright © 2015 Samsung Electronics Co., Ltd - * Copyright 2016, 2017 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "test-config.h" -#include "shared/os-compatibility.h" -#include "shared/xalloc.h" -#include -#include "weston-test-client-helper.h" - -#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 -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; -} - -int -frame_callback_wait_nofail(struct client *client, int *done) -{ - while (!*done) { - if (wl_display_dispatch(client->wl_display) < 0) - return 0; - } - - return 1; -} - -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; - weston_test_move_surface(client->test->weston_test, surface->wl_surface, - surface->x, surface->y); - /* The attach here is necessary because commit() will call configure - * only on surfaces newly attached, and the one that sets the surface - * position is the configure. */ - wl_surface_attach(surface->wl_surface, surface->buffer->proxy, 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; - - if (wl_surface) - pointer->focus = wl_surface_get_user_data(wl_surface); - else - pointer->focus = NULL; - - pointer->x = wl_fixed_to_int(x); - pointer->y = wl_fixed_to_int(y); - - testlog("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; - - testlog("test-client: got pointer leave, surface %p\n", - wl_surface ? wl_surface_get_user_data(wl_surface) : NULL); -} - -static void -pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, - uint32_t time_msec, 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); - pointer->motion_time_msec = time_msec; - pointer->motion_time_timespec = pointer->input_timestamp; - pointer->input_timestamp = (struct timespec) { 0 }; - - testlog("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_msec, uint32_t button, - uint32_t state) -{ - struct pointer *pointer = data; - - pointer->button = button; - pointer->state = state; - pointer->button_time_msec = time_msec; - pointer->button_time_timespec = pointer->input_timestamp; - pointer->input_timestamp = (struct timespec) { 0 }; - - testlog("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_msec, uint32_t axis, wl_fixed_t value) -{ - struct pointer *pointer = data; - - pointer->axis = axis; - pointer->axis_value = wl_fixed_to_double(value); - pointer->axis_time_msec = time_msec; - pointer->axis_time_timespec = pointer->input_timestamp; - pointer->input_timestamp = (struct timespec) { 0 }; - - testlog("test-client: got pointer axis %u %f\n", - axis, wl_fixed_to_double(value)); -} - -static void -pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) -{ - testlog("test-client: got pointer frame\n"); -} - -static void -pointer_handle_axis_source(void *data, struct wl_pointer *wl_pointer, - uint32_t source) -{ - testlog("test-client: got pointer axis source %u\n", source); -} - -static void -pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer, - uint32_t time_msec, uint32_t axis) -{ - struct pointer *pointer = data; - - pointer->axis = axis; - pointer->axis_stop_time_msec = time_msec; - pointer->axis_stop_time_timespec = pointer->input_timestamp; - pointer->input_timestamp = (struct timespec) { 0 }; - - testlog("test-client: got pointer axis stop %u\n", axis); -} - -static void -pointer_handle_axis_discrete(void *data, struct wl_pointer *wl_pointer, - uint32_t axis, int32_t value) -{ - testlog("test-client: got pointer axis discrete %u %d\n", axis, value); -} - -static const struct wl_pointer_listener pointer_listener = { - pointer_handle_enter, - pointer_handle_leave, - pointer_handle_motion, - pointer_handle_button, - pointer_handle_axis, - pointer_handle_frame, - pointer_handle_axis_source, - pointer_handle_axis_stop, - pointer_handle_axis_discrete, -}; - -static void -keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, - uint32_t format, int fd, uint32_t size) -{ - close(fd); - - testlog("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; - - if (wl_surface) - keyboard->focus = wl_surface_get_user_data(wl_surface); - else - keyboard->focus = NULL; - - testlog("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; - - testlog("test-client: got keyboard leave, surface %p\n", - wl_surface ? wl_surface_get_user_data(wl_surface) : NULL); -} - -static void -keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, - uint32_t serial, uint32_t time_msec, uint32_t key, - uint32_t state) -{ - struct keyboard *keyboard = data; - - keyboard->key = key; - keyboard->state = state; - keyboard->key_time_msec = time_msec; - keyboard->key_time_timespec = keyboard->input_timestamp; - keyboard->input_timestamp = (struct timespec) { 0 }; - - testlog("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; - - testlog("test-client: got keyboard modifiers %u %u %u %u\n", - mods_depressed, mods_latched, mods_locked, group); -} - -static void -keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard, - int32_t rate, int32_t delay) -{ - struct keyboard *keyboard = data; - - keyboard->repeat_info.rate = rate; - keyboard->repeat_info.delay = delay; - - testlog("test-client: got keyboard repeat_info %d %d\n", rate, delay); -} - -static const struct wl_keyboard_listener keyboard_listener = { - keyboard_handle_keymap, - keyboard_handle_enter, - keyboard_handle_leave, - keyboard_handle_key, - keyboard_handle_modifiers, - keyboard_handle_repeat_info, -}; - -static void -touch_handle_down(void *data, struct wl_touch *wl_touch, - uint32_t serial, uint32_t time_msec, - struct wl_surface *surface, int32_t id, - wl_fixed_t x_w, wl_fixed_t y_w) -{ - struct touch *touch = data; - - touch->down_x = wl_fixed_to_int(x_w); - touch->down_y = wl_fixed_to_int(y_w); - touch->id = id; - touch->down_time_msec = time_msec; - touch->down_time_timespec = touch->input_timestamp; - touch->input_timestamp = (struct timespec) { 0 }; - - testlog("test-client: got touch down %d %d, surf: %p, id: %d\n", - touch->down_x, touch->down_y, surface, id); -} - -static void -touch_handle_up(void *data, struct wl_touch *wl_touch, - uint32_t serial, uint32_t time_msec, int32_t id) -{ - struct touch *touch = data; - touch->up_id = id; - touch->up_time_msec = time_msec; - touch->up_time_timespec = touch->input_timestamp; - touch->input_timestamp = (struct timespec) { 0 }; - - testlog("test-client: got touch up, id: %d\n", id); -} - -static void -touch_handle_motion(void *data, struct wl_touch *wl_touch, - uint32_t time_msec, int32_t id, - wl_fixed_t x_w, wl_fixed_t y_w) -{ - struct touch *touch = data; - touch->x = wl_fixed_to_int(x_w); - touch->y = wl_fixed_to_int(y_w); - touch->motion_time_msec = time_msec; - touch->motion_time_timespec = touch->input_timestamp; - touch->input_timestamp = (struct timespec) { 0 }; - - testlog("test-client: got touch motion, %d %d, id: %d\n", - touch->x, touch->y, id); -} - -static void -touch_handle_frame(void *data, struct wl_touch *wl_touch) -{ - struct touch *touch = data; - - ++touch->frame_no; - - testlog("test-client: got touch frame (%d)\n", touch->frame_no); -} - -static void -touch_handle_cancel(void *data, struct wl_touch *wl_touch) -{ - struct touch *touch = data; - - ++touch->cancel_no; - - testlog("test-client: got touch cancel (%d)\n", touch->cancel_no); -} - -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 -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); - - testlog("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; - - testlog("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 -}; - -static struct buffer * -create_shm_buffer(struct client *client, int width, int height, - pixman_format_code_t format, uint32_t wlfmt) -{ - struct wl_shm *shm = client->wl_shm; - struct buffer *buf; - size_t stride_bytes; - struct wl_shm_pool *pool; - int fd; - void *data; - size_t bytes_pp; - - assert(width > 0); - assert(height > 0); - - buf = xzalloc(sizeof *buf); - - bytes_pp = PIXMAN_FORMAT_BPP(format) / 8; - stride_bytes = width * bytes_pp; - /* round up to multiple of 4 bytes for Pixman */ - stride_bytes = (stride_bytes + 3) & ~3u; - assert(stride_bytes / bytes_pp >= (unsigned)width); - - buf->len = stride_bytes * height; - assert(buf->len / stride_bytes == (unsigned)height); - - fd = os_create_anonymous_file(buf->len); - assert(fd >= 0); - - data = mmap(NULL, buf->len, 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, buf->len); - buf->proxy = wl_shm_pool_create_buffer(pool, 0, width, height, - stride_bytes, wlfmt); - wl_shm_pool_destroy(pool); - close(fd); - - buf->image = pixman_image_create_bits(format, width, height, - data, stride_bytes); - - assert(buf->proxy); - assert(buf->image); - - return buf; -} - -struct buffer * -create_shm_buffer_a8r8g8b8(struct client *client, int width, int height) -{ - assert(client->has_argb); - - return create_shm_buffer(client, width, height, - PIXMAN_a8r8g8b8, WL_SHM_FORMAT_ARGB8888); -} - -void -buffer_destroy(struct buffer *buf) -{ - void *pixels; - - pixels = pixman_image_get_data(buf->image); - - if (buf->proxy) { - wl_buffer_destroy(buf->proxy); - assert(munmap(pixels, buf->len) == 0); - } - - assert(pixman_image_unref(buf->image)); - - free(buf); -} - -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 weston_test *weston_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); - - testlog("test-client: got global pointer %d %d\n", - test->pointer_x, test->pointer_y); -} - -static void -test_handle_capture_screenshot_done(void *data, struct weston_test *weston_test) -{ - struct test *test = data; - - testlog("Screenshot has been captured\n"); - test->buffer_copy_done = 1; -} - -static const struct weston_test_listener test_listener = { - test_handle_pointer_position, - test_handle_capture_screenshot_done, -}; - -static void -input_destroy(struct input *inp) -{ - if (inp->pointer) { - wl_pointer_release(inp->pointer->wl_pointer); - free(inp->pointer); - } - if (inp->keyboard) { - wl_keyboard_release(inp->keyboard->wl_keyboard); - free(inp->keyboard); - } - if (inp->touch) { - wl_touch_release(inp->touch->wl_touch); - free(inp->touch); - } - wl_list_remove(&inp->link); - wl_seat_release(inp->wl_seat); - free(inp->seat_name); - free(inp); -} - -static void -input_update_devices(struct input *input) -{ - struct pointer *pointer; - struct keyboard *keyboard; - struct touch *touch; - - struct wl_seat *seat = input->wl_seat; - enum wl_seat_capability caps = input->caps; - - 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; - } - - if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !input->touch) { - touch = xzalloc(sizeof *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); - input->touch = touch; - } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && input->touch) { - wl_touch_destroy(input->touch->wl_touch); - free(input->touch); - input->touch = NULL; - } -} - -static void -seat_handle_capabilities(void *data, struct wl_seat *seat, - enum wl_seat_capability caps) -{ - struct input *input = data; - - input->caps = caps; - - /* we will create/update the devices only with the right (test) seat. - * If we haven't discovered which seat is the test seat, just - * store capabilities and bail out */ - if (input->seat_name && strcmp(input->seat_name, "test-seat") == 0) - input_update_devices(input); - - testlog("test-client: got seat %p capabilities: %x\n", input, caps); -} - -static void -seat_handle_name(void *data, struct wl_seat *seat, const char *name) -{ - struct input *input = data; - - input->seat_name = strdup(name); - assert(input->seat_name && "No memory"); - - /* We only update the devices and set client input for the test seat */ - if (strcmp(name, "test-seat") == 0) { - assert(!input->client->input && - "Multiple test seats detected!"); - - input_update_devices(input); - input->client->input = input; - } - - testlog("test-client: got seat %p name: \'%s\'\n", input, name); -} - -static const struct wl_seat_listener seat_listener = { - seat_handle_capabilities, - seat_handle_name, -}; - -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 void -output_handle_scale(void *data, - struct wl_output *wl_output, - int scale) -{ - struct output *output = data; - - output->scale = scale; -} - -static void -output_handle_done(void *data, - struct wl_output *wl_output) -{ - struct output *output = data; - - output->initialized = 1; -} - -static const struct wl_output_listener output_listener = { - output_handle_geometry, - output_handle_mode, - output_handle_done, - output_handle_scale, -}; - -static void -output_destroy(struct output *output) -{ - assert(wl_proxy_get_version((struct wl_proxy *)output->wl_output) >= 3); - wl_output_release(output->wl_output); - wl_list_remove(&output->link); - free(output); -} - -static void -handle_global(void *data, struct wl_registry *registry, - uint32_t id, const char *interface, uint32_t version) -{ - struct client *client = data; - struct output *output; - struct test *test; - struct global *global; - struct input *input; - - 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); - - /* We deliberately bind all globals with the maximum (advertised) - * version, because this test suite must be kept up-to-date with - * Weston. We must always implement at least the version advertised - * by Weston. This is not ok for normal clients, but it is ok in - * this test suite. - */ - - if (strcmp(interface, "wl_compositor") == 0) { - client->wl_compositor = - wl_registry_bind(registry, id, - &wl_compositor_interface, version); - } else if (strcmp(interface, "wl_seat") == 0) { - input = xzalloc(sizeof *input); - input->client = client; - input->global_name = global->name; - input->wl_seat = - wl_registry_bind(registry, id, - &wl_seat_interface, version); - wl_seat_add_listener(input->wl_seat, &seat_listener, input); - wl_list_insert(&client->inputs, &input->link); - } else if (strcmp(interface, "wl_shm") == 0) { - client->wl_shm = - wl_registry_bind(registry, id, - &wl_shm_interface, version); - 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, version); - wl_output_add_listener(output->wl_output, - &output_listener, output); - wl_list_insert(&client->output_list, &output->link); - client->output = output; - } else if (strcmp(interface, "weston_test") == 0) { - test = xzalloc(sizeof *test); - test->weston_test = - wl_registry_bind(registry, id, - &weston_test_interface, version); - weston_test_add_listener(test->weston_test, &test_listener, test); - client->test = test; - } else if (strcmp(interface, "wl_drm") == 0) { - client->has_wl_drm = true; - } -} - -static struct global * -client_find_global_with_name(struct client *client, uint32_t name) -{ - struct global *global; - - wl_list_for_each(global, &client->global_list, link) { - if (global->name == name) - return global; - } - - return NULL; -} - -static struct input * -client_find_input_with_name(struct client *client, uint32_t name) -{ - struct input *input; - - wl_list_for_each(input, &client->inputs, link) { - if (input->global_name == name) - return input; - } - - return NULL; -} - -static void -global_destroy(struct global *global) -{ - wl_list_remove(&global->link); - free(global->interface); - free(global); -} - -static void -handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) -{ - struct client *client = data; - struct global *global; - struct input *input; - - global = client_find_global_with_name(client, name); - assert(global && "Request to remove unknown global"); - - if (strcmp(global->interface, "wl_seat") == 0) { - input = client_find_input_with_name(client, name); - if (input) { - if (client->input == input) - client->input = NULL; - input_destroy(input); - } - } - - /* XXX: handle wl_output */ - - global_destroy(global); -} - -static const struct wl_registry_listener registry_listener = { - handle_global, - handle_global_remove, -}; - -void -skip(const char *fmt, ...) -{ - va_list argp; - - va_start(argp, fmt); - vfprintf(stderr, fmt, argp); - va_end(argp); - - /* automake tests uses exit code 77. weston-test-runner will see - * this and use it, and then weston-test's sigchld handler (in the - * weston process) will use that as an exit status, which is what - * ninja will see in the end. */ - exit(77); -} - -void -expect_protocol_error(struct client *client, - const struct wl_interface *intf, - uint32_t code) -{ - int err; - uint32_t errcode, failed = 0; - const struct wl_interface *interface; - unsigned int id; - - /* if the error has not come yet, make it happen */ - wl_display_roundtrip(client->wl_display); - - err = wl_display_get_error(client->wl_display); - - assert(err && "Expected protocol error but nothing came"); - assert(err == EPROTO && "Expected protocol error but got local error"); - - errcode = wl_display_get_protocol_error(client->wl_display, - &interface, &id); - - /* check error */ - if (errcode != code) { - testlog("Should get error code %d but got %d\n", code, errcode); - failed = 1; - } - - /* this should be definitely set */ - assert(interface); - - if (strcmp(intf->name, interface->name) != 0) { - testlog("Should get interface '%s' but got '%s'\n", - intf->name, interface->name); - failed = 1; - } - - if (failed) { - testlog("Expected other protocol error\n"); - abort(); - } - - /* all OK */ - testlog("Got expected protocol error on '%s' (object id: %d) " - "with code %d\n", interface->name, id, errcode); -} - -static void -log_handler(const char *fmt, va_list args) -{ - fprintf(stderr, "libwayland: "); - vfprintf(stderr, fmt, args); -} - -struct client * -create_client(void) -{ - struct client *client; - - 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); - wl_list_init(&client->inputs); - wl_list_init(&client->output_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); - - /* this roundtrip makes sure we have all globals and we bound to them */ - client_roundtrip(client); - /* this roundtrip makes sure we got all wl_shm.format and wl_seat.* - * events */ - client_roundtrip(client); - - /* must have WL_SHM_FORMAT_ARGB32 */ - assert(client->has_argb); - - /* must have weston_test interface */ - assert(client->test); - - /* must have an output */ - assert(client->output); - - /* the output must be initialized */ - assert(client->output->initialized == 1); - - /* must have seat set */ - assert(client->input); - - return client; -} - -struct surface * -create_test_surface(struct client *client) -{ - struct surface *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); - - wl_surface_set_user_data(surface->wl_surface, surface); - - return surface; -} - -void -surface_destroy(struct surface *surface) -{ - if (surface->wl_surface) - wl_surface_destroy(surface->wl_surface); - if (surface->buffer) - buffer_destroy(surface->buffer); - free(surface); -} - -struct client * -create_client_and_test_surface(int x, int y, int width, int height) -{ - struct client *client; - struct surface *surface; - pixman_color_t color = { 16384, 16384, 16384, 16384 }; /* uint16_t */ - pixman_image_t *solid; - - client = create_client(); - - /* initialize the client surface */ - surface = create_test_surface(client); - client->surface = surface; - - surface->width = width; - surface->height = height; - surface->buffer = create_shm_buffer_a8r8g8b8(client, width, height); - - solid = pixman_image_create_solid_fill(&color); - pixman_image_composite32(PIXMAN_OP_SRC, - solid, /* src */ - NULL, /* mask */ - surface->buffer->image, /* dst */ - 0, 0, /* src x,y */ - 0, 0, /* mask x,y */ - 0, 0, /* dst x,y */ - width, height); - pixman_image_unref(solid); - - move_client(client, x, y); - - return client; -} - -void -client_destroy(struct client *client) -{ - if (client->surface) - surface_destroy(client->surface); - - while (!wl_list_empty(&client->inputs)) { - input_destroy(container_of(client->inputs.next, - struct input, link)); - } - - while (!wl_list_empty(&client->output_list)) { - output_destroy(container_of(client->output_list.next, - struct output, link)); - } - - while (!wl_list_empty(&client->global_list)) { - global_destroy(container_of(client->global_list.next, - struct global, link)); - } - - if (client->test) { - weston_test_destroy(client->test->weston_test); - free(client->test); - } - - if (client->wl_shm) - wl_shm_destroy(client->wl_shm); - if (client->wl_compositor) - wl_compositor_destroy(client->wl_compositor); - if (client->wl_registry) - wl_registry_destroy(client->wl_registry); - - client_roundtrip(client); - - if (client->wl_display) - wl_display_disconnect(client->wl_display); - free(client); -} - -static const char* -output_path(void) -{ - char *path = getenv("WESTON_TEST_OUTPUT_PATH"); - - if (!path) - return "."; - - return path; -} - -char* -screenshot_output_filename(const char *basename, uint32_t seq) -{ - char *filename; - - if (asprintf(&filename, "%s/%s-%02d.png", - output_path(), basename, seq) < 0) - return NULL; - return filename; -} - -static const char* -reference_path(void) -{ - char *path = getenv("WESTON_TEST_REFERENCE_PATH"); - - if (!path) - return WESTON_TEST_REFERENCE_PATH; - return path; -} - -char* -screenshot_reference_filename(const char *basename, uint32_t seq) -{ - char *filename; - - if (asprintf(&filename, "%s/%s-%02d.png", - reference_path(), basename, seq) < 0) - return NULL; - return filename; -} - -char * -image_filename(const char *basename) -{ - char *filename; - - if (asprintf(&filename, "%s/%s.png", reference_path(), basename) < 0) - assert(0); - return filename; -} - -struct format_map_entry { - cairo_format_t cairo; - pixman_format_code_t pixman; -}; - -static const struct format_map_entry format_map[] = { - { CAIRO_FORMAT_ARGB32, PIXMAN_a8r8g8b8 }, - { CAIRO_FORMAT_RGB24, PIXMAN_x8r8g8b8 }, - { CAIRO_FORMAT_A8, PIXMAN_a8 }, - { CAIRO_FORMAT_RGB16_565, PIXMAN_r5g6b5 }, -}; - -static pixman_format_code_t -format_cairo2pixman(cairo_format_t fmt) -{ - unsigned i; - - for (i = 0; i < ARRAY_LENGTH(format_map); i++) - if (format_map[i].cairo == fmt) - return format_map[i].pixman; - - assert(0 && "unknown Cairo pixel format"); -} - -static cairo_format_t -format_pixman2cairo(pixman_format_code_t fmt) -{ - unsigned i; - - for (i = 0; i < ARRAY_LENGTH(format_map); i++) - if (format_map[i].pixman == fmt) - return format_map[i].cairo; - - assert(0 && "unknown Pixman pixel format"); -} - -/** - * Validate range - * - * \param r Range to validate or NULL. - * \return The given range, or {0, 0} for NULL. - * - * Will abort if range is invalid, that is a > b. - */ -static struct range -range_get(const struct range *r) -{ - if (!r) - return (struct range){ 0, 0 }; - - assert(r->a <= r->b); - return *r; -} - -/** - * Compute the ROI for image comparisons - * - * \param img_a An image. - * \param img_b Another image. - * \param clip_rect Explicit ROI, or NULL for using the whole - * image area. - * - * \return The region of interest (ROI) that is guaranteed to be inside both - * images. - * - * If clip_rect is given, it must fall inside of both images. - * If clip_rect is NULL, the images must be of the same size. - * If any precondition is violated, this function aborts with an error. - * - * The ROI is given as pixman_box32_t, where x2,y2 are non-inclusive. - */ -static pixman_box32_t -image_check_get_roi(pixman_image_t *img_a, pixman_image_t *img_b, - const struct rectangle *clip_rect) -{ - int width_a; - int width_b; - int height_a; - int height_b; - pixman_box32_t box; - - width_a = pixman_image_get_width(img_a); - height_a = pixman_image_get_height(img_a); - - width_b = pixman_image_get_width(img_b); - height_b = pixman_image_get_height(img_b); - - if (clip_rect) { - box.x1 = clip_rect->x; - box.y1 = clip_rect->y; - box.x2 = clip_rect->x + clip_rect->width; - box.y2 = clip_rect->y + clip_rect->height; - } else { - box.x1 = 0; - box.y1 = 0; - box.x2 = max(width_a, width_b); - box.y2 = max(height_a, height_b); - } - - assert(box.x1 >= 0); - assert(box.y1 >= 0); - assert(box.x2 > box.x1); - assert(box.y2 > box.y1); - assert(box.x2 <= width_a); - assert(box.x2 <= width_b); - assert(box.y2 <= height_a); - assert(box.y2 <= height_b); - - return box; -} - -struct image_iterator { - char *data; - int stride; /* bytes */ -}; - -static void -image_iter_init(struct image_iterator *it, pixman_image_t *image) -{ - pixman_format_code_t fmt; - - it->stride = pixman_image_get_stride(image); - it->data = (void *)pixman_image_get_data(image); - - fmt = pixman_image_get_format(image); - assert(PIXMAN_FORMAT_BPP(fmt) == 32); -} - -static uint32_t * -image_iter_get_row(struct image_iterator *it, int y) -{ - return (uint32_t *)(it->data + y * it->stride); -} - -struct pixel_diff_stat { - struct pixel_diff_stat_channel { - int min_diff; - int max_diff; - } ch[4]; -}; - -static void -testlog_pixel_diff_stat(const struct pixel_diff_stat *stat) -{ - int i; - - testlog("Image difference statistics:\n"); - for (i = 0; i < 4; i++) { - testlog("\tch %d: [%d, %d]\n", - i, stat->ch[i].min_diff, stat->ch[i].max_diff); - } -} - -static bool -fuzzy_match_pixels(uint32_t pix_a, uint32_t pix_b, - const struct range *fuzz, - struct pixel_diff_stat *stat) -{ - bool ret = true; - int shift; - int i; - - for (shift = 0, i = 0; i < 4; shift += 8, i++) { - int val_a = (pix_a >> shift) & 0xffu; - int val_b = (pix_b >> shift) & 0xffu; - int d = val_b - val_a; - - stat->ch[i].min_diff = min(stat->ch[i].min_diff, d); - stat->ch[i].max_diff = max(stat->ch[i].max_diff, d); - - if (d < fuzz->a || d > fuzz->b) - ret = false; - } - - return ret; -} - -/** - * Test if a given region within two images are pixel-identical - * - * Returns true if the two images pixel-wise identical, and false otherwise. - * - * \param img_a First image. - * \param img_b Second image. - * \param clip_rect The region of interest, or NULL for comparing the whole - * images. - * \param prec Per-channel allowed difference, or NULL for identical match - * required. - * - * This function hard-fails if clip_rect is not inside both images. If clip_rect - * is given, the images do not have to match in size, otherwise size mismatch - * will be a hard failure. - * - * The per-pixel, per-channel difference is computed as img_b - img_a which is - * required to be in the range [prec->a, prec->b] inclusive. The difference is - * signed. All four channels are compared the same way, without any special - * meaning on alpha channel. - */ -bool -check_images_match(pixman_image_t *img_a, pixman_image_t *img_b, - const struct rectangle *clip_rect, const struct range *prec) -{ - struct range fuzz = range_get(prec); - struct pixel_diff_stat diffstat = {}; - struct image_iterator it_a; - struct image_iterator it_b; - pixman_box32_t box; - int x, y; - uint32_t *pix_a; - uint32_t *pix_b; - - box = image_check_get_roi(img_a, img_b, clip_rect); - - image_iter_init(&it_a, img_a); - image_iter_init(&it_b, img_b); - - for (y = box.y1; y < box.y2; y++) { - pix_a = image_iter_get_row(&it_a, y) + box.x1; - pix_b = image_iter_get_row(&it_b, y) + box.x1; - - for (x = box.x1; x < box.x2; x++) { - if (!fuzzy_match_pixels(*pix_a, *pix_b, - &fuzz, &diffstat)) - return false; - - pix_a++; - pix_b++; - } - } - - return true; -} - -/** - * Tint a color - * - * \param src Source pixel as x8r8g8b8. - * \param add The tint as x8r8g8b8, x8 must be zero; r8, g8 and b8 must be - * no greater than 0xc0 to avoid overflow to another channel. - * \return The tinted pixel color as x8r8g8b8, x8 guaranteed to be 0xff. - * - * The source pixel RGB values are divided by 4, and then the tint is added. - * To achieve colors outside of the range of src, a tint color channel must be - * at least 0x40. (0xff / 4 = 0x3f, 0xff - 0x3f = 0xc0) - */ -static uint32_t -tint(uint32_t src, uint32_t add) -{ - uint32_t v; - - v = ((src & 0xfcfcfcfc) >> 2) | 0xff000000; - - return v + add; -} - -/** - * Create a visualization of image differences. - * - * \param img_a First image, which is used as the basis for the output. - * \param img_b Second image. - * \param clip_rect The region of interest, or NULL for comparing the whole - * images. - * \param prec Per-channel allowed difference, or NULL for identical match - * required. - * \return A new image with the differences highlighted. - * - * Regions outside of the region of interest are shaded with black, matching - * pixels are shaded with green, and differing pixels are shaded with - * bright red. - * - * This function hard-fails if clip_rect is not inside both images. If clip_rect - * is given, the images do not have to match in size, otherwise size mismatch - * will be a hard failure. - * - * The per-pixel, per-channel difference is computed as img_b - img_a which is - * required to be in the range [prec->a, prec->b] inclusive. The difference is - * signed. All four channels are compared the same way, without any special - * meaning on alpha channel. - */ -pixman_image_t * -visualize_image_difference(pixman_image_t *img_a, pixman_image_t *img_b, - const struct rectangle *clip_rect, - const struct range *prec) -{ - struct range fuzz = range_get(prec); - struct pixel_diff_stat diffstat = {}; - pixman_image_t *diffimg; - pixman_image_t *shade; - struct image_iterator it_a; - struct image_iterator it_b; - struct image_iterator it_d; - int width; - int height; - pixman_box32_t box; - int x, y; - uint32_t *pix_a; - uint32_t *pix_b; - uint32_t *pix_d; - pixman_color_t shade_color = { 0, 0, 0, 32768 }; - - width = pixman_image_get_width(img_a); - height = pixman_image_get_height(img_a); - box = image_check_get_roi(img_a, img_b, clip_rect); - - diffimg = pixman_image_create_bits_no_clear(PIXMAN_x8r8g8b8, - width, height, NULL, 0); - - /* Fill diffimg with a black-shaded copy of img_a, and then fill - * the clip_rect area with original img_a. - */ - shade = pixman_image_create_solid_fill(&shade_color); - pixman_image_composite32(PIXMAN_OP_SRC, img_a, shade, diffimg, - 0, 0, 0, 0, 0, 0, width, height); - pixman_image_unref(shade); - pixman_image_composite32(PIXMAN_OP_SRC, img_a, NULL, diffimg, - box.x1, box.y1, 0, 0, box.x1, box.y1, - box.x2 - box.x1, box.y2 - box.y1); - - image_iter_init(&it_a, img_a); - image_iter_init(&it_b, img_b); - image_iter_init(&it_d, diffimg); - - for (y = box.y1; y < box.y2; y++) { - pix_a = image_iter_get_row(&it_a, y) + box.x1; - pix_b = image_iter_get_row(&it_b, y) + box.x1; - pix_d = image_iter_get_row(&it_d, y) + box.x1; - - for (x = box.x1; x < box.x2; x++) { - if (fuzzy_match_pixels(*pix_a, *pix_b, - &fuzz, &diffstat)) - *pix_d = tint(*pix_d, 0x00008000); /* green */ - else - *pix_d = tint(*pix_d, 0x00c00000); /* red */ - - pix_a++; - pix_b++; - pix_d++; - } - } - - testlog_pixel_diff_stat(&diffstat); - - return diffimg; -} - -/** - * Write an image into a PNG file. - * - * \param image The image. - * \param fname The name and path for the file. - * - * \returns true if successfully saved file; false otherwise. - * - * \note Only image formats directly supported by Cairo are accepted, not all - * Pixman formats. - */ -bool -write_image_as_png(pixman_image_t *image, const char *fname) -{ - cairo_surface_t *cairo_surface; - cairo_status_t status; - cairo_format_t fmt; - - fmt = format_pixman2cairo(pixman_image_get_format(image)); - - cairo_surface = cairo_image_surface_create_for_data( - (void *)pixman_image_get_data(image), - fmt, - pixman_image_get_width(image), - pixman_image_get_height(image), - pixman_image_get_stride(image)); - - status = cairo_surface_write_to_png(cairo_surface, fname); - if (status != CAIRO_STATUS_SUCCESS) { - testlog("Failed to save image '%s': %s\n", fname, - cairo_status_to_string(status)); - - return false; - } - - cairo_surface_destroy(cairo_surface); - - return true; -} - -static pixman_image_t * -image_convert_to_a8r8g8b8(pixman_image_t *image) -{ - pixman_image_t *ret; - int width; - int height; - - if (pixman_image_get_format(image) == PIXMAN_a8r8g8b8) - return pixman_image_ref(image); - - width = pixman_image_get_width(image); - height = pixman_image_get_height(image); - - ret = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8, width, height, - NULL, 0); - assert(ret); - - pixman_image_composite32(PIXMAN_OP_SRC, image, NULL, ret, - 0, 0, 0, 0, 0, 0, width, height); - - return ret; -} - -static void -destroy_cairo_surface(pixman_image_t *image, void *data) -{ - cairo_surface_t *surface = data; - - cairo_surface_destroy(surface); -} - -/** - * Load an image from a PNG file - * - * Reads a PNG image from disk using the given filename (and path) - * and returns as a Pixman image. Use pixman_image_unref() to free it. - * - * The returned image is always in PIXMAN_a8r8g8b8 format. - * - * @returns Pixman image, or NULL in case of error. - */ -pixman_image_t * -load_image_from_png(const char *fname) -{ - pixman_image_t *image; - pixman_image_t *converted; - cairo_format_t cairo_fmt; - pixman_format_code_t pixman_fmt; - cairo_surface_t *reference_cairo_surface; - cairo_status_t status; - int width; - int height; - int stride; - void *data; - - reference_cairo_surface = cairo_image_surface_create_from_png(fname); - cairo_surface_flush(reference_cairo_surface); - status = cairo_surface_status(reference_cairo_surface); - if (status != CAIRO_STATUS_SUCCESS) { - testlog("Could not open %s: %s\n", fname, - cairo_status_to_string(status)); - cairo_surface_destroy(reference_cairo_surface); - return NULL; - } - - cairo_fmt = cairo_image_surface_get_format(reference_cairo_surface); - pixman_fmt = format_cairo2pixman(cairo_fmt); - - width = cairo_image_surface_get_width(reference_cairo_surface); - height = cairo_image_surface_get_height(reference_cairo_surface); - stride = cairo_image_surface_get_stride(reference_cairo_surface); - data = cairo_image_surface_get_data(reference_cairo_surface); - - /* The Cairo surface will own the data, so we keep it around. */ - image = pixman_image_create_bits_no_clear(pixman_fmt, - width, height, data, stride); - assert(image); - - pixman_image_set_destroy_function(image, destroy_cairo_surface, - reference_cairo_surface); - - converted = image_convert_to_a8r8g8b8(image); - pixman_image_unref(image); - - return converted; -} - -/** - * Take screenshot of a single output - * - * Requests a screenshot from the server of the output that the - * client appears on. This implies that the compositor goes through an output - * repaint to provide the screenshot before this function returns. This - * function is therefore both a server roundtrip and a wait for a repaint. - * - * @returns A new buffer object, that should be freed with buffer_destroy(). - */ -struct buffer * -capture_screenshot_of_output(struct client *client) -{ - struct buffer *buffer; - - buffer = create_shm_buffer_a8r8g8b8(client, - client->output->width, - client->output->height); - - client->test->buffer_copy_done = 0; - weston_test_capture_screenshot(client->test->weston_test, - client->output->wl_output, - buffer->proxy); - while (client->test->buffer_copy_done == 0) - if (wl_display_dispatch(client->wl_display) < 0) - break; - - /* FIXME: Document somewhere the orientation the screenshot is taken - * and how the clip coords are interpreted, in case of scaling/transform. - * If we're using read_pixels() just make sure it is documented somewhere. - * Protocol docs in the XML, comparison function docs in Doxygen style. - */ - - return buffer; -} - -static void -write_visual_diff(pixman_image_t *ref_image, - struct buffer *shot, - const struct rectangle *clip, - const char *test_name, - int seq_no, - const struct range *fuzz) -{ - char *fname; - char *ext_test_name; - pixman_image_t *diff; - int ret; - - ret = asprintf(&ext_test_name, "%s-diff", test_name); - assert(ret >= 0); - - fname = screenshot_output_filename(ext_test_name, seq_no); - diff = visualize_image_difference(ref_image, shot->image, clip, fuzz); - write_image_as_png(diff, fname); - - pixman_image_unref(diff); - free(fname); - free(ext_test_name); -} - -/** - * Take a screenshot and verify its contents - * - * Takes a screenshot and writes the image into a PNG file named with - * get_test_name() and seq_no. Compares the contents to the given reference - * image over the given clip rectangle, reports whether they match to the - * test log, and if they do not match writes a visual diff into a PNG file. - * - * The compositor output size and the reference image size must both contain - * the clip rectangle. - * - * This function uses the pixel value allowed fuzz approriate for GL-renderer - * with 8 bits per channel data. - * - * \param client The client, for connecting to the compositor. - * \param ref_image The reference image file basename, without sequence number - * and .png suffix. - * \param ref_seq_no The reference image sequence number. - * \param clip The region of interest, or NULL for comparing the whole - * images. - * \param seq_no Test sequence number, for writing output files. - * \return True if the screen contents matches the reference image, - * false otherwise. - * - * For bootstrapping, ref_image can be NULL or the file can be missing. - * In that case the screenshot file is written but no comparison is performed, - * and false is returned. - */ -bool -verify_screen_content(struct client *client, - const char *ref_image, - int ref_seq_no, - const struct rectangle *clip, - int seq_no) -{ - const char *test_name = get_test_name(); - const struct range gl_fuzz = { -3, 4 }; - struct buffer *shot; - pixman_image_t *ref = NULL; - char *ref_fname = NULL; - char *shot_fname; - bool match; - - shot = capture_screenshot_of_output(client); - assert(shot); - shot_fname = screenshot_output_filename(test_name, seq_no); - write_image_as_png(shot->image, shot_fname); - - if (ref_image) { - ref_fname = screenshot_reference_filename(ref_image, ref_seq_no); - ref = load_image_from_png(ref_fname); - } - - if (ref) { - match = check_images_match(ref, shot->image, clip, &gl_fuzz); - testlog("Verify reference image %s vs. shot %s: %s\n", - ref_fname, shot_fname, match ? "PASS" : "FAIL"); - - if (!match) { - write_visual_diff(ref, shot, clip, - test_name, seq_no, &gl_fuzz); - } - - pixman_image_unref(ref); - } else { - testlog("No reference image, shot %s: FAIL\n", shot_fname); - match = false; - } - - free(ref_fname); - buffer_destroy(shot); - free(shot_fname); - - return match; -} - -/** - * Create a wl_buffer from a PNG file - * - * Loads the named PNG file from the directory of reference images, - * creates a wl_buffer with scale times the image dimensions in pixels, - * and copies the image content into the buffer using nearest-neighbor filter. - * - * \param client The client, for the Wayland connection. - * \param basename The PNG file name without .png suffix. - * \param scale Upscaling factor >= 1. - */ -struct buffer * -client_buffer_from_image_file(struct client *client, - const char *basename, - int scale) -{ - struct buffer *buf; - char *fname; - pixman_image_t *img; - int buf_w, buf_h; - pixman_transform_t scaling; - - assert(scale >= 1); - - fname = image_filename(basename); - img = load_image_from_png(fname); - free(fname); - assert(img); - - buf_w = scale * pixman_image_get_width(img); - buf_h = scale * pixman_image_get_height(img); - buf = create_shm_buffer_a8r8g8b8(client, buf_w, buf_h); - - pixman_transform_init_scale(&scaling, - pixman_fixed_1 / scale, - pixman_fixed_1 / scale); - pixman_image_set_transform(img, &scaling); - pixman_image_set_filter(img, PIXMAN_FILTER_NEAREST, NULL, 0); - - pixman_image_composite32(PIXMAN_OP_SRC, - img, /* src */ - NULL, /* mask */ - buf->image, /* dst */ - 0, 0, /* src x,y */ - 0, 0, /* mask x,y */ - 0, 0, /* dst x,y */ - buf_w, buf_h); - pixman_image_unref(img); - - return buf; -} - -/** - * Bind to a singleton global in wl_registry - * - * \param client Client whose registry and globals to use. - * \param iface The Wayland interface to look for. - * \param version The version to bind the interface with. - * \return A struct wl_proxy, which you need to cast to the proper type. - * - * Asserts that the global being searched for is a singleton and is found. - * - * Binds with the exact version given, does not take compositor interface - * version into account. - */ -void * -bind_to_singleton_global(struct client *client, - const struct wl_interface *iface, - int version) -{ - struct global *tmp; - struct global *g = NULL; - struct wl_proxy *proxy; - - wl_list_for_each(tmp, &client->global_list, link) { - if (strcmp(tmp->interface, iface->name)) - continue; - - assert(!g && "multiple singleton objects"); - g = tmp; - } - - assert(g && "singleton not found"); - - proxy = wl_registry_bind(client->wl_registry, g->name, iface, version); - assert(proxy); - - return proxy; -} - -/** - * Create a wp_viewport for the client surface - * - * \param client The client->surface to use. - * \return A fresh viewport object. - */ -struct wp_viewport * -client_create_viewport(struct client *client) -{ - struct wp_viewporter *viewporter; - struct wp_viewport *viewport; - - viewporter = bind_to_singleton_global(client, - &wp_viewporter_interface, 1); - viewport = wp_viewporter_get_viewport(viewporter, - client->surface->wl_surface); - assert(viewport); - wp_viewporter_destroy(viewporter); - - return viewport; -} - -/** - * Fill the image with the given color - * - * \param image The image to write to. - * \param color The color to use. - */ -void -fill_image_with_color(pixman_image_t *image, pixman_color_t *color) -{ - pixman_image_t *solid; - int width; - int height; - - width = pixman_image_get_width(image); - height = pixman_image_get_height(image); - - solid = pixman_image_create_solid_fill(color); - pixman_image_composite32(PIXMAN_OP_SRC, - solid, /* src */ - NULL, /* mask */ - image, /* dst */ - 0, 0, /* src x,y */ - 0, 0, /* mask x,y */ - 0, 0, /* dst x,y */ - width, height); - pixman_image_unref(solid); -} - -/** - * Convert 8-bit RGB to opaque Pixman color - * - * \param tmp Pixman color struct to fill in. - * \param r Red value, 0 - 255. - * \param g Green value, 0 - 255. - * \param b Blue value, 0 - 255. - * \return tmp - */ -pixman_color_t * -color_rgb888(pixman_color_t *tmp, uint8_t r, uint8_t g, uint8_t b) -{ - tmp->alpha = 65535; - tmp->red = (r << 8) + r; - tmp->green = (g << 8) + g; - tmp->blue = (b << 8) + b; - - return tmp; -} diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h deleted file mode 100644 index bda330e..0000000 --- a/tests/weston-test-client-helper.h +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _WESTON_TEST_CLIENT_HELPER_H_ -#define _WESTON_TEST_CLIENT_HELPER_H_ - -#include "config.h" - -#include -#include -#include -#include -#include - -#include -#include "weston-test-runner.h" -#include "weston-test-client-protocol.h" -#include "viewporter-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; - /* the seat that is actually used for input events */ - struct input *input; - /* server can have more wl_seats. We need keep them all until we - * find the one that we need. After that, the others - * will be destroyed, so this list will have the length of 1. - * If some day in the future we will need the other seats, - * we can just keep them here. */ - struct wl_list inputs; - struct output *output; - struct surface *surface; - int has_argb; - struct wl_list global_list; - bool has_wl_drm; - struct wl_list output_list; /* struct output::link */ -}; - -struct global { - uint32_t name; - char *interface; - uint32_t version; - struct wl_list link; -}; - -struct test { - struct weston_test *weston_test; - int pointer_x; - int pointer_y; - uint32_t n_egl_buffers; - int buffer_copy_done; -}; - -struct input { - struct client *client; - uint32_t global_name; - struct wl_seat *wl_seat; - struct pointer *pointer; - struct keyboard *keyboard; - struct touch *touch; - char *seat_name; - enum wl_seat_capability caps; - struct wl_list link; -}; - -struct pointer { - struct wl_pointer *wl_pointer; - struct surface *focus; - int x; - int y; - uint32_t button; - uint32_t state; - uint32_t axis; - double axis_value; - uint32_t motion_time_msec; - uint32_t button_time_msec; - uint32_t axis_time_msec; - uint32_t axis_stop_time_msec; - struct timespec input_timestamp; - struct timespec motion_time_timespec; - struct timespec button_time_timespec; - struct timespec axis_time_timespec; - struct timespec axis_stop_time_timespec; -}; - -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 { - int rate; - int delay; - } repeat_info; - uint32_t key_time_msec; - struct timespec input_timestamp; - struct timespec key_time_timespec; -}; - -struct touch { - struct wl_touch *wl_touch; - int down_x; - int down_y; - int x; - int y; - int id; - int up_id; /* id of last wl_touch.up event */ - int frame_no; - int cancel_no; - uint32_t down_time_msec; - uint32_t up_time_msec; - uint32_t motion_time_msec; - struct timespec input_timestamp; - struct timespec down_time_timespec; - struct timespec up_time_timespec; - struct timespec motion_time_timespec; -}; - -struct output { - struct wl_output *wl_output; - struct wl_list link; /* struct client::output_list */ - int x; - int y; - int width; - int height; - int scale; - int initialized; -}; - -struct buffer { - struct wl_buffer *proxy; - size_t len; - pixman_image_t *image; -}; - -struct surface { - struct wl_surface *wl_surface; - struct output *output; /* not owned */ - int x; - int y; - int width; - int height; - struct buffer *buffer; -}; - -struct rectangle { - int x; - int y; - int width; - int height; -}; - -struct range { - int a; - int b; -}; - -struct client * -create_client(void); - -void -client_destroy(struct client *client); - -struct surface * -create_test_surface(struct client *client); - -void -surface_destroy(struct surface *surface); - -struct client * -create_client_and_test_surface(int x, int y, int width, int height); - -struct buffer * -create_shm_buffer_a8r8g8b8(struct client *client, int width, int height); - -void -buffer_destroy(struct buffer *buf); - -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); - -int -frame_callback_wait_nofail(struct client *client, int *done); - -#define frame_callback_wait(c, d) assert(frame_callback_wait_nofail((c), (d))) - -void -skip(const char *fmt, ...); - -void -expect_protocol_error(struct client *client, - const struct wl_interface *intf, uint32_t code); - -char * -screenshot_output_filename(const char *basename, uint32_t seq); - -char * -screenshot_reference_filename(const char *basename, uint32_t seq); - -char * -image_filename(const char *basename); - -bool -check_images_match(pixman_image_t *img_a, pixman_image_t *img_b, - const struct rectangle *clip, - const struct range *prec); - -pixman_image_t * -visualize_image_difference(pixman_image_t *img_a, pixman_image_t *img_b, - const struct rectangle *clip_rect, - const struct range *prec); - -bool -write_image_as_png(pixman_image_t *image, const char *fname); - -pixman_image_t * -load_image_from_png(const char *fname); - -struct buffer * -capture_screenshot_of_output(struct client *client); - -bool -verify_screen_content(struct client *client, - const char *ref_image, - int ref_seq_no, - const struct rectangle *clip, - int seq_no); - -struct buffer * -client_buffer_from_image_file(struct client *client, - const char *basename, - int scale); - -void * -bind_to_singleton_global(struct client *client, - const struct wl_interface *iface, - int version); - -struct wp_viewport * -client_create_viewport(struct client *client); - -void -fill_image_with_color(pixman_image_t *image, pixman_color_t *color); - -pixman_color_t * -color_rgb888(pixman_color_t *tmp, uint8_t r, uint8_t g, uint8_t b); - -#endif diff --git a/tests/weston-test-desktop-shell.c b/tests/weston-test-desktop-shell.c deleted file mode 100644 index f3d881b..0000000 --- a/tests/weston-test-desktop-shell.c +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright © 2010-2012 Intel Corporation - * Copyright © 2011-2012 Collabora, Ltd. - * Copyright © 2013 Raspberry Pi Foundation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "compositor/weston.h" -#include -#include "shared/helpers.h" -#include - -struct desktest_shell { - struct wl_listener compositor_destroy_listener; - struct weston_desktop *desktop; - struct weston_layer background_layer; - struct weston_surface *background_surface; - struct weston_view *background_view; - struct weston_layer layer; - struct weston_view *view; -}; - -static void -desktop_surface_added(struct weston_desktop_surface *desktop_surface, - void *shell) -{ - struct desktest_shell *dts = shell; - - assert(!dts->view); - - dts->view = weston_desktop_surface_create_view(desktop_surface); - - assert(dts->view); -} - -static void -desktop_surface_removed(struct weston_desktop_surface *desktop_surface, - void *shell) -{ - struct desktest_shell *dts = shell; - - assert(dts->view); - - weston_desktop_surface_unlink_view(dts->view); - weston_view_destroy(dts->view); - dts->view = NULL; -} - -static void -desktop_surface_committed(struct weston_desktop_surface *desktop_surface, - int32_t sx, int32_t sy, void *shell) -{ - struct desktest_shell *dts = shell; - struct weston_surface *surface = - weston_desktop_surface_get_surface(desktop_surface); - struct weston_geometry geometry = - weston_desktop_surface_get_geometry(desktop_surface); - - assert(dts->view); - - if (weston_surface_is_mapped(surface)) - return; - - surface->is_mapped = true; - weston_layer_entry_insert(&dts->layer.view_list, &dts->view->layer_link); - weston_view_set_position(dts->view, 0 - geometry.x, 0 - geometry.y); - weston_view_update_transform(dts->view); - dts->view->is_mapped = true; -} - -static void -desktop_surface_move(struct weston_desktop_surface *desktop_surface, - struct weston_seat *seat, uint32_t serial, void *shell) -{ -} - -static void -desktop_surface_resize(struct weston_desktop_surface *desktop_surface, - struct weston_seat *seat, uint32_t serial, - enum weston_desktop_surface_edge edges, void *shell) -{ -} - -static void -desktop_surface_fullscreen_requested(struct weston_desktop_surface *desktop_surface, - bool fullscreen, - struct weston_output *output, void *shell) -{ -} - -static void -desktop_surface_maximized_requested(struct weston_desktop_surface *desktop_surface, - bool maximized, void *shell) -{ -} - -static void -desktop_surface_minimized_requested(struct weston_desktop_surface *desktop_surface, - void *shell) -{ -} - -static void -desktop_surface_ping_timeout(struct weston_desktop_client *desktop_client, - void *shell) -{ -} - -static void -desktop_surface_pong(struct weston_desktop_client *desktop_client, - void *shell) -{ -} - -static const struct weston_desktop_api shell_desktop_api = { - .struct_size = sizeof(struct weston_desktop_api), - .surface_added = desktop_surface_added, - .surface_removed = desktop_surface_removed, - .committed = desktop_surface_committed, - .move = desktop_surface_move, - .resize = desktop_surface_resize, - .fullscreen_requested = desktop_surface_fullscreen_requested, - .maximized_requested = desktop_surface_maximized_requested, - .minimized_requested = desktop_surface_minimized_requested, - .ping_timeout = desktop_surface_ping_timeout, - .pong = desktop_surface_pong, -}; - -static void -shell_destroy(struct wl_listener *listener, void *data) -{ - struct desktest_shell *dts; - - dts = container_of(listener, struct desktest_shell, - compositor_destroy_listener); - - weston_desktop_destroy(dts->desktop); - weston_view_destroy(dts->background_view); - weston_surface_destroy(dts->background_surface); - free(dts); -} - -WL_EXPORT int -wet_shell_init(struct weston_compositor *ec, - int *argc, char *argv[]) -{ - struct desktest_shell *dts; - - dts = zalloc(sizeof *dts); - if (!dts) - return -1; - - if (!weston_compositor_add_destroy_listener_once(ec, - &dts->compositor_destroy_listener, - shell_destroy)) { - free(dts); - return 0; - } - - weston_layer_init(&dts->layer, ec); - weston_layer_init(&dts->background_layer, ec); - - weston_layer_set_position(&dts->layer, - WESTON_LAYER_POSITION_NORMAL); - weston_layer_set_position(&dts->background_layer, - WESTON_LAYER_POSITION_BACKGROUND); - - dts->background_surface = weston_surface_create(ec); - if (dts->background_surface == NULL) - goto out_free; - - dts->background_view = weston_view_create(dts->background_surface); - if (dts->background_view == NULL) - goto out_surface; - - weston_surface_set_color(dts->background_surface, 0.16, 0.32, 0.48, 1.); - pixman_region32_fini(&dts->background_surface->opaque); - pixman_region32_init_rect(&dts->background_surface->opaque, 0, 0, 2000, 2000); - pixman_region32_fini(&dts->background_surface->input); - pixman_region32_init_rect(&dts->background_surface->input, 0, 0, 2000, 2000); - - weston_surface_set_size(dts->background_surface, 2000, 2000); - weston_view_set_position(dts->background_view, 0, 0); - weston_layer_entry_insert(&dts->background_layer.view_list, &dts->background_view->layer_link); - weston_view_update_transform(dts->background_view); - - dts->desktop = weston_desktop_create(ec, &shell_desktop_api, dts); - if (dts->desktop == NULL) - goto out_view; - - return 0; - -out_view: - weston_view_destroy(dts->background_view); - -out_surface: - weston_surface_destroy(dts->background_surface); - -out_free: - wl_list_remove(&dts->compositor_destroy_listener.link); - free(dts); - - return -1; -} diff --git a/tests/weston-test-fixture-compositor.c b/tests/weston-test-fixture-compositor.c deleted file mode 100644 index 0c9855f..0000000 --- a/tests/weston-test-fixture-compositor.c +++ /dev/null @@ -1,427 +0,0 @@ -/* - * Copyright 2019 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "shared/helpers.h" -#include "weston-test-fixture-compositor.h" -#include "weston.h" -#include "test-config.h" - -struct prog_args { - int argc; - char **argv; - char **saved; - int alloc; -}; - -static void -prog_args_init(struct prog_args *p) -{ - memset(p, 0, sizeof(*p)); -} - -static void -prog_args_take(struct prog_args *p, char *arg) -{ - assert(arg); - - if (p->argc == p->alloc) { - p->alloc += 10; - p->argv = realloc(p->argv, sizeof(char *) * p->alloc); - assert(p->argv); - } - - p->argv[p->argc++] = arg; -} - -/* - * The program to be executed will trample on argv, hence we need a copy to - * be able to free all our args. - */ -static void -prog_args_save(struct prog_args *p) -{ - assert(p->saved == NULL); - - p->saved = calloc(p->argc, sizeof(char *)); - assert(p->saved); - - memcpy(p->saved, p->argv, sizeof(char *) * p->argc); -} - -static void -prog_args_fini(struct prog_args *p) -{ - int i; - - assert(p->saved); - - for (i = 0; i < p->argc; i++) - free(p->saved[i]); - free(p->saved); - free(p->argv); - prog_args_init(p); -} - -static char * -get_lock_path(void) -{ - const char *env_path, *suffix; - char *lock_path; - - suffix = "weston-test-suite-drm-lock"; - env_path = getenv("XDG_RUNTIME_DIR"); - if (!env_path) { - fprintf(stderr, "Failed to compute lock file path. " \ - "XDG_RUNTIME_DIR is not set.\n"); - return NULL; - } - - if (asprintf(&lock_path, "%s/%s", env_path, suffix) == -1) - return NULL; - - return lock_path; -} - -/* - * DRM-backend tests need to be run sequentially, since there can only be - * one user at a time with master status in a DRM KMS device. Since the - * test suite runs the tests in parallel, there's a mechanism to assure - * only one DRM-backend test is running at a time: tests of this type keep - * waiting until they acquire a lock (which is hold until they end). - * - * Returns -1 in failure and fd in success. - */ -static int -wait_for_lock(void) -{ - char *lock_path; - int fd; - - lock_path = get_lock_path(); - if (!lock_path) - goto err_path; - - fd = open(lock_path, O_RDWR | O_CLOEXEC | O_CREAT, 00700); - if (fd == -1) { - fprintf(stderr, "Could not open lock file %s: %s\n", lock_path, strerror(errno)); - goto err_open; - } - fprintf(stderr, "Waiting for lock on %s...\n", lock_path); - - /* The call is blocking, so we don't need a 'while'. Also, as we - * have a timeout for each test, this won't get stuck waiting. */ - if (flock(fd, LOCK_EX) == -1) { - fprintf(stderr, "Could not lock %s: %s\n", lock_path, strerror(errno)); - goto err_lock; - } - fprintf(stderr, "Lock %s acquired.\n", lock_path); - free(lock_path); - - return fd; - -err_lock: - close(fd); -err_open: - free(lock_path); -err_path: - return -1; -} - -/** Initialize part of compositor setup - * - * \param setup The variable to initialize. - * \param testset_name Value for testset_name member. - * - * \ingroup testharness_private - */ -void -compositor_setup_defaults_(struct compositor_setup *setup, - const char *testset_name) -{ - *setup = (struct compositor_setup) { - .backend = WESTON_BACKEND_HEADLESS, - .renderer = RENDERER_NOOP, - .shell = SHELL_DESKTOP, - .xwayland = false, - .width = 320, - .height = 240, - .scale = 1, - .transform = WL_OUTPUT_TRANSFORM_NORMAL, - .config_file = NULL, - .extra_module = NULL, - .logging_scopes = NULL, - .testset_name = testset_name, - }; -} - -static const char * -backend_to_str(enum weston_compositor_backend b) -{ - static const char * const names[] = { - [WESTON_BACKEND_DRM] = "drm-backend.so", - [WESTON_BACKEND_FBDEV] = "fbdev-backend.so", - [WESTON_BACKEND_HEADLESS] = "headless-backend.so", - [WESTON_BACKEND_RDP] = "rdp-backend.so", - [WESTON_BACKEND_WAYLAND] = "wayland-backend.so", - [WESTON_BACKEND_X11] = "X11-backend.so", - }; - assert(b >= 0 && b < ARRAY_LENGTH(names)); - return names[b]; -} - -static const char * -renderer_to_arg(enum weston_compositor_backend b, enum renderer_type r) -{ - static const char * const headless_names[] = { - [RENDERER_NOOP] = NULL, - [RENDERER_PIXMAN] = "--use-pixman", - [RENDERER_GL] = "--use-gl", - }; - static const char * const drm_names[] = { - [RENDERER_PIXMAN] = "--use-pixman", - [RENDERER_GL] = NULL, - }; - - switch (b) { - case WESTON_BACKEND_HEADLESS: - assert(r >= RENDERER_NOOP && r <= RENDERER_GL); - return headless_names[r]; - case WESTON_BACKEND_DRM: - assert(r >= RENDERER_PIXMAN && r <= RENDERER_GL); - return drm_names[r]; - default: - assert(0 && "renderer_to_str() does not know the backend"); - } - - return NULL; -} - -static const char * -shell_to_str(enum shell_type t) -{ - static const char * const names[] = { - [SHELL_TEST_DESKTOP] = "weston-test-desktop-shell.so", - [SHELL_DESKTOP] = "desktop-shell.so", - [SHELL_FULLSCREEN] = "fullscreen-shell.so", - [SHELL_IVI] = "ivi-shell.so", - }; - assert(t >= 0 && t < ARRAY_LENGTH(names)); - return names[t]; -} - -static const char * -transform_to_str(enum wl_output_transform t) -{ - static const char * const names[] = { - [WL_OUTPUT_TRANSFORM_NORMAL] = "normal", - [WL_OUTPUT_TRANSFORM_90] = "rotate-90", - [WL_OUTPUT_TRANSFORM_180] = "rotate-180", - [WL_OUTPUT_TRANSFORM_270] = "rotate-270", - [WL_OUTPUT_TRANSFORM_FLIPPED] = "flipped", - [WL_OUTPUT_TRANSFORM_FLIPPED_90] = "flipped-rotate-90", - [WL_OUTPUT_TRANSFORM_FLIPPED_180] = "flipped-rotate-180", - [WL_OUTPUT_TRANSFORM_FLIPPED_270] = "flipped-rotate-270", - }; - - assert(t < ARRAY_LENGTH(names) && names[t]); - return names[t]; -} - -/** Execute compositor - * - * Manufactures the compositor command line and calls wet_main(). - * - * Returns RESULT_SKIP if the given setup contains features that were disabled - * in the build, e.g. GL-renderer or DRM-backend. - * - * \ingroup testharness_private - */ -int -execute_compositor(const struct compositor_setup *setup, - struct wet_testsuite_data *data) -{ - struct prog_args args; - char *tmp; - const char *ctmp, *drm_device; - int ret, lock_fd = -1; - - if (setenv("WESTON_MODULE_MAP", WESTON_MODULE_MAP, 0) < 0 || - setenv("WESTON_DATA_DIR", WESTON_DATA_DIR, 0) < 0) { - fprintf(stderr, "Error: environment setup failed.\n"); - return RESULT_HARD_ERROR; - } - -#ifndef BUILD_DRM_COMPOSITOR - if (setup->backend == WESTON_BACKEND_DRM) { - fprintf(stderr, "DRM-backend required but not built, skipping.\n"); - return RESULT_SKIP; - } -#endif - -#ifndef BUILD_FBDEV_COMPOSITOR - if (setup->backend == WESTON_BACKEND_FBDEV) { - fprintf(stderr, "fbdev-backend required but not built, skipping.\n"); - return RESULT_SKIP; - } -#endif - -#ifndef BUILD_RDP_COMPOSITOR - if (setup->backend == WESTON_BACKEND_RDP) { - fprintf(stderr, "RDP-backend required but not built, skipping.\n"); - return RESULT_SKIP; - } -#endif - -#ifndef BUILD_WAYLAND_COMPOSITOR - if (setup->backend == WESTON_BACKEND_WAYLAND) { - fprintf(stderr, "wayland-backend required but not built, skipping.\n"); - return RESULT_SKIP; - } -#endif - -#ifndef BUILD_X11_COMPOSITOR - if (setup->backend == WESTON_BACKEND_X11) { - fprintf(stderr, "X11-backend required but not built, skipping.\n"); - return RESULT_SKIP; - } -#endif - -#ifndef ENABLE_EGL - if (setup->renderer == RENDERER_GL) { - fprintf(stderr, "GL-renderer required but not built, skipping.\n"); - return RESULT_SKIP; - } -#endif - -#if !TEST_GL_RENDERER - if (setup->renderer == RENDERER_GL) { - fprintf(stderr, "GL-renderer disabled for tests, skipping.\n"); - return RESULT_SKIP; - } -#endif - - prog_args_init(&args); - - /* argv[0] */ - asprintf(&tmp, "weston-%s", setup->testset_name); - prog_args_take(&args, tmp); - - asprintf(&tmp, "--backend=%s", backend_to_str(setup->backend)); - prog_args_take(&args, tmp); - - if (setup->backend == WESTON_BACKEND_DRM) { - - drm_device = getenv("WESTON_TEST_SUITE_DRM_DEVICE"); - if (!drm_device) { - fprintf(stderr, "Skipping DRM-backend tests because " \ - "WESTON_TEST_SUITE_DRM_DEVICE is not set. " \ - "See test suite documentation to learn how " \ - "to run them.\n"); - return RESULT_SKIP; - } - asprintf(&tmp, "--drm-device=%s", drm_device); - prog_args_take(&args, tmp); - - prog_args_take(&args, strdup("--seat=weston-test-seat")); - - prog_args_take(&args, strdup("--continue-without-input")); - - lock_fd = wait_for_lock(); - if (lock_fd == -1) - return RESULT_FAIL; - } - - asprintf(&tmp, "--socket=%s", setup->testset_name); - prog_args_take(&args, tmp); - - asprintf(&tmp, "--modules=%s%s%s", TESTSUITE_PLUGIN_PATH, - setup->extra_module ? "," : "", - setup->extra_module ? setup->extra_module : ""); - prog_args_take(&args, tmp); - - if (setup->backend != WESTON_BACKEND_DRM && - setup->backend != WESTON_BACKEND_FBDEV) { - asprintf(&tmp, "--width=%d", setup->width); - prog_args_take(&args, tmp); - - asprintf(&tmp, "--height=%d", setup->height); - prog_args_take(&args, tmp); - } - - if (setup->scale != 1) { - asprintf(&tmp, "--scale=%d", setup->scale); - prog_args_take(&args, tmp); - } - - if (setup->transform != WL_OUTPUT_TRANSFORM_NORMAL) { - asprintf(&tmp, "--transform=%s", - transform_to_str(setup->transform)); - prog_args_take(&args, tmp); - } - - if (setup->config_file) { - asprintf(&tmp, "--config=%s", setup->config_file); - prog_args_take(&args, tmp); - } else { - prog_args_take(&args, strdup("--no-config")); - } - - ctmp = renderer_to_arg(setup->backend, setup->renderer); - if (ctmp) - prog_args_take(&args, strdup(ctmp)); - - asprintf(&tmp, "--shell=%s", shell_to_str(setup->shell)); - prog_args_take(&args, tmp); - - if (setup->logging_scopes) { - asprintf(&tmp, "--logger-scopes=%s", setup->logging_scopes); - prog_args_take(&args, tmp); - } - - if (setup->xwayland) - prog_args_take(&args, strdup("--xwayland")); - - wet_testsuite_data_set(data); - prog_args_save(&args); - ret = wet_main(args.argc, args.argv); - - prog_args_fini(&args); - - /* We acquired a lock (if this is a DRM-backend test) and now we can - * close its fd and release it, as it has already been run. */ - if (lock_fd != -1) - close(lock_fd); - - return ret; -} diff --git a/tests/weston-test-fixture-compositor.h b/tests/weston-test-fixture-compositor.h deleted file mode 100644 index fd8d0e5..0000000 --- a/tests/weston-test-fixture-compositor.h +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2019 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_TEST_FIXTURE_COMPOSITOR_H -#define WESTON_TEST_FIXTURE_COMPOSITOR_H - -#include -#include - -#include "weston-testsuite-data.h" - -/** Weston renderer type - * - * \sa compositor_setup - * \ingroup testharness - */ -enum renderer_type { - /** Dummy renderer that does nothing. */ - RENDERER_NOOP = 0, - /** Pixman-renderer */ - RENDERER_PIXMAN, - /** GL-renderer */ - RENDERER_GL -}; - -/** Weston shell plugin - * - * \sa compositor_setup - * \ingroup testharness - */ -enum shell_type { - /** Desktop test-shell with predictable window placement and - * no helper clients */ - SHELL_TEST_DESKTOP = 0, - /** The full desktop shell. */ - SHELL_DESKTOP, - /** The ivi-shell. */ - SHELL_IVI, - /** The fullscreen-shell. */ - SHELL_FULLSCREEN -}; - -/** Weston compositor configuration - * - * This structure determines the Weston compositor command line arguments. - * You should always use compositor_setup_defaults() to initialize this, then - * override any members you need with assignments. - * - * \ingroup testharness - */ -struct compositor_setup { - /** The backend to use. */ - enum weston_compositor_backend backend; - /** The renderer to use. */ - enum renderer_type renderer; - /** The shell plugin to use. */ - enum shell_type shell; - /** Whether to enable xwayland support. */ - bool xwayland; - /** Default output width. */ - unsigned width; - /** Default output height. */ - unsigned height; - /** Default output scale. */ - int scale; - /** Default output transform, one of WL_OUTPUT_TRANSFORM_*. */ - enum wl_output_transform transform; - /** The absolute path to \c weston.ini to use, - * or NULL for \c --no-config . */ - const char *config_file; - /** Full path to an extra plugin to load, or NULL for none. */ - const char *extra_module; - /** Debug scopes for the compositor log, - * or NULL for compositor defaults. */ - const char *logging_scopes; - /** The name of this test program, used as a unique identifier. */ - const char *testset_name; -}; - -void -compositor_setup_defaults_(struct compositor_setup *setup, - const char *testset_name); - -/** Initialize compositor setup to defaults - * - * \param s The variable to initialize. - * - * The defaults are: - * - backend: headless - * - renderer: noop - * - shell: desktop shell - * - xwayland: no - * - width: 320 - * - height: 240 - * - scale: 1 - * - transform: WL_OUTPUT_TRANSFORM_NORMAL - * - config_file: none - * - extra_module: none - * - logging_scopes: compositor defaults - * - testset_name: the test name from meson.build - * - * \ingroup testharness - */ -#define compositor_setup_defaults(s) do {\ - compositor_setup_defaults_(s, THIS_TEST_NAME); \ -} while (0) - -int -execute_compositor(const struct compositor_setup *setup, - struct wet_testsuite_data *data); - -#endif /* WESTON_TEST_FIXTURE_COMPOSITOR_H */ diff --git a/tests/weston-test-runner.c b/tests/weston-test-runner.c deleted file mode 100644 index 8e92170..0000000 --- a/tests/weston-test-runner.c +++ /dev/null @@ -1,620 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "weston-test-runner.h" -#include "weston-testsuite-data.h" -#include "shared/string-helpers.h" - -/** - * \defgroup testharness Test harness - * \defgroup testharness_private Test harness private - */ - -extern const struct weston_test_entry __start_test_section, __stop_test_section; - -struct weston_test_run_info { - char name[512]; - int fixture_nr; -}; - -static const struct weston_test_run_info *test_run_info_; - -/** Get the test name string with counter - * - * \return The test name with fixture number \c -f%%d added. For an array - * driven test, e.g. defined with TEST_P(), the name has also a \c -e%%d - * suffix to indicate the array element number. - * - * This is only usable from code paths inside TEST(), TEST_P(), PLUGIN_TEST() - * etc. defined functions. - * - * \ingroup testharness - */ -const char * -get_test_name(void) -{ - return test_run_info_->name; -} - -/** Get the current fixture index - * - * Returns the current fixture index which can be used directly as an index - * into the array passed as an argument to DECLARE_FIXTURE_SETUP_WITH_ARG(). - * - * This is only usable from code paths inside TEST(), TEST_P(), PLUGIN_TEST() - * etc. defined functions. - * - * \ingroup testharness - */ -int -get_test_fixture_index(void) -{ - return test_run_info_->fixture_nr - 1; -} - -/** Print into test log - * - * This is exactly like printf() except the output goes to the test log, - * which is at stderr. - * - * \param fmt printf format string - * - * \ingroup testharness - */ -void -testlog(const char *fmt, ...) -{ - va_list argp; - - va_start(argp, fmt); - vfprintf(stderr, fmt, argp); - va_end(argp); -} - -static const struct weston_test_entry * -find_test(const char *name) -{ - const struct weston_test_entry *t; - - for (t = &__start_test_section; t < &__stop_test_section; t++) - if (strcmp(t->name, name) == 0) - return t; - - return NULL; -} - -static enum test_result_code -run_test(int fixture_nr, const struct weston_test_entry *t, void *data, - int iteration) -{ - struct weston_test_run_info info; - - if (data) { - snprintf(info.name, sizeof(info.name), "%s-f%02d-e%02d", - t->name, fixture_nr, iteration); - } else { - snprintf(info.name, sizeof(info.name), "%s-f%02d", - t->name, fixture_nr); - } - info.fixture_nr = fixture_nr; - - test_run_info_ = &info; - t->run(data); - test_run_info_ = NULL; - - /* - * XXX: We should return t->run(data); but that requires changing - * the function signature and stop using assert() in tests. - * https://gitlab.freedesktop.org/wayland/weston/issues/311 - */ - return RESULT_OK; -} - -static void -list_tests(void) -{ - const struct fixture_setup_array *fsa; - const struct weston_test_entry *t; - - fsa = fixture_setup_array_get_(); - - printf("Fixture setups: %d\n", fsa->n_elements); - - for (t = &__start_test_section; t < &__stop_test_section; t++) { - printf(" %s\n", t->name); - if (t->n_elements > 1) - printf(" with array of %d cases\n", t->n_elements); - } -} - -struct weston_test_harness { - int32_t fixt_ind; - char *chosen_testname; - int32_t case_ind; - - struct wet_testsuite_data data; -}; - -typedef void (*weston_test_cb)(struct wet_testsuite_data *suite_data, - const struct weston_test_entry *t, - const void *test_data, - int iteration); - -static void -for_each_test_case(struct wet_testsuite_data *data, weston_test_cb cb) -{ - unsigned i; - - for (i = 0; i < data->tests_count; i++) { - const struct weston_test_entry *t = &data->tests[i]; - const void *current_test_data = t->table_data; - int elem; - int elem_end; - - if (data->case_index == -1) { - elem = 0; - elem_end = t->n_elements; - } else { - elem = data->case_index; - elem_end = elem + 1; - } - - for (; elem < elem_end; elem++) { - current_test_data = (char *)t->table_data + - elem * t->element_size; - cb(data, t, current_test_data, elem); - } - } -} - -static const char * -result_to_str(enum test_result_code ret) -{ - static const char *names[] = { - [RESULT_FAIL] = "fail", - [RESULT_HARD_ERROR] = "hard error", - [RESULT_OK] = "ok", - [RESULT_SKIP] = "skip", - }; - - assert(ret >= 0 && ret < ARRAY_LENGTH(names)); - return names[ret]; -} - -static void -run_case(struct wet_testsuite_data *suite_data, - const struct weston_test_entry *t, - const void *test_data, - int iteration) -{ - enum test_result_code ret; - const char *fail = ""; - const char *skip = ""; - int fixture_nr = suite_data->fixture_iteration + 1; - int iteration_nr = iteration + 1; - - testlog("*** Run fixture %d, %s/%d\n", - fixture_nr, t->name, iteration_nr); - - if (suite_data->type == TEST_TYPE_PLUGIN) { - ret = run_test(fixture_nr, t, suite_data->compositor, - iteration); - } else { - ret = run_test(fixture_nr, t, (void *)test_data, iteration); - } - - switch (ret) { - case RESULT_OK: - suite_data->passed++; - break; - case RESULT_FAIL: - case RESULT_HARD_ERROR: - suite_data->failed++; - fail = "not "; - break; - case RESULT_SKIP: - suite_data->skipped++; - skip = " # SKIP"; - break; - } - - testlog("*** Result fixture %d, %s/%d: %s\n", - fixture_nr, t->name, iteration_nr, result_to_str(ret)); - - suite_data->counter++; - printf("%sok %d fixture %d %s/%d%s\n", fail, suite_data->counter, - fixture_nr, t->name, iteration_nr, skip); -} - -/* This function might run in a new thread */ -static void -testsuite_run(struct wet_testsuite_data *data) -{ - for_each_test_case(data, run_case); -} - -static void -count_case(struct wet_testsuite_data *suite_data, - const struct weston_test_entry *t, - const void *test_data, - int iteration) -{ - suite_data->total++; -} - -static void -tap_plan(struct wet_testsuite_data *data, int count_fixtures) -{ - data->total = 0; - for_each_test_case(data, count_case); - - printf("1..%d\n", data->total * count_fixtures); -} - -static void -skip_case(struct wet_testsuite_data *suite_data, - const struct weston_test_entry *t, - const void *test_data, - int iteration) -{ - int fixture_nr = suite_data->fixture_iteration + 1; - int iteration_nr = iteration + 1; - - suite_data->counter++; - printf("ok %d fixture %d %s/%d # SKIP fixture\n", suite_data->counter, - fixture_nr, t->name, iteration_nr); -} - -static void -tap_skip_fixture(struct wet_testsuite_data *data) -{ - for_each_test_case(data, skip_case); -} - -static void -help(const char *exe) -{ - printf( - "Usage: %s [options] [testname [index]]\n" - "\n" - "This is a Weston test suite executable that runs some tests.\n" - "Options:\n" - " -f, --fixture N Run only fixture index N. Indices start from 1.\n" - " -h, --help Print this help and exit with success.\n" - " -l, --list List all tests in this executable and exit with success.\n" - "testname: Optional; name of the test to execute instead of all tests.\n" - "index: Optional; for a multi-case test, run the given case only.\n", - exe); -} - -static void -parse_command_line(struct weston_test_harness *harness, int argc, char **argv) -{ - int c; - static const struct option opts[] = { - { "fixture", required_argument, NULL, 'f' }, - { "help", no_argument, NULL, 'h' }, - { "list", no_argument, NULL, 'l' }, - { 0, 0, NULL, 0 } - }; - - while ((c = getopt_long(argc, argv, "f:hl", opts, NULL)) != -1) { - switch (c) { - case 'f': - if (!safe_strtoint(optarg, &harness->fixt_ind)) { - fprintf(stderr, - "Error: '%s' does not look like a number (command line).\n", - optarg); - exit(RESULT_HARD_ERROR); - } - harness->fixt_ind--; /* convert base-1 to base 0 */ - break; - case 'h': - help(argv[0]); - exit(RESULT_OK); - case 'l': - list_tests(); - exit(RESULT_OK); - case 0: - break; - default: - exit(RESULT_HARD_ERROR); - } - } - - if (optind < argc) - harness->chosen_testname = argv[optind++]; - - if (optind < argc) { - if (!safe_strtoint(argv[optind], &harness->case_ind)) { - fprintf(stderr, - "Error: '%s' does not look like a number (command line).\n", - argv[optind]); - exit(RESULT_HARD_ERROR); - } - harness->case_ind--; /* convert base-1 to base 0 */ - optind++; - } - - if (optind < argc) { - fprintf(stderr, "Unexpected extra arguments given (command line).\n\n"); - help(argv[0]); - exit(RESULT_HARD_ERROR); - } -} - -static struct weston_test_harness * -weston_test_harness_create(int argc, char **argv) -{ - const struct fixture_setup_array *fsa; - struct weston_test_harness *harness; - - harness = zalloc(sizeof(*harness)); - assert(harness); - - harness->fixt_ind = -1; - harness->case_ind = -1; - parse_command_line(harness, argc, argv); - - fsa = fixture_setup_array_get_(); - if (harness->fixt_ind < -1 || harness->fixt_ind >= fsa->n_elements) { - fprintf(stderr, - "Error: fixture index %d (command line) is invalid for this program.\n", - harness->fixt_ind + 1); - exit(RESULT_HARD_ERROR); - } - - if (harness->chosen_testname) { - const struct weston_test_entry *t; - - t = find_test(harness->chosen_testname); - if (!t) { - fprintf(stderr, - "Error: test '%s' not found (command line).\n", - harness->chosen_testname); - exit(RESULT_HARD_ERROR); - } - - if (harness->case_ind < -1 || - harness->case_ind >= t->n_elements) { - fprintf(stderr, - "Error: case index %d (command line) is invalid for this test.\n", - harness->case_ind + 1); - exit(RESULT_HARD_ERROR); - } - - harness->data.tests = t; - harness->data.tests_count = 1; - harness->data.case_index = harness->case_ind; - } else { - harness->data.tests = &__start_test_section; - harness->data.tests_count = - &__stop_test_section - &__start_test_section; - harness->data.case_index = -1; - } - - harness->data.run = testsuite_run; - - return harness; -} - -static void -weston_test_harness_destroy(struct weston_test_harness *harness) -{ - free(harness); -} - -static enum test_result_code -counts_to_result(const struct wet_testsuite_data *data) -{ - /* RESULT_SKIP is reserved for fixture setup itself skipping everything */ - if (data->total == data->passed + data->skipped) - return RESULT_OK; - return RESULT_FAIL; -} - -/** Execute all tests as client tests - * - * \param harness The test harness context. - * \param setup The compositor configuration. - * - * Initializes the compositor with the given setup and executes the compositor. - * The compositor creates a new thread where all tests in the test program are - * serially executed. Once the thread finishes, the compositor returns from its - * event loop and cleans up. - * - * Returns RESULT_SKIP if the requested compositor features, e.g. GL-renderer, - * are not built. - * - * \sa DECLARE_FIXTURE_SETUP(), DECLARE_FIXTURE_SETUP_WITH_ARG() - * \ingroup testharness - */ -enum test_result_code -weston_test_harness_execute_as_client(struct weston_test_harness *harness, - const struct compositor_setup *setup) -{ - struct wet_testsuite_data *data = &harness->data; - - data->type = TEST_TYPE_CLIENT; - return execute_compositor(setup, data); -} - -/** Execute all tests as plugin tests - * - * \param harness The test harness context. - * \param setup The compositor configuration. - * - * Initializes the compositor with the given setup and executes the compositor. - * The compositor executes all tests in the test program serially from an idle - * handler, then returns from its event loop and cleans up. - * - * Returns RESULT_SKIP if the requested compositor features, e.g. GL-renderer, - * are not built. - * - * \sa DECLARE_FIXTURE_SETUP(), DECLARE_FIXTURE_SETUP_WITH_ARG() - * \ingroup testharness - */ -enum test_result_code -weston_test_harness_execute_as_plugin(struct weston_test_harness *harness, - const struct compositor_setup *setup) -{ - struct wet_testsuite_data *data = &harness->data; - - data->type = TEST_TYPE_PLUGIN; - return execute_compositor(setup, data); -} - -/** Execute all tests as standalone tests - * - * \param harness The test harness context. - * - * Executes all tests in the test program serially without any further setup, - * particularly without any compositor instance created. - * - * \sa DECLARE_FIXTURE_SETUP(), DECLARE_FIXTURE_SETUP_WITH_ARG() - * \ingroup testharness - */ -enum test_result_code -weston_test_harness_execute_standalone(struct weston_test_harness *harness) -{ - struct wet_testsuite_data *data = &harness->data; - - data->type = TEST_TYPE_STANDALONE; - data->run(data); - - return RESULT_OK; -} - -/** Fixture data array getter method - * - * DECLARE_FIXTURE_SETUP_WITH_ARG() overrides this in test programs. - * The default implementation has no data and makes the tests run once. - * - * \ingroup testharness - */ -__attribute__((weak)) const struct fixture_setup_array * -fixture_setup_array_get_(void) -{ - /* A dummy fixture without a data array. */ - static const struct fixture_setup_array default_fsa = { - .array = NULL, - .element_size = 0, - .n_elements = 1, - }; - - return &default_fsa; -} - -/** Fixture setup function - * - * DECLARE_FIXTURE_SETUP() and DECLARE_FIXTURE_SETUP_WITH_ARG() override - * this in test programs. - * The default implementation just calls - * weston_test_harness_execute_standalone(). - * - * \ingroup testharness - */ -__attribute__((weak)) enum test_result_code -fixture_setup_run_(struct weston_test_harness *harness, const void *arg_) -{ - return weston_test_harness_execute_standalone(harness); -} - -static void -fixture_report(const struct wet_testsuite_data *d, enum test_result_code ret) -{ - int fixture_nr = d->fixture_iteration + 1; - - testlog("--- Fixture %d %s: passed %d, skipped %d, failed %d, total %d\n", - fixture_nr, result_to_str(ret), - d->passed, d->skipped, d->failed, d->total); -} - -int -main(int argc, char *argv[]) -{ - struct weston_test_harness *harness; - enum test_result_code ret; - enum test_result_code result = RESULT_OK; - const struct fixture_setup_array *fsa; - const char *array_data; - int fi; - int fi_end; - - harness = weston_test_harness_create(argc, argv); - - fsa = fixture_setup_array_get_(); - array_data = fsa->array; - - if (harness->fixt_ind == -1) { - fi = 0; - fi_end = fsa->n_elements; - } else { - fi = harness->fixt_ind; - fi_end = fi + 1; - } - - tap_plan(&harness->data, fi_end - fi); - testlog("Iterating through %d fixtures.\n", fi_end - fi); - - for (; fi < fi_end; fi++) { - const void *arg = array_data + fi * fsa->element_size; - - testlog("--- Fixture %d...\n", fi + 1); - harness->data.fixture_iteration = fi; - harness->data.passed = 0; - harness->data.skipped = 0; - harness->data.failed = 0; - - ret = fixture_setup_run_(harness, arg); - fixture_report(&harness->data, ret); - - if (ret == RESULT_SKIP) { - tap_skip_fixture(&harness->data); - continue; - } - - if (ret != RESULT_OK && result != RESULT_HARD_ERROR) - result = ret; - else if (counts_to_result(&harness->data) != RESULT_OK) - result = RESULT_FAIL; - } - - weston_test_harness_destroy(harness); - - return result; -} diff --git a/tests/weston-test-runner.h b/tests/weston-test-runner.h deleted file mode 100644 index 9e545cd..0000000 --- a/tests/weston-test-runner.h +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * Copyright © 2013 Sam Spilsbury - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _WESTON_TEST_RUNNER_H_ -#define _WESTON_TEST_RUNNER_H_ - -#include "config.h" - -#include - -#include -#include "shared/helpers.h" -#include "weston-test-fixture-compositor.h" -#include "weston-testsuite-data.h" - -#ifdef NDEBUG -#error "Tests must not be built with NDEBUG defined, they rely on assert()." -#endif - -/** Test program entry - * - * Each invocation of TEST(), TEST_P(), or PLUGIN_TEST() will create one - * more weston_test_entry in a custom named section in the final binary. - * Iterating through the section then allows to iterate through all - * the defined tests. - * - * \ingroup testharness_private - */ -struct weston_test_entry { - const char *name; - void (*run)(void *); - const void *table_data; - size_t element_size; - int n_elements; -} __attribute__ ((aligned (32))); - -#define TEST_BEGIN(name, arg) \ - static void name(arg) - -#define TEST_COMMON(func, name, data, size, n_elem) \ - static void func(void *); \ - \ - const struct weston_test_entry test##name \ - __attribute__ ((used, section ("test_section"))) = \ - { \ - #name, func, data, size, n_elem \ - }; - -#define NO_ARG_TEST(name) \ - TEST_COMMON(wrap##name, name, NULL, 0, 1) \ - static void name(void); \ - static void wrap##name(void *data) \ - { \ - (void) data; \ - name(); \ - } \ - \ - TEST_BEGIN(name, void) - -#define ARG_TEST(name, test_data) \ - TEST_COMMON(name, name, test_data, \ - sizeof(test_data[0]), \ - ARRAY_LENGTH(test_data)) \ - TEST_BEGIN(name, void *data) \ - -/** Add a test with no parameters - * - * This defines one test as a new function. Use this macro in place of the - * function signature and put the function body after this. - * - * \param name Name for the test, must be a valid function name. - * - * \ingroup testharness - */ -#define TEST(name) NO_ARG_TEST(name) - -/** Add an array driven test with a parameter - * - * This defines an array of tests as a new function. Use this macro in place - * of the function signature and put the function body after this. The function - * will be executed once for each element in \c data_array, passing the - * element as the argument void *data to the function. - * - * This macro is not usable if fixture setup is using - * weston_test_harness_execute_as_plugin(). - * - * \param name Name for the test, must be a valid function name. - * \param data_array A static const array of any type. The length will be - * recorded automatically. - * - * \ingroup testharness - */ -#define TEST_P(name, data_array) ARG_TEST(name, data_array) - -/** Add a test with weston_compositor argument - * - * This defines one test as a new function. Use this macro in place of the - * function signature and put the function body after this. The function - * will have one argument struct weston_compositor *compositor. - * - * This macro is only usable if fixture setup is using - * weston_test_harness_execute_as_plugin(). - * - * \param name Name for the test, must be a valid function name. - * - * \ingroup testharness - */ -#define PLUGIN_TEST(name) \ - TEST_COMMON(wrap##name, name, NULL, 0, 1) \ - static void name(struct weston_compositor *); \ - static void wrap##name(void *compositor) \ - { \ - name(compositor); \ - } \ - TEST_BEGIN(name, struct weston_compositor *compositor) - -void -testlog(const char *fmt, ...) WL_PRINTF(1, 2); - -const char * -get_test_name(void); - -int -get_test_fixture_index(void); - -/** Fixture setup array record - * - * Helper to store the attributes of the data array passed in to - * DECLARE_FIXTURE_SETUP_WITH_ARG(). - * - * \ingroup testharness_private - */ -struct fixture_setup_array { - const void *array; - size_t element_size; - int n_elements; -}; - -const struct fixture_setup_array * -fixture_setup_array_get_(void); - -/** Test harness context - * - * \ingroup testharness - */ -struct weston_test_harness; - -enum test_result_code -fixture_setup_run_(struct weston_test_harness *harness, const void *arg_); - -/** Register a fixture setup function - * - * This registers the given (preferably static) function to be used for setting - * up any fixtures you might need. The function must have the signature: - * - * \code - * enum test_result_code func_(struct weston_test_harness *harness) - * \endcode - * - * The function must call one of weston_test_harness_execute_standalone(), - * weston_test_harness_execute_as_plugin() or - * weston_test_harness_execute_as_client() passing in the \c harness argument, - * and return the return value from that call. The function can also return a - * test_result_code on its own if it does not want to run the tests, - * e.g. RESULT_SKIP or RESULT_HARD_ERROR. - * - * The function will be called once to run all tests. - * - * \param func_ The function to be used as fixture setup. - * - * \ingroup testharness - */ -#define DECLARE_FIXTURE_SETUP(func_) \ - enum test_result_code \ - fixture_setup_run_(struct weston_test_harness *harness, \ - const void *arg_) \ - { \ - return func_(harness); \ - } - -/** Register a fixture setup function with a data array - * - * This registers the given (preferably static) function to be used for setting - * up any fixtures you might need. The function must have the signature: - * - * \code - * enum test_result_code func_(struct weston_test_harness *harness, typeof(array_[0]) *arg) - * \endcode - * - * The function must call one of weston_test_harness_execute_standalone(), - * weston_test_harness_execute_as_plugin() or - * weston_test_harness_execute_as_client() passing in the \c harness argument, - * and return the return value from that call. The function can also return a - * test_result_code on its own if it does not want to run the tests, - * e.g. RESULT_SKIP or RESULT_HARD_ERROR. - * - * The function will be called once with each element of the array pointed to - * by \c arg, so that all tests would be repeated for each element in turn. - * - * \param func_ The function to be used as fixture setup. - * \param array_ A static const array of arbitrary type. - * - * \ingroup testharness - */ -#define DECLARE_FIXTURE_SETUP_WITH_ARG(func_, array_) \ - const struct fixture_setup_array * \ - fixture_setup_array_get_(void) \ - { \ - static const struct fixture_setup_array arr = { \ - .array = array_, \ - .element_size = sizeof(array_[0]), \ - .n_elements = ARRAY_LENGTH(array_) \ - }; \ - return &arr; \ - } \ - \ - enum test_result_code \ - fixture_setup_run_(struct weston_test_harness *harness, \ - const void *arg_) \ - { \ - typeof(array_[0]) *arg = arg_; \ - return func_(harness, arg); \ - } - -enum test_result_code -weston_test_harness_execute_as_client(struct weston_test_harness *harness, - const struct compositor_setup *setup); - -enum test_result_code -weston_test_harness_execute_as_plugin(struct weston_test_harness *harness, - const struct compositor_setup *setup); - -enum test_result_code -weston_test_harness_execute_standalone(struct weston_test_harness *harness); - -#endif diff --git a/tests/weston-test.c b/tests/weston-test.c deleted file mode 100644 index e3a0cb6..0000000 --- a/tests/weston-test.c +++ /dev/null @@ -1,849 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include "backend.h" -#include "libweston-internal.h" -#include "compositor/weston.h" -#include "weston-test-server-protocol.h" -#include "weston.h" -#include "weston-testsuite-data.h" - -#include "shared/helpers.h" -#include "shared/timespec-util.h" - -#define MAX_TOUCH_DEVICES 32 - -struct weston_test { - struct weston_compositor *compositor; - struct wl_listener destroy_listener; - - struct weston_log_scope *log; - - struct weston_layer layer; - struct weston_seat seat; - struct weston_touch_device *touch_device[MAX_TOUCH_DEVICES]; - int nr_touch_devices; - bool is_seat_initialized; - - pthread_t client_thread; - struct wl_event_source *client_source; -}; - -struct weston_test_surface { - struct weston_surface *surface; - struct weston_view *view; - int32_t x, y; - struct weston_test *test; -}; - -static void -touch_device_add(struct weston_test *test) -{ - char buf[128]; - int i = test->nr_touch_devices; - - assert(i < MAX_TOUCH_DEVICES); - assert(!test->touch_device[i]); - - snprintf(buf, sizeof buf, "test-touch-device-%d", i); - - test->touch_device[i] = weston_touch_create_touch_device( - test->seat.touch_state, buf, NULL, NULL); - test->nr_touch_devices++; -} - -static void -touch_device_remove(struct weston_test *test) -{ - int i = test->nr_touch_devices - 1; - - assert(i >= 0); - assert(test->touch_device[i]); - weston_touch_device_destroy(test->touch_device[i]); - test->touch_device[i] = NULL; - --test->nr_touch_devices; -} - -static int -test_seat_init(struct weston_test *test) -{ - assert(!test->is_seat_initialized && - "Trying to add already added test seat"); - - /* create our own seat */ - weston_seat_init(&test->seat, test->compositor, "test-seat"); - test->is_seat_initialized = true; - - /* add devices */ - weston_seat_init_pointer(&test->seat); - if (weston_seat_init_keyboard(&test->seat, NULL) < 0) - return -1; - weston_seat_init_touch(&test->seat); - touch_device_add(test); - - return 0; -} - -static void -test_seat_release(struct weston_test *test) -{ - while (test->nr_touch_devices > 0) - touch_device_remove(test); - - assert(test->is_seat_initialized && - "Trying to release already released test seat"); - test->is_seat_initialized = false; - weston_seat_release(&test->seat); - memset(&test->seat, 0, sizeof test->seat); -} - -static struct weston_seat * -get_seat(struct weston_test *test) -{ - return &test->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 = weston_seat_get_pointer(seat); - - weston_test_send_pointer_position(resource, pointer->x, pointer->y); -} - -static void -test_surface_committed(struct weston_surface *surface, int32_t sx, int32_t sy) -{ - struct weston_test_surface *test_surface = surface->committed_private; - struct weston_test *test = test_surface->test; - - if (wl_list_empty(&test_surface->view->layer_link.link)) - weston_layer_entry_insert(&test->layer.view_list, - &test_surface->view->layer_link); - - weston_view_set_position(test_surface->view, - test_surface->x, test_surface->y); - - weston_view_update_transform(test_surface->view); - - test_surface->surface->is_mapped = true; - test_surface->view->is_mapped = true; -} - -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; - - test_surface = surface->committed_private; - if (!test_surface) { - test_surface = malloc(sizeof *test_surface); - if (!test_surface) { - wl_resource_post_no_memory(resource); - return; - } - - test_surface->view = weston_view_create(surface); - if (!test_surface->view) { - wl_resource_post_no_memory(resource); - free(test_surface); - return; - } - - surface->committed_private = test_surface; - surface->committed = test_surface_committed; - } - - 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, - uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, - 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 = weston_seat_get_pointer(seat); - struct weston_pointer_motion_event event = { 0 }; - struct timespec time; - - event = (struct weston_pointer_motion_event) { - .mask = WESTON_POINTER_MOTION_REL, - .dx = wl_fixed_to_double(wl_fixed_from_int(x) - pointer->x), - .dy = wl_fixed_to_double(wl_fixed_from_int(y) - pointer->y), - }; - - timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); - - notify_motion(seat, &time, &event); - - notify_pointer_position(test, resource); -} - -static void -send_button(struct wl_client *client, struct wl_resource *resource, - uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, - int32_t button, uint32_t state) -{ - struct timespec time; - - struct weston_test *test = wl_resource_get_user_data(resource); - struct weston_seat *seat = get_seat(test); - - timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); - - notify_button(seat, &time, button, state); -} - -static void -send_axis(struct wl_client *client, struct wl_resource *resource, - uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, - uint32_t axis, wl_fixed_t value) -{ - struct weston_test *test = wl_resource_get_user_data(resource); - struct weston_seat *seat = get_seat(test); - struct timespec time; - struct weston_pointer_axis_event axis_event; - - timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); - axis_event.axis = axis; - axis_event.value = wl_fixed_to_double(value); - axis_event.has_discrete = false; - axis_event.discrete = 0; - - notify_axis(seat, &time, &axis_event); -} - -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; - struct weston_keyboard *keyboard; - - seat = get_seat(test); - keyboard = weston_seat_get_keyboard(seat); - if (surface) { - weston_seat_set_keyboard_focus(seat, surface); - notify_keyboard_focus_in(seat, &keyboard->keys, - STATE_UPDATE_AUTOMATIC); - } - else { - notify_keyboard_focus_out(seat); - weston_seat_set_keyboard_focus(seat, surface); - } -} - -static void -send_key(struct wl_client *client, struct wl_resource *resource, - uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, - 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); - struct timespec time; - - timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); - - notify_key(seat, &time, key, state, STATE_UPDATE_AUTOMATIC); -} - -static void -device_release(struct wl_client *client, - struct wl_resource *resource, const char *device) -{ - struct weston_test *test = wl_resource_get_user_data(resource); - struct weston_seat *seat = get_seat(test); - - if (strcmp(device, "pointer") == 0) { - weston_seat_release_pointer(seat); - } else if (strcmp(device, "keyboard") == 0) { - weston_seat_release_keyboard(seat); - } else if (strcmp(device, "touch") == 0) { - touch_device_remove(test); - weston_seat_release_touch(seat); - } else if (strcmp(device, "seat") == 0) { - test_seat_release(test); - } else { - assert(0 && "Unsupported device"); - } -} - -static void -device_add(struct wl_client *client, - struct wl_resource *resource, const char *device) -{ - struct weston_test *test = wl_resource_get_user_data(resource); - struct weston_seat *seat = get_seat(test); - - if (strcmp(device, "pointer") == 0) { - weston_seat_init_pointer(seat); - } else if (strcmp(device, "keyboard") == 0) { - weston_seat_init_keyboard(seat, NULL); - } else if (strcmp(device, "touch") == 0) { - weston_seat_init_touch(seat); - touch_device_add(test); - } else if (strcmp(device, "seat") == 0) { - test_seat_init(test); - } else { - assert(0 && "Unsupported device"); - } -} - -enum weston_test_screenshot_outcome { - WESTON_TEST_SCREENSHOT_SUCCESS, - WESTON_TEST_SCREENSHOT_NO_MEMORY, - WESTON_TEST_SCREENSHOT_BAD_BUFFER - }; - -typedef void (*weston_test_screenshot_done_func_t)(void *data, - enum weston_test_screenshot_outcome outcome); - -struct test_screenshot { - struct weston_compositor *compositor; - struct wl_global *global; - struct wl_client *client; - struct weston_process process; - struct wl_listener destroy_listener; -}; - -struct test_screenshot_frame_listener { - struct wl_listener listener; - struct weston_buffer *buffer; - struct weston_output *output; - weston_test_screenshot_done_func_t done; - void *data; -}; - -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 -test_screenshot_frame_notify(struct wl_listener *listener, void *data) -{ - struct test_screenshot_frame_listener *l = - container_of(listener, - struct test_screenshot_frame_listener, listener); - struct weston_output *output = l->output; - struct weston_compositor *compositor = output->compositor; - int32_t stride; - uint8_t *pixels, *d, *s; - - weston_output_disable_planes_decr(output); - 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) { - l->done(l->data, WESTON_TEST_SCREENSHOT_NO_MEMORY); - free(l); - return; - } - - /* FIXME: Needs to handle output transformations */ - - 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); - - wl_shm_buffer_begin_access(l->buffer->shm_buffer); - - /* XXX: It would be nice if we used Pixman to do all this rather - * than our own implementation - */ - 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; - } - - wl_shm_buffer_end_access(l->buffer->shm_buffer); - - l->done(l->data, WESTON_TEST_SCREENSHOT_SUCCESS); - free(pixels); - free(l); -} - -static bool -weston_test_screenshot_shoot(struct weston_output *output, - struct weston_buffer *buffer, - weston_test_screenshot_done_func_t done, - void *data) -{ - struct test_screenshot_frame_listener *l; - - /* Get the shm buffer resource the client created */ - if (!wl_shm_buffer_get(buffer->resource)) { - done(data, WESTON_TEST_SCREENSHOT_BAD_BUFFER); - return false; - } - - 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); - - /* Verify buffer is big enough */ - if (buffer->width < output->current_mode->width || - buffer->height < output->current_mode->height) { - done(data, WESTON_TEST_SCREENSHOT_BAD_BUFFER); - return false; - } - - /* allocate the frame listener */ - l = malloc(sizeof *l); - if (l == NULL) { - done(data, WESTON_TEST_SCREENSHOT_NO_MEMORY); - return false; - } - - /* Set up the listener */ - l->buffer = buffer; - l->output = output; - l->done = done; - l->data = data; - l->listener.notify = test_screenshot_frame_notify; - wl_signal_add(&output->frame_signal, &l->listener); - - /* Fire off a repaint */ - weston_output_disable_planes_incr(output); - weston_output_schedule_repaint(output); - - return true; -} - -static void -capture_screenshot_done(void *data, enum weston_test_screenshot_outcome outcome) -{ - struct wl_resource *resource = data; - - switch (outcome) { - case WESTON_TEST_SCREENSHOT_SUCCESS: - weston_test_send_capture_screenshot_done(resource); - break; - case WESTON_TEST_SCREENSHOT_NO_MEMORY: - wl_resource_post_no_memory(resource); - break; - default: - break; - } -} - - -/** - * Grabs a snapshot of the screen. - */ -static void -capture_screenshot(struct wl_client *client, - struct wl_resource *resource, - struct wl_resource *output_resource, - struct wl_resource *buffer_resource) -{ - struct weston_output *output = - weston_head_from_resource(output_resource)->output; - struct weston_buffer *buffer = - weston_buffer_from_resource(buffer_resource); - - if (buffer == NULL) { - wl_resource_post_no_memory(resource); - return; - } - - weston_test_screenshot_shoot(output, buffer, - capture_screenshot_done, resource); -} - -static void -send_touch(struct wl_client *client, struct wl_resource *resource, - uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, - int32_t touch_id, wl_fixed_t x, wl_fixed_t y, uint32_t touch_type) -{ - struct weston_test *test = wl_resource_get_user_data(resource); - struct weston_touch_device *device = test->touch_device[0]; - struct timespec time; - - assert(device); - - timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); - - notify_touch(device, &time, touch_id, wl_fixed_to_double(x), - wl_fixed_to_double(y), touch_type); -} - -static const struct weston_test_interface test_implementation = { - move_surface, - move_pointer, - send_button, - send_axis, - activate_surface, - send_key, - device_release, - device_add, - capture_screenshot, - send_touch, -}; - -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, &weston_test_interface, 1, id); - if (!resource) { - wl_client_post_no_memory(client); - return; - } - - wl_resource_set_implementation(resource, - &test_implementation, test, NULL); - - notify_pointer_position(test, resource); -} - -static void -client_thread_cleanup(void *data_) -{ - struct wet_testsuite_data *data = data_; - - close(data->thread_event_pipe); - data->thread_event_pipe = -1; -} - -static void * -client_thread_routine(void *data_) -{ - struct wet_testsuite_data *data = data_; - - pthread_setname_np(pthread_self(), "client"); - pthread_cleanup_push(client_thread_cleanup, data); - data->run(data); - pthread_cleanup_pop(true); - - return NULL; -} - -static void -client_thread_join(struct weston_test *test) -{ - assert(test->client_source); - - pthread_join(test->client_thread, NULL); - wl_event_source_remove(test->client_source); - test->client_source = NULL; - - weston_log_scope_printf(test->log, "Test thread reaped.\n"); -} - -static int -handle_client_thread_event(int fd, uint32_t mask, void *data_) -{ - struct weston_test *test = data_; - - weston_log_scope_printf(test->log, - "Received thread event mask 0x%x\n", mask); - - if (mask != WL_EVENT_HANGUP) - weston_log("%s: unexpected event %u\n", __func__, mask); - - client_thread_join(test); - weston_compositor_exit(test->compositor); - - return 0; -} - -static int -create_client_thread(struct weston_test *test, struct wet_testsuite_data *data) -{ - struct wl_event_loop *loop; - int pipefd[2] = { -1, -1 }; - sigset_t saved; - sigset_t blocked; - int ret; - - weston_log_scope_printf(test->log, "Creating a thread for running tests...\n"); - - if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) < 0) { - weston_log("Creating pipe for a client thread failed: %s\n", - strerror(errno)); - return -1; - } - - loop = wl_display_get_event_loop(test->compositor->wl_display); - test->client_source = wl_event_loop_add_fd(loop, pipefd[0], - WL_EVENT_READABLE, - handle_client_thread_event, - test); - close(pipefd[0]); - - if (!test->client_source) { - weston_log("Adding client thread fd to event loop failed.\n"); - goto out_pipe; - } - - data->thread_event_pipe = pipefd[1]; - - /* Ensure we don't accidentally get signals to the thread. */ - sigfillset(&blocked); - sigdelset(&blocked, SIGSEGV); - sigdelset(&blocked, SIGFPE); - sigdelset(&blocked, SIGILL); - sigdelset(&blocked, SIGCONT); - sigdelset(&blocked, SIGSYS); - if (pthread_sigmask(SIG_BLOCK, &blocked, &saved) != 0) - goto out_source; - - ret = pthread_create(&test->client_thread, NULL, - client_thread_routine, data); - - pthread_sigmask(SIG_SETMASK, &saved, NULL); - - if (ret != 0) { - weston_log("Creating client thread failed: %s (%d)\n", - strerror(ret), ret); - goto out_source; - } - - return 0; - -out_source: - data->thread_event_pipe = -1; - wl_event_source_remove(test->client_source); - test->client_source = NULL; - -out_pipe: - close(pipefd[1]); - - return -1; -} - -static void -idle_launch_testsuite(void *test_) -{ - struct weston_test *test = test_; - struct wet_testsuite_data *data = wet_testsuite_data_get(); - - if (!data) - return; - - switch (data->type) { - case TEST_TYPE_CLIENT: - if (create_client_thread(test, data) < 0) { - weston_log("Error: creating client thread for test suite failed.\n"); - weston_compositor_exit_with_code(test->compositor, - RESULT_HARD_ERROR); - } - break; - - case TEST_TYPE_PLUGIN: - data->compositor = test->compositor; - weston_log_scope_printf(test->log, - "Running tests from idle handler...\n"); - data->run(data); - weston_compositor_exit(test->compositor); - break; - - case TEST_TYPE_STANDALONE: - weston_log("Error: unknown test internal type %d.\n", - data->type); - weston_compositor_exit_with_code(test->compositor, - RESULT_HARD_ERROR); - } -} - -static void -handle_compositor_destroy(struct wl_listener *listener, - void *weston_compositor) -{ - struct weston_test *test; - - test = wl_container_of(listener, test, destroy_listener); - - if (test->client_source) { - weston_log_scope_printf(test->log, "Cancelling client thread...\n"); - pthread_cancel(test->client_thread); - client_thread_join(test); - } - - if (test->is_seat_initialized) - test_seat_release(test); - - wl_list_remove(&test->layer.view_list.link); - wl_list_remove(&test->layer.link); - - weston_log_scope_destroy(test->log); - free(test); -} - -WL_EXPORT int -wet_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; - - if (!weston_compositor_add_destroy_listener_once(ec, - &test->destroy_listener, - handle_compositor_destroy)) { - free(test); - return 0; - } - - test->compositor = ec; - weston_layer_init(&test->layer, ec); - weston_layer_set_position(&test->layer, WESTON_LAYER_POSITION_CURSOR - 1); - - test->log = weston_compositor_add_log_scope(ec, "test-harness-plugin", - "weston-test plugin's own actions", - NULL, NULL, NULL); - - if (wl_global_create(ec->wl_display, &weston_test_interface, 1, - test, bind_test) == NULL) - goto out_free; - - if (test_seat_init(test) == -1) - goto out_free; - - loop = wl_display_get_event_loop(ec->wl_display); - wl_event_loop_add_idle(loop, idle_launch_testsuite, test); - - return 0; - -out_free: - wl_list_remove(&test->destroy_listener.link); - free(test); - return -1; -} diff --git a/tests/weston-testsuite-data.h b/tests/weston-testsuite-data.h deleted file mode 100644 index 06c35bd..0000000 --- a/tests/weston-testsuite-data.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2019 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef WESTON_TESTSUITE_DATA_H -#define WESTON_TESTSUITE_DATA_H - -/** Standard return codes - * - * Both Autotools and Meson use these codes as test program exit codes - * to denote the test result for the whole process. - * - * \ingroup testharness - */ -enum test_result_code { - RESULT_OK = 0, - RESULT_SKIP = 77, - RESULT_FAIL = 1, - RESULT_HARD_ERROR = 99, -}; - -struct weston_test; -struct weston_compositor; - -/** Weston test types - * - * \sa weston_test_harness_execute_standalone - * weston_test_harness_execute_as_plugin - * weston_test_harness_execute_as_client - * - * \ingroup testharness_private - */ -enum test_type { - TEST_TYPE_STANDALONE, - TEST_TYPE_PLUGIN, - TEST_TYPE_CLIENT, -}; - -/** Test harness specific data for running tests - * - * \ingroup testharness_private - */ -struct wet_testsuite_data { - void (*run)(struct wet_testsuite_data *); - - /* test definitions */ - const struct weston_test_entry *tests; - unsigned tests_count; - int case_index; - enum test_type type; - struct weston_compositor *compositor; - - /* client thread control */ - int thread_event_pipe; - - /* informational run state */ - int fixture_iteration; - - /* test counts */ - unsigned counter; - unsigned passed; - unsigned skipped; - unsigned failed; - unsigned total; -}; - -#endif /* WESTON_TESTSUITE_DATA_H */ diff --git a/tests/xwayland-test.c b/tests/xwayland-test.c deleted file mode 100644 index 665f6ea..0000000 --- a/tests/xwayland-test.c +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * xwayland-test: Confirm that we can map a window and we're running - * under Xwayland, not just X. - * - * This is done in steps: - * 1) Confirm that the WL_SURFACE_ID atom exists - * 2) Confirm that the window manager's name is "Weston WM" - * 3) Make sure we can map a window - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "weston-test-runner.h" -#include "weston-test-fixture-compositor.h" - -static enum test_result_code -fixture_setup(struct weston_test_harness *harness) -{ - struct compositor_setup setup; - - compositor_setup_defaults(&setup); - setup.xwayland = true; - - return weston_test_harness_execute_as_client(harness, &setup); -} -DECLARE_FIXTURE_SETUP(fixture_setup); - -TEST(xwayland_client_test) -{ - Display *display; - Window window, root, *support; - XEvent event; - int screen, status, actual_format; - unsigned long nitems, bytes; - Atom atom, type_atom, actual_type; - char *wm_name; - - if (access(XSERVER_PATH, X_OK) != 0) - exit(77); - - display = XOpenDisplay(NULL); - if (!display) - exit(EXIT_FAILURE); - - atom = XInternAtom(display, "WL_SURFACE_ID", True); - assert(atom != None); - - atom = XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", True); - assert(atom != None); - - screen = DefaultScreen(display); - root = RootWindow(display, screen); - - status = XGetWindowProperty(display, root, atom, 0L, ~0L, - False, XA_WINDOW, &actual_type, - &actual_format, &nitems, &bytes, - (void *)&support); - assert(status == Success); - - atom = XInternAtom(display, "_NET_WM_NAME", True); - assert(atom != None); - type_atom = XInternAtom(display, "UTF8_STRING", True); - assert(atom != None); - status = XGetWindowProperty(display, *support, atom, 0L, BUFSIZ, - False, type_atom, &actual_type, - &actual_format, &nitems, &bytes, - (void *)&wm_name); - assert(status == Success); - assert(nitems); - assert(strcmp("Weston WM", wm_name) == 0); - free(support); - free(wm_name); - - window = XCreateSimpleWindow(display, root, 100, 100, 300, 300, 1, - BlackPixel(display, screen), - WhitePixel(display, screen)); - XSelectInput(display, window, ExposureMask); - XMapWindow(display, window); - - alarm(4); - while (1) { - XNextEvent(display, &event); - if (event.type == Expose) - break; - } - - XCloseDisplay(display); -} diff --git a/tools/zunitc/doc/zunitc.dox b/tools/zunitc/doc/zunitc.dox deleted file mode 100644 index 6fc6858..0000000 --- a/tools/zunitc/doc/zunitc.dox +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/** -@page zunitc zunitc - -- @ref zunitc_overview - - @ref zunitc_overview_return -- @ref zunitc_execution - - @ref zunitc_execution_commandline - - @ref zunitc_execution_matching - - @ref zunitc_execution_wildcards - - @ref zunitc_execution_repeat - - @ref zunitc_execution_randomize -- @ref zunitc_fixtures -- @ref zunitc_functions - -@section zunitc_overview Overview - -A simple test framework in plain C suitable for basic unit and integration -testing. - -The main rationale for creating this framework was to have a simple to use -testing framework with tests implemented in C using common patterns and under a -compatible license. The structure of the test code and macro use is intended to -follow common patterns established by frameworks such as Boost Test and Google -Test. - - -To get started, one or more tests should be defined via ZUC_TEST() and/or -ZUC_TEST_F(), which set up automatic test registration via gcc extensions. -To actually execute tests, ZUC_RUN_TESTS() should be called. - - -Tests can use several ZUC_ASSERT_* or ZUC_ASSERTG_* checks to validate -conditions. The ZUC_ASSERT_* ones upon failure will mark the current test -as failing and immediately execute a return. On the other hand, the -ZUC_ASSERTG_* tests will mark the current test as failed and then execute a -'goto' targeting the specified label. See @ref zunitc_overview_return for more -detail. - -The set of fatal test checks are - -- ZUC_ASSERT_TRUE() -- ZUC_ASSERT_FALSE() -- ZUC_ASSERT_NULL() -- ZUC_ASSERT_NOT_NULL() -- ZUC_ASSERT_EQ() -- ZUC_ASSERT_NE() -- ZUC_ASSERT_LT() -- ZUC_ASSERT_LE() -- ZUC_ASSERT_GT() -- ZUC_ASSERT_GE() -- ZUC_ASSERT_STREQ() -- ZUC_ASSERT_STRNE() - -and - -- ZUC_ASSERTG_TRUE() -- ZUC_ASSERTG_FALSE() -- ZUC_ASSERTG_NULL() -- ZUC_ASSERTG_NOT_NULL() -- ZUC_ASSERTG_EQ() -- ZUC_ASSERTG_NE() -- ZUC_ASSERTG_LT() -- ZUC_ASSERTG_LE() -- ZUC_ASSERTG_GT() -- ZUC_ASSERTG_GE() -- ZUC_ASSERTG_STREQ() -- ZUC_ASSERTG_STRNE() - -Unconditional test values for logging and termination are -- ZUC_SKIP() -- ZUC_FATAL() - -Unconditional message logging for failure cases only is -- ZUC_TRACEPOINT() - -@subsection zunitc_overview_return Test Termination and Return - -One key point where this framework differs from some others (especially many -common ad hoc test programs) is that it does not use assert() nor exceptions. - -- does not use assert() -- can not use throw - -One main implication of this is that when a check fails the current function -will be terminated via a 'return' statement and control passed back to the -calling function. If the check fails in an individual ZUC_TEST() or ZUC_TEST_F() -test function then control is returned to the framework and the current test is -deemed completed (either successfully or with failure). - -On the other hand, if a check fails that is being executed in a function called -by an individual test, then control will stay in the current test. In order to -successfully exit the current test a follow-up check needs to be done after -calling functions that might cause a failure. - -@code{.c} -... - - /* Call a function that might contain ZUC_ASSERT_* use. */ - some_helper_function(); - - /* Stop the current test if something failed. */ - ZUC_ASSERT_FALSE(zuc_has_failure()); - - /* execution will only reach this point if no failures occurred. */ - -... -@endcode - -Use of the ZUC_ASSERTG_*() variants can help in certain situations as check -failure will trigger a 'goto' statement as opposed to a general 'return'. - -@code{.c} -... - - /* Call a function that might contain ZUC_ASSERT_* use. */ - some_helper_function(); - - /* Stop the current test if something failed. */ - ZUC_ASSERTG_FALSE(zuc_has_failure(), err); - - /* execution will only reach this point if no failures occurred. */ -... - -err: - free(str_a); -} -... -@endcode - -@section zunitc_execution Controlling Execution - -To control execution, the various zuc_set_* functions can be called -before invoking ZUC_RUN_TESTS(). - -@subsection zunitc_execution_commandline Command-line Parameters - -The current implementation defers processing of command-line parameters -to the main application hosting the testing. It is possible that a -helper function to process certain parameters may be added. - -@subsection zunitc_execution_matching Matching Patterns for Tests - -The function zuc_set_filter() can be used to specify a pattern for -matching or excluding tests from a run. The general form is - match1[:match2[:match3..n]][:-exclude1[:exclude2[:exclude3..n]]] - -@subsection zunitc_execution_wildcards Matching Wildcards - -Wildcards can be used in the match/exclude patterns and recognize the -following two special characters: -- '*' matches any number of characters including zero. -- '?' matches any single character. - -This pattern matching is consistent with other testing frameworks and -has been chosen in order to make it easier for developers to move -between different testing frameworks. - -Calling zuc_list_tests() after zuc_set_filter() can be done to show the -effects of the matching without needing to actually run tests. - -@subsection zunitc_execution_repeat Repeating Tests - -Setting the repeat count higher than 1 ( via zuc_set_repeat() ) will -cause the tests to be executed several times in a row. This can be -useful for stress testing, checking for leaks, etc. - -@subsection zunitc_execution_randomize Randomizing Tests - -Test ordering can be randomized by setting a non-zero positive value to -zuc_set_random(). Setting it to 1 will cause the framework to pick a -random seed based on the time. A value greater than 1 will be taken as a -random seed itself. And setting it to 0 will disable randomization and -allow the tests to be executed in their natural ordering. - -@section zunitc_fixtures Fixtures - -Per-suite and per-test setup and teardown fixtures can be implemented by -defining an instance of struct zuc_fixture and using it as the first -parameter to ZUC_TEST_F(). - -@section zunitc_functions Functions - -- ZUC_TEST() -- ZUC_TEST_F() -- ZUC_RUN_TESTS() -- zuc_cleanup() -- zuc_list_tests() -- zuc_set_filter() -- zuc_set_random() -- zuc_set_spawn() -- zuc_set_output_junit() -- zuc_has_skip() -- zuc_has_failure() - -*/ diff --git a/tools/zunitc/inc/zunitc/zunitc.h b/tools/zunitc/inc/zunitc/zunitc.h deleted file mode 100644 index 16b211b..0000000 --- a/tools/zunitc/inc/zunitc/zunitc.h +++ /dev/null @@ -1,743 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef Z_UNIT_C_H -#define Z_UNIT_C_H - -#include -#include -#include -#include - -#include "zunitc/zunitc_impl.h" - -#if !__GNUC__ -#error Framework currently requires gcc or compatible compiler. -#endif - -#if INTPTR_MAX < INT_MAX -#error Odd platform requires rework of value type from intptr_t to custom. -#endif - -/** - * @file - * Simple unit test framework declarations. - */ - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page zunitc - */ - -/** - * Structure to use when defining a test fixture. - * @note likely pending refactoring as use cases are refined. - * @see ZUC_TEST_F() - */ -struct zuc_fixture { - /** - * Initial optional seed data to pass to setup functions and/or tests. - */ - const void *data; - - /** - * Per-suite setup called before invoking any of the tests - * contained in the suite. - * - * @return a pointer to test data, or NULL. - */ - void *(*set_up_test_case)(const void *data); - - /** - * Per-suite tear-down called after invoking all of the tests - * contained in the suite. - * - * @param data pointer returned from the setup function. - */ - void (*tear_down_test_case)(void *data); - - /** - * Setup called before running each of the tests in the suite. - * - * @param data optional data from suite setup, or NULL. - * @return a pointer to test data, or NULL. - */ - void *(*set_up)(void *data); - - /** - * Tear-down called after running each of the tests in the suite. - * - * @param data pointer returned from the setup function. - */ - void (*tear_down)(void *data); -}; - -/** - * Process exit code to mark skipped tests, consistent with - * automake tests. - */ -#define ZUC_EXIT_SKIP 77 - -/** - * Accesses the test executable program name. - * This version will include any full or partial path used to - * launch the executable. - * - * @note This depends on zuc_initialize() having been called. - * - * @return the name of the running program. - * The caller should not free nor hold this pointer. It will not stay - * valid across calls to zuc_initialize() or zuc_cleanup(). - * @see zuc_get_program_basename() - */ -const char * -zuc_get_program_name(void); - -/** - * Accesses the test executable program name in trimmed format. - * If the program is launched via a partial or full path, this - * version trims to return only the basename. - * - * @note This depends on zuc_initialize() having been called. - * - * @return the name of the running program. - * The caller should not free nor hold this pointer. It will not stay - * valid across calls to zuc_initialize() or zuc_cleanup(). - * @see zuc_get_program_name() - */ -const char * -zuc_get_program_basename(void); - -/** - * Initializes the test framework and consumes any known command-line - * parameters from the list. - * The exception is 'h/help' which will be left in place for follow-up - * processing by the hosting app if so desired. - * - * @param argc pointer to argc value to read and possibly change. - * @param argv array of parameter pointers to read and possibly change. - * @param help_flagged if non-NULL will be set to true if the user - * specifies the help flag (and framework help has been output). - * @return EXIT_SUCCESS upon success setting or help, EXIT_FAILURE otherwise. - */ -int zuc_initialize(int *argc, char *argv[], bool *help_flagged); - -/** - * Runs all tests that have been registered. - * Expected return values include EXIT_FAILURE if any errors or failures - * have occurred, ::ZUC_EXIT_SKIP if no failures have occurred but at least - * one test reported skipped, otherwise EXIT_SUCCESS if nothing of note - * was recorded. - * - * @note for consistency with other frameworks and to allow for additional - * cleanup to be added later this is implemented as a wrapper macro. - * - * @return expected exit status - normally EXIT_SUCCESS, ::ZUC_EXIT_SKIP, - * or EXIT_FAILURE. - * Normally an application can use this value directly in calling exit(), - * however there could be cases where some additional processing such as - * resource cleanup or library shutdown that a program might want to do - * first. - */ -#define ZUC_RUN_TESTS() \ - zucimpl_run_tests() - -/** - * Clears the test system in preparation for application shutdown. - */ -void -zuc_cleanup(void); - -/** - * Displays all known tests. - * The list returned is affected by any filtering in place. - * - * @see zuc_set_filter() - */ -void -zuc_list_tests(void); - -/** - * Sets the filter string to use for tests. - * The format is a series of patterns separated by a colon, with wildcards - * and an optional flag for negative matching. For wildcards, the '*' - * character will match any sequence and the '?' character will match any - * single character. - * The '-' character at the start of a pattern marks the end of the - * patterns required to match and the beginning of patterns that names - * must not match. - * Defaults to use all tests. - * - * @param filter the filter string to apply to tests. - */ -void -zuc_set_filter(const char *filter); - -/** - * Trigger specific failure/signal upon test failures; useful when - * running under a debugger. - * Currently this is implemented to raise a SIGABRT signal when any - * failure is reported. - * Defaults to false. - * - * @param break_on_failure true to cause a break when tests fail, false to - * allow normal operation upon failures. - */ -void -zuc_set_break_on_failure(bool break_on_failure); - -/** - * Sets the number of times to repeat the tests. - * Any number higher than 1 will cause the tests to be repeated the - * specified number of times. - * Defaults to 1/no repeating. - * - * @param repeat number of times to repeat the tests. - */ -void -zuc_set_repeat(int repeat); - -/** - * Randomizes the order in which tests are executed. - * A value of 0 (the default) means tests are executed in their natural - * ordering. A value of 1 will pick a random seed based on the time to - * use for running tests in a pseudo-random order. A value greater than 1 - * will be used directly for the initial seed. - * - * If the tests are also repeated, the seed will be incremented for each - * subsequent run. - * Defaults to 0/not randomize. - * - * @param random 0|1|seed value. - * @see zuc_set_repeat() - */ -void -zuc_set_random(int random); - -/** - * Controls whether or not to run the tests as forked child processes. - * Defaults to true. - * - * @param spawn true to spawn each test in a forked child process, - * false to run tests directly. - */ -void -zuc_set_spawn(bool spawn); - -/** - * Enables output in the JUnit XML format. - * Defaults to false. - * - * @param enable true to generate JUnit XML output, false to disable. - */ -void -zuc_set_output_junit(bool enable); - -/** - * Defines a test case that can be registered to run. - * - * @param tcase name to use as the containing test case. - * @param test name used for the test under a given test case. - */ -#define ZUC_TEST(tcase, test) \ - static void zuctest_##tcase##_##test(void); \ - \ - const struct zuc_registration zzz_##tcase##_##test \ - __attribute__ ((used, section ("zuc_tsect"))) = \ - { \ - #tcase, #test, 0, \ - zuctest_##tcase##_##test, \ - 0 \ - }; \ - \ - static void zuctest_##tcase##_##test(void) - -/** - * Defines a test case that can be registered to run along with setup/teardown - * support per-test and/or per test case. - * - * @note This defines a test that *uses* a fixture, it does not - * actually define a test fixture itself. - * - * @param tcase name to use as the containing test case/fixture. - * The name used must represent a test fixture instance. It also - * must not duplicate any name used in a non-fixture ZUC_TEST() - * test. - * @note the test case name must be the name of a fixture struct - * to be passed to the test. - * @param test name used for the test under a given test case. - * @param param name for the fixture data pointer. - * @see struct zuc_fixture - */ -#define ZUC_TEST_F(tcase, test, param) \ - static void zuctest_##tcase##_##test(void *param); \ - \ - const struct zuc_registration zzz_##tcase##_##test \ - __attribute__ ((used, section ("zuc_tsect"))) = \ - { \ - #tcase, #test, &tcase, \ - 0, \ - zuctest_##tcase##_##test \ - }; \ - \ - static void zuctest_##tcase##_##test(void *param) - - -/** - * Returns true if the currently executing test has encountered any skips. - * - * @return true if there is currently a test executing and it has - * encountered any skips. - * @see zuc_has_failure - * @see ZUC_SKIP() - */ -bool -zuc_has_skip(void); - -/** - * Returns true if the currently executing test has encountered any failures. - * - * @return true if there is currently a test executing and it has - * encountered any failures. - * @see zuc_has_skip - */ -bool -zuc_has_failure(void); - -/** - * Marks the running test as skipped without marking it as failed, and returns - * from the current function. - * - * For details on return and test termination see @ref zunitc_overview_return. - * - * @param message the message to log as to why the test has been skipped. - */ -#define ZUC_SKIP(message) \ - do { \ - zucimpl_terminate(__FILE__, __LINE__, false, false, #message); \ - return; \ - } \ - while (0) - -/** - * Marks the running test as failed and returns from the current function. - * - * For details on return and test termination see @ref zunitc_overview_return. - * - * @param message the message to log as to why the test has failed. - */ -#define ZUC_FATAL(message) \ - do { \ - zucimpl_terminate(__FILE__, __LINE__, true, true, #message); \ - return; \ - } \ - while (0) - -/** - * Marks the current test as failed with a fatal issue, but does not - * immediately return from the current function. ZUC_FATAL() is normally - * preferred, but when further cleanup is needed, or the current function - * needs to return a value, this macro may be required. - * - * @param message the message to log as to why the test has failed. - * @see ZUC_FATAL() - */ -#define ZUC_MARK_FATAL(message) \ - do { \ - zucimpl_terminate(__FILE__, __LINE__, true, true, #message); \ - } \ - while (0) - -/** - * Creates a message that will be processed in the case of failure. - * If the test encounters any failures (fatal or non-fatal) then these - * messages are included in output. Otherwise they are discarded at the - * end of the test run. - * - * @param message the format string style message. - */ -#define ZUC_TRACEPOINT(message, ...) \ - zucimpl_tracepoint(__FILE__, __LINE__, message, ##__VA_ARGS__); - -/** - * Internal use macro for ASSERT implementation. - * Should not be used directly in code. - */ -#define ZUCIMPL_ASSERT(opcode, valtype, lhs, rhs) \ - do { \ - if (zucimpl_expect_pred2(__FILE__, __LINE__, \ - (opcode), (valtype), true, \ - (intptr_t)(lhs), (intptr_t)(rhs), \ - #lhs, #rhs)) { \ - return; \ - } \ - } \ - while (0) - -/** - * Internal use macro for ASSERT with Goto implementation. - * Should not be used directly in code. - */ -#define ZUCIMPL_ASSERTG(label, opcode, valtype, lhs, rhs) \ - do { \ - if (zucimpl_expect_pred2(__FILE__, __LINE__, \ - (opcode), (valtype), true, \ - (intptr_t)(lhs), (intptr_t)(rhs), \ - #lhs, #rhs)) { \ - goto label; \ - } \ - } \ - while (0) - -/** - * Verifies that the specified expression is true, marks the test as failed - * and exits the current function via 'return' if it is not. - * - * For details on return and test termination see @ref zunitc_overview_return. - * - * @param condition the expression that is expected to be true. - * @note it is far better to use a more specific check when possible - * (e.g. ZUC_ASSERT_EQ(), ZUC_ASSERT_NE(), etc.) - * @see ZUC_ASSERTG_TRUE() - */ -#define ZUC_ASSERT_TRUE(condition) \ - ZUCIMPL_ASSERT(ZUC_OP_TRUE, ZUC_VAL_INT, condition, 0) - -/** - * Verifies that the specified expression is false, marks the test as - * failed and exits the current function via 'return' if it is not. - * - * For details on return and test termination see @ref zunitc_overview_return. - * - * @param condition the expression that is expected to be false. - * @note it is far better to use a more specific check when possible - * (e.g. ZUC_ASSERT_EQ(), ZUC_ASSERT_NE(), etc.) - * @see ZUC_ASSERTG_FALSE() - */ -#define ZUC_ASSERT_FALSE(condition) \ - ZUCIMPL_ASSERT(ZUC_OP_FALSE, ZUC_VAL_INT, condition, 0) - -/** - * Verifies that the specified expression is NULL, marks the test as failed - * and exits the current function via 'return' if it is not. - * - * For details on return and test termination see @ref zunitc_overview_return. - * - * @param condition the expression that is expected to be a NULL pointer. - * @see ZUC_ASSERTG_NULL() - */ -#define ZUC_ASSERT_NULL(condition) \ - ZUCIMPL_ASSERT(ZUC_OP_NULL, ZUC_VAL_PTR, condition, 0) - -/** - * Verifies that the specified expression is non-NULL, marks the test as - * failed and exits the current function via 'return' if it is not. - * - * For details on return and test termination see @ref zunitc_overview_return. - * - * @param condition the expression that is expected to be a non-NULL pointer. - * @see ZUC_ASSERTG_NOT_NULL() - */ -#define ZUC_ASSERT_NOT_NULL(condition) \ - ZUCIMPL_ASSERT(ZUC_OP_NOT_NULL, ZUC_VAL_PTR, condition, 0) - -/** - * Verifies that the values of the specified expressions match, marks the - * test as failed and exits the current function via 'return' if they do not. - * - * For details on return and test termination see @ref zunitc_overview_return. - * - * @param expected the value the result should hold. - * @param actual the actual value seen in testing. - * @see ZUC_ASSERTG_EQ() - */ -#define ZUC_ASSERT_EQ(expected, actual) \ - ZUCIMPL_ASSERT(ZUC_OP_EQ, ZUC_VAL_INT, expected, actual) - -/** - * Verifies that the values of the specified expressions differ, marks the - * test as failed and exits the current function via 'return' if they do not. - * - * For details on return and test termination see @ref zunitc_overview_return. - * - * @param expected the value the result should not hold. - * @param actual the actual value seen in testing. - * @see ZUC_ASSERTG_NE() - */ -#define ZUC_ASSERT_NE(expected, actual) \ - ZUCIMPL_ASSERT(ZUC_OP_NE, ZUC_VAL_INT, expected, actual) - -/** - * Verifies that the value of the first expression is less than the value - * of the second expression, marks the test as failed and exits the current - * function via 'return' if it is not. - * - * For details on return and test termination see @ref zunitc_overview_return. - * - * @param lesser the expression whose value should be lesser than the other. - * @param greater the expression whose value should be greater than the other. - * @see ZUC_ASSERTG_LT() - */ -#define ZUC_ASSERT_LT(lesser, greater) \ - ZUCIMPL_ASSERT(ZUC_OP_LT, ZUC_VAL_INT, lesser, greater) - -/** - * Verifies that the value of the first expression is less than or equal - * to the value of the second expression, marks the test as failed and - * exits the current function via 'return' if it is not. - * - * For details on return and test termination see @ref zunitc_overview_return. - * - * @param lesser the expression whose value should be lesser than or equal to - * the other. - * @param greater the expression whose value should be greater than or equal to - * the other. - * @see ZUC_ASSERTG_LE() - */ -#define ZUC_ASSERT_LE(lesser, greater) \ - ZUCIMPL_ASSERT(ZUC_OP_LE, ZUC_VAL_INT, lesser, greater) - -/** - * Verifies that the value of the first expression is greater than the - * value of the second expression, marks the test as failed and exits the - * current function via 'return' if it is not. - * - * For details on return and test termination see @ref zunitc_overview_return. - * - * @param greater the expression whose value should be greater than the other. - * @param lesser the expression whose value should be lesser than the other. - * @see ZUC_ASSERTG_GT() - */ -#define ZUC_ASSERT_GT(greater, lesser) \ - ZUCIMPL_ASSERT(ZUC_OP_GT, ZUC_VAL_INT, greater, lesser) - -/** - * Verifies that the value of the first expression is greater than or equal - * to the value of the second expression, marks the test as failed and exits - * the current function via 'return' if it is not. - * - * For details on return and test termination see @ref zunitc_overview_return. - * - * @param greater the expression whose value should be greater than or equal to - * the other. - * @param lesser the expression whose value should be lesser than or equal to - * the other. - * @see ZUC_ASSERTG_GE() - */ -#define ZUC_ASSERT_GE(greater, lesser) \ - ZUCIMPL_ASSERT(ZUC_OP_GE, ZUC_VAL_INT, greater, lesser) - -/** - * Verifies that the values of the specified expressions match when - * compared as null-terminated C-style strings, marks the test as failed - * and exits the current function via 'return' if they do not. - * - * For details on return and test termination see @ref zunitc_overview_return. - * - * @param expected the value the result should hold. - * @param actual the actual value seen in testing. - * @see ZUC_ASSERTG_STREQ() - */ -#define ZUC_ASSERT_STREQ(expected, actual) \ - ZUCIMPL_ASSERT(ZUC_OP_EQ, ZUC_VAL_CSTR, expected, actual) - -/** - * Verifies that the values of the specified expressions differ when - * compared as null-terminated C-style strings, marks the test as failed - * and exits the current function via 'return' if they do not. - * - * For details on return and test termination see @ref zunitc_overview_return. - * - * @param expected the value the result should not hold. - * @param actual the actual value seen in testing. - * @see ZUC_ASSERTG_STRNE() - */ -#define ZUC_ASSERT_STRNE(expected, actual) \ - ZUCIMPL_ASSERT(ZUC_OP_NE, ZUC_VAL_CSTR, expected, actual) - -/** - * Verifies that the specified expression is true, marks the test as failed - * and interrupts the current execution via a 'goto' if it is not. - * - * @param condition the expression that is expected to be true. - * @note it is far better to use a more specific check when possible - * (e.g. ZUC_ASSERTG_EQ(), ZUC_ASSERTG_NE(), etc.) - * @param label the target for 'goto' if the assertion fails. - * @see ZUC_ASSERT_TRUE() - */ -#define ZUC_ASSERTG_TRUE(condition, label) \ - ZUCIMPL_ASSERTG(label, ZUC_OP_TRUE, ZUC_VAL_INT, condition, 0) - -/** - * Verifies that the specified expression is false, marks the test as - * failed and interrupts the current execution via a 'goto' if it is not. - * - * @param condition the expression that is expected to be false. - * @note it is far better to use a more specific check when possible - * (e.g. ZUC_ASSERTG_EQ(), ZUC_ASSERTG_NE(), etc.) - * @param label the target for 'goto' if the assertion fails. - * @see ZUC_ASSERT_FALSE() - */ -#define ZUC_ASSERTG_FALSE(condition, label) \ - ZUCIMPL_ASSERTG(label, ZUC_OP_FALSE, ZUC_VAL_INT, condition, 0) - -/** - * Verifies that the specified expression is NULL, marks the test as failed - * and interrupts the current execution via a 'goto' if it is not. - * - * @param condition the expression that is expected to be a NULL pointer. - * @param label the target for 'goto' if the assertion fails. - * @see ZUC_ASSERT_NULL() - */ -#define ZUC_ASSERTG_NULL(condition, label) \ - ZUCIMPL_ASSERTG(label, ZUC_OP_NULL, ZUC_VAL_PTR, condition, 0) - -/** - * Verifies that the specified expression is non-NULL, marks the test as - * failed and interrupts the current execution via a 'goto' if it is not. - * - * @param condition the expression that is expected to be a non-NULL pointer. - * @param label the target for 'goto' if the assertion fails. - * @see ZUC_ASSERT_NOT_NULL() - */ -#define ZUC_ASSERTG_NOT_NULL(condition, label) \ - ZUCIMPL_ASSERTG(label, ZUC_OP_NOT_NULL, ZUC_VAL_PTR, condition, 0) - -/** - * Verifies that the values of the specified expressions match, marks the - * test as failed and interrupts the current execution via a 'goto' if they - * do not. - * - * @param expected the value the result should hold. - * @param actual the actual value seen in testing. - * @param label the target for 'goto' if the assertion fails. - * @see ZUC_ASSERT_EQ() - */ -#define ZUC_ASSERTG_EQ(expected, actual, label) \ - ZUCIMPL_ASSERTG(label, ZUC_OP_EQ, ZUC_VAL_INT, expected, actual) - -/** - * Verifies that the values of the specified expressions differ, marks the - * test as failed and interrupts the current execution via a 'goto' if they - * do not. - * - * @param expected the value the result should not hold. - * @param actual the actual value seen in testing. - * @param label the target for 'goto' if the assertion fails. - * @see ZUC_ASSERT_NE() - */ -#define ZUC_ASSERTG_NE(expected, actual, label) \ - ZUCIMPL_ASSERTG(label, ZUC_OP_NE, ZUC_VAL_INT, expected, actual) - -/** - * Verifies that the value of the first expression is less than the value - * of the second expression, marks the test as failed and interrupts the - * current execution via a 'goto' if it is not. - * - * @param lesser the expression whose value should be lesser than the other. - * @param greater the expression whose value should be greater than the other. - * @param label the target for 'goto' if the assertion fails. - * @see ZUC_ASSERT_LT() - */ -#define ZUC_ASSERTG_LT(lesser, greater, label) \ - ZUCIMPL_ASSERTG(label, ZUC_OP_LT, ZUC_VAL_INT, lesser, greater) - -/** - * Verifies that the value of the first expression is less than or equal - * to the value of the second expression, marks the test as failed and - * interrupts the current execution via a 'goto' if it is not. - * - * @param lesser the expression whose value should be lesser than or equal to - * the other. - * @param greater the expression whose value should be greater than or equal to - * the other. - * @param label the target for 'goto' if the assertion fails. - * @see ZUC_ASSERT_LE() - */ -#define ZUC_ASSERTG_LE(lesser, greater, label) \ - ZUCIMPL_ASSERTG(label, ZUC_OP_LE, ZUC_VAL_INT, lesser, greater) - -/** - * Verifies that the value of the first expression is greater than the - * value of the second expression, marks the test as failed and interrupts the - * current execution via a 'goto' if it is not. - * - * @param greater the expression whose value should be greater than the other. - * @param lesser the expression whose value should be lesser than the other. - * @param label the target for 'goto' if the assertion fails. - * @see ZUC_ASSERT_GT() - */ -#define ZUC_ASSERTG_GT(greater, lesser, label) \ - ZUCIMPL_ASSERTG(label, ZUC_OP_GT, ZUC_VAL_INT, greater, lesser) - -/** - * Verifies that the value of the first expression is greater than or equal - * to the value of the second expression, marks the test as failed and - * interrupts the current execution via a 'goto' if it is not. - * - * @param greater the expression whose value should be greater than or equal to - * the other. - * @param lesser the expression whose value should be lesser than or equal to - * the other. - * @param label the target for 'goto' if the assertion fails. - * @see ZUC_ASSERT_GE() - */ -#define ZUC_ASSERTG_GE(greater, lesser, label) \ - ZUCIMPL_ASSERTG(label, ZUC_OP_GE, ZUC_VAL_INT, greater, lesser) - -/** - * Verifies that the values of the specified expressions match when - * compared as null-terminated C-style strings, marks the test as failed - * and interrupts the current execution via a 'goto' if they do not. - * - * @param expected the value the result should hold. - * @param actual the actual value seen in testing. - * @param label the target for 'goto' if the assertion fails. - * @see ZUC_ASSERT_STREQ() - */ -#define ZUC_ASSERTG_STREQ(expected, actual, label) \ - ZUCIMPL_ASSERTG(label, ZUC_OP_EQ, ZUC_VAL_CSTR, expected, actual) - -/** - * Verifies that the values of the specified expressions differ when - * compared as null-terminated C-style strings, marks the test as failed - * and interrupts the current execution via a 'goto' if they do not. - * - * @param expected the value the result should not hold. - * @param actual the actual value seen in testing. - * @param label the target for 'goto' if the assertion fails. - * @see ZUC_ASSERT_STRNE() - */ -#define ZUC_ASSERTG_STRNE(expected, actual, label) \ - ZUCIMPL_ASSERTG(label, ZUC_OP_NE, ZUC_VAL_CSTR, expected, actual) - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* Z_UNIT_C_H */ diff --git a/tools/zunitc/inc/zunitc/zunitc_impl.h b/tools/zunitc/inc/zunitc/zunitc_impl.h deleted file mode 100644 index f053368..0000000 --- a/tools/zunitc/inc/zunitc/zunitc_impl.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef Z_UNIT_C_IMPL_H -#define Z_UNIT_C_IMPL_H - -/** - * @file - * Internal details to bridge the public API - should not be used - * directly in user code. - */ - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -enum zuc_check_op -{ - ZUC_OP_TRUE, - ZUC_OP_FALSE, - ZUC_OP_NULL, - ZUC_OP_NOT_NULL, - ZUC_OP_EQ, - ZUC_OP_NE, - ZUC_OP_GE, - ZUC_OP_GT, - ZUC_OP_LE, - ZUC_OP_LT, - ZUC_OP_TERMINATE, - ZUC_OP_TRACEPOINT -}; - -enum zuc_check_valtype -{ - ZUC_VAL_INT, - ZUC_VAL_CSTR, - ZUC_VAL_PTR, -}; - -typedef void (*zucimpl_test_fn)(void); - -typedef void (*zucimpl_test_fn_f)(void *); - -/** - * Internal use structure for automatic test case registration. - * Should not be used directly in code. - */ -struct zuc_registration { - const char *tcase; /**< Name of the test case. */ - const char *test; /**< Name of the specific test. */ - const struct zuc_fixture* fxt; /**< Optional fixture for test/case. */ - zucimpl_test_fn fn; /**< function implementing base test. */ - zucimpl_test_fn_f fn_f; /**< function implementing test with - fixture. */ -} __attribute__ ((aligned (32))); - - -int -zucimpl_run_tests(void); - -void -zucimpl_terminate(char const *file, int line, - bool fail, bool fatal, const char *msg); - -int -zucimpl_tracepoint(char const *file, int line, const char *fmt, ...) - __attribute__ ((format (printf, 3, 4))); - -int -zucimpl_expect_pred2(char const *file, int line, - enum zuc_check_op, enum zuc_check_valtype valtype, - bool fatal, - intptr_t lhs, intptr_t rhs, - const char *lhs_str, const char* rhs_str); - -#ifdef __cplusplus -} -#endif - -#endif /* Z_UNIT_C_IMPL_H */ diff --git a/tools/zunitc/src/main.c b/tools/zunitc/src/main.c deleted file mode 100644 index 8179807..0000000 --- a/tools/zunitc/src/main.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -/* - * Common main() for test programs. - */ - -#include -#include - -#include "zunitc/zunitc.h" - -int -main(int argc, char* argv[]) -{ - bool helped = false; - int rc = zuc_initialize(&argc, argv, &helped); - - if ((rc == EXIT_SUCCESS) && !helped) { - /* Stop if any unrecognized parameters were encountered. */ - if (argc > 1) { - printf("%s: unrecognized option '%s'\n", - argv[0], argv[1]); - printf("Try '%s --help' for more information.\n", - argv[0]); - rc = EXIT_FAILURE; - } else { - rc = ZUC_RUN_TESTS(); - } - } - - zuc_cleanup(); - return rc; -} diff --git a/tools/zunitc/src/zuc_base_logger.c b/tools/zunitc/src/zuc_base_logger.c deleted file mode 100644 index 4b54344..0000000 --- a/tools/zunitc/src/zuc_base_logger.c +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include "zuc_base_logger.h" - -#include -#include -#include -#include -#include -#include - -#include "zuc_event_listener.h" -#include "zuc_types.h" - -#include - -/* a few sequences for rudimentary ANSI graphics. */ -#define CSI_GRN "\x1b[0;32m" -#define CSI_RED "\x1b[0;31m" -#define CSI_YLW "\x1b[0;33m" -#define CSI_RST "\x1b[m" - -/** - * Logical mappings of style levels. - */ -enum style_level { - STYLE_GOOD, - STYLE_WARN, - STYLE_BAD -}; - -/** - * Structure for internal context. - */ -struct base_data { - bool use_color; -}; - -/** - * Prints a formatted string with optional ANSI coloring. - * - * @param use_color true to colorize the output, false to output normally. - * @param slevel the logical type to color for. - * @param fmt the format string to print with. - */ -static void -styled_printf(bool use_color, enum style_level slevel, const char *fmt, ...); - -static void -destroy(void *data); - -static void -pre_run(void *data, int pass_count, int pass_num, int seed, const char *filter); - -static void -run_started(void *data, int live_case_count, - int live_test_count, int disabled_count); - -static void -run_ended(void *data, int case_count, struct zuc_case **cases, - int live_case_count, int live_test_count, int total_passed, - int total_failed, int total_disabled, long total_elapsed); - -static void -case_started(void *data, struct zuc_case *test_case, int live_test_count, - int disabled_count); - -static void -case_ended(void *data, struct zuc_case *test_case); - -static void -test_started(void *data, struct zuc_test *test); - -static void -test_ended(void *data, struct zuc_test *test); - -static void -check_triggered(void *data, char const *file, int line, - enum zuc_fail_state state, enum zuc_check_op op, - enum zuc_check_valtype valtype, - intptr_t val1, intptr_t val2, - const char *expr1, const char *expr2); - -struct zuc_event_listener * -zuc_base_logger_create(void) -{ - struct zuc_event_listener *listener = - zalloc(sizeof(struct zuc_event_listener)); - - listener->data = zalloc(sizeof(struct base_data)); - listener->destroy = destroy; - listener->pre_run = pre_run; - listener->run_started = run_started; - listener->run_ended = run_ended; - listener->case_started = case_started; - listener->case_ended = case_ended; - listener->test_started = test_started; - listener->test_ended = test_ended; - listener->check_triggered = check_triggered; - - return listener; -} - -void -styled_printf(bool use_color, enum style_level slevel, const char *fmt, ...) -{ - va_list argp; - - if (use_color) - switch (slevel) { - case STYLE_GOOD: - printf(CSI_GRN); - break; - case STYLE_WARN: - printf(CSI_YLW); - break; - case STYLE_BAD: - printf(CSI_RED); - break; - default: - break; - } - - va_start(argp, fmt); - vprintf(fmt, argp); - va_end(argp); - - if (use_color) - printf(CSI_RST); -} - -void -destroy(void *data) -{ - free(data); -} - -void -pre_run(void *data, int pass_count, int pass_num, int seed, const char *filter) -{ - struct base_data *bdata = data; - - bdata->use_color = isatty(fileno(stdout)) - && getenv("TERM") && strcmp(getenv("TERM"), "dumb"); - - if (pass_count > 1) - printf("\nRepeating all tests (iteration %d) . . .\n\n", - pass_num); - - if (filter && filter[0]) - styled_printf(bdata->use_color, STYLE_WARN, - "Note: test filter = %s\n", - filter); - - if (seed > 0) - styled_printf(bdata->use_color, STYLE_WARN, - "Note: Randomizing tests' orders" - " with a seed of %u .\n", - seed); -} - -void -run_started(void *data, int live_case_count, - int live_test_count, int disabled_count) -{ - struct base_data *bdata = data; - - styled_printf(bdata->use_color, STYLE_GOOD, "[==========]"); - printf(" Running %d %s from %d test %s.\n", - live_test_count, - (live_test_count == 1) ? "test" : "tests", - live_case_count, - (live_case_count == 1) ? "case" : "cases"); -} - -void -run_ended(void *data, int case_count, struct zuc_case **cases, - int live_case_count, int live_test_count, int total_passed, - int total_failed, int total_disabled, long total_elapsed) -{ - struct base_data *bdata = data; - styled_printf(bdata->use_color, STYLE_GOOD, "[==========]"); - printf(" %d %s from %d test %s ran. (%ld ms)\n", - live_test_count, - (live_test_count == 1) ? "test" : "tests", - live_case_count, - (live_case_count == 1) ? "case" : "cases", - total_elapsed); - - if (total_passed) { - styled_printf(bdata->use_color, STYLE_GOOD, "[ PASSED ]"); - printf(" %d %s.\n", total_passed, - (total_passed == 1) ? "test" : "tests"); - } - - if (total_failed) { - int case_num; - styled_printf(bdata->use_color, STYLE_BAD, "[ FAILED ]"); - printf(" %d %s, listed below:\n", - total_failed, (total_failed == 1) ? "test" : "tests"); - - for (case_num = 0; case_num < case_count; ++case_num) { - int i; - for (i = 0; i < cases[case_num]->test_count; ++i) { - struct zuc_test *curr = - cases[case_num]->tests[i]; - if (curr->failed || curr->fatal) { - styled_printf(bdata->use_color, - STYLE_BAD, - "[ FAILED ]"); - printf(" %s.%s\n", - cases[case_num]->name, - curr->name); - } - } - } - } - - if (total_failed || total_disabled) - printf("\n"); - - if (total_failed) - printf(" %d FAILED %s\n", - total_failed, - (total_failed == 1) ? "TEST" : "TESTS"); - - if (total_disabled) - styled_printf(bdata->use_color, STYLE_WARN, - " YOU HAVE %d DISABLED %s\n", - total_disabled, - (total_disabled == 1) ? "TEST" : "TESTS"); -} - -void -case_started(void *data, struct zuc_case *test_case, int live_test_count, - int disabled_count) -{ - struct base_data *bdata = data; - styled_printf(bdata->use_color, STYLE_GOOD, "[----------]"); - printf(" %d %s from %s.\n", - live_test_count, - (live_test_count == 1) ? "test" : "tests", - test_case->name); - -} - -void -case_ended(void *data, struct zuc_case *test_case) -{ - struct base_data *bdata = data; - styled_printf(bdata->use_color, STYLE_GOOD, "[----------]"); - printf(" %d %s from %s (%ld ms)\n", - test_case->test_count, - (test_case->test_count == 1) ? "test" : "tests", - test_case->name, - test_case->elapsed); - printf("\n"); -} - -void -test_started(void *data, struct zuc_test *test) -{ - struct base_data *bdata = data; - styled_printf(bdata->use_color, STYLE_GOOD, "[ RUN ]"); - printf(" %s.%s\n", test->test_case->name, test->name); -} - -void -test_ended(void *data, struct zuc_test *test) -{ - struct base_data *bdata = data; - if (test->failed || test->fatal) { - styled_printf(bdata->use_color, STYLE_BAD, "[ FAILED ]"); - printf(" %s.%s (%ld ms)\n", - test->test_case->name, test->name, test->elapsed); - } else { - styled_printf(bdata->use_color, STYLE_GOOD, "[ OK ]"); - printf(" %s.%s (%ld ms)\n", - test->test_case->name, test->name, test->elapsed); - } -} - -const char * -zuc_get_opstr(enum zuc_check_op op) -{ - switch (op) { - case ZUC_OP_EQ: - return "="; - case ZUC_OP_NE: - return "!="; - case ZUC_OP_GE: - return ">="; - case ZUC_OP_GT: - return ">"; - case ZUC_OP_LE: - return "<="; - case ZUC_OP_LT: - return "<"; - default: - return "???"; - } -} - -void -check_triggered(void *data, char const *file, int line, - enum zuc_fail_state state, enum zuc_check_op op, - enum zuc_check_valtype valtype, - intptr_t val1, intptr_t val2, - const char *expr1, const char *expr2) -{ - switch (op) { - case ZUC_OP_TRUE: - printf("%s:%d: error: Value of: %s\n", file, line, expr1); - printf(" Actual: false\n"); - printf("Expected: true\n"); - break; - case ZUC_OP_FALSE: - printf("%s:%d: error: Value of: %s\n", file, line, expr1); - printf(" Actual: true\n"); - printf("Expected: false\n"); - break; - case ZUC_OP_NULL: - printf("%s:%d: error: Value of: %s\n", file, line, expr1); - printf(" Actual: %p\n", (void *)val1); - printf("Expected: %p\n", NULL); - break; - case ZUC_OP_NOT_NULL: - printf("%s:%d: error: Value of: %s\n", file, line, expr1); - printf(" Actual: %p\n", (void *)val1); - printf("Expected: not %p\n", NULL); - break; - case ZUC_OP_EQ: - if (valtype == ZUC_VAL_CSTR) { - printf("%s:%d: error: Value of: %s\n", file, line, - expr2); - printf(" Actual: %s\n", (const char *)val2); - printf("Expected: %s\n", expr1); - printf("Which is: %s\n", (const char *)val1); - } else { - printf("%s:%d: error: Value of: %s\n", file, line, - expr2); - printf(" Actual: %"PRIdPTR"\n", val2); - printf("Expected: %s\n", expr1); - printf("Which is: %"PRIdPTR"\n", val1); - } - break; - case ZUC_OP_NE: - if (valtype == ZUC_VAL_CSTR) { - printf("%s:%d: error: ", file, line); - printf("Expected: (%s) %s (%s), actual: %s == %s\n", - expr1, zuc_get_opstr(op), expr2, - (char *)val1, (char *)val2); - } else { - printf("%s:%d: error: ", file, line); - printf("Expected: (%s) %s (%s), actual: %"PRIdPTR" vs " - "%"PRIdPTR"\n", expr1, zuc_get_opstr(op), expr2, - val1, val2); - } - break; - case ZUC_OP_TERMINATE: { - char const *level = (val1 == 0) ? "error" - : (val1 == 1) ? "warning" - : "note"; - printf("%s:%d: %s: %s\n", file, line, level, expr1); - break; - } - case ZUC_OP_TRACEPOINT: - printf("%s:%d: note: %s\n", file, line, expr1); - break; - default: - printf("%s:%d: error: ", file, line); - printf("Expected: (%s) %s (%s), actual: %"PRIdPTR" vs " - "%"PRIdPTR"\n", expr1, zuc_get_opstr(op), expr2, val1, - val2); - } -} diff --git a/tools/zunitc/src/zuc_base_logger.h b/tools/zunitc/src/zuc_base_logger.h deleted file mode 100644 index 80b108e..0000000 --- a/tools/zunitc/src/zuc_base_logger.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef ZUC_BASE_LOGGER_H -#define ZUC_BASE_LOGGER_H - -struct zuc_event_listener; - -/** - * Creates a new logger that outputs data to console in the default - * format. - */ -struct zuc_event_listener * -zuc_base_logger_create(void); - -#endif /* ZUC_BASE_LOGGER_H */ diff --git a/tools/zunitc/src/zuc_collector.c b/tools/zunitc/src/zuc_collector.c deleted file mode 100644 index 035c9ce..0000000 --- a/tools/zunitc/src/zuc_collector.c +++ /dev/null @@ -1,427 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include "zuc_collector.h" - -#include -#include -#include -#include - -#include -#include "zuc_event_listener.h" -#include "zunitc/zunitc_impl.h" - -#include -#include -#include - -/** - * @file - * General handling of collecting events during testing to pass back to - * main tracking of fork()'d tests. - * - * @note implementation of zuc_process_message() is included here so that - * all child->parent IPC is in a single source file for easier maintenance - * and updating. - */ - -/** - * Internal data struct for processing. - */ -struct collector_data -{ - int *fd; /**< file descriptor to output to. */ - struct zuc_test *test; /**< current test, or NULL. */ -}; - -/** - * Stores an int32_t into the given buffer. - * - * @param ptr the buffer to store to. - * @param val the value to store. - * @return a pointer to the position in the buffer after the stored value. - */ -static char * -pack_int32(char *ptr, int32_t val); - -/** - * Stores an intptr_t into the given buffer. - * - * @param ptr the buffer to store to. - * @param val the value to store. - * @return a pointer to the position in the buffer after the stored value. - */ -static char * -pack_intptr_t(char *ptr, intptr_t val); - -/** - * Extracts a int32_t from the given buffer. - * - * @param ptr the buffer to extract from. - * @param val the value to set. - * @return a pointer to the position in the buffer after the extracted - * value. - */ -static char const * -unpack_int32(char const *ptr, int32_t *val); - -/** - * Extracts a intptr_t from the given buffer. - * - * @param ptr the buffer to extract from. - * @param val the value to set. - * @return a pointer to the position in the buffer after the extracted - * value. - */ -static char const * -unpack_intptr_t(char const *ptr, intptr_t *val); - -/** - * Extracts a length-prefixed string from the given buffer. - * - * @param ptr the buffer to extract from. - * @param str the value to set. - * @return a pointer to the position in the buffer after the extracted - * value. - */ -static char const * -unpack_string(char const *ptr, char **str); - -/** - * Extracts an event from the given buffer. - * - * @param ptr the buffer to extract from. - * @param len the length of the given buffer. - * @return an event that was packed in the buffer - */ -static struct zuc_event * -unpack_event(char const *ptr, int32_t len); - -/** - * Handles an event by either attaching it directly or sending it over IPC - * as needed. - */ -static void -store_event(struct collector_data *cdata, - enum zuc_event_type event_type, char const *file, int line, - enum zuc_fail_state state, enum zuc_check_op op, - enum zuc_check_valtype valtype, - intptr_t val1, intptr_t val2, const char *expr1, const char *expr2); - -static void -destroy(void *data); - -static void -test_started(void *data, struct zuc_test *test); - -static void -test_ended(void *data, struct zuc_test *test); - -static void -check_triggered(void *data, char const *file, int line, - enum zuc_fail_state state, enum zuc_check_op op, - enum zuc_check_valtype valtype, - intptr_t val1, intptr_t val2, - const char *expr1, const char *expr2); - -static void -collect_event(void *data, char const *file, int line, const char *expr1); - -struct zuc_event_listener * -zuc_collector_create(int *pipe_fd) -{ - struct zuc_event_listener *listener = - zalloc(sizeof(struct zuc_event_listener)); - - listener->data = zalloc(sizeof(struct collector_data)); - ((struct collector_data *)listener->data)->fd = pipe_fd; - listener->destroy = destroy; - listener->test_started = test_started; - listener->test_ended = test_ended; - listener->check_triggered = check_triggered; - listener->collect_event = collect_event; - - return listener; -} - -char * -pack_int32(char *ptr, int32_t val) -{ - memcpy(ptr, &val, sizeof(val)); - return ptr + sizeof(val); -} - -char * -pack_intptr_t(char *ptr, intptr_t val) -{ - memcpy(ptr, &val, sizeof(val)); - return ptr + sizeof(val); -} - -static char * -pack_cstr(char *ptr, intptr_t val, int len) -{ - if (val == 0) { /* a null pointer */ - ptr = pack_int32(ptr, -1); - } else { - ptr = pack_int32(ptr, len); - memcpy(ptr, (const char *)val, len); - ptr += len; - } - return ptr; -} - -void -destroy(void *data) -{ - free(data); -} - -void -test_started(void *data, struct zuc_test *test) -{ - struct collector_data *cdata = data; - cdata->test = test; -} - -void -test_ended(void *data, struct zuc_test *test) -{ - struct collector_data *cdata = data; - cdata->test = NULL; -} - -void -check_triggered(void *data, char const *file, int line, - enum zuc_fail_state state, enum zuc_check_op op, - enum zuc_check_valtype valtype, - intptr_t val1, intptr_t val2, - const char *expr1, const char *expr2) -{ - struct collector_data *cdata = data; - if (op != ZUC_OP_TRACEPOINT) - store_event(cdata, ZUC_EVENT_IMMEDIATE, file, line, state, op, - valtype, - val1, val2, expr1, expr2); -} - -void -collect_event(void *data, char const *file, int line, const char *expr1) -{ - struct collector_data *cdata = data; - store_event(cdata, ZUC_EVENT_DEFERRED, file, line, ZUC_CHECK_OK, - ZUC_OP_TRACEPOINT, ZUC_VAL_INT, - 0, 0, expr1, ""); -} - -void -store_event(struct collector_data *cdata, - enum zuc_event_type event_type, char const *file, int line, - enum zuc_fail_state state, enum zuc_check_op op, - enum zuc_check_valtype valtype, - intptr_t val1, intptr_t val2, const char *expr1, const char *expr2) -{ - struct zuc_event *event = zalloc(sizeof(*event)); - event->file = strdup(file); - event->line = line; - event->state = state; - event->op = op; - event->valtype = valtype; - event->val1 = val1; - event->val2 = val2; - if (valtype == ZUC_VAL_CSTR) { - if (val1) - event->val1 = (intptr_t)strdup((const char *)val1); - if (val2) - event->val2 = (intptr_t)strdup((const char *)val2); - } - event->expr1 = strdup(expr1); - event->expr2 = strdup(expr2); - - zuc_attach_event(cdata->test, event, event_type, false); - - if (*cdata->fd == -1) { - } else { - /* Need to pass it back */ - int sent; - int count; - int expr1_len = strlen(expr1); - int expr2_len = strlen(expr2); - int val1_len = - ((valtype == ZUC_VAL_CSTR) && val1) ? - strlen((char *)val1) : 0; - int val2_len = - ((valtype == ZUC_VAL_CSTR) && val2) ? - strlen((char *)val2) : 0; - int file_len = strlen(file); - int len = (sizeof(int32_t) * 9) + file_len - + (sizeof(intptr_t) * 2) - + ((valtype == ZUC_VAL_CSTR) ? - (sizeof(int32_t) * 2) + val1_len + val2_len : 0) - + expr1_len + expr2_len; - char *buf = zalloc(len); - - char *ptr = pack_int32(buf, len - 4); - ptr = pack_int32(ptr, event_type); - ptr = pack_int32(ptr, file_len); - memcpy(ptr, file, file_len); - ptr += file_len; - ptr = pack_int32(ptr, line); - ptr = pack_int32(ptr, state); - ptr = pack_int32(ptr, op); - ptr = pack_int32(ptr, valtype); - if (valtype == ZUC_VAL_CSTR) { - ptr = pack_cstr(ptr, val1, val1_len); - ptr = pack_cstr(ptr, val2, val2_len); - } else { - ptr = pack_intptr_t(ptr, val1); - ptr = pack_intptr_t(ptr, val2); - } - - ptr = pack_int32(ptr, expr1_len); - if (expr1_len) { - memcpy(ptr, expr1, expr1_len); - ptr += expr1_len; - } - ptr = pack_int32(ptr, expr2_len); - if (expr2_len) { - memcpy(ptr, expr2, expr2_len); - ptr += expr2_len; - } - - - sent = 0; - while (sent < len) { - count = write(*cdata->fd, buf, len); - if (count == -1) - break; - sent += count; - } - - free(buf); - } -} - -char const * -unpack_int32(char const *ptr, int32_t *val) -{ - memcpy(val, ptr, sizeof(*val)); - return ptr + sizeof(*val); -} - -char const * -unpack_intptr_t(char const *ptr, intptr_t *val) -{ - memcpy(val, ptr, sizeof(*val)); - return ptr + sizeof(*val); -} - -char const * -unpack_string(char const *ptr, char **str) -{ - int32_t len = 0; - ptr = unpack_int32(ptr, &len); - *str = zalloc(len + 1); - if (len) - memcpy(*str, ptr, len); - ptr += len; - return ptr; -} - -static char const * -unpack_cstr(char const *ptr, char **str) -{ - int32_t len = 0; - ptr = unpack_int32(ptr, &len); - if (len < 0) { - *str = NULL; - } else { - *str = zalloc(len + 1); - if (len) - memcpy(*str, ptr, len); - ptr += len; - } - return ptr; -} - -struct zuc_event * -unpack_event(char const *ptr, int32_t len) -{ - int32_t val = 0; - struct zuc_event *evt = zalloc(sizeof(*evt)); - char const *tmp = unpack_string(ptr, &evt->file); - tmp = unpack_int32(tmp, &evt->line); - - tmp = unpack_int32(tmp, &val); - evt->state = val; - tmp = unpack_int32(tmp, &val); - evt->op = val; - tmp = unpack_int32(tmp, &val); - evt->valtype = val; - - if (evt->valtype == ZUC_VAL_CSTR) { - char *ptr = NULL; - tmp = unpack_cstr(tmp, &ptr); - evt->val1 = (intptr_t)ptr; - tmp = unpack_cstr(tmp, &ptr); - evt->val2 = (intptr_t)ptr; - } else { - tmp = unpack_intptr_t(tmp, &evt->val1); - tmp = unpack_intptr_t(tmp, &evt->val2); - } - - tmp = unpack_string(tmp, &evt->expr1); - tmp = unpack_string(tmp, &evt->expr2); - - return evt; -} - -int -zuc_process_message(struct zuc_test *test, int fd) -{ - char buf[4] = {}; - int got = read(fd, buf, 4); - if (got == 4) { - enum zuc_event_type event_type = ZUC_EVENT_IMMEDIATE; - int32_t val = 0; - int32_t len = 0; - const char *tmp = NULL; - char *raw = NULL; - unpack_int32(buf, &len); - raw = zalloc(len); - got = read(fd, raw, len); - - tmp = unpack_int32(raw, &val); - event_type = val; - - struct zuc_event *evt = unpack_event(tmp, len - (tmp - raw)); - zuc_attach_event(test, evt, event_type, true); - free(raw); - } - return got; -} diff --git a/tools/zunitc/src/zuc_collector.h b/tools/zunitc/src/zuc_collector.h deleted file mode 100644 index 56f8a5f..0000000 --- a/tools/zunitc/src/zuc_collector.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef ZUC_COLLECTOR_H -#define ZUC_COLLECTOR_H - -struct zuc_event_listener; -struct zuc_test; - -/** - * Creates a new instance of an event collector that will attach events - * to the current test directly or via connection from child to parent. - * - * @param pipe_fd pointer to the file descriptor to use for communication if - * needed. If the value is -1 the events will be attached directly to the - * current test. Otherwise events will be passed back via IPC over this - * pipe with the expectation that the payload will be handled in the parent - * process via zuc_process_message(). - * @return a new collector instance. - * @see zuc_process_message() - */ -struct zuc_event_listener * -zuc_collector_create(int *pipe_fd); - -/** - * Reads events from the given pipe and processes them. - * - * @param test the currently active test to attach events for. - * @param pipe_fd the file descriptor of the pipe to read from. - * @return a positive value if a message was received, 0 if the end has - * been reached and -1 if an error has occurred. - */ -int -zuc_process_message(struct zuc_test *test, int pipe_fd); - -#endif /* ZUC_COLLECTOR_H */ diff --git a/tools/zunitc/src/zuc_context.h b/tools/zunitc/src/zuc_context.h deleted file mode 100644 index 609f34b..0000000 --- a/tools/zunitc/src/zuc_context.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef ZUC_CONTEXT_H -#define ZUC_CONTEXT_H - -#include "zuc_types.h" - -struct zuc_slinked; - -/** - * Internal context for processing. - * Collecting data members here minimizes use of globals. - */ -struct zuc_context { - int case_count; - struct zuc_case **cases; - - bool fatal; - int repeat; - int random; - unsigned int seed; - bool spawn; - bool break_on_failure; - bool output_tap; - bool output_junit; - int fds[2]; - char *filter; - - struct zuc_slinked *listeners; - - struct zuc_case *curr_case; - struct zuc_test *curr_test; -}; - -#endif /* ZUC_CONTEXT_H */ diff --git a/tools/zunitc/src/zuc_event.h b/tools/zunitc/src/zuc_event.h deleted file mode 100644 index ccb2f7b..0000000 --- a/tools/zunitc/src/zuc_event.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef ZUC_EVENT_H -#define ZUC_EVENT_H - -#include - -#include "zunitc/zunitc_impl.h" - -/** - * - */ -enum zuc_event_type -{ - ZUC_EVENT_IMMEDIATE, - ZUC_EVENT_DEFERRED -}; - -/** - * Status enum for posted events. - */ -enum zuc_fail_state -{ - ZUC_CHECK_OK, /**< no problem. */ - ZUC_CHECK_SKIP, /**< runtime skip directive encountered. */ - ZUC_CHECK_FAIL, /**< non-fatal check fails. */ - ZUC_CHECK_FATAL, /**< fatal assertion/check fails. */ - ZUC_CHECK_ERROR /**< internal level problem. */ -}; - -/** - * Record of an event that occurs during testing such as assert macro - * failures. - */ -struct zuc_event -{ - char *file; - int32_t line; - enum zuc_fail_state state; - enum zuc_check_op op; - enum zuc_check_valtype valtype; - intptr_t val1; - intptr_t val2; - char *expr1; - char *expr2; - - struct zuc_event *next; -}; - -/** - * Attaches an event to the specified test. - * - * @param test the test to attach to. - * @param event the event to attach. - * @param event_type of event (immediate or deferred) to attach. - * @param transferred true if the event has been processed elsewhere and - * is being transferred for storage, false otherwise. - */ -void -zuc_attach_event(struct zuc_test *test, struct zuc_event *event, - enum zuc_event_type event_type, bool transferred); - -#endif /* ZUC_EVENT_H */ diff --git a/tools/zunitc/src/zuc_event_listener.h b/tools/zunitc/src/zuc_event_listener.h deleted file mode 100644 index 41c5fbd..0000000 --- a/tools/zunitc/src/zuc_event_listener.h +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef ZUC_EVENT_HANDLER_H -#define ZUC_EVENT_HANDLER_H - -#include - -#include "zuc_context.h" -#include "zuc_event.h" - -struct zuc_test; -struct zuc_case; - -/** - * Interface to allow components to process testing events as they occur. - * - * Event listeners that will stream output as testing progresses are often - * named "*_logger" whereas those that produce their output upon test run - * completion are named "*_reporter". - */ -struct zuc_event_listener { - /** - * User data pointer. - */ - void *data; - - /** - * Destructor. - * @param data the user data associated with this instance. - */ - void (*destroy)(void *data); - - /** - * Handler for simple pre-run state. - * - * @param pass_count total number of expected test passes. - * @param pass_num current pass iteration number. - * @param seed random seed being used, or 0 for no randomization. - * @param filter filter string used for tests, or NULL/blank for none. - */ - void (*pre_run)(void *data, - int pass_count, - int pass_num, - int seed, - const char *filter); - - /** - * Handler for test runs starting. - * - * @param data the user data associated with this instance. - * @param live_case_count number of active test cases in this run. - * @param live_test_count number of active tests in this run. - * @param disabled_count number of disabled tests in this run. - */ - void (*run_started)(void *data, - int live_case_count, - int live_test_count, - int disabled_count); - - /** - * Handler for test runs ending. - * - * @param data the user data associated with this instance. - */ - void (*run_ended)(void *data, - int case_count, - struct zuc_case** cases, - int live_case_count, - int live_test_count, - int total_passed, - int total_failed, - int total_disabled, - long total_elapsed); - - /** - * Handler for test case starting. - * - * @param data the user data associated with this instance. - */ - void (*case_started)(void *data, - struct zuc_case *test_case, - int live_test_count, - int disabled_count); - - /** - * Handler for test case ending. - * - * @param data the user data associated with this instance. - */ - void (*case_ended)(void *data, - struct zuc_case *test_case); - - /** - * Handler for test starting. - * - * @param data the user data associated with this instance. - */ - void (*test_started)(void *data, - struct zuc_test *test); - - /** - * Handler for test ending. - * - * @param data the user data associated with this instance. - */ - void (*test_ended)(void *data, - struct zuc_test *test); - - /** - * Handler for disabled test notification. - * - * @param data the user data associated with this instance. - */ - void (*test_disabled)(void *data, - struct zuc_test *test); - - /** - * Handler for check/assertion fired due to failure, warning, etc. - * - * @param data the user data associated with this instance. - */ - void (*check_triggered)(void *data, - char const *file, - int line, - enum zuc_fail_state state, - enum zuc_check_op op, - enum zuc_check_valtype valtype, - intptr_t val1, - intptr_t val2, - const char *expr1, - const char *expr2); - - /** - * Handler for tracepoints and such that may be displayed later. - * - * @param data the user data associated with this instance. - */ - void (*collect_event)(void *data, - char const *file, - int line, - const char *expr1); -}; - -/** - * Registers an event listener instance to be called. - */ -void -zuc_add_event_listener(struct zuc_event_listener *event_listener); - - -#endif /* ZUC_EVENT_HANDLER_H */ diff --git a/tools/zunitc/src/zuc_junit_reporter.c b/tools/zunitc/src/zuc_junit_reporter.c deleted file mode 100644 index 42acad9..0000000 --- a/tools/zunitc/src/zuc_junit_reporter.c +++ /dev/null @@ -1,479 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include "zuc_junit_reporter.h" - -#if ENABLE_JUNIT_XML - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "zuc_event_listener.h" -#include "zuc_types.h" - -#include - -/** - * Hardcoded output name. - * @todo follow-up with refactoring to avoid filename hardcoding. - * Will allow for better testing in parallel etc. in general. - */ -#define XML_FNAME "test_detail.xml" - -#define ISO_8601_FORMAT "%Y-%m-%dT%H:%M:%SZ" - -#if LIBXML_VERSION >= 20904 -#define STRPRINTF_CAST -#else -#define STRPRINTF_CAST BAD_CAST -#endif - -/** - * Internal data. - */ -struct junit_data -{ - int fd; - time_t begin; -}; - -#define MAX_64BIT_STRLEN 20 - -static void -set_attribute(xmlNodePtr node, const char *name, int value) -{ - xmlChar scratch[MAX_64BIT_STRLEN + 1] = {}; - xmlStrPrintf(scratch, sizeof(scratch), STRPRINTF_CAST "%d", value); - xmlSetProp(node, BAD_CAST name, scratch); -} - -/** - * Output the given event. - * - * @param parent the parent node to add new content to. - * @param event the event to write out. - */ -static void -emit_event(xmlNodePtr parent, struct zuc_event *event) -{ - char *msg = NULL; - - switch (event->op) { - case ZUC_OP_TRUE: - if (asprintf(&msg, "%s:%d: error: Value of: %s\n" - " Actual: false\n" - "Expected: true\n", event->file, event->line, - event->expr1) < 0) { - msg = NULL; - } - break; - case ZUC_OP_FALSE: - if (asprintf(&msg, "%s:%d: error: Value of: %s\n" - " Actual: true\n" - "Expected: false\n", event->file, event->line, - event->expr1) < 0) { - msg = NULL; - } - break; - case ZUC_OP_NULL: - if (asprintf(&msg, "%s:%d: error: Value of: %s\n" - " Actual: %p\n" - "Expected: %p\n", event->file, event->line, - event->expr1, (void *)event->val1, NULL) < 0) { - msg = NULL; - } - break; - case ZUC_OP_NOT_NULL: - if (asprintf(&msg, "%s:%d: error: Value of: %s\n" - " Actual: %p\n" - "Expected: not %p\n", event->file, event->line, - event->expr1, (void *)event->val1, NULL) < 0) { - msg = NULL; - } - break; - case ZUC_OP_EQ: - if (event->valtype == ZUC_VAL_CSTR) { - if (asprintf(&msg, "%s:%d: error: Value of: %s\n" - " Actual: %s\n" - "Expected: %s\n" - "Which is: %s\n", - event->file, event->line, event->expr2, - (char *)event->val2, event->expr1, - (char *)event->val1) < 0) { - msg = NULL; - } - } else { - if (asprintf(&msg, "%s:%d: error: Value of: %s\n" - " Actual: %"PRIdPTR"\n" - "Expected: %s\n" - "Which is: %"PRIdPTR"\n", - event->file, event->line, event->expr2, - event->val2, event->expr1, - event->val1) < 0) { - msg = NULL; - } - } - break; - case ZUC_OP_NE: - if (event->valtype == ZUC_VAL_CSTR) { - if (asprintf(&msg, "%s:%d: error: " - "Expected: (%s) %s (%s)," - " actual: %s == %s\n", - event->file, event->line, - event->expr1, zuc_get_opstr(event->op), - event->expr2, (char *)event->val1, - (char *)event->val2) < 0) { - msg = NULL; - } - } else { - if (asprintf(&msg, "%s:%d: error: " - "Expected: (%s) %s (%s)," - " actual: %"PRIdPTR" vs %"PRIdPTR"\n", - event->file, event->line, - event->expr1, zuc_get_opstr(event->op), - event->expr2, event->val1, - event->val2) < 0) { - msg = NULL; - } - } - break; - case ZUC_OP_TERMINATE: - { - char const *level = (event->val1 == 0) ? "error" - : (event->val1 == 1) ? "warning" - : "note"; - if (asprintf(&msg, "%s:%d: %s: %s\n", - event->file, event->line, level, - event->expr1) < 0) { - msg = NULL; - } - break; - } - case ZUC_OP_TRACEPOINT: - if (asprintf(&msg, "%s:%d: note: %s\n", - event->file, event->line, event->expr1) < 0) { - msg = NULL; - } - break; - default: - if (asprintf(&msg, "%s:%d: error: " - "Expected: (%s) %s (%s), actual: %"PRIdPTR" vs " - "%"PRIdPTR"\n", - event->file, event->line, - event->expr1, zuc_get_opstr(event->op), - event->expr2, event->val1, event->val2) < 0) { - msg = NULL; - } - } - - if ((event->op == ZUC_OP_TERMINATE) && (event->val1 > 1)) { - xmlNewChild(parent, NULL, BAD_CAST "skipped", NULL); - } else { - xmlNodePtr node = xmlNewChild(parent, NULL, - BAD_CAST "failure", NULL); - - if (msg) { - xmlSetProp(node, BAD_CAST "message", BAD_CAST msg); - } - xmlSetProp(node, BAD_CAST "type", BAD_CAST ""); - if (msg) { - xmlNodePtr cdata = xmlNewCDataBlock(node->doc, - BAD_CAST msg, - strlen(msg)); - xmlAddChild(node, cdata); - } - } - - free(msg); -} - -/** - * Formats a time in milliseconds to the normal JUnit elapsed form, or - * NULL if there is a problem. - * The caller should release this with free() - * - * @return the formatted time string upon success, NULL otherwise. - */ -static char * -as_duration(long ms) -{ - char *str = NULL; - - if (asprintf(&str, "%1.3f", ms / 1000.0) < 0) { - str = NULL; - } else { - /* - * Special case to match behavior of standard JUnit output - * writers. Assumption is certain readers might have - * limitations, etc. so it is best to keep 100% identical - * output. - */ - if (!strcmp("0.000", str)) { - free(str); - str = strdup("0"); - } - } - return str; -} - -/** - * Returns the status string for the tests (run/notrun). - * - * @param test the test to check status of. - * @return the status string. - */ -static char const * -get_test_status(struct zuc_test *test) -{ - if (test->disabled || test->skipped) - return "notrun"; - else - return "run"; -} - -/** - * Output the given test. - * - * @param parent the parent node to add new content to. - * @param test the test to write out. - */ -static void -emit_test(xmlNodePtr parent, struct zuc_test *test) -{ - char *time_str = as_duration(test->elapsed); - xmlNodePtr node = xmlNewChild(parent, NULL, BAD_CAST "testcase", NULL); - - xmlSetProp(node, BAD_CAST "name", BAD_CAST test->name); - xmlSetProp(node, BAD_CAST "status", BAD_CAST get_test_status(test)); - - if (time_str) { - xmlSetProp(node, BAD_CAST "time", BAD_CAST time_str); - - free(time_str); - time_str = NULL; - } - - xmlSetProp(node, BAD_CAST "classname", BAD_CAST test->test_case->name); - - if ((test->failed || test->fatal || test->skipped) && test->events) { - struct zuc_event *evt; - for (evt = test->events; evt; evt = evt->next) - emit_event(node, evt); - } -} - -/** - * Output the given test case. - * - * @param parent the parent node to add new content to. - * @param test_case the test case to write out. - */ -static void -emit_case(xmlNodePtr parent, struct zuc_case *test_case) -{ - int i; - int skipped = 0; - int disabled = 0; - int failures = 0; - xmlNodePtr node = NULL; - char *time_str = as_duration(test_case->elapsed); - - for (i = 0; i < test_case->test_count; ++i) { - if (test_case->tests[i]->disabled ) - disabled++; - if (test_case->tests[i]->skipped ) - skipped++; - if (test_case->tests[i]->failed - || test_case->tests[i]->fatal ) - failures++; - } - - node = xmlNewChild(parent, NULL, BAD_CAST "testsuite", NULL); - xmlSetProp(node, BAD_CAST "name", BAD_CAST test_case->name); - - set_attribute(node, "tests", test_case->test_count); - set_attribute(node, "failures", failures); - set_attribute(node, "disabled", disabled); - set_attribute(node, "skipped", skipped); - - if (time_str) { - xmlSetProp(node, BAD_CAST "time", BAD_CAST time_str); - free(time_str); - time_str = NULL; - } - - for (i = 0; i < test_case->test_count; ++i) - emit_test(node, test_case->tests[i]); -} - -/** - * Formats a time in milliseconds to the full ISO-8601 date/time string - * format, or NULL if there is a problem. - * The caller should release this with free() - * - * @return the formatted time string upon success, NULL otherwise. - */ -static char * -as_iso_8601(time_t const *t) -{ - char *result = NULL; - char buf[32] = {}; - struct tm when; - - if (gmtime_r(t, &when) != NULL) - if (strftime(buf, sizeof(buf), ISO_8601_FORMAT, &when)) - result = strdup(buf); - - return result; -} - - -static void -run_started(void *data, int live_case_count, int live_test_count, - int disabled_count) -{ - struct junit_data *jdata = data; - - jdata->begin = time(NULL); - jdata->fd = open(XML_FNAME, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); -} - -static void -run_ended(void *data, int case_count, struct zuc_case **cases, - int live_case_count, int live_test_count, int total_passed, - int total_failed, int total_disabled, long total_elapsed) -{ - int i; - long time = 0; - char *time_str = NULL; - char *timestamp = NULL; - xmlNodePtr root = NULL; - xmlDocPtr doc = NULL; - xmlChar *xmlchars = NULL; - int xmlsize = 0; - struct junit_data *jdata = data; - - for (i = 0; i < case_count; ++i) - time += cases[i]->elapsed; - - time_str = as_duration(time); - timestamp = as_iso_8601(&jdata->begin); - - /* here would be where to add errors? */ - - doc = xmlNewDoc(BAD_CAST "1.0"); - root = xmlNewNode(NULL, BAD_CAST "testsuites"); - xmlDocSetRootElement(doc, root); - - set_attribute(root, "tests", live_test_count); - set_attribute(root, "failures", total_failed); - set_attribute(root, "disabled", total_disabled); - - if (timestamp) { - xmlSetProp(root, BAD_CAST "timestamp", BAD_CAST timestamp); - free(timestamp); - timestamp = NULL; - } - - if (time_str) { - xmlSetProp(root, BAD_CAST "time", BAD_CAST time_str); - free(time_str); - time_str = NULL; - } - - xmlSetProp(root, BAD_CAST "name", BAD_CAST "AllTests"); - - for (i = 0; i < case_count; ++i) { - emit_case(root, cases[i]); - } - - xmlDocDumpFormatMemoryEnc(doc, &xmlchars, &xmlsize, "UTF-8", 1); - dprintf(jdata->fd, "%s", (char *) xmlchars); - xmlFree(xmlchars); - xmlchars = NULL; - xmlFreeDoc(doc); - - if ((jdata->fd != fileno(stdout)) - && (jdata->fd != fileno(stderr)) - && (jdata->fd != -1)) { - close(jdata->fd); - jdata->fd = -1; - } -} - -static void -destroy(void *data) -{ - xmlCleanupParser(); - - free(data); -} - -struct zuc_event_listener * -zuc_junit_reporter_create(void) -{ - struct zuc_event_listener *listener = - zalloc(sizeof(struct zuc_event_listener)); - - struct junit_data *data = zalloc(sizeof(struct junit_data)); - data->fd = -1; - - listener->data = data; - listener->destroy = destroy; - listener->run_started = run_started; - listener->run_ended = run_ended; - - return listener; -} - -#else /* ENABLE_JUNIT_XML */ - -#include -#include "zuc_event_listener.h" - -/* - * Simple stub version if junit output (including libxml2 support) has - * been disabled. - * Will return NULL to cause failures as calling this when the #define - * has not been enabled is an invalid scenario. - */ - -struct zuc_event_listener * -zuc_junit_reporter_create(void) -{ - return NULL; -} - -#endif /* ENABLE_JUNIT_XML */ diff --git a/tools/zunitc/src/zuc_junit_reporter.h b/tools/zunitc/src/zuc_junit_reporter.h deleted file mode 100644 index 66f3c7b..0000000 --- a/tools/zunitc/src/zuc_junit_reporter.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef ZUC_JUNIT_REPORTER_H -#define ZUC_JUNIT_REPORTER_H - -struct zuc_event_listener; - -/** - * Creates an instance of a reporter that will write data in the JUnit - * XML format. - */ -struct zuc_event_listener * -zuc_junit_reporter_create(void); - -#endif /* ZUC_JUNIT_REPORTER_H */ diff --git a/tools/zunitc/src/zuc_types.h b/tools/zunitc/src/zuc_types.h deleted file mode 100644 index 4ed9342..0000000 --- a/tools/zunitc/src/zuc_types.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef ZUC_TYPES_H -#define ZUC_TYPES_H - -#include "zunitc/zunitc_impl.h" - -struct zuc_case; - -/** - * Represents a specific test. - */ -struct zuc_test -{ - int order; - struct zuc_case *test_case; - zucimpl_test_fn fn; - zucimpl_test_fn_f fn_f; - char *name; - int disabled; - int skipped; - int failed; - int fatal; - long elapsed; - struct zuc_event *events; - struct zuc_event *deferred; -}; - -/** - * Represents a test case that can hold a collection of tests. - */ -struct zuc_case -{ - int order; - char *name; - const struct zuc_fixture* fxt; - int disabled; - int skipped; - int failed; - int fatal; - int passed; - long elapsed; - int test_count; - struct zuc_test **tests; -}; - -/** - * Returns a human-readable version of the comparison opcode. - * - * @param op the opcode to get a string version of. - * @return a human-readable string of the opcode. - * (This value should not be freed) - */ -const char * -zuc_get_opstr(enum zuc_check_op op); - -#endif /* ZUC_TYPES_H */ diff --git a/tools/zunitc/src/zunitc_impl.c b/tools/zunitc/src/zunitc_impl.c deleted file mode 100644 index 395bdd7..0000000 --- a/tools/zunitc/src/zunitc_impl.c +++ /dev/null @@ -1,1578 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "zunitc/zunitc_impl.h" -#include "zunitc/zunitc.h" - -#include "zuc_base_logger.h" -#include "zuc_collector.h" -#include "zuc_context.h" -#include "zuc_event_listener.h" -#include "zuc_junit_reporter.h" - -#include -#include "shared/helpers.h" -#include - -/* - * If CLOCK_MONOTONIC is present on the system it will give us reliable - * results under certain edge conditions that normally require manual - * admin actions to trigger. If not, CLOCK_REALTIME is a reasonable - * fallback. - */ -#if _POSIX_MONOTONIC_CLOCK -static const clockid_t TARGET_TIMER = CLOCK_MONOTONIC; -#else -static const clockid_t TARGET_TIMER = CLOCK_REALTIME; -#endif - -static char const DISABLED_PREFIX[] = "DISABLED_"; - -#define MS_PER_SEC 1000L -#define NANO_PER_MS 1000000L - -/** - * Simple single-linked list structure. - */ -struct zuc_slinked { - void *data; - struct zuc_slinked *next; -}; - -static struct zuc_context g_ctx = { - .case_count = 0, - .cases = NULL, - - .fatal = false, - .repeat = 0, - .random = 0, - .spawn = true, - .break_on_failure = false, - .fds = {-1, -1}, - - .listeners = NULL, - - .curr_case = NULL, - .curr_test = NULL, -}; - -static char *g_progname = NULL; -static char *g_progbasename = NULL; - -typedef int (*comp_pred2)(intptr_t lhs, intptr_t rhs); - -static bool -test_has_skip(struct zuc_test *test) -{ - return test->skipped; -} - -static bool -test_has_failure(struct zuc_test *test) -{ - return test->fatal || test->failed; -} - -bool -zuc_has_skip(void) -{ - return g_ctx.curr_test ? - test_has_skip(g_ctx.curr_test) : false; -} - -bool -zuc_has_failure(void) -{ - return g_ctx.curr_test ? - test_has_failure(g_ctx.curr_test) : false; -} - -void -zuc_set_filter(const char *filter) -{ - g_ctx.filter = strdup(filter); -} - -void -zuc_set_repeat(int repeat) -{ - g_ctx.repeat = repeat; -} - -void -zuc_set_random(int random) -{ - g_ctx.random = random; -} - -void -zuc_set_spawn(bool spawn) -{ - g_ctx.spawn = spawn; -} - -void -zuc_set_break_on_failure(bool break_on_failure) -{ - g_ctx.break_on_failure = break_on_failure; -} - -void -zuc_set_output_junit(bool enable) -{ - g_ctx.output_junit = enable; -} - -const char * -zuc_get_program_name(void) -{ - return g_progname; -} - -const char * -zuc_get_program_basename(void) -{ - return g_progbasename; -} - -static struct zuc_test * -create_test(int order, zucimpl_test_fn fn, zucimpl_test_fn_f fn_f, - char const *case_name, char const *test_name, - struct zuc_case *parent) -{ - struct zuc_test *test = zalloc(sizeof(struct zuc_test)); - ZUC_ASSERTG_NOT_NULL(test, out); - test->order = order; - test->fn = fn; - test->fn_f = fn_f; - test->name = strdup(test_name); - if ((!fn && !fn_f) || - (strncmp(DISABLED_PREFIX, - test_name, sizeof(DISABLED_PREFIX) - 1) == 0)) - test->disabled = 1; - - test->test_case = parent; - -out: - return test; -} - -static int -compare_regs(const void *lhs, const void *rhs) -{ - int rc = strcmp((*(struct zuc_registration **)lhs)->tcase, - (*(struct zuc_registration **)rhs)->tcase); - if (rc == 0) - rc = strcmp((*(struct zuc_registration **)lhs)->test, - (*(struct zuc_registration **)rhs)->test); - - return rc; -} - -/* gcc-specific markers for auto test case registration: */ -extern const struct zuc_registration __start_zuc_tsect; -extern const struct zuc_registration __stop_zuc_tsect; - -static void -register_tests(void) -{ - size_t case_count = 0; - size_t count = &__stop_zuc_tsect - &__start_zuc_tsect; - size_t i; - int idx = 0; - const char *last_name = NULL; - void **array = zalloc(sizeof(void *) * count); - ZUC_ASSERT_NOT_NULL(array); - for (i = 0; i < count; ++i) - array[i] = (void *)(&__start_zuc_tsect + i); - - qsort(array, count, sizeof(array[0]), compare_regs); - - /* Count transitions to get number of test cases. */ - last_name = NULL; - for (i = 0; i < count; ++i) { - const struct zuc_registration *reg = - (const struct zuc_registration *)array[i]; - if (!last_name || (strcmp(last_name, reg->tcase))) { - last_name = reg->tcase; - case_count++; - } - } - - /* Create test case data items. */ - struct zuc_case **case_array = - zalloc(sizeof(struct zuc_case *) * case_count); - ZUC_ASSERT_NOT_NULL(case_array); - struct zuc_case *last_case = NULL; - size_t case_num = 0; - for (i = 0; i < count; ++i) { - const struct zuc_registration *reg = - (const struct zuc_registration *)array[i]; - if (!last_case || (strcmp(last_case->name, reg->tcase))) { - last_case = zalloc(sizeof(struct zuc_case)); - ZUC_ASSERT_NOT_NULL(last_case); - last_case->order = count; - last_case->name = strdup(reg->tcase); - last_case->fxt = reg->fxt; - last_case->test_count = i; - if (case_num > 0) { - int tcount = i - - case_array[case_num - 1]->test_count; - case_array[case_num - 1]->test_count = tcount; - } - case_array[case_num++] = last_case; - } - } - case_array[case_count - 1]->test_count = count - - case_array[case_count - 1]->test_count; - - /* Reserve space for tests per test case. */ - for (case_num = 0; case_num < case_count; ++case_num) { - case_array[case_num]->tests = - zalloc(case_array[case_num]->test_count - * sizeof(struct zuc_test *)); - ZUC_ASSERT_NOT_NULL(case_array[case_num]->tests); - } - - last_name = NULL; - case_num = -1; - for (i = 0; i < count; ++i) { - const struct zuc_registration *reg = - (const struct zuc_registration *)array[i]; - int order = count - (1 + (reg - &__start_zuc_tsect)); - - if (!last_name || (strcmp(last_name, reg->tcase))) { - last_name = reg->tcase; - case_num++; - idx = 0; - } - if (order < case_array[case_num]->order) - case_array[case_num]->order = order; - case_array[case_num]->tests[idx] = - create_test(order, reg->fn, reg->fn_f, - reg->tcase, reg->test, - case_array[case_num]); - - if (case_array[case_num]->fxt != reg->fxt) - printf("%s:%d: error: Mismatched fixtures for '%s'\n", - __FILE__, __LINE__, case_array[case_num]->name); - - idx++; - } - free(array); - - g_ctx.case_count = case_count; - g_ctx.cases = case_array; -} - -static int -compare_case_order(const void *lhs, const void *rhs) -{ - return (*(struct zuc_case **)lhs)->order - - (*(struct zuc_case **)rhs)->order; -} - -static int -compare_test_order(const void *lhs, const void *rhs) -{ - return (*(struct zuc_test **)lhs)->order - - (*(struct zuc_test **)rhs)->order; -} - -static void -order_cases(int count, struct zuc_case **cases) -{ - int i; - qsort(cases, count, sizeof(*cases), compare_case_order); - for (i = 0; i < count; ++i) { - qsort(cases[i]->tests, cases[i]->test_count, - sizeof(*cases[i]->tests), compare_test_order); - } -} - -static void -free_events(struct zuc_event **events) -{ - struct zuc_event *evt = *events; - *events = NULL; - while (evt) { - struct zuc_event *old = evt; - evt = evt->next; - free(old->file); - if (old->valtype == ZUC_VAL_CSTR) { - free((void *)old->val1); - free((void *)old->val2); - } - free(old->expr1); - free(old->expr2); - free(old); - } -} - -static void -free_test(struct zuc_test *test) -{ - free(test->name); - free_events(&test->events); - free_events(&test->deferred); - free(test); -} - -static void -free_test_case(struct zuc_case *test_case) -{ - int i; - free(test_case->name); - for (i = test_case->test_count - 1; i >= 0; --i) { - free_test(test_case->tests[i]); - test_case->tests[i] = NULL; - } - free(test_case->tests); - free(test_case); -} - -/** - * A very simple matching that is compatible with the algorithm used in - * Google Test. - * - * @param wildcard sequence of '?', '*' or normal characters to match. - * @param str string to check for matching. - * @return true if the wildcard matches the entire string, false otherwise. - */ -static bool -wildcard_matches(char const *wildcard, char const *str) -{ - switch (*wildcard) { - case '\0': - return !*str; - case '?': - return *str && wildcard_matches(wildcard + 1, str + 1); - case '*': - return (*str && wildcard_matches(wildcard, str + 1)) - || wildcard_matches(wildcard + 1, str); - default: - return (*wildcard == *str) - && wildcard_matches(wildcard + 1, str + 1); - }; -} - -static char** -segment_str(char *str) -{ - int count = 1; - char **parts = NULL; - char *saved = NULL; - char *tok = NULL; - int i = 0; - for (i = 0; str[i]; ++i) - if (str[i] == ':') - count++; - parts = zalloc(sizeof(char*) * (count + 1)); - ZUC_ASSERTG_NOT_NULL(parts, out); - tok = strtok_r(str, ":", &saved); - i = 0; - parts[i++] = tok; - while (tok) { - tok = strtok_r(NULL, ":", &saved); - parts[i++] = tok; - } -out: - return parts; -} - -static void -filter_cases(int *count, struct zuc_case **cases, char const *filter) -{ - int i = 0; - int j = 0; - int num_pos = 0; - int negative = -1; - char *buf = strdup(filter); - char **parts = segment_str(buf); - - for (i = 0; parts[i]; ++i) { - if (parts[i][0] == '-') { - parts[i]++; - negative = i; - break; - } - num_pos++; - } - - for (i = 0; i < *count; ++i) { - /* Walk backwards for easier pruning. */ - for (j = cases[i]->test_count - 1; j >= 0; --j) { - int x; - bool keep = num_pos == 0; - char *name = NULL; - struct zuc_test *test = cases[i]->tests[j]; - if (asprintf(&name, "%s.%s", cases[i]->name, - test->name) < 0) { - printf("%s:%d: error: %d\n", __FILE__, __LINE__, - errno); - exit(EXIT_FAILURE); - } - for (x = 0; (x < num_pos) && !keep; ++x) - keep = wildcard_matches(parts[x], name); - if (keep && (negative >= 0)) - for (x = negative; parts[x] && keep; ++x) - keep &= !wildcard_matches(parts[x], - name); - if (!keep) { - int w; - free_test(test); - for (w = j + 1; w < cases[i]->test_count; w++) - cases[i]->tests[w - 1] = - cases[i]->tests[w]; - cases[i]->test_count--; - } - - free(name); - } - } - free(parts); - parts = NULL; - free(buf); - buf = NULL; - - /* Prune any cases with no more tests. */ - for (i = *count - 1; i >= 0; --i) { - if (cases[i]->test_count < 1) { - free_test_case(cases[i]); - for (j = i + 1; j < *count; ++j) - cases[j - 1] = cases[j]; - cases[*count - 1] = NULL; - (*count)--; - } - } -} - -static unsigned int -get_seed_from_time(void) -{ - time_t sec = time(NULL); - unsigned int seed = (unsigned int) sec % 100000; - if (seed < 2) - seed = 2; - - return seed; -} - -static void -initialize(void) -{ - static bool init = false; - if (init) - return; - - init = true; - register_tests(); - if (g_ctx.fatal) - return; - - if (g_ctx.random > 1) { - g_ctx.seed = g_ctx.random; - } else if (g_ctx.random == 1) { - g_ctx.seed = get_seed_from_time(); - } - - if (g_ctx.case_count) { - order_cases(g_ctx.case_count, g_ctx.cases); - if (g_ctx.filter && g_ctx.filter[0]) - filter_cases(&g_ctx.case_count, g_ctx.cases, - g_ctx.filter); - } -} - -int -zuc_initialize(int *argc, char *argv[], bool *help_flagged) -{ - int rc = EXIT_FAILURE; - bool opt_help = false; - bool opt_nofork = false; - bool opt_list = false; - int opt_repeat = 0; - int opt_random = 0; - bool opt_break_on_failure = false; - bool opt_junit = false; - char *opt_filter = NULL; - - char *help_param = NULL; - int argc_in = *argc; - - const struct weston_option options[] = { - { WESTON_OPTION_BOOLEAN, "zuc-nofork", 0, &opt_nofork }, - { WESTON_OPTION_BOOLEAN, "zuc-list-tests", 0, &opt_list }, - { WESTON_OPTION_INTEGER, "zuc-repeat", 0, &opt_repeat }, - { WESTON_OPTION_INTEGER, "zuc-random", 0, &opt_random }, - { WESTON_OPTION_BOOLEAN, "zuc-break-on-failure", 0, - &opt_break_on_failure }, -#if ENABLE_JUNIT_XML - { WESTON_OPTION_BOOLEAN, "zuc-output-xml", 0, &opt_junit }, -#endif - { WESTON_OPTION_STRING, "zuc-filter", 0, &opt_filter }, - }; - - /* - *If a test binary is linked to our libzunitcmain it might want - * to access the program 'name' from argv[0] - */ - free(g_progname); - g_progname = NULL; - free(g_progbasename); - g_progbasename = NULL; - if ((*argc > 0) && argv) { - char *path = NULL; - char *base = NULL; - - g_progname = strdup(argv[0]); - - /* basename() might modify the input, so needs a writeable - * string. - * It also may return a statically allocated buffer subject to - * being overwritten so needs to be dup'd. - */ - path = strdup(g_progname); - base = basename(path); - g_progbasename = strdup(base); - free(path); - } else { - g_progname = strdup(""); - printf("%s:%d: warning: No valid argv[0] for initialization\n", - __FILE__, __LINE__); - } - - - initialize(); - if (g_ctx.fatal) - return EXIT_FAILURE; - - if (help_flagged) - *help_flagged = false; - - { - /* Help param will be a special case and need restoring. */ - int i = 0; - char **argv_in = NULL; - const struct weston_option help_options[] = { - { WESTON_OPTION_BOOLEAN, "help", 'h', &opt_help }, - }; - argv_in = zalloc(sizeof(char *) * argc_in); - if (!argv_in) { - printf("%s:%d: error: alloc failed.\n", - __FILE__, __LINE__); - return EXIT_FAILURE; - } - for (i = 0; i < argc_in; ++i) - argv_in[i] = argv[i]; - - parse_options(help_options, ARRAY_LENGTH(help_options), - argc, argv); - if (*argc < argc_in) { - for (i = 1; (i < argc_in) && !help_param; ++i) { - bool found = false; - int j = 0; - for (j = 0; (j < *argc) && !found; ++j) - found = (argv[j] == argv_in[i]); - - if (!found) - help_param = argv_in[i]; - } - } - free(argv_in); - } - - parse_options(options, ARRAY_LENGTH(options), argc, argv); - - if (help_param && (*argc < argc_in)) - argv[(*argc)++] = help_param; - - if (opt_filter) { - zuc_set_filter(opt_filter); - free(opt_filter); - } - - if (opt_help) { - printf("Usage: %s [OPTIONS]\n" - " --zuc-break-on-failure\n" - " --zuc-filter=FILTER\n" - " --zuc-list-tests\n" - " --zuc-nofork\n" -#if ENABLE_JUNIT_XML - " --zuc-output-xml\n" -#endif - " --zuc-random=N [0|1|]\n" - " --zuc-repeat=N\n" - " --help\n", - argv[0]); - if (help_flagged) - *help_flagged = true; - rc = EXIT_SUCCESS; - } else if (opt_list) { - zuc_list_tests(); - rc = EXIT_FAILURE; - } else { - zuc_set_repeat(opt_repeat); - zuc_set_random(opt_random); - zuc_set_spawn(!opt_nofork); - zuc_set_break_on_failure(opt_break_on_failure); - zuc_set_output_junit(opt_junit); - rc = EXIT_SUCCESS; - } - - return rc; -} - -static void -dispatch_pre_run(struct zuc_context *ctx, int pass_count, int pass_num, - int seed, const char *filter) -{ - struct zuc_slinked *curr; - for (curr = ctx->listeners; curr; curr = curr->next) { - struct zuc_event_listener *listener = curr->data; - if (listener->pre_run) - listener->pre_run(listener->data, - pass_count, - pass_num, - seed, - filter); - } -} - -static void -dispatch_run_started(struct zuc_context *ctx, int live_case_count, - int live_test_count, int disabled_count) -{ - struct zuc_slinked *curr; - for (curr = ctx->listeners; curr; curr = curr->next) { - struct zuc_event_listener *listener = curr->data; - if (listener->run_started) - listener->run_started(listener->data, - live_case_count, - live_test_count, - disabled_count); - } -} - -static void -dispatch_run_ended(struct zuc_context *ctx, - int live_case_count, int live_test_count, int total_passed, - int total_failed, int total_disabled, long total_elapsed) -{ - struct zuc_slinked *curr; - for (curr = ctx->listeners; curr; curr = curr->next) { - struct zuc_event_listener *listener = curr->data; - if (listener->run_ended) - listener->run_ended(listener->data, - ctx->case_count, - ctx->cases, - live_case_count, - live_test_count, - total_passed, - total_failed, - total_disabled, - total_elapsed); - } -} - -static void -dispatch_case_started(struct zuc_context *ctx,struct zuc_case *test_case, - int live_test_count, int disabled_count) -{ - struct zuc_slinked *curr; - for (curr = ctx->listeners; curr; curr = curr->next) { - struct zuc_event_listener *listener = curr->data; - if (listener->case_started) - listener->case_started(listener->data, - test_case, - live_test_count, - disabled_count); - } -} - -static void -dispatch_case_ended(struct zuc_context *ctx, struct zuc_case *test_case) -{ - struct zuc_slinked *curr; - for (curr = ctx->listeners; curr; curr = curr->next) { - struct zuc_event_listener *listener = curr->data; - if (listener->case_ended) - listener->case_ended(listener->data, test_case); - } -} - -static void -dispatch_test_started(struct zuc_context *ctx, struct zuc_test *test) -{ - struct zuc_slinked *curr; - for (curr = ctx->listeners; curr; curr = curr->next) { - struct zuc_event_listener *listener = curr->data; - if (listener->test_started) - listener->test_started(listener->data, test); - } -} - -static void -dispatch_test_ended(struct zuc_context *ctx, struct zuc_test *test) -{ - struct zuc_slinked *curr; - for (curr = ctx->listeners; curr; curr = curr->next) { - struct zuc_event_listener *listener = curr->data; - if (listener->test_ended) - listener->test_ended(listener->data, test); - } -} - -static void -dispatch_test_disabled(struct zuc_context *ctx, struct zuc_test *test) -{ - struct zuc_slinked *curr; - for (curr = ctx->listeners; curr; curr = curr->next) { - struct zuc_event_listener *listener = curr->data; - if (listener->test_disabled) - listener->test_disabled(listener->data, test); - } -} - -static void -dispatch_check_triggered(struct zuc_context *ctx, char const *file, int line, - enum zuc_fail_state state, enum zuc_check_op op, - enum zuc_check_valtype valtype, - intptr_t val1, intptr_t val2, - const char *expr1, const char *expr2) -{ - struct zuc_slinked *curr; - for (curr = ctx->listeners; curr; curr = curr->next) { - struct zuc_event_listener *listener = curr->data; - if (listener->check_triggered) - listener->check_triggered(listener->data, - file, line, - state, op, valtype, - val1, val2, - expr1, expr2); - } -} - -static void -dispatch_collect_event(struct zuc_context *ctx, char const *file, int line, - const char *expr1) -{ - struct zuc_slinked *curr; - for (curr = ctx->listeners; curr; curr = curr->next) { - struct zuc_event_listener *listener = curr->data; - if (listener->collect_event) - listener->collect_event(listener->data, - file, line, expr1); - } -} - -static void -migrate_deferred_events(struct zuc_test *test, bool transferred) -{ - struct zuc_event *evt = test->deferred; - if (!evt) - return; - - test->deferred = NULL; - if (test->events) { - struct zuc_event *old = test->events; - while (old->next) - old = old->next; - old->next = evt; - } else { - test->events = evt; - } - while (evt && !transferred) { - dispatch_check_triggered(&g_ctx, - evt->file, evt->line, - evt->state, evt->op, - evt->valtype, - evt->val1, evt->val2, - evt->expr1, evt->expr2); - evt = evt->next; - } -} - -static void -mark_single_failed(struct zuc_test *test, enum zuc_fail_state state) -{ - switch (state) { - case ZUC_CHECK_OK: - /* no internal state to change */ - break; - case ZUC_CHECK_SKIP: - if (test) - test->skipped = 1; - break; - case ZUC_CHECK_FAIL: - if (test) - test->failed = 1; - break; - case ZUC_CHECK_ERROR: - case ZUC_CHECK_FATAL: - if (test) - test->fatal = 1; - break; - } - - if (g_ctx.break_on_failure) - raise(SIGABRT); - -} - -static void -mark_failed(struct zuc_test *test, enum zuc_fail_state state) -{ - if (!test && g_ctx.curr_test) - test = g_ctx.curr_test; - - if (test) { - mark_single_failed(test, state); - } else if (g_ctx.curr_case) { - /* In setup or tear-down of test suite */ - int i; - for (i = 0; i < g_ctx.curr_case->test_count; ++i) - mark_single_failed(g_ctx.curr_case->tests[i], state); - } - if ((state == ZUC_CHECK_FATAL) || (state == ZUC_CHECK_ERROR)) - g_ctx.fatal = true; -} - -void -zuc_attach_event(struct zuc_test *test, struct zuc_event *event, - enum zuc_event_type event_type, bool transferred) -{ - if (!test) { - /* - * consider adding events directly to the case. - * would be for use during per-suite setup and teardown. - */ - printf("%s:%d: error: No current test.\n", __FILE__, __LINE__); - } else if (event_type == ZUC_EVENT_DEFERRED) { - if (test->deferred) { - struct zuc_event *curr = test->deferred; - while (curr->next) - curr = curr->next; - curr->next = event; - } else { - test->deferred = event; - } - } else { - if (test) - migrate_deferred_events(test, transferred); - - if (test->events) { - struct zuc_event *curr = test->events; - while (curr->next) - curr = curr->next; - curr->next = event; - } else { - test->events = event; - } - mark_failed(test, event->state); - } -} - -void -zuc_add_event_listener(struct zuc_event_listener *event_listener) -{ - if (!event_listener) /* ensure null entries are not added */ - return; - - if (!g_ctx.listeners) { - g_ctx.listeners = zalloc(sizeof(struct zuc_slinked)); - ZUC_ASSERT_NOT_NULL(g_ctx.listeners); - g_ctx.listeners->data = event_listener; - } else { - struct zuc_slinked *curr = g_ctx.listeners; - while (curr->next) - curr = curr->next; - curr->next = zalloc(sizeof(struct zuc_slinked)); - ZUC_ASSERT_NOT_NULL(curr->next); - curr->next->data = event_listener; - } -} - - -void -zuc_cleanup(void) -{ - int i; - - free(g_ctx.filter); - g_ctx.filter = 0; - for (i = 0; i < 2; ++i) - if (g_ctx.fds[i] != -1) { - close(g_ctx.fds[i]); - g_ctx.fds[i] = -1; - } - - if (g_ctx.listeners) { - struct zuc_slinked *curr = g_ctx.listeners; - while (curr) { - struct zuc_slinked *old = curr; - struct zuc_event_listener *listener = curr->data; - if (listener->destroy) - listener->destroy(listener->data); - free(listener); - curr = curr->next; - free(old); - } - g_ctx.listeners = NULL; - } - - for (i = g_ctx.case_count - 1; i >= 0; --i) { - free_test_case(g_ctx.cases[i]); - g_ctx.cases[i] = NULL; - } - free(g_ctx.cases); - g_ctx.cases = NULL; - - free(g_progname); - g_progname = NULL; - free(g_progbasename); - g_progbasename = NULL; -} - -static void -shuffle_cases(int count, struct zuc_case **cases, - unsigned int seed) -{ - int i; - unsigned int rseed = seed; - for (i = 0; i < count; ++i) { - int j; - for (j = cases[i]->test_count - 1; j > 0 ; --j) { - int val = rand_r(&rseed); - int b = ((val / (double)RAND_MAX) * j + 0.5); - if (j != b) { - struct zuc_test *tmp = cases[i]->tests[j]; - cases[i]->tests[j] = cases[i]->tests[b]; - cases[i]->tests[b] = tmp; - } - } - } - - for (i = count - 1; i > 0; --i) { - int val = rand_r(&rseed); - int j = ((val / (double)RAND_MAX) * i + 0.5); - - if (i != j) { - struct zuc_case *tmp = cases[i]; - cases[i] = cases[j]; - cases[j] = tmp; - } - } -} - -void -zuc_list_tests(void) -{ - int i; - int j; - initialize(); - if (g_ctx.fatal) - return; - for (i = 0; i < g_ctx.case_count; ++i) { - printf("%s.\n", g_ctx.cases[i]->name); - for (j = 0; j < g_ctx.cases[i]->test_count; ++j) { - printf(" %s\n", g_ctx.cases[i]->tests[j]->name); - } - } -} - -static void -spawn_test(struct zuc_test *test, void *test_data, - void (*cleanup_fn)(void *data), void *cleanup_data) -{ - pid_t pid = -1; - - if (!test || (!test->fn && !test->fn_f)) - return; - - if (pipe2(g_ctx.fds, O_CLOEXEC)) { - printf("%s:%d: error: Unable to create pipe: %d\n", - __FILE__, __LINE__, errno); - mark_failed(test, ZUC_CHECK_ERROR); - return; - } - - fflush(NULL); /* important. avoid duplication of output */ - pid = fork(); - switch (pid) { - case -1: /* Error forking */ - printf("%s:%d: error: Problem with fork: %d\n", - __FILE__, __LINE__, errno); - mark_failed(test, ZUC_CHECK_ERROR); - close(g_ctx.fds[0]); - g_ctx.fds[0] = -1; - close(g_ctx.fds[1]); - g_ctx.fds[1] = -1; - break; - case 0: { /* child */ - int rc = EXIT_SUCCESS; - close(g_ctx.fds[0]); - g_ctx.fds[0] = -1; - - if (test->fn_f) - test->fn_f(test_data); - else - test->fn(); - - if (test_has_failure(test)) - rc = EXIT_FAILURE; - else if (test_has_skip(test)) - rc = ZUC_EXIT_SKIP; - - /* Avoid confusing memory tools like valgrind */ - if (cleanup_fn) - cleanup_fn(cleanup_data); - - zuc_cleanup(); - exit(rc); - } - default: { /* parent */ - ssize_t rc = 0; - siginfo_t info = {}; - - close(g_ctx.fds[1]); - g_ctx.fds[1] = -1; - - do { - rc = zuc_process_message(g_ctx.curr_test, - g_ctx.fds[0]); - } while (rc > 0); - close(g_ctx.fds[0]); - g_ctx.fds[0] = -1; - - if (waitid(P_ALL, 0, &info, WEXITED)) { - printf("%s:%d: error: waitid failed. (%d)\n", - __FILE__, __LINE__, errno); - mark_failed(test, ZUC_CHECK_ERROR); - } else { - switch (info.si_code) { - case CLD_EXITED: { - int exit_code = info.si_status; - switch(exit_code) { - case EXIT_SUCCESS: - break; - case ZUC_EXIT_SKIP: - if (!test_has_skip(g_ctx.curr_test) && - !test_has_failure(g_ctx.curr_test)) - ZUC_SKIP("Child exited SKIP"); - break; - default: - /* unexpected failure */ - if (!test_has_failure(g_ctx.curr_test)) - ZUC_ASSERT_EQ(0, exit_code); - } - break; - } - case CLD_KILLED: - case CLD_DUMPED: - printf("%s:%d: error: signaled: %d\n", - __FILE__, __LINE__, info.si_status); - mark_failed(test, ZUC_CHECK_ERROR); - break; - } - } - } - } -} - -static void -run_single_test(struct zuc_test *test,const struct zuc_fixture *fxt, - void *case_data, bool spawn) -{ - long elapsed = 0; - struct timespec begin; - struct timespec end; - void *test_data = NULL; - void *cleanup_data = NULL; - void (*cleanup_fn)(void *data) = NULL; - memset(&begin, 0, sizeof(begin)); - memset(&end, 0, sizeof(end)); - - g_ctx.curr_test = test; - dispatch_test_started(&g_ctx, test); - - cleanup_fn = fxt ? fxt->tear_down : NULL; - cleanup_data = NULL; - - if (fxt && fxt->set_up) { - test_data = fxt->set_up(case_data); - cleanup_data = test_data; - } else { - test_data = case_data; - } - - clock_gettime(TARGET_TIMER, &begin); - - /* Need to re-check these, as fixtures might have changed test state. */ - if (!test->fatal && !test->skipped) { - if (spawn) { - spawn_test(test, test_data, - cleanup_fn, cleanup_data); - } else { - if (test->fn_f) - test->fn_f(test_data); - else - test->fn(); - } - } - - clock_gettime(TARGET_TIMER, &end); - - elapsed = (end.tv_sec - begin.tv_sec) * MS_PER_SEC; - if (end.tv_sec != begin.tv_sec) { - elapsed -= (begin.tv_nsec) / NANO_PER_MS; - elapsed += (end.tv_nsec) / NANO_PER_MS; - } else { - elapsed += (end.tv_nsec - begin.tv_nsec) / NANO_PER_MS; - } - test->elapsed = elapsed; - - if (cleanup_fn) - cleanup_fn(cleanup_data); - - if (test->deferred) { - if (test_has_failure(test)) - migrate_deferred_events(test, false); - else - free_events(&test->deferred); - } - - dispatch_test_ended(&g_ctx, test); - - g_ctx.curr_test = NULL; -} - -static void -run_single_case(struct zuc_case *test_case) -{ - int count_live = test_case->test_count - test_case->disabled; - g_ctx.curr_case = test_case; - if (count_live) { - int i = 0; - const struct zuc_fixture *fxt = test_case->fxt; - void *case_data = fxt ? (void *)fxt->data : NULL; - - dispatch_case_started(&g_ctx, test_case, - count_live, test_case->disabled); - - if (fxt && fxt->set_up_test_case) - case_data = fxt->set_up_test_case(fxt->data); - - for (i = 0; i < test_case->test_count; ++i) { - struct zuc_test *curr = test_case->tests[i]; - if (curr->disabled) { - dispatch_test_disabled(&g_ctx, curr); - } else { - run_single_test(curr, fxt, case_data, - g_ctx.spawn); - if (curr->skipped) - test_case->skipped++; - if (curr->failed) - test_case->failed++; - if (curr->fatal) - test_case->fatal++; - if (!curr->failed && !curr->fatal) - test_case->passed++; - test_case->elapsed += curr->elapsed; - } - } - - if (fxt && fxt->tear_down_test_case) - fxt->tear_down_test_case(case_data); - - dispatch_case_ended(&g_ctx, test_case); - } - g_ctx.curr_case = NULL; -} - -static void -reset_test_values(struct zuc_case **cases, int case_count) -{ - int i; - for (i = 0; i < case_count; ++i) { - int j; - cases[i]->disabled = 0; - cases[i]->skipped = 0; - cases[i]->failed = 0; - cases[i]->fatal = 0; - cases[i]->passed = 0; - cases[i]->elapsed = 0; - for (j = 0; j < cases[i]->test_count; ++j) { - struct zuc_test *test = cases[i]->tests[j]; - if (test->disabled) - cases[i]->disabled++; - test->skipped = 0; - test->failed = 0; - test->fatal = 0; - test->elapsed = 0; - - free_events(&test->events); - free_events(&test->deferred); - } - } -} - -static int -run_single_pass(void) -{ - long total_elapsed = 0; - int total_passed = 0; - int total_failed = 0; - int total_skipped = 0; - int live_case_count = 0; - int live_test_count = 0; - int disabled_test_count = 0; - int i; - - reset_test_values(g_ctx.cases, g_ctx.case_count); - for (i = 0; i < g_ctx.case_count; ++i) { - int live = g_ctx.cases[i]->test_count - - g_ctx.cases[i]->disabled; - if (live) { - live_test_count += live; - live_case_count++; - } - if (g_ctx.cases[i]->disabled) - disabled_test_count++; - } - - dispatch_run_started(&g_ctx, live_case_count, live_test_count, - disabled_test_count); - - for (i = 0; i < g_ctx.case_count; ++i) { - run_single_case(g_ctx.cases[i]); - total_failed += g_ctx.cases[i]->test_count - - (g_ctx.cases[i]->passed + g_ctx.cases[i]->disabled); - total_passed += g_ctx.cases[i]->passed; - total_elapsed += g_ctx.cases[i]->elapsed; - total_skipped += g_ctx.cases[i]->skipped; - } - - dispatch_run_ended(&g_ctx, live_case_count, live_test_count, - total_passed, total_failed, disabled_test_count, - total_elapsed); - - if (total_failed) - return EXIT_FAILURE; - else if (total_skipped) - return ZUC_EXIT_SKIP; - else - return EXIT_SUCCESS; -} - -int -zucimpl_run_tests(void) -{ - int rc = EXIT_SUCCESS; - int i; - int limit = g_ctx.repeat > 0 ? g_ctx.repeat : 1; - - initialize(); - if (g_ctx.fatal) - return EXIT_FAILURE; - - if (g_ctx.listeners == NULL) { - zuc_add_event_listener(zuc_collector_create(&(g_ctx.fds[1]))); - zuc_add_event_listener(zuc_base_logger_create()); - if (g_ctx.output_junit) - zuc_add_event_listener(zuc_junit_reporter_create()); - } - - if (g_ctx.case_count < 1) { - printf("%s:%d: error: Setup error: test tree is empty\n", - __FILE__, __LINE__); - rc = EXIT_FAILURE; - } - - for (i = 0; (i < limit) && (g_ctx.case_count > 0); ++i) { - int pass_code = EXIT_SUCCESS; - dispatch_pre_run(&g_ctx, limit, i + 1, - (g_ctx.random > 0) ? g_ctx.seed : 0, - g_ctx.filter); - - order_cases(g_ctx.case_count, g_ctx.cases); - if (g_ctx.random > 0) - shuffle_cases(g_ctx.case_count, g_ctx.cases, - g_ctx.seed); - - pass_code = run_single_pass(); - if (pass_code == EXIT_FAILURE) - rc = EXIT_FAILURE; - else if ((pass_code == ZUC_EXIT_SKIP) && (rc == EXIT_SUCCESS)) - rc = ZUC_EXIT_SKIP; - - g_ctx.seed++; - } - - return rc; -} - -int -zucimpl_tracepoint(char const *file, int line, char const *fmt, ...) -{ - int rc = -1; - va_list argp; - char *msg = NULL; - - - va_start(argp, fmt); - rc = vasprintf(&msg, fmt, argp); - if (rc == -1) { - msg = NULL; - } - va_end(argp); - - dispatch_collect_event(&g_ctx, - file, line, - msg); - - free(msg); - - return rc; -} - -void -zucimpl_terminate(char const *file, int line, - bool fail, bool fatal, const char *msg) -{ - enum zuc_fail_state state = ZUC_CHECK_SKIP; - int level = 2; - if (fail && fatal) { - state = ZUC_CHECK_FATAL; - level = 0; - } else if (fail && !fatal) { - state = ZUC_CHECK_FAIL; - level = 0; - } - - mark_failed(g_ctx.curr_test, state); - - if ((state != ZUC_CHECK_OK) && g_ctx.curr_test) - migrate_deferred_events(g_ctx.curr_test, false); - - dispatch_check_triggered(&g_ctx, - file, line, - state, - ZUC_OP_TERMINATE, ZUC_VAL_INT, - level, 0, - msg, ""); -} - -static void -validate_types(enum zuc_check_op op, enum zuc_check_valtype valtype) -{ - bool is_valid = true; - - switch (op) { - case ZUC_OP_NULL: - case ZUC_OP_NOT_NULL: - is_valid = is_valid && (valtype == ZUC_VAL_PTR); - break; - default: - ; /* all rest OK */ - } - - switch (valtype) { - case ZUC_VAL_CSTR: - is_valid = is_valid && ((op == ZUC_OP_EQ) || (op == ZUC_OP_NE)); - break; - default: - ; /* all rest OK */ - } - - if (!is_valid) - printf("%s:%d: warning: Unexpected op+type %d/%d.\n", - __FILE__, __LINE__, op, valtype); -} - -static int -pred2_unknown(intptr_t lhs, intptr_t rhs) -{ - return 0; -} - -static int -pred2_true(intptr_t lhs, intptr_t rhs) -{ - return lhs; -} - -static int -pred2_false(intptr_t lhs, intptr_t rhs) -{ - return !lhs; -} - -static int -pred2_eq(intptr_t lhs, intptr_t rhs) -{ - return lhs == rhs; -} - -static int -pred2_streq(intptr_t lhs, intptr_t rhs) -{ - int status = 0; - const char *lhptr = (const char *)lhs; - const char *rhptr = (const char *)rhs; - - if (!lhptr && !rhptr) - status = 1; - else if (lhptr && rhptr) - status = strcmp(lhptr, rhptr) == 0; - - return status; -} - -static int -pred2_ne(intptr_t lhs, intptr_t rhs) -{ - return lhs != rhs; -} - -static int -pred2_strne(intptr_t lhs, intptr_t rhs) -{ - int status = 0; - const char *lhptr = (const char *)lhs; - const char *rhptr = (const char *)rhs; - - if (lhptr != rhptr) { - if (!lhptr || !rhptr) - status = 1; - else - status = strcmp(lhptr, rhptr) != 0; - } - - return status; -} - -static int -pred2_ge(intptr_t lhs, intptr_t rhs) -{ - return lhs >= rhs; -} - -static int -pred2_gt(intptr_t lhs, intptr_t rhs) -{ - return lhs > rhs; -} - -static int -pred2_le(intptr_t lhs, intptr_t rhs) -{ - return lhs <= rhs; -} - -static int -pred2_lt(intptr_t lhs, intptr_t rhs) -{ - return lhs < rhs; -} - -static comp_pred2 -get_pred2(enum zuc_check_op op, enum zuc_check_valtype valtype) -{ - switch (op) { - case ZUC_OP_TRUE: - return pred2_true; - case ZUC_OP_FALSE: - return pred2_false; - case ZUC_OP_NULL: - return pred2_false; - case ZUC_OP_NOT_NULL: - return pred2_true; - case ZUC_OP_EQ: - if (valtype == ZUC_VAL_CSTR) - return pred2_streq; - else - return pred2_eq; - case ZUC_OP_NE: - if (valtype == ZUC_VAL_CSTR) - return pred2_strne; - else - return pred2_ne; - case ZUC_OP_GE: - return pred2_ge; - case ZUC_OP_GT: - return pred2_gt; - case ZUC_OP_LE: - return pred2_le; - case ZUC_OP_LT: - return pred2_lt; - default: - return pred2_unknown; - } -} - -int -zucimpl_expect_pred2(char const *file, int line, - enum zuc_check_op op, enum zuc_check_valtype valtype, - bool fatal, - intptr_t lhs, intptr_t rhs, - const char *lhs_str, const char* rhs_str) -{ - enum zuc_fail_state state = fatal ? ZUC_CHECK_FATAL : ZUC_CHECK_FAIL; - comp_pred2 pred = get_pred2(op, valtype); - int failed = !pred(lhs, rhs); - - validate_types(op, valtype); - - if (failed) { - mark_failed(g_ctx.curr_test, state); - - if (g_ctx.curr_test) - migrate_deferred_events(g_ctx.curr_test, false); - - dispatch_check_triggered(&g_ctx, - file, line, - fatal ? ZUC_CHECK_FATAL - : ZUC_CHECK_FAIL, - op, valtype, - lhs, rhs, - lhs_str, rhs_str); - } - return failed; -} diff --git a/tools/zunitc/test/fixtures_test.c b/tools/zunitc/test/fixtures_test.c deleted file mode 100644 index 04a0ba9..0000000 --- a/tools/zunitc/test/fixtures_test.c +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -/** - * Tests of fixtures. - */ - -#include -#include -#include -#include - -#include "zunitc/zunitc.h" - - - -/* Use a simple string for a simplistic fixture */ -static struct zuc_fixture fixture_minimal = { - .data = "for all good men to", -}; - -ZUC_TEST_F(fixture_minimal, just_as_is, data) -{ - const char *str = data; - ZUC_ASSERT_NOT_NULL(str); - - ZUC_ASSERT_EQ(0, strcmp("for all good men to", str)); -} - -/* - * Not important what or how data is manipulated, just that this function - * does something non-transparent to it. - */ -static void * -setup_test_config(void *data) -{ - int i; - const char *str = data; - char *upper = NULL; - ZUC_ASSERTG_NOT_NULL(data, out); - - upper = strdup(str); - ZUC_ASSERTG_NOT_NULL(upper, out); - - for (i = 0; upper[i]; ++i) - upper[i] = (char)toupper(upper[i]); - -out: - return upper; -} - -static void -teardown_test_config(void *data) -{ - ZUC_ASSERT_NOT_NULL(data); - free(data); -} - -static struct zuc_fixture fixture_data0 = { - .data = "Now is the time", - .set_up = setup_test_config, - .tear_down = teardown_test_config -}; - -ZUC_TEST_F(fixture_data0, base, data) -{ - const char *str = data; - ZUC_ASSERT_NOT_NULL(str); - - ZUC_ASSERT_EQ(0, strcmp("NOW IS THE TIME", str)); -} - -/* Use the same fixture for a second test. */ -ZUC_TEST_F(fixture_data0, no_lower, data) -{ - int i; - const char *str = data; - ZUC_ASSERT_NOT_NULL(str); - - for (i = 0; str[i]; ++i) - ZUC_ASSERT_EQ(0, islower(str[i])); -} diff --git a/tools/zunitc/test/zunitc_test.c b/tools/zunitc/test/zunitc_test.c deleted file mode 100644 index 8000aa8..0000000 --- a/tools/zunitc/test/zunitc_test.c +++ /dev/null @@ -1,464 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -/* - * A simple file to show tests being setup and run. - */ - -#include -#include -#include -#include - -#include "zunitc/zunitc.h" - -/* - * The SKIP and FAIL sets of tests are those that will cause 'make check' - * to fail so are disabled by default. They can be re-enabled when working - * on the test framework itself. - */ - -/* #define ENABLE_FAIL_TESTS */ -/* #define ENABLE_SKIP_TESTS */ - -ZUC_TEST(base_test, math_is_sane) -{ - ZUC_ASSERT_EQ(4, 2 + 2); -} - -ZUC_TEST(base_test, math_is_hard) -{ - ZUC_TRACEPOINT("Tracepoint here."); - - ZUC_TRACEPOINT("Checking %d", 4); - -#ifdef ENABLE_FAIL_TESTS - - ZUC_ASSERT_EQ(5, 2 + 2); - ZUC_TRACEPOINT("flip %1.3f", 3.1415927); /* not seen */ -#endif -} - -#ifdef ENABLE_FAIL_TESTS -ZUC_TEST(base_test, tracepoint_after_assert) -{ - ZUC_TRACEPOINT("Should be seen in output"); - - ZUC_ASSERT_EQ(5, 2 + 2); - - ZUC_TRACEPOINT("Should NOT be seen in output"); -} -#endif - -#ifdef ENABLE_FAIL_TESTS -ZUC_TEST(base_test, math_is_more_hard) -{ - ZUC_ASSERT_EQ(5, 2 + 2); -} - -ZUC_TEST(base_test, math_is_more_hard2) -{ - ZUC_ASSERT_EQ(7, 9); -} -#endif - -ZUC_TEST(base_test, time_counted) -{ - ZUC_TRACEPOINT("Never seen"); - - ZUC_TRACEPOINT("Sleepy Time %d", 10000 * 5); - ZUC_ASSERT_EQ(0, usleep(10000 * 5)); /* 50ms to show up in reporting */ -} - -ZUC_TEST(other_test, math_monkey) -{ - ZUC_ASSERT_TRUE(1); - ZUC_ASSERT_TRUE(3); - ZUC_ASSERT_FALSE(0); - - ZUC_ASSERT_TRUE(1); - ZUC_ASSERT_TRUE(3); - ZUC_ASSERT_FALSE(0); - - ZUC_ASSERT_EQ(5, 2 + 3); - ZUC_ASSERT_EQ(5, 2 + 3); - - int b = 9; - ZUC_ASSERT_NE(1, 2); - ZUC_ASSERT_NE(b, b + 2); - - ZUC_ASSERT_NE(1, 2); - ZUC_ASSERT_NE(b, b + 1); - - ZUC_ASSERT_LT(1, 2); - ZUC_ASSERT_LT(1, 3); - - ZUC_ASSERT_LE(1, 2); - ZUC_ASSERT_LE(1, 3); - - ZUC_ASSERT_LE(1, 1); - ZUC_ASSERT_LE(1, 1); - - ZUC_ASSERT_GT(2, 1); - ZUC_ASSERT_GT(3, 1); - - ZUC_ASSERT_GE(1, 1); - ZUC_ASSERT_GE(1, 1); - - ZUC_ASSERT_GE(2, 1); - ZUC_ASSERT_GE(3, 1); -} - -static void -force_fatal_failure(void) -{ -#ifdef ENABLE_FAIL_TESTS - bool expected_to_fail_here = true; - ZUC_ASSERT_FALSE(expected_to_fail_here); - - ZUC_FATAL("Should never reach here"); - ZUC_ASSERT_NE(1, 1); -#endif -} - -#ifdef ENABLE_FAIL_TESTS -ZUC_TEST(infrastructure, fail_keeps_testing) -{ - ZUC_FATAL("Should always reach here"); - ZUC_ASSERT_NE(1, 1); /* in case ZUC_FATAL doesn't work. */ -} -#endif - -#ifdef ENABLE_FAIL_TESTS -ZUC_TEST(infrastructure, fatal_stops_test) -{ - ZUC_FATAL("Time to kill testing"); - - ZUC_FATAL("Should never reach here"); - ZUC_ASSERT_NE(1, 1); /* in case ZUC_FATAL doesn't work. */ -} -#endif - -#ifdef ENABLE_SKIP_TESTS -ZUC_TEST(infrastructure, skip_stops_test) -{ - ZUC_SKIP("Time to skip testing"); - - ZUC_FATAL("Should never reach here"); - ZUC_ASSERT_NE(1, 1); /* in case ZUC_FATAL doesn't work. */ -} -#endif - -struct fixture_data { - int case_counter; - int test_counter; -}; - -static struct fixture_data fixture_info = {0, 0}; - -static void * -complex_test_set_up_case(const void *data) -{ - fixture_info.case_counter++; - return &fixture_info; -} - -static void -complex_test_tear_down_case(void *data) -{ - ZUC_ASSERT_TRUE(&fixture_info == data); - fixture_info.case_counter--; -} - -static void * -complex_test_set_up(void *data) -{ - fixture_info.test_counter = fixture_info.case_counter; - return &fixture_info; -} - -static void -complex_test_tear_down(void *data) -{ - ZUC_ASSERT_EQ(1, fixture_info.case_counter); -} - -struct zuc_fixture complex_test = { - .set_up = complex_test_set_up, - .tear_down = complex_test_tear_down, - .set_up_test_case = complex_test_set_up_case, - .tear_down_test_case = complex_test_tear_down_case -}; - -/* - * Note that these next cases all try to modify the test_counter member, - * but the fixture should reset that. -*/ - -ZUC_TEST_F(complex_test, bases_cenario, data) -{ - struct fixture_data *fdata = data; - ZUC_ASSERT_NOT_NULL(fdata); - - ZUC_ASSERT_EQ(4, 3 + 1); - ZUC_ASSERT_EQ(1, fdata->case_counter); - ZUC_ASSERT_EQ(1, fdata->test_counter); - fdata->test_counter++; - ZUC_ASSERT_EQ(2, fdata->test_counter); -} - -ZUC_TEST_F(complex_test, something, data) -{ - struct fixture_data *fdata = data; - ZUC_ASSERT_NOT_NULL(fdata); - - ZUC_ASSERT_EQ(4, 3 + 1); - ZUC_ASSERT_EQ(1, fdata->case_counter); - ZUC_ASSERT_EQ(1, fdata->test_counter); - fdata->test_counter++; - ZUC_ASSERT_EQ(2, fdata->test_counter); -} - -ZUC_TEST_F(complex_test, else_here, data) -{ - struct fixture_data *fdata = data; - ZUC_ASSERT_NOT_NULL(fdata); - - ZUC_ASSERT_EQ(4, 3 + 1); - ZUC_ASSERT_EQ(1, fdata->case_counter); - ZUC_ASSERT_EQ(1, fdata->test_counter); - fdata->test_counter++; - ZUC_ASSERT_EQ(2, fdata->test_counter); -} - -ZUC_TEST(more, DISABLED_not_run) -{ - ZUC_ASSERT_EQ(1, 2); -} - -ZUC_TEST(more, failure_states) -{ -#ifdef ENABLE_FAIL_TESTS - bool expected_to_fail_here = true; -#endif - - ZUC_ASSERT_FALSE(zuc_has_failure()); - -#ifdef ENABLE_FAIL_TESTS - ZUC_ASSERT_FALSE(expected_to_fail_here); /* should fail */ - - ZUC_ASSERT_TRUE(zuc_has_failure()); -#endif -} - -ZUC_TEST(more, failure_sub_fatal) -{ - ZUC_ASSERT_FALSE(zuc_has_failure()); - - force_fatal_failure(); - -#ifdef ENABLE_FAIL_TESTS - ZUC_ASSERT_TRUE(zuc_has_failure()); -#endif -} - -ZUC_TEST(pointers, null) -{ - const char *a = NULL; - - ZUC_ASSERT_NULL(NULL); - ZUC_ASSERT_NULL(0); - ZUC_ASSERT_NULL(a); -} - -#ifdef ENABLE_FAIL_TESTS -ZUC_TEST(pointers, null_fail) -{ - const char *a = "a"; - - ZUC_ASSERT_NULL(!NULL); - ZUC_ASSERT_NULL(!0); - ZUC_ASSERT_NULL(a); -} -#endif - -ZUC_TEST(pointers, not_null) -{ - const char *a = "a"; - - ZUC_ASSERT_NOT_NULL(!NULL); - ZUC_ASSERT_NOT_NULL(!0); - ZUC_ASSERT_NOT_NULL(a); -} - -#ifdef ENABLE_FAIL_TESTS -ZUC_TEST(pointers, not_null_fail) -{ - const char *a = NULL; - - ZUC_ASSERT_NOT_NULL(NULL); - ZUC_ASSERT_NOT_NULL(0); - ZUC_ASSERT_NOT_NULL(a); -} -#endif - -ZUC_TEST(strings, eq) -{ - /* Note that we use strdup() to ensure different addresses. */ - char *str_a = strdup("a"); - const char *str_nil = NULL; - - ZUC_ASSERT_STREQ(str_a, str_a); - ZUC_ASSERT_STREQ("a", str_a); - ZUC_ASSERT_STREQ(str_a, "a"); - - ZUC_ASSERT_STREQ(str_nil, str_nil); - ZUC_ASSERT_STREQ(NULL, str_nil); - ZUC_ASSERT_STREQ(str_nil, NULL); - - free(str_a); -} - -#ifdef ENABLE_FAIL_TESTS -ZUC_TEST(strings, eq_fail) -{ - /* Note that we use strdup() to ensure different addresses. */ - char *str_a = strdup("a"); - char *str_b = strdup("b"); - const char *str_nil = NULL; - - ZUC_ASSERT_STREQ(str_a, str_b); - ZUC_ASSERT_STREQ("b", str_a); - ZUC_ASSERT_STREQ(str_a, "b"); - - ZUC_ASSERT_STREQ(str_nil, str_a); - ZUC_ASSERT_STREQ(str_nil, str_b); - ZUC_ASSERT_STREQ(str_a, str_nil); - ZUC_ASSERT_STREQ(str_b, str_nil); - - ZUC_ASSERT_STREQ(NULL, str_a); - ZUC_ASSERT_STREQ(NULL, str_b); - ZUC_ASSERT_STREQ(str_a, NULL); - ZUC_ASSERT_STREQ(str_b, NULL); - - free(str_a); - free(str_b); -} -#endif - -ZUC_TEST(strings, ne) -{ - /* Note that we use strdup() to ensure different addresses. */ - char *str_a = strdup("a"); - char *str_b = strdup("b"); - const char *str_nil = NULL; - - ZUC_ASSERT_STRNE(str_a, str_b); - ZUC_ASSERT_STRNE("b", str_a); - ZUC_ASSERT_STRNE(str_a, "b"); - - ZUC_ASSERT_STRNE(str_nil, str_a); - ZUC_ASSERT_STRNE(str_nil, str_b); - ZUC_ASSERT_STRNE(str_a, str_nil); - ZUC_ASSERT_STRNE(str_b, str_nil); - - ZUC_ASSERT_STRNE(NULL, str_a); - ZUC_ASSERT_STRNE(NULL, str_b); - ZUC_ASSERT_STRNE(str_a, NULL); - ZUC_ASSERT_STRNE(str_b, NULL); - - free(str_a); - free(str_b); -} - -#ifdef ENABLE_FAIL_TESTS -ZUC_TEST(strings, ne_fail01) -{ - /* Note that we use strdup() to ensure different addresses. */ - char *str_a = strdup("a"); - - ZUC_ASSERTG_STRNE(str_a, str_a, err); - -err: - free(str_a); -} - -ZUC_TEST(strings, ne_fail02) -{ - /* Note that we use strdup() to ensure different addresses. */ - char *str_a = strdup("a"); - - ZUC_ASSERTG_STRNE("a", str_a, err); - -err: - free(str_a); -} - -ZUC_TEST(strings, ne_fail03) -{ - /* Note that we use strdup() to ensure different addresses. */ - char *str_a = strdup("a"); - - ZUC_ASSERTG_STRNE(str_a, "a", err); - -err: - free(str_a); -} - -ZUC_TEST(strings, ne_fail04) -{ - const char *str_nil = NULL; - - ZUC_ASSERT_STRNE(str_nil, str_nil); -} - -ZUC_TEST(strings, ne_fail05) -{ - const char *str_nil = NULL; - - ZUC_ASSERT_STRNE(NULL, str_nil); -} - -ZUC_TEST(strings, ne_fail06) -{ - const char *str_nil = NULL; - - ZUC_ASSERT_STRNE(str_nil, NULL); -} -#endif - -ZUC_TEST(base_test, later) -{ - /* an additional test for the same case but later in source */ - ZUC_ASSERT_EQ(3, 5 - 2); -} - -ZUC_TEST(base_test, zed) -{ - /* an additional test for the same case but later in source */ - ZUC_ASSERT_EQ(3, 5 - 2); -} diff --git a/xwayland/dnd.c b/xwayland/dnd.c deleted file mode 100644 index e981e81..0000000 --- a/xwayland/dnd.c +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright © 2013 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "xwayland.h" - -#include "hash.h" - -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); - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - 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 = zalloc(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_pointer_start_drag(pointer, &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); - 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 values[1], version = 4, 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); - - 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); -} diff --git a/xwayland/hash.c b/xwayland/hash.c deleted file mode 100644 index 820e51f..0000000 --- a/xwayland/hash.c +++ /dev/null @@ -1,309 +0,0 @@ -/* - * 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 "config.h" - -#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/xwayland/hash.h b/xwayland/hash.h deleted file mode 100644 index 334d8f4..0000000 --- a/xwayland/hash.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 - -#include - -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/xwayland/launcher.c b/xwayland/launcher.c deleted file mode 100644 index 60854c8..0000000 --- a/xwayland/launcher.c +++ /dev/null @@ -1,409 +0,0 @@ -/* - * Copyright © 2011 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "xwayland.h" -#include -#include "shared/helpers.h" -#include "shared/string-helpers.h" - -static int -weston_xserver_handle_event(int listen_fd, uint32_t mask, void *data) -{ - struct weston_xserver *wxs = data; - char display[8]; - - snprintf(display, sizeof display, ":%d", wxs->display); - - wxs->pid = wxs->spawn_func(wxs->user_data, display, wxs->abstract_fd, wxs->unix_fd); - if (wxs->pid == -1) { - weston_log("Failed to spawn the Xwayland server\n"); - return 1; - } - - weston_log("Spawned Xwayland server, pid %d\n", wxs->pid); - wl_event_source_remove(wxs->abstract_source); - wl_event_source_remove(wxs->unix_source); - - 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->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->wm = NULL; - } - wxs->loop = NULL; -} - -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) -{ - /* 10 decimal characters, trailing LF and NUL byte; see comment - * at end of function. */ - char pid[11]; - 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; - } - - /* Trim the trailing LF, or at least ensure it's NULL. */ - pid[10] = '\0'; - - if (!safe_strtoint(pid, &other)) { - 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. - * Also subtle is that we don't emit a trailing NUL to the file, so - * our size here is 11 rather than 12. */ - size = dprintf(fd, "%10d\n", getpid()); - if (size != 11) { - 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); - - weston_log_scope_destroy(wxs->wm_debug); - - free(wxs); -} - -static struct weston_xwayland * -weston_xwayland_get(struct weston_compositor *compositor) -{ - struct wl_listener *listener; - struct weston_xserver *wxs; - - listener = wl_signal_get(&compositor->destroy_signal, - weston_xserver_destroy); - if (!listener) - return NULL; - - wxs = wl_container_of(listener, wxs, destroy_listener); - return (struct weston_xwayland *)wxs; -} - -static int -weston_xwayland_listen(struct weston_xwayland *xwayland, void *user_data, - weston_xwayland_spawn_xserver_func_t spawn_func) -{ - struct weston_xserver *wxs = (struct weston_xserver *)xwayland; - char lockfile[256], display_name[8]; - - wxs->user_data = user_data; - wxs->spawn_func = spawn_func; - -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(wxs->wl_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); - - return 0; -} - -static void -weston_xwayland_xserver_loaded(struct weston_xwayland *xwayland, - struct wl_client *client, int wm_fd) -{ - struct weston_xserver *wxs = (struct weston_xserver *)xwayland; - wxs->wm = weston_wm_create(wxs, wm_fd); - wxs->client = client; -} - -static void -weston_xwayland_xserver_exited(struct weston_xwayland *xwayland, - int exit_status) -{ - struct weston_xserver *wxs = (struct weston_xserver *)xwayland; - - wxs->pid = 0; - wxs->client = 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", exit_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", exit_status); - weston_xserver_shutdown(wxs); - } -} - -const struct weston_xwayland_api api = { - weston_xwayland_get, - weston_xwayland_listen, - weston_xwayland_xserver_loaded, - weston_xwayland_xserver_exited, -}; -extern const struct weston_xwayland_surface_api surface_api; - -WL_EXPORT int -weston_module_init(struct weston_compositor *compositor) - -{ - struct wl_display *display = compositor->wl_display; - struct weston_xserver *wxs; - int ret; - - wxs = zalloc(sizeof *wxs); - if (wxs == NULL) - return -1; - wxs->wl_display = display; - wxs->compositor = compositor; - - if (!weston_compositor_add_destroy_listener_once(compositor, - &wxs->destroy_listener, - weston_xserver_destroy)) { - free(wxs); - return 0; - } - - if (weston_xwayland_get_api(compositor) != NULL || - weston_xwayland_surface_get_api(compositor) != NULL) { - weston_log("The xwayland module APIs are already loaded.\n"); - goto out_free; - } - - ret = weston_plugin_api_register(compositor, WESTON_XWAYLAND_API_NAME, - &api, sizeof(api)); - if (ret < 0) { - weston_log("Failed to register the xwayland module API.\n"); - goto out_free; - } - - ret = weston_plugin_api_register(compositor, - WESTON_XWAYLAND_SURFACE_API_NAME, - &surface_api, sizeof(surface_api)); - if (ret < 0) { - weston_log("Failed to register the xwayland surface API.\n"); - goto out_free; - } - - wxs->wm_debug = - weston_compositor_add_log_scope(wxs->compositor, "xwm-wm-x11", - "XWM's window management X11 events\n", - NULL, NULL, NULL); - - return 0; - -out_free: - wl_list_remove(&wxs->destroy_listener.link); - free(wxs); - return -1; -} diff --git a/xwayland/meson.build b/xwayland/meson.build deleted file mode 100644 index 896d631..0000000 --- a/xwayland/meson.build +++ /dev/null @@ -1,44 +0,0 @@ -if not get_option('xwayland') - subdir_done() -endif - -srcs_xwayland = [ - 'launcher.c', - 'window-manager.c', - 'selection.c', - 'dnd.c', - 'hash.c', -] - -dep_names_xwayland = [ - 'xcb', - 'xcb-composite', - 'xcb-shape', - 'xcb-xfixes', - 'xcursor', - 'cairo-xcb', -] - -deps_xwayland = [ dep_libweston_public ] - -foreach name : dep_names_xwayland - d = dependency(name, required: false) - if not d.found() - error('xwayland requires @0@ which was not found. Or, you can use \'-Dxwayland=false\'.'.format(name)) - endif - deps_xwayland += d -endforeach - -plugin_xwayland = shared_library( - 'xwayland', - srcs_xwayland, - link_with: lib_cairo_shared, - include_directories: common_inc, - dependencies: deps_xwayland, - name_prefix: '', - install: true, - install_dir: dir_module_libweston -) -env_modmap += 'xwayland.so=@0@;'.format(plugin_xwayland.full_path()) - -install_headers(xwayland_api_h, subdir: dir_include_libweston_install) diff --git a/xwayland/selection.c b/xwayland/selection.c deleted file mode 100644 index c4845f2..0000000 --- a/xwayland/selection.c +++ /dev/null @@ -1,762 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include -#include "xwayland.h" -#include "shared/helpers.h" - -#ifdef WM_DEBUG -#define wm_log(...) weston_log(__VA_ARGS__) -#else -#define wm_log(...) do {} while (0) -#endif - -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); - wm->property_source = NULL; - close(fd); - weston_log("write error to target fd: %s\n", strerror(errno)); - 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); - wm->property_source = NULL; - - 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; - FILE *fp; - char *logstr; - size_t logsize; - - 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); - if (reply == NULL) - return; - - fp = open_memstream(&logstr, &logsize); - if (fp) { - dump_property(fp, wm, wm->atom.wl_selection, reply); - if (fclose(fp) == 0) - wm_log("%s", logstr); - free(logstr); - } - - if (xcb_get_property_value_length(reply) > 0) { - /* reply's ownership is transferred to wm, which is responsible - * for freeing it */ - 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; - FILE *fp; - char *logstr; - size_t logsize; - - 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); - if (reply == NULL) - return; - - fp = open_memstream(&logstr, &logsize); - if (fp) { - dump_property(fp, wm, wm->atom.wl_selection, reply); - if (fclose(fp) == 0) - wm_log("%s", logstr); - free(logstr); - } - - if (reply->type != XCB_ATOM_ATOM) { - free(reply); - return; - } - - source = zalloc(sizeof *source); - if (source == NULL) { - free(reply); - 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; - FILE *fp; - char *logstr; - size_t logsize; - - 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); - - fp = open_memstream(&logstr, &logsize); - if (fp) { - dump_property(fp, wm, wm->atom.wl_selection, reply); - if (fclose(fp) == 0) - wm_log("%s", logstr); - free(logstr); - } - - if (reply == NULL) { - return; - } else if (reply->type == wm->atom.incr) { - wm->incr = 1; - free(reply); - } else { - wm->incr = 0; - /* reply's ownership is transferred to wm, which is responsible - * for freeing it */ - 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: %s\n", - strerror(errno)); - weston_wm_send_selection_notify(wm, XCB_ATOM_NONE); - if (wm->property_source) - wl_event_source_remove(wm->property_source); - wm->property_source = NULL; - close(fd); - wl_array_release(&wm->source_data); - return 1; - } - - 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; - if (wm->property_source) - wl_event_source_remove(wm->property_source); - wm->property_source = NULL; - 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; - if (wm->property_source) - wl_event_source_remove(wm->property_source); - wm->property_source = NULL; - } else { - weston_log("got %zu bytes, " - "property deleted, setting 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); - if (wm->property_source) - wl_event_source_remove(wm->property_source); - wm->property_source = NULL; - 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, setting new property\n", - wm->source_data.size); - weston_wm_flush_source_data(wm); - } - xcb_flush(wm->conn); - if (wm->property_source) - wl_event_source_remove(wm->property_source); - wm->property_source = NULL; - 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: %s\n", strerror(errno)); - 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 property 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; - - 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; - - xcb_set_selection_owner(wm->conn, - wm->selection_window, - 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; - - wl_list_init(&wm->selection_listener.link); - - 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); - if (seat == NULL) - return; - 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/xwayland/window-manager.c b/xwayland/window-manager.c deleted file mode 100644 index de86821..0000000 --- a/xwayland/window-manager.c +++ /dev/null @@ -1,2986 +0,0 @@ -/* - * Copyright © 2011 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "xwayland.h" -#include "xwayland-internal-interface.h" - -#include "shared/cairo-util.h" -#include "hash.h" -#include "shared/helpers.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_DECOR_EVERYTHING \ - (MWM_DECOR_BORDER | MWM_DECOR_RESIZEH | MWM_DECOR_TITLE | \ - MWM_DECOR_MENU | MWM_DECOR_MINIMIZE | MWM_DECOR_MAXIMIZE) - -#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_output_weak_ref { - struct weston_output *output; - struct wl_listener destroy_listener; -}; - -struct weston_wm_window { - struct weston_wm *wm; - xcb_window_t id; - xcb_window_t frame_id; - struct frame *frame; - cairo_surface_t *cairo_surface; - uint32_t surface_id; - struct weston_surface *surface; - struct weston_desktop_xwayland_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; - int y; - bool pos_dirty; - int map_request_x; - int map_request_y; - struct weston_output_weak_ref legacy_fullscreen_output; - int saved_width, saved_height; - int decorate; - uint32_t last_button_time; - int did_double; - int override_redirect; - int fullscreen; - int has_alpha; - int delete_window; - int maximized_vert; - int maximized_horz; - struct wm_size_hints size_hints; - struct motif_wm_hints motif_hints; - struct wl_list link; -}; - -static void -weston_wm_window_set_allow_commits(struct weston_wm_window *window, bool allow); - -static struct weston_wm_window * -get_wm_window(struct weston_surface *surface); - -static void -weston_wm_set_net_active_window(struct weston_wm *wm, xcb_window_t window); - -static void -weston_wm_window_schedule_repaint(struct weston_wm_window *window); - -static int -legacy_fullscreen(struct weston_wm *wm, - struct weston_wm_window *window, - struct weston_output **output_ret); - -static void -xserver_map_shell_surface(struct weston_wm_window *window, - struct weston_surface *surface); - -static bool -wm_debug_is_enabled(struct weston_wm *wm) -{ - return weston_log_scope_is_enabled(wm->server->wm_debug); -} - -static void __attribute__ ((format (printf, 2, 3))) -wm_printf(struct weston_wm *wm, const char *fmt, ...) -{ - va_list ap; - char timestr[128]; - - if (wm_debug_is_enabled(wm)) - weston_log_scope_printf(wm->server->wm_debug, "%s ", - weston_log_scope_timestamp(wm->server->wm_debug, - timestr, sizeof timestr)); - - va_start(ap, fmt); - weston_log_scope_vprintf(wm->server->wm_debug, fmt, ap); - va_end(ap); -} -static void -weston_output_weak_ref_init(struct weston_output_weak_ref *ref) -{ - ref->output = NULL; -} - -static void -weston_output_weak_ref_clear(struct weston_output_weak_ref *ref) -{ - if (!ref->output) - return; - - wl_list_remove(&ref->destroy_listener.link); - ref->output = NULL; -} - -static void -weston_output_weak_ref_handle_destroy(struct wl_listener *listener, void *data) -{ - struct weston_output_weak_ref *ref; - - ref = wl_container_of(listener, ref, destroy_listener); - assert(ref->output == data); - - weston_output_weak_ref_clear(ref); -} - -static void -weston_output_weak_ref_set(struct weston_output_weak_ref *ref, - struct weston_output *output) -{ - weston_output_weak_ref_clear(ref); - - if (!output) - return; - - ref->destroy_listener.notify = weston_output_weak_ref_handle_destroy; - wl_signal_add(&output->destroy_signal, &ref->destroy_listener); - ref->output = output; -} - -static bool __attribute__ ((warn_unused_result)) -wm_lookup_window(struct weston_wm *wm, xcb_window_t hash, - struct weston_wm_window **window) -{ - *window = hash_table_lookup(wm->window_hash, hash); - if (*window) - return true; - return false; -} - -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; -} - -static unsigned -dump_cardinal_array_elem(FILE *fp, unsigned format, - void *arr, unsigned len, unsigned ind) -{ - const char *comma; - - /* If more than 16 elements, print 0-14, ..., last */ - if (ind > 14 && ind < len - 1) { - fprintf(fp, ", ..."); - return len - 1; - } - - comma = ind ? ", " : ""; - - switch (format) { - case 32: - fprintf(fp, "%s%" PRIu32, comma, ((uint32_t *)arr)[ind]); - break; - case 16: - fprintf(fp, "%s%" PRIu16, comma, ((uint16_t *)arr)[ind]); - break; - case 8: - fprintf(fp, "%s%" PRIu8, comma, ((uint8_t *)arr)[ind]); - break; - default: - fprintf(fp, "%s???", comma); - } - - return ind + 1; -} - -static void -dump_cardinal_array(FILE *fp, xcb_get_property_reply_t *reply) -{ - unsigned i = 0; - void *arr; - - assert(reply->type == XCB_ATOM_CARDINAL); - - arr = xcb_get_property_value(reply); - - fprintf(fp, "["); - while (i < reply->value_len) - i = dump_cardinal_array_elem(fp, reply->format, - arr, reply->value_len, i); - fprintf(fp, "]"); -} - -void -dump_property(FILE *fp, 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; - xcb_window_t *window_value; - int width, len; - uint32_t i; - - width = fprintf(fp, "%s: ", get_atom_name(wm->conn, property)); - if (reply == NULL) { - fprintf(fp, "(no reply)\n"); - return; - } - - width += fprintf(fp, "%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); - fprintf(fp, "%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; - fprintf(fp, "\"%.*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) { - fprintf(fp, "\n "); - width = 4; - } else if (i > 0) { - width += fprintf(fp, ", "); - } - - width += fprintf(fp, "%s", name); - } - fprintf(fp, "\n"); - } else if (reply->type == XCB_ATOM_CARDINAL) { - dump_cardinal_array(fp, reply); - fprintf(fp, "\n"); - } else if (reply->type == XCB_ATOM_WINDOW && reply->format == 32) { - window_value = xcb_get_property_value(reply); - fprintf(fp, "win %u\n", *window_value); - } else { - fprintf(fp, "huh?\n"); - } -} - -static void -read_and_dump_property(FILE *fp, 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(fp, wm, property, reply); - - free(reply); -} - -/* We reuse some predefined, but otherwise useles atoms - * as local type placeholders that never touch the X11 server, - * to make weston_wm_window_read_properties() less exceptional. - */ -#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; - -#define F(field) (&window->field) - const struct { - xcb_atom_t atom; - xcb_atom_t type; - void *ptr; - } 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, NULL }, - { wm->atom.wm_normal_hints, TYPE_WM_NORMAL_HINTS, NULL }, - { wm->atom.net_wm_state, TYPE_NET_WM_STATE, NULL }, - { 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, NULL }, - { 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; - char name[1024]; - - 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 ? 0 : MWM_DECOR_EVERYTHING; - window->size_hints.flags = 0; - window->motif_hints.flags = 0; - window->delete_window = 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 = props[i].ptr; - - 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); - if (!wm_lookup_window(wm, *xid, p)) - weston_log("XCB_ATOM_WINDOW contains window" - " id not found in hash table.\n"); - 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: - atom = xcb_get_property_value(reply); - for (i = 0; i < reply->value_len; i++) - if (atom[i] == wm->atom.wm_delete_window) { - window->delete_window = 1; - break; - } - 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; - if (atom[i] == wm->atom.net_wm_state_maximized_vert) - window->maximized_vert = 1; - if (atom[i] == wm->atom.net_wm_state_maximized_horz) - window->maximized_horz = 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) { - if (window->motif_hints.decorations & MWM_DECOR_ALL) - /* MWM_DECOR_ALL means all except the other values listed. */ - window->decorate = - MWM_DECOR_EVERYTHING & (~window->motif_hints.decorations); - else - window->decorate = - window->motif_hints.decorations; - } - break; - default: - break; - } - free(reply); - } - - if (window->pid > 0) { - gethostname(name, sizeof(name)); - for (i = 0; i < sizeof(name); i++) { - if (name[i] == '\0') - break; - } - if (i == sizeof(name)) - name[0] = '\0'; /* ignore stupid hostnames */ - - /* 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 (!window->machine || strcmp(window->machine, name)) - window->pid = 0; - } -} - -#undef TYPE_WM_PROTOCOLS -#undef TYPE_MOTIF_WM_HINTS -#undef TYPE_NET_WM_STATE -#undef TYPE_WM_NORMAL_HINTS - -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 && window->frame) { - *width = frame_width(window->frame); - *height = frame_height(window->frame); - } 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 && window->frame) { - frame_interior(window->frame, x, y, NULL, NULL); - } 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_configure_window(struct weston_wm *wm, xcb_window_t window_id, - uint16_t mask, const uint32_t *values) -{ - static const struct { - xcb_config_window_t bitmask; - const char *name; - } names[] = { - { XCB_CONFIG_WINDOW_X, "x" }, - { XCB_CONFIG_WINDOW_Y, "y" }, - { XCB_CONFIG_WINDOW_WIDTH, "width" }, - { XCB_CONFIG_WINDOW_HEIGHT, "height" }, - { XCB_CONFIG_WINDOW_BORDER_WIDTH, "border_width" }, - { XCB_CONFIG_WINDOW_SIBLING, "sibling" }, - { XCB_CONFIG_WINDOW_STACK_MODE, "stack_mode" }, - }; - char *buf = NULL; - size_t sz = 0; - FILE *fp; - unsigned i, v; - - xcb_configure_window(wm->conn, window_id, mask, values); - - if (!wm_debug_is_enabled(wm)) - return; - - fp = open_memstream(&buf, &sz); - if (!fp) - return; - - fprintf(fp, "XWM: configure window %d:", window_id); - for (i = 0, v = 0; i < ARRAY_LENGTH(names); i++) { - if (mask & names[i].bitmask) - fprintf(fp, " %s=%d", names[i].name, values[v++]); - } - fclose(fp); - - wm_printf(wm, "%s\n", buf); - free(buf); -} - -static void -weston_wm_window_configure_frame(struct weston_wm_window *window) -{ - uint16_t mask; - uint32_t values[2]; - int width, height; - - if (!window->frame_id) - return; - - weston_wm_window_get_frame_size(window, &width, &height); - values[0] = width; - values[1] = height; - mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; - weston_wm_configure_window(window->wm, window->frame_id, mask, values); -} - -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 values[16]; - uint16_t mask; - int x, y; - int i = 0; - - wm_printf(wm, "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); - - if (!wm_lookup_window(wm, configure_request->window, &window)) - return; - - 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; - - if (window->frame) { - weston_wm_window_set_allow_commits(window, false); - frame_resize_inside(window->frame, window->width, window->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; - } - - weston_wm_configure_window(wm, window->id, mask, values); - weston_wm_window_configure_frame(window); - 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; - const struct weston_desktop_xwayland_interface *xwayland_api = - wm->server->compositor->xwayland_interface; - struct weston_wm_window *window; - - wm_printf(wm, "XCB_CONFIGURE_NOTIFY (window %d) %d,%d @ %dx%d%s\n", - configure_notify->window, - configure_notify->x, configure_notify->y, - configure_notify->width, configure_notify->height, - configure_notify->override_redirect ? ", override" : ""); - - if (!wm_lookup_window(wm, configure_notify->window, &window)) - return; - - window->x = configure_notify->x; - window->y = configure_notify->y; - window->pos_dirty = false; - - if (window->override_redirect) { - window->width = configure_notify->width; - window->height = configure_notify->height; - if (window->frame) - frame_resize_inside(window->frame, - window->width, window->height); - - /* We should check if shsurf has been created because sometimes - * there are races - * (configure_notify is sent before xserver_map_surface) */ - if (window->shsurf) - xwayland_api->set_xwayland(window->shsurf, - window->x, window->y); - } -} - -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); - if (!window) - return; - - if (window->pid > 0) - kill(window->pid, SIGKILL); -} - -static void -weston_wm_create_surface(struct wl_listener *listener, void *data) -{ - struct weston_surface *surface = data; - struct weston_wm *wm = - container_of(listener, - struct weston_wm, create_surface_listener); - struct weston_wm_window *window; - - if (wl_resource_get_client(surface->resource) != wm->server->client) - return; - - wm_printf(wm, "XWM: create weston_surface %p\n", surface); - - wl_list_for_each(window, &wm->unpaired_window_list, link) - if (window->surface_id == - wl_resource_get_id(surface->resource)) { - xserver_map_shell_surface(window, surface); - window->surface_id = 0; - wl_list_remove(&window->link); - break; - } -} - -static void -weston_wm_send_focus_window(struct weston_wm *wm, - struct weston_wm_window *window) -{ - xcb_client_message_event_t client_message; - - if (window) { - uint32_t values[1]; - - if (window->override_redirect) - return; - - 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); - - values[0] = XCB_STACK_MODE_ABOVE; - weston_wm_configure_window(wm, window->frame_id, - XCB_CONFIG_WINDOW_STACK_MODE, - values); - } else { - xcb_set_input_focus (wm->conn, - XCB_INPUT_FOCUS_POINTER_ROOT, - XCB_NONE, - XCB_TIME_CURRENT_TIME); - } -} - -static void -weston_wm_window_activate(struct wl_listener *listener, void *data) -{ - struct weston_surface_activation_data *activation_data = data; - struct weston_surface *surface = activation_data->surface; - struct weston_wm_window *window = NULL; - struct weston_wm *wm = - container_of(listener, struct weston_wm, activate_listener); - - if (surface) { - window = get_wm_window(surface); - } - - if (window) { - weston_wm_set_net_active_window(wm, window->id); - } else { - weston_wm_set_net_active_window(wm, XCB_WINDOW_NONE); - } - - weston_wm_send_focus_window(wm, window); - - if (wm->focus_window) { - if (wm->focus_window->frame) - frame_unset_flag(wm->focus_window->frame, FRAME_FLAG_ACTIVE); - weston_wm_window_schedule_repaint(wm->focus_window); - } - wm->focus_window = window; - if (wm->focus_window) { - if (wm->focus_window->frame) - frame_set_flag(wm->focus_window->frame, FRAME_FLAG_ACTIVE); - weston_wm_window_schedule_repaint(wm->focus_window); - } - - xcb_flush(wm->conn); - -} - -/** Control Xwayland wl_surface.commit behaviour - * - * This function sets the "_XWAYLAND_ALLOW_COMMITS" property of the frame window - * (not the content window!) to \p allow. - * - * If the property is set to \c true, Xwayland will commit whenever it likes. - * If the property is set to \c false, Xwayland will not commit. - * If the property is not set at all, Xwayland assumes it is \c true. - * - * \param window The XWM window to control. - * \param allow Whether Xwayland is allowed to wl_surface.commit for the window. - */ -static void -weston_wm_window_set_allow_commits(struct weston_wm_window *window, bool allow) -{ - struct weston_wm *wm = window->wm; - uint32_t property[1]; - - assert(window->frame_id != XCB_WINDOW_NONE); - - wm_printf(wm, "XWM: window %d set _XWAYLAND_ALLOW_COMMITS = %s\n", - window->id, allow ? "true" : "false"); - - property[0] = allow ? 1 : 0; - - xcb_change_property(wm->conn, - XCB_PROP_MODE_REPLACE, - window->frame_id, - wm->atom.allow_commits, - XCB_ATOM_CARDINAL, - 32, /* format */ - 1, property); - 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[3]; - int i; - - i = 0; - if (window->fullscreen) - property[i++] = wm->atom.net_wm_state_fullscreen; - if (window->maximized_vert) - property[i++] = wm->atom.net_wm_state_maximized_vert; - if (window->maximized_horz) - property[i++] = wm->atom.net_wm_state_maximized_horz; - - 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; - int buttons = FRAME_BUTTON_CLOSE; - - if (window->decorate & MWM_DECOR_MAXIMIZE) - buttons |= FRAME_BUTTON_MAXIMIZE; - - window->frame = frame_create(window->wm->theme, - window->width, window->height, - buttons, window->name, NULL); - - if (!window->frame) - return; - - frame_resize_inside(window->frame, window->width, window->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; - weston_wm_configure_window(wm, 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); -} - -/* - * Sets the _NET_WM_DESKTOP property for the window to 'desktop'. - * Passing a <0 desktop value deletes the property. - */ -static void -weston_wm_window_set_virtual_desktop(struct weston_wm_window *window, - int desktop) -{ - if (desktop >= 0) { - xcb_change_property(window->wm->conn, - XCB_PROP_MODE_REPLACE, - window->id, - window->wm->atom.net_wm_desktop, - XCB_ATOM_CARDINAL, - 32, /* format */ - 1, &desktop); - } else { - xcb_delete_property(window->wm->conn, - window->id, - window->wm->atom.net_wm_desktop); - } -} - -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; - struct weston_output *output; - - if (our_resource(wm, map_request->window)) { - wm_printf(wm, "XCB_MAP_REQUEST (window %d, ours)\n", - map_request->window); - return; - } - - if (!wm_lookup_window(wm, map_request->window, &window)) - return; - - weston_wm_window_read_properties(window); - - /* For a new Window, MapRequest happens before the Window is realized - * in Xwayland. We do the real xcb_map_window() here as a response to - * MapRequest. The Window will get realized (wl_surface created in - * Wayland and WL_SURFACE_ID sent in X11) when it has been mapped for - * real. - * - * MapRequest only happens for (X11) unmapped Windows. On UnmapNotify, - * we reset shsurf to NULL, so even if X11 connection races far ahead - * of the Wayland connection and the X11 client is repeatedly mapping - * and unmapping, we will never have shsurf set on MapRequest. - */ - assert(!window->shsurf); - - window->map_request_x = window->x; - window->map_request_y = window->y; - - if (window->frame_id == XCB_WINDOW_NONE) - weston_wm_window_create_frame(window); /* sets frame_id */ - assert(window->frame_id != XCB_WINDOW_NONE); - - wm_printf(wm, "XCB_MAP_REQUEST (window %d, %p, frame %d, %dx%d @ %d,%d)\n", - window->id, window, window->frame_id, - window->width, window->height, - window->map_request_x, window->map_request_y); - - weston_wm_window_set_allow_commits(window, false); - weston_wm_window_set_wm_state(window, ICCCM_NORMAL_STATE); - weston_wm_window_set_net_wm_state(window); - weston_wm_window_set_virtual_desktop(window, 0); - - if (legacy_fullscreen(wm, window, &output)) { - window->fullscreen = 1; - weston_output_weak_ref_set(&window->legacy_fullscreen_output, - output); - } - - xcb_map_window(wm->conn, map_request->window); - xcb_map_window(wm->conn, window->frame_id); - - /* Mapped in the X server, we can draw immediately. - * Cannot set pending state though, no weston_surface until - * xserver_map_shell_surface() time. */ - weston_wm_window_schedule_repaint(window); -} - -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_printf(wm, "XCB_MAP_NOTIFY (window %d, ours)\n", - map_notify->window); - return; - } - - wm_printf(wm, "XCB_MAP_NOTIFY (window %d%s)\n", map_notify->window, - map_notify->override_redirect ? ", override" : ""); -} - -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_printf(wm, "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; - - if (!wm_lookup_window(wm, unmap_notify->window, &window)) - return; - - if (window->surface_id) { - /* Make sure we're not on the unpaired surface list or we - * could be assigned a surface during surface creation that - * was mapped before this unmap request. - */ - wl_list_remove(&window->link); - window->surface_id = 0; - } - 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; - - weston_wm_window_set_wm_state(window, ICCCM_WITHDRAWN_STATE); - weston_wm_window_set_virtual_desktop(window, -1); - - xcb_unmap_window(wm->conn, window->frame_id); -} - -static void -weston_wm_window_draw_decoration(struct weston_wm_window *window) -{ - cairo_t *cr; - int width, height; - const char *how; - - weston_wm_window_get_frame_size(window, &width, &height); - - cairo_xcb_surface_set_size(window->cairo_surface, width, height); - cr = cairo_create(window->cairo_surface); - - if (window->fullscreen) { - how = "fullscreen"; - /* nothing */ - } else if (window->decorate) { - how = "decorate"; - frame_set_title(window->frame, window->name); - frame_repaint(window->frame, cr); - } else { - how = "shadow"; - cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_rgba(cr, 0, 0, 0, 0); - cairo_paint(cr); - - render_shadow(cr, window->wm->theme->shadow, - 2, 2, width + 8, height + 8, 64, 64); - } - - wm_printf(window->wm, "XWM: draw decoration, win %d, %s\n", - window->id, how); - - cairo_destroy(cr); - cairo_surface_flush(window->cairo_surface); - xcb_flush(window->wm->conn); -} - -static void -weston_wm_window_set_pending_state(struct weston_wm_window *window) -{ - int x, y, width, height; - int32_t input_x, input_y, input_w, input_h; - const struct weston_desktop_xwayland_interface *xwayland_interface = - window->wm->server->compositor->xwayland_interface; - - if (!window->surface) - return; - - weston_wm_window_get_frame_size(window, &width, &height); - weston_wm_window_get_child_position(window, &x, &y); - - 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); - } - - if (window->decorate && !window->fullscreen) { - frame_input_rect(window->frame, &input_x, &input_y, - &input_w, &input_h); - } else { - input_x = x; - input_y = y; - input_w = width; - input_h = height; - } - - wm_printf(window->wm, "XWM: win %d geometry: %d,%d %dx%d\n", - window->id, input_x, input_y, input_w, input_h); - - pixman_region32_fini(&window->surface->pending.input); - pixman_region32_init_rect(&window->surface->pending.input, - input_x, input_y, input_w, input_h); - - xwayland_interface->set_window_geometry(window->shsurf, - input_x, input_y, - input_w, input_h); - if (window->name) - xwayland_interface->set_title(window->shsurf, window->name); - if (window->pid > 0) - xwayland_interface->set_pid(window->shsurf, window->pid); -} - -static void -weston_wm_window_do_repaint(void *data) -{ - struct weston_wm_window *window = data; - - window->repaint_source = NULL; - - weston_wm_window_set_allow_commits(window, false); - weston_wm_window_read_properties(window); - - weston_wm_window_draw_decoration(window); - weston_wm_window_set_pending_state(window); - weston_wm_window_set_allow_commits(window, true); -} - -static void -weston_wm_window_set_pending_state_OR(struct weston_wm_window *window) -{ - int width, height; - - /* for override-redirect windows */ - assert(window->frame_id == XCB_WINDOW_NONE); - - if (!window->surface) - return; - - 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); - } -} - -static void -weston_wm_window_schedule_repaint(struct weston_wm_window *window) -{ - struct weston_wm *wm = window->wm; - - if (window->frame_id == XCB_WINDOW_NONE) { - /* Override-redirect windows go through here, but we - * cannot assert(window->override_redirect); because - * we do not deal with changing OR flag yet. - * XXX: handle OR flag changes in message handlers - */ - weston_wm_window_set_pending_state_OR(window); - return; - } - - if (window->repaint_source) - return; - - wm_printf(wm, "XWM: schedule repaint, win %d\n", window->id); - - window->repaint_source = - wl_event_loop_add_idle(wm->server->loop, - weston_wm_window_do_repaint, 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; - FILE *fp = NULL; - char *logstr; - size_t logsize; - char timestr[128]; - - if (!wm_lookup_window(wm, property_notify->window, &window)) - return; - - window->properties_dirty = 1; - - if (wm_debug_is_enabled(wm)) - fp = open_memstream(&logstr, &logsize); - - if (fp) { - fprintf(fp, "%s XCB_PROPERTY_NOTIFY: window %d, ", - weston_log_scope_timestamp(wm->server->wm_debug, - timestr, sizeof timestr), - property_notify->window); - if (property_notify->state == XCB_PROPERTY_DELETE) - fprintf(fp, "deleted %s\n", - get_atom_name(wm->conn, property_notify->atom)); - else - read_and_dump_property(fp, wm, property_notify->window, - property_notify->atom); - - if (fclose(fp) == 0) - weston_log_scope_write(wm->server->wm_debug, - logstr, logsize); - free(logstr); - } else { - /* read_and_dump_property() is a X11 roundtrip. - * Mimic it to maintain ordering semantics between debug - * and non-debug paths. - */ - get_atom_name(wm->conn, 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_printf(wm, "failed to allocate window\n"); - return; - } - - geometry_cookie = xcb_get_geometry(wm->conn, id); - - values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE | - XCB_EVENT_MASK_FOCUS_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; - window->pos_dirty = false; - window->map_request_x = INT_MIN; /* out of range for valid positions */ - window->map_request_y = INT_MIN; /* out of range for valid positions */ - weston_output_weak_ref_init(&window->legacy_fullscreen_output); - - 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; - - weston_output_weak_ref_clear(&window->legacy_fullscreen_output); - - if (window->configure_source) - wl_event_source_remove(window->configure_source); - 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); - weston_wm_window_set_virtual_desktop(window, -1); - hash_table_remove(wm->window_hash, window->frame_id); - window->frame_id = XCB_WINDOW_NONE; - } - - if (window->frame) - frame_destroy(window->frame); - - if (window->surface_id) - wl_list_remove(&window->link); - - if (window->surface) - wl_list_remove(&window->surface_destroy_listener.link); - - 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_printf(wm, "XCB_CREATE_NOTIFY (window %d, at (%d, %d), width %d, height %d%s%s)\n", - create_notify->window, - create_notify->x, create_notify->y, - 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_printf(wm, "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; - - if (!wm_lookup_window(wm, destroy_notify->window, &window)) - return; - - 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_printf(wm, "XCB_REPARENT_NOTIFY (window %d, parent %d, event %d%s)\n", - reparent_notify->window, - reparent_notify->parent, - reparent_notify->event, - reparent_notify->override_redirect ? ", override" : ""); - - 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)) { - if (!wm_lookup_window(wm, reparent_notify->window, &window)) - return; - weston_wm_window_destroy(window); - } -} - -struct weston_seat * -weston_wm_pick_seat(struct weston_wm *wm) -{ - struct wl_list *seats = wm->server->compositor->seat_list.next; - if (wl_list_empty(seats)) - return NULL; - return container_of(seats, struct weston_seat, link); -} - -static struct weston_seat * -weston_wm_pick_seat_for_window(struct weston_wm_window *window) -{ - struct weston_wm *wm = window->wm; - struct weston_seat *seat, *s; - - seat = NULL; - wl_list_for_each(s, &wm->server->compositor->seat_list, link) { - struct weston_pointer *pointer = weston_seat_get_pointer(s); - struct weston_pointer *old_pointer = - weston_seat_get_pointer(seat); - - if (pointer && pointer->focus && - pointer->focus->surface == window->surface && - pointer->button_count > 0 && - (!seat || - pointer->grab_serial - - old_pointer->grab_serial < (1 << 30))) - seat = s; - } - - return seat; -} - -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_for_window(window); - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - int detail; - const struct weston_desktop_xwayland_interface *xwayland_interface = - wm->server->compositor->xwayland_interface; - - if (!pointer || pointer->button_count != 1 - || !pointer->focus - || pointer->focus->surface != window->surface) - return; - - detail = client_message->data.data32[2]; - switch (detail) { - case _NET_WM_MOVERESIZE_MOVE: - xwayland_interface->move(window->shsurf, pointer); - 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: - xwayland_interface->resize(window->shsurf, pointer, 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_set_toplevel(struct weston_wm_window *window) -{ - const struct weston_desktop_xwayland_interface *xwayland_interface = - window->wm->server->compositor->xwayland_interface; - - xwayland_interface->set_toplevel(window->shsurf); - window->width = window->saved_width; - window->height = window->saved_height; - if (window->frame) - frame_resize_inside(window->frame, - window->width, - window->height); - weston_wm_window_configure(window); -} - -static inline bool -weston_wm_window_is_maximized(struct weston_wm_window *window) -{ - return window->maximized_horz && window->maximized_vert; -} - -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; - const struct weston_desktop_xwayland_interface *xwayland_interface = - wm->server->compositor->xwayland_interface; - uint32_t action, property1, property2; - int maximized = weston_wm_window_is_maximized(window); - - action = client_message->data.data32[0]; - property1 = client_message->data.data32[1]; - property2 = client_message->data.data32[2]; - - if ((property1 == wm->atom.net_wm_state_fullscreen || - property2 == 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) - xwayland_interface->set_fullscreen(window->shsurf, - NULL); - } else { - if (window->shsurf) - weston_wm_window_set_toplevel(window); - } - } else { - if ((property1 == wm->atom.net_wm_state_maximized_vert || - property2 == wm->atom.net_wm_state_maximized_vert) && - update_state(action, &window->maximized_vert)) - weston_wm_window_set_net_wm_state(window); - if ((property1 == wm->atom.net_wm_state_maximized_horz || - property2 == wm->atom.net_wm_state_maximized_horz) && - update_state(action, &window->maximized_horz)) - weston_wm_window_set_net_wm_state(window); - - if (maximized != weston_wm_window_is_maximized(window)) { - if (weston_wm_window_is_maximized(window)) { - window->saved_width = window->width; - window->saved_height = window->height; - - if (window->shsurf) - xwayland_interface->set_maximized(window->shsurf); - } else if (window->shsurf) { - weston_wm_window_set_toplevel(window); - } - } - } -} - -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_printf(window->wm, "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 void -weston_wm_window_handle_surface_id(struct weston_wm_window *window, - xcb_client_message_event_t *client_message) -{ - struct weston_wm *wm = window->wm; - struct wl_resource *resource; - - if (window->surface_id != 0) { - wm_printf(wm, "already have surface id for window %d\n", - window->id); - return; - } - - /* Xwayland will send the wayland requests to create the - * wl_surface before sending this client message. Even so, we - * can end up handling the X event before the wayland requests - * and thus when we try to look up the surface ID, the surface - * hasn't been created yet. In that case put the window on - * the unpaired window list and continue when the surface gets - * created. */ - uint32_t id = client_message->data.data32[0]; - resource = wl_client_get_object(wm->server->client, id); - if (resource) { - window->surface_id = 0; - xserver_map_shell_surface(window, - wl_resource_get_user_data(resource)); - } - else { - window->surface_id = id; - wl_list_insert(&wm->unpaired_window_list, &window->link); - } -} - -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; - - wm_printf(wm, "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); - - /* The window may get created and destroyed before we actually - * handle the message. If it doesn't exist, bail. - */ - if (!wm_lookup_window(wm, client_message->window, &window)) - return; - - 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); - else if (client_message->type == wm->atom.wl_surface_id) - weston_wm_window_handle_surface_id(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(enum theme_location location) -{ - 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_window_close(struct weston_wm_window *window, xcb_timestamp_t time) -{ - xcb_client_message_event_t client_message; - - if (window->delete_window) { - client_message.response_type = XCB_CLIENT_MESSAGE; - client_message.format = 32; - client_message.window = window->id; - client_message.type = window->wm->atom.wm_protocols; - client_message.data.data32[0] = - window->wm->atom.wm_delete_window; - client_message.data.data32[1] = time; - - xcb_send_event(window->wm->conn, 0, window->id, - XCB_EVENT_MASK_NO_EVENT, - (char *) &client_message); - } else { - xcb_kill_client(window->wm->conn, window->id); - } -} - -#define DOUBLE_CLICK_PERIOD 250 -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; - const struct weston_desktop_xwayland_interface *xwayland_interface = - wm->server->compositor->xwayland_interface; - struct weston_seat *seat; - struct weston_pointer *pointer; - struct weston_wm_window *window; - enum theme_location location; - enum wl_pointer_button_state button_state; - uint32_t button_id; - uint32_t double_click = 0; - - wm_printf(wm, "XCB_BUTTON_%s (detail %d)\n", - button->response_type == XCB_BUTTON_PRESS ? - "PRESS" : "RELEASE", button->detail); - - if (!wm_lookup_window(wm, button->event, &window) || - !window->decorate) - return; - - if (button->detail != 1 && button->detail != 2) - return; - - seat = weston_wm_pick_seat_for_window(window); - pointer = weston_seat_get_pointer(seat); - - button_state = button->response_type == XCB_BUTTON_PRESS ? - WL_POINTER_BUTTON_STATE_PRESSED : - WL_POINTER_BUTTON_STATE_RELEASED; - button_id = button->detail == 1 ? BTN_LEFT : BTN_RIGHT; - - if (button_state == WL_POINTER_BUTTON_STATE_PRESSED) { - if (button->time - window->last_button_time <= DOUBLE_CLICK_PERIOD) { - double_click = 1; - window->did_double = 1; - } else - window->did_double = 0; - - window->last_button_time = button->time; - } else if (window->did_double == 1) { - double_click = 1; - window->did_double = 0; - } - - /* Make sure we're looking at the right location. The frame - * could have received a motion event from a pointer from a - * different wl_seat, but under X it looks like our core - * pointer moved. Move the frame pointer to the button press - * location before deciding what to do. */ - location = frame_pointer_motion(window->frame, NULL, - button->event_x, button->event_y); - if (double_click) - location = frame_double_click(window->frame, NULL, - button_id, button_state); - else - location = frame_pointer_button(window->frame, NULL, - button_id, button_state); - - if (frame_status(window->frame) & FRAME_STATUS_REPAINT) - weston_wm_window_schedule_repaint(window); - - if (frame_status(window->frame) & FRAME_STATUS_MOVE) { - if (pointer) - xwayland_interface->move(window->shsurf, pointer); - frame_status_clear(window->frame, FRAME_STATUS_MOVE); - } - - if (frame_status(window->frame) & FRAME_STATUS_RESIZE) { - if (pointer) - xwayland_interface->resize(window->shsurf, pointer, location); - frame_status_clear(window->frame, FRAME_STATUS_RESIZE); - } - - if (frame_status(window->frame) & FRAME_STATUS_CLOSE) { - weston_wm_window_close(window, button->time); - frame_status_clear(window->frame, FRAME_STATUS_CLOSE); - } - - if (frame_status(window->frame) & FRAME_STATUS_MAXIMIZE) { - window->maximized_horz = !window->maximized_horz; - window->maximized_vert = !window->maximized_vert; - if (weston_wm_window_is_maximized(window)) { - window->saved_width = window->width; - window->saved_height = window->height; - xwayland_interface->set_maximized(window->shsurf); - } else { - weston_wm_window_set_toplevel(window); - } - frame_status_clear(window->frame, FRAME_STATUS_MAXIMIZE); - } -} - -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; - enum theme_location location; - int cursor; - - if (!wm_lookup_window(wm, motion->event, &window) || - !window->decorate) - return; - - location = frame_pointer_motion(window->frame, NULL, - motion->event_x, motion->event_y); - if (frame_status(window->frame) & FRAME_STATUS_REPAINT) - weston_wm_window_schedule_repaint(window); - - cursor = get_cursor_for_location(location); - 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; - enum theme_location location; - int cursor; - - if (!wm_lookup_window(wm, enter->event, &window) || - !window->decorate) - return; - - location = frame_pointer_enter(window->frame, NULL, - enter->event_x, enter->event_y); - if (frame_status(window->frame) & FRAME_STATUS_REPAINT) - weston_wm_window_schedule_repaint(window); - - cursor = get_cursor_for_location(location); - 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; - - if (!wm_lookup_window(wm, leave->event, &window) || - !window->decorate) - return; - - frame_pointer_leave(window->frame, NULL); - if (frame_status(window->frame) & FRAME_STATUS_REPAINT) - weston_wm_window_schedule_repaint(window); - - weston_wm_window_set_cursor(wm, window->frame_id, XWM_CURSOR_LEFT_PTR); -} - -static void -weston_wm_handle_focus_in(struct weston_wm *wm, xcb_generic_event_t *event) -{ - xcb_focus_in_event_t *focus = (xcb_focus_in_event_t *) event; - - /* Do not interfere with grabs */ - if (focus->mode == XCB_NOTIFY_MODE_GRAB || - focus->mode == XCB_NOTIFY_MODE_UNGRAB) - return; - - /* Do not let X clients change the focus behind the compositor's - * back. Reset the focus to the old one if it changed. */ - if (!wm->focus_window || focus->event != wm->focus_window->id) - weston_wm_send_focus_window(wm, wm->focus_window); -} - -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_printf(wm, "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; - case XCB_FOCUS_IN: - weston_wm_handle_focus_in(wm, event); - break; - } - - free(event); - count++; - } - - if (count != 0) - xcb_flush(wm->conn); - - return count; -} - -static void -weston_wm_set_net_active_window(struct weston_wm *wm, xcb_window_t window) { - xcb_change_property(wm->conn, XCB_PROP_MODE_REPLACE, - wm->screen->root, wm->atom.net_active_window, - wm->atom.window, 32, 1, &window); -} - -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_MAXIMIZED_VERT", F(atom.net_wm_state_maximized_vert) }, - { "_NET_WM_STATE_MAXIMIZED_HORZ", F(atom.net_wm_state_maximized_horz) }, - { "_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_DESKTOP", F(atom.net_wm_desktop) }, - { "_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) }, - { "_NET_ACTIVE_WINDOW", F(atom.net_active_window) }, - { "_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) }, - { "WINDOW", F(atom.window) }, - { "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) }, - { "_XWAYLAND_ALLOW_COMMITS", F(atom.allow_commits) }, - { "WL_SURFACE_ID", F(atom.wl_surface_id) } - }; -#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 support - * 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, int fd) -{ - struct weston_wm *wm; - struct wl_event_loop *loop; - xcb_screen_iterator_t s; - uint32_t values[1]; - xcb_atom_t supported[6]; - - 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; - } - - /* xcb_connect_to_fd takes ownership of the fd. */ - wm->conn = xcb_connect_to_fd(fd, NULL); - if (xcb_connection_has_error(wm->conn)) { - weston_log("xcb_connect_to_fd failed\n"); - close(fd); - 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, fd, - 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(); - - supported[0] = wm->atom.net_wm_moveresize; - supported[1] = wm->atom.net_wm_state; - supported[2] = wm->atom.net_wm_state_fullscreen; - supported[3] = wm->atom.net_wm_state_maximized_vert; - supported[4] = wm->atom.net_wm_state_maximized_horz; - supported[5] = wm->atom.net_active_window; - 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_set_net_active_window(wm, XCB_WINDOW_NONE); - - weston_wm_selection_init(wm); - - weston_wm_dnd_init(wm); - - xcb_flush(wm->conn); - - wm->create_surface_listener.notify = weston_wm_create_surface; - wl_signal_add(&wxs->compositor->create_surface_signal, - &wm->create_surface_listener); - wm->activate_listener.notify = weston_wm_window_activate; - wl_signal_add(&wxs->compositor->activate_signal, - &wm->activate_listener); - wm->kill_listener.notify = weston_wm_kill_client; - wl_signal_add(&wxs->compositor->kill_signal, - &wm->kill_listener); - wl_list_init(&wm->unpaired_window_list); - - weston_wm_create_cursors(wm); - weston_wm_window_set_cursor(wm, wm->screen->root, XWM_CURSOR_LEFT_PTR); - - /* Create wm window and take WM_S0 selection last, which - * signals to Xwayland that we're done with setup. */ - weston_wm_create_wm_window(wm); - - weston_log("created wm, root %d\n", wm->screen->root); - - 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->create_surface_listener.link); - - free(wm); -} - -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 bool -is_wm_window(struct weston_surface *surface) -{ - return get_wm_window(surface) != 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; - - if (window->configure_source) { - wl_event_source_remove(window->configure_source); - window->configure_source = NULL; - } - - weston_wm_window_set_allow_commits(window, false); - - weston_wm_window_get_child_position(window, &x, &y); - values[0] = x; - values[1] = y; - values[2] = window->width; - values[3] = window->height; - weston_wm_configure_window(wm, window->id, - XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT, - values); - - weston_wm_window_configure_frame(window); - weston_wm_window_schedule_repaint(window); -} - -static void -send_configure(struct weston_surface *surface, int32_t width, int32_t height) -{ - struct weston_wm_window *window = get_wm_window(surface); - struct weston_wm *wm; - struct theme *t; - int new_width, new_height; - int vborder, hborder; - - if (!window || !window->wm) - return; - wm = window->wm; - t = wm->theme; - - if (window->decorate && !window->fullscreen) { - hborder = 2 * t->width; - vborder = t->titlebar_height + t->width; - } else { - hborder = 0; - vborder = 0; - } - - if (width > hborder) - new_width = width - hborder; - else - new_width = 1; - - if (height > vborder) - new_height = height - vborder; - else - new_height = 1; - - if (window->width != new_width || window->height != new_height) { - window->width = new_width; - window->height = new_height; - - if (window->frame) { - frame_resize_inside(window->frame, - window->width, window->height); - } - } - - if (window->configure_source) - return; - - window->configure_source = - wl_event_loop_add_idle(wm->server->loop, - weston_wm_window_configure, window); -} - -static void -send_position(struct weston_surface *surface, int32_t x, int32_t y) -{ - struct weston_wm_window *window = get_wm_window(surface); - struct weston_wm *wm; - uint32_t values[2]; - uint16_t mask; - - if (!window || !window->wm) - return; - - wm = window->wm; - /* We use pos_dirty to tell whether a configure message is in flight. - * This is needed in case we send two configure events in a very - * short time, since window->x/y is set in after a roundtrip, hence - * we cannot just check if the current x and y are different. */ - if (window->x != x || window->y != y || window->pos_dirty) { - window->pos_dirty = true; - values[0] = x; - values[1] = y; - mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; - - weston_wm_configure_window(wm, window->frame_id, mask, values); - xcb_flush(wm->conn); - } -} - -static const struct weston_xwayland_client_interface 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 bool -weston_wm_window_is_positioned(struct weston_wm_window *window) -{ - if (window->map_request_x == INT_MIN || - window->map_request_y == INT_MIN) - weston_log("XWM warning: win %d did not see map request\n", - window->id); - - return window->map_request_x != 0 || window->map_request_y != 0; -} - -static bool -weston_wm_window_type_inactive(struct weston_wm_window *window) -{ - struct weston_wm *wm = window->wm; - - return window->type == wm->atom.net_wm_window_type_tooltip || - window->type == wm->atom.net_wm_window_type_dropdown || - window->type == wm->atom.net_wm_window_type_dnd || - window->type == wm->atom.net_wm_window_type_combo || - window->type == wm->atom.net_wm_window_type_popup || - window->type == wm->atom.net_wm_window_type_utility; -} - -static void -xserver_map_shell_surface(struct weston_wm_window *window, - struct weston_surface *surface) -{ - struct weston_wm *wm = window->wm; - struct weston_desktop_xwayland *xwayland = - wm->server->compositor->xwayland; - const struct weston_desktop_xwayland_interface *xwayland_interface = - wm->server->compositor->xwayland_interface; - struct weston_wm_window *parent; - - /* This should be necessary only for override-redirected windows, - * because otherwise MapRequest handler would have already updated - * the properties. However, if X11 clients set properties after - * sending MapWindow, here we can still process them. The decorations - * have already been drawn once with the old property values, so if the - * app changes something affecting decor after MapWindow, we glitch. - * We only hit xserver_map_shell_surface() once per MapWindow and - * wl_surface, so better ensure we get the window type right. - */ - 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 = surface; - window->surface_destroy_listener.notify = surface_destroy; - wl_signal_add(&window->surface->destroy_signal, - &window->surface_destroy_listener); - - if (!xwayland_interface) - return; - - if (window->surface->committed) { - weston_log("warning, unexpected in %s: " - "surface's configure hook is already set.\n", - __func__); - return; - } - - window->shsurf = - xwayland_interface->create_surface(xwayland, - window->surface, - &shell_client); - - wm_printf(wm, "XWM: map shell surface, win %d, weston_surface %p, xwayland surface %p\n", - window->id, window->surface, window->shsurf); - - if (window->name) - xwayland_interface->set_title(window->shsurf, window->name); - if (window->pid > 0) - xwayland_interface->set_pid(window->shsurf, window->pid); - - if (window->fullscreen) { - window->saved_width = window->width; - window->saved_height = window->height; - xwayland_interface->set_fullscreen(window->shsurf, - window->legacy_fullscreen_output.output); - } else if (window->override_redirect) { - xwayland_interface->set_xwayland(window->shsurf, - window->x, window->y); - } else if (window->transient_for && window->transient_for->surface) { - parent = window->transient_for; - if (weston_wm_window_type_inactive(window)) { - xwayland_interface->set_transient(window->shsurf, - parent->surface, - window->x - parent->x, - window->y - parent->y); - } else { - xwayland_interface->set_toplevel(window->shsurf); - xwayland_interface->set_parent(window->shsurf, - parent->surface); - } - } else if (weston_wm_window_is_maximized(window)) { - xwayland_interface->set_maximized(window->shsurf); - } else { - if (weston_wm_window_type_inactive(window)) { - xwayland_interface->set_xwayland(window->shsurf, - window->x, - window->y); - } else if (weston_wm_window_is_positioned(window)) { - xwayland_interface->set_toplevel_with_position(window->shsurf, - window->map_request_x, - window->map_request_y); - } else { - xwayland_interface->set_toplevel(window->shsurf); - } - } - - if (window->frame_id == XCB_WINDOW_NONE) { - weston_wm_window_set_pending_state_OR(window); - } else { - weston_wm_window_set_pending_state(window); - weston_wm_window_set_allow_commits(window, true); - xcb_flush(wm->conn); - } -} - -const struct weston_xwayland_surface_api surface_api = { - is_wm_window, - send_position, -}; diff --git a/xwayland/xwayland-internal-interface.h b/xwayland/xwayland-internal-interface.h deleted file mode 100644 index 1096444..0000000 --- a/xwayland/xwayland-internal-interface.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright © 2016 Quentin "Sardem FF7" Glidic - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef XWAYLAND_INTERNAL_INTERFACE_H -#define XWAYLAND_INTERNAL_INTERFACE_H - -struct weston_desktop_xwayland; -struct weston_desktop_xwayland_surface; - -struct weston_xwayland_client_interface { - void (*send_configure)(struct weston_surface *surface, int32_t width, int32_t height); -}; - -struct weston_desktop_xwayland_interface { - struct weston_desktop_xwayland_surface *(*create_surface)(struct weston_desktop_xwayland *xwayland, - struct weston_surface *surface, - const struct weston_xwayland_client_interface *client); - void (*set_toplevel)(struct weston_desktop_xwayland_surface *shsurf); - void (*set_toplevel_with_position)(struct weston_desktop_xwayland_surface *shsurf, - int32_t x, int32_t y); - void (*set_parent)(struct weston_desktop_xwayland_surface *shsurf, - struct weston_surface *parent); - void (*set_transient)(struct weston_desktop_xwayland_surface *shsurf, - struct weston_surface *parent, int x, int y); - void (*set_fullscreen)(struct weston_desktop_xwayland_surface *shsurf, - struct weston_output *output); - void (*set_xwayland)(struct weston_desktop_xwayland_surface *shsurf, - int x, int y); - int (*move)(struct weston_desktop_xwayland_surface *shsurf, - struct weston_pointer *pointer); - int (*resize)(struct weston_desktop_xwayland_surface *shsurf, - struct weston_pointer *pointer, uint32_t edges); - void (*set_title)(struct weston_desktop_xwayland_surface *shsurf, - const char *title); - void (*set_window_geometry)(struct weston_desktop_xwayland_surface *shsurf, - int32_t x, int32_t y, - int32_t width, int32_t height); - void (*set_maximized)(struct weston_desktop_xwayland_surface *shsurf); - void (*set_pid)(struct weston_desktop_xwayland_surface *shsurf, pid_t pid); -}; - -#endif diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h deleted file mode 100644 index 3ef0404..0000000 --- a/xwayland/xwayland.h +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#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; - int abstract_fd; - struct wl_event_source *abstract_source; - int unix_fd; - struct wl_event_source *unix_source; - int display; - pid_t pid; - struct wl_client *client; - struct weston_compositor *compositor; - struct weston_wm *wm; - struct wl_listener destroy_listener; - weston_xwayland_spawn_xserver_func_t spawn_func; - void *user_data; - - struct weston_log_scope *wm_debug; -}; - -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 create_surface_listener; - struct wl_listener activate_listener; - struct wl_listener kill_listener; - struct wl_list unpaired_window_list; - - 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_maximized_vert; - xcb_atom_t net_wm_state_maximized_horz; - 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_desktop; - 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 net_active_window; - 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 window; - 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; - xcb_atom_t wl_surface_id; - xcb_atom_t allow_commits; - } atom; -}; - -void -dump_property(FILE *fp, 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); - -struct weston_wm * -weston_wm_create(struct weston_xserver *wxs, int fd); -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); -- Gitee